fix: inherit records, not every trailer paragraph, on squash-preserve - #1154
Merged
Merged
Conversation
git decides what a trailer block is, and a message's last paragraph qualifies whatever its keys mean. A merge commit whose final paragraph held one `Claude-Session:` line was therefore collected as an inherited record, stamped with `Provenance: inherited <sha>`, and the composed squash message was refused by `commitlore validate` with `unknown-key Claude-Session` -- blocking the merge that `squash-preserve` had been run to protect. An inherited block now carries only the keys this protocol defines, and a block left with none contributes nothing. The filter is per trailer rather than per block because a paragraph mixing `Limit:` with a foreign key is a real record: dropping the block would lose it, and inheriting it whole would still fail validate on the foreign key. The collision check had to agree. Stripping those keys makes a faithful copy differ from its origin, and the mixed case stopped failing on `unknown-key` and started failing on `duplicate-id` -- so the inherited-copy comparison compares the record's own content and ignores what the vocabulary does not cover, the same exemption #1148 made for the transport stamp. Both halves are load-bearing: the new tests were run with each change absent in turn and fail without it. Limit: git's grammar makes a message's last paragraph a trailer block whatever its keys mean, so the paragraph cannot be refused at parse time Limit: the inherited-copy comparison now ignores keys outside the vocabulary, so a copy carrying a different value for one than its origin did is no longer reported as a collision Ruled-out: dropping only the blocks whose every key is foreign | a paragraph mixing `Limit:` with `Claude-Session:` is a real record, and inheriting it whole still composes a message validate refuses on the foreign key Ruled-out: comparing an inherited copy against its origin in full | stripping the foreign keys makes every faithful copy read as a divergent re-declaration, the false refusal #1148 removed for the provenance stamp Warn: a commit that already landed with an inherited non-record block re-squashes to a block whose only key is `Provenance:` -- it validates, and it says nothing Blast: module Undo: easy Certainty: firm Record-Id: r-nonrecordblock1153 Provenance: drafted
CommitLore — record lintTrailers: clean — 1 commit in Active constraints for the paths this PR touchesLimits (37)
Ruled out (58)
Warnings (26)
Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR. |
This was referenced Oct 3, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1153
What happened
squash-preservecollected a merge commit whose final paragraph held oneClaude-Session:line as an inherited record, stamped it withProvenance: inherited <sha>, and the composed message was then refused:git decides what a trailer block is, and a message's last paragraph qualifies
whatever its keys mean — so the paragraph cannot be refused at parse time. The
filter has to run on what the parse returns, which is the question
types.ts isCommitLoreKeyalready answers for the index, where paragraphs likesha256:andTests:were served to agents as recorded decisions (#335).The change
core/squash.ts— an inherited block carries only the keys this protocoldefines, and a block left with none contributes nothing. Applied where both
channels become candidate records, before
mergeCommitBlocksmatches them,so neither channel can reintroduce what the other dropped.
core/stale.ts— the inherited-copy comparison in the collision checkcompares the record's own content and ignores keys outside the vocabulary.
Without this the mixed case stops failing on
unknown-keyand starts failingon
duplicate-id: stripping the foreign keys makes a faithful copy differfrom its origin.
Per trailer rather than per block, because a paragraph mixing
Limit:with aforeign key is a real record — dropping the whole block would lose it, and
inheriting it whole would still fail
validate. ASigned-off-by:on theorigin goes the same way: it is an attestation about that commit, and
inheriting it would assert a sign-off on a commit nobody signed.
Verification
test/squash.test.ts: the reported paragraph, and the mixedblock. Run against the unfixed build first — both fail, the first with exactly
the reported
{"rule": "unknown-key", "key": "Claude-Session"}.squash.tschange the mixed casefails on
duplicate-id; both halves are load-bearing.tsc --noEmitclean.Known residual
A commit that already landed with an inherited non-record block re-squashes to
a block whose only key is
Provenance:— it validates, and it says nothing.Recorded as a
Warn:on the commit rather than filtered, since nothing reportedthat shape and the output is valid.
dist/is deliberately absent:canonical-merge.ymlrebuilds it andregenerates the manifest.