fix(api): UpdateNote reserves updated_at/supersedes/supersede_reason from the caller's fragment (#137)
CI / Lint / Test / Vet (push) Successful in 39s
CI / Mirror to GitHub (push) Successful in 3s

Closes #125. Skips updated_at/supersedes/supersede_reason in the caller fragment merge loop so they cannot be set via the fragment, only via the intended entry points.
delete_branch_after_merge=true

Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #137.
This commit is contained in:
2026-09-23 08:18:30 +00:00
committed by mathias
parent 7ed2a7d3b4
commit a5f502eb9c
2 changed files with 54 additions and 2 deletions
+15 -2
View File
@@ -110,9 +110,22 @@ func UpdateNote(brainDir string, opts UpdateNoteOptions) (relPath, contentHash,
fm, _ := parseFrontmatter(string(prior))
for _, l := range contentFM.lines {
if l.key != "" {
fm.set(l.key, l.value)
// updated_at/supersedes/supersede_reason are this function's own
// output, not something a caller's fragment sets — updated_at and
// supersedes are already unconditionally re-set below regardless of
// what the loop does with them, but supersede_reason is only
// re-set when opts.Reason is non-empty, so without this skip a
// caller could set it via its own fragment and have it survive
// even when Reason is empty, bypassing the one intended entry
// point for that field (hyperguild#125, found in #111's review).
// Skipping all three here rather than relying on the unconditional
// overwrites below to save the other two keeps the invariant
// explicit at the one place it can be silently broken by a future
// reordering, not implicit in call order.
if l.key == "" || l.key == "updated_at" || l.key == "supersedes" || l.key == "supersede_reason" {
continue
}
fm.set(l.key, l.value)
}
fm.set("updated_at", time.Now().UTC().Format(time.RFC3339))
fm.set("supersedes", priorHash)