Closes#125, a non-blocking observation from #111's review decided now rather than left implicit.
UpdateNote's merge loop applied every field from the caller's leading frontmatter fragment onto the restamped note, including updated_at/supersedes/supersede_reason. updated_at/supersedes were already safe — both get unconditionally re-set right after the loop regardless — but supersede_reason is only re-set when opts.Reason is non-empty, so a caller could set it via its own fragment and have it survive even with the Reason option left empty, bypassing the one intended entry point for that field.
Decision
These three fields are this function's own output, not caller-settable, full stop — skip all three in the merge loop rather than lean on the other two happening to be safe via call order. Makes the invariant explicit at the one place it could be silently broken by a future reordering.
Verification
task check exit 0. Flip-tested: reverting the skip to just l.key == "" lets the caller's supersede_reason survive with Reason empty — the new test catches it. Full module suite green.
Closes #125, a non-blocking observation from #111's review decided now rather than left implicit.
`UpdateNote`'s merge loop applied every field from the caller's leading frontmatter fragment onto the restamped note, including `updated_at`/`supersedes`/`supersede_reason`. `updated_at`/`supersedes` were already safe — both get unconditionally re-set right after the loop regardless — but `supersede_reason` is only re-set when `opts.Reason` is non-empty, so a caller could set it via its own fragment and have it survive even with the `Reason` option left empty, bypassing the one intended entry point for that field.
## Decision
These three fields are this function's own output, not caller-settable, full stop — skip all three in the merge loop rather than lean on the other two happening to be safe via call order. Makes the invariant explicit at the one place it could be silently broken by a future reordering.
## Verification
`task check` exit 0. Flip-tested: reverting the skip to just `l.key == ""` lets the caller's `supersede_reason` survive with `Reason` empty — the new test catches it. Full module suite green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
_Created via git-mcp on behalf of @mathias_
hyperguild#125, found in #111's review as a non-blocking observation,
decided now rather than left implicit: UpdateNote's merge loop applied
every field from the caller's leading frontmatter fragment onto the
restamped note, including updated_at/supersedes/supersede_reason.
updated_at and supersedes were already safe -- both get unconditionally
re-set right after the loop regardless of what the loop did with them
-- but supersede_reason is only re-set when opts.Reason is non-empty, so
a caller could set it via its own fragment and have it survive even
with the Reason option left empty, bypassing the one intended entry
point for that field.
Decision: these three fields are this function's own output, not
caller-settable, full stop -- skip all three in the merge loop rather
than lean on the other two happening to be safe via call order. Makes
the invariant explicit at the one place it could be silently broken by
a future reordering, instead of implicit in which fm.set calls happen
to run after the loop.
Flip-tested: reverting the skip to just `l.key == ""` lets the caller's
supersede_reason survive with Reason empty -- the new test catches it.
Full module suite green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mathias
merged commit a5f502eb9c into main2026-09-23 08:18:30 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #125, a non-blocking observation from #111's review decided now rather than left implicit.
UpdateNote's merge loop applied every field from the caller's leading frontmatter fragment onto the restamped note, includingupdated_at/supersedes/supersede_reason.updated_at/supersedeswere already safe — both get unconditionally re-set right after the loop regardless — butsupersede_reasonis only re-set whenopts.Reasonis non-empty, so a caller could set it via its own fragment and have it survive even with theReasonoption left empty, bypassing the one intended entry point for that field.Decision
These three fields are this function's own output, not caller-settable, full stop — skip all three in the merge loop rather than lean on the other two happening to be safe via call order. Makes the invariant explicit at the one place it could be silently broken by a future reordering.
Verification
task checkexit 0. Flip-tested: reverting the skip to justl.key == ""lets the caller'ssupersede_reasonsurvive withReasonempty — the new test catches it. Full module suite green.Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Created via git-mcp on behalf of @mathias