fix: the capture ledger says what it skipped, stays inside the checkout, and defers a record past one cut - #723
Merged
Merged
Conversation
…ng layer The bare status board excluded a record the reader refused from all three totals and printed a bare error beside it, so a reader could not tell whether the writer or the validator was the side that was wrong, and the header under-reported the ledger. Each SkipRecord now carries the reader layer that refused it (filename, read, frontmatter, schema, invariant), StatusResult carries skipped_count, and the header counts skipped records beside the totals. The list and mentions renders name the layer too. The other halves of the record were already fixed at the base: the slug truncation trims its separator (recordid.Slug) and the --severity, --category and --source help names each vocabulary (enumHelp). Refs: iss-2609120452071388 Assisted-by: Claude:claude-opus-5-5
…ator The property half of iss-2609120452071388's acceptance: 20000 seeded adversarial inputs (separator runs, punctuation, non-ASCII, lengths either side of the 60-character budget) each derive a slug the reader's SlugRe accepts and that normalisation leaves unchanged. The trim that makes it hold already stands in recordid.Slug; the property had no test. Refs: iss-2609120452071388 Assisted-by: Claude:claude-opus-5-5
…er-named The slug and help halves were fixed before this lane; the skip count and layer diagnostic land in 8c09f03. The autonomous-hunt vocabulary question is captured separately as iss-2609251151293535 for a product ruling. Resolves: iss-2609120452071388 Refs: iss-2609251151293535 Assisted-by: Claude:claude-opus-5-5
A bidi override, zero-width rune, C1 control or DEL typed into a capture body, a resolution or wontfix note, a grounds entry or an intent headline reached the committed record verbatim. termsafe gains EncodeHiddenRunesBlock, the multi-line form of EncodeHiddenRunes that keeps the line feed, the tab and a CRLF pair, and every ledger and intent-draft free-text write now passes through one of the two after redaction. grounds.New encodes the text it validates, so both grounds writers are covered at one site. The grounds a wontfix derives from its reason now go through grounds.NewDerived, which applies ValidateText's control-character check (held once as validateControl) and the encoding, leaving only the substance floor off. A control character in a wontfix reason is refused at the grounds boundary before any write, not by the serialiser under the ledger lock. Refs: iss-2608301206073609, iss-2608301244450106 Assisted-by: Claude:claude-opus-5-5
…write Resolves: iss-2608301206073609 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2608301244450106 Assisted-by: Claude:claude-opus-5-5
The ledger's writes were absolute-path operations, each guarded by its own Lstat or O_NOFOLLOW check, which left the window between a check and the write it licensed to a local racer: an ancestor swapped for a symlink in that window carried a directory or a record out of the checkout. Every ledger write now resolves inside an os.Root opened on the checkout (ledgerroot.go): the directory walk creates through root.Mkdir, the placeholder is an exclusive no-follow create in the root, the commit, the transition, the link, promote and migrate stamps write through WriteFileAtomicPreserveModeInRoot, the removals go through root.Remove, and the allocator lock opens through the new fsutil.WithFileLockIn. That lock open uses the plain-open / exclusive-create sequence AppendLineIn already uses, because a non-exclusive openat(O_CREAT) races to ENOENT on darwin. The per-segment Lstat walk still refuses a committed symlink by name. A test seam (ledgerRaceHook) drives the swap at the mkdir and write windows. reading.go's own directory provisioning keeps its path-operand call: safeMkdirLeaf stays path-based (now resolved inside a root on the parent) so that file is untouched. Refs: iss-2609012037143368 Assisted-by: Claude:claude-opus-5-5
The two ledger writes left in reading.go (a reading run's records and a disposition) go through the same os.Root helper as every other ledger write. Three lines; the file's symlink guard is untouched. Refs: iss-2609012037143368 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609012037143368 Assisted-by: Claude:claude-opus-5-5
…answer discoverRepoRoot fell back, where git would not answer, to walking upward and accepting any directory whose .git entry merely existed, with neither the shape check nor the ownership gate the rules-root resolver grew. It now delegates to gitutil.CheckoutRoot, the isolated rev-parse the front doors already resolve through, and returns no root when git names none. Nothing needed the walk: every front door resolves through LedgerRoot, which refuses the same states. Refs: iss-2609090947359464 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609090947359464 Assisted-by: Claude:claude-opus-5-5
Confirmed while fixing the remedy's delimiter: a promotion given no grounds whose stamp fails after the mint dereferences the absent grounds. Refs: iss-2609251208394294 Assisted-by: Claude:claude-opus-5-5
The post-mint stamp-failure report attached the link remedy unconditionally and composed it with three defects: - a promotion that lost the race to a concurrent promotion of the same issue was told to link its draft, which is refused as already promoted; the already-promoted refusal now wraps ErrAlreadyPromoted, and that failure names the winner and says to delete the duplicate draft; - the remedy was delimited with single backticks while carrying free-prose grounds, so a backtick in the grounds closed it early; it is now a CommonMark code span one backtick longer than any run inside it; - a promotion given no grounds (optional on the issue route) dereferenced the nil grounds composing the remedy and panicked; the remedy now names --grounds only when grounds were given. One helper, orphanDraftError, serves both the issue and the reading route. Refs: iss-258, iss-2609020154474224, iss-2609251208394294 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-258 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609020154474224 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609251208394294 Assisted-by: Claude:claude-opus-5-5
record-lint's issue_impact_valid and the release derivation read impact
as the raw scalar, so a quoted legal value ("fix") and a quoted empty one
("") are blocked there; the capture parser unquoted them first and the
reader accepted both. The parser now keeps a quoted impact's raw token
(rawScalarKeys), so validateStrict reaches the gate's verdict. A
differential test runs one table of spellings through both readings.
Refs: iss-2608261133218490
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2608261133218490 Assisted-by: Claude:claude-opus-5-5
…the board A ledger record held only as an untracked file is in no state to anyone but the checkout holding it: no branch cut from the default branch sees it, and no gate reading the committed tree reads it. capture wrote such a record and reported plain success, and the status board listed it as an equal member of its folder. One git status read over the ledger (uncommittedLedgerPaths) now drives CaptureResult.uncommitted, Issue.uncommitted on status and list rows, and StatusResult.uncommitted_count. The write prints an "uncommitted:" line; the board marks the row and counts the records. Where git cannot answer, nothing is marked. Refs: iss-2609100508570527 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609100508570527 Assisted-by: Claude:claude-opus-5-5
The release cut's finding guard reads deferred_after and deferral_reason, and no verb wrote them: a deferral was a hand edit of frontmatter that bypassed every validator, done by script in a managed repository across three cuts. capture defer <iss-N> --after <vX.Y.Z> --reason <text> writes the pair and appends a dated "## Deferral <date>" body section; the record stays in open/. It refuses, with nothing written, everything the cut's reader would not honour: a tag that is not the checkout's newest release tag, an empty reason, a record that is not open, and a grade other than major or critical. The grade is judged before the tag, so a refusal about the record names the checkout's own. Re-deferring replaces the pair and appends a new section. Wired from the CLI and commands/capture.md; commands/launch.md's waiver route names the verb; the capture and launch brief chapters, the principle, the generated reference, the surface snapshot and the appendix move with it. Refs: iss-2609181223260994 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609181223260994 Assisted-by: Claude:claude-opus-5-5
Runs changelog.GuardFindings over the committed record capture defer wrote, and asserts the finding is waived with its reason rather than blocking. Refs: iss-2609181223260994 Assisted-by: Claude:claude-opus-5-5
…ressed
The ledger is per worktree and said so nowhere: a record filed in one
worktree read "not found" from another with no hint which ledger had been
searched. Every capture verb now names it — one stderr line
"abcd capture: ledger of <checkout> on branch <branch>" in the plain
render, written before the verb runs so a refusal carries it too, and a
`ledger` member {checkout, branch} appended to the --json envelope
(renderLedger). The record dispatcher does the same for an issue id. The
checkout is home-relative where it can be; the branch comes from
symbolic-ref, so an unborn branch is named too.
The intent audit's half is captured separately as iss-2609251235119402:
its envelope belongs to the intent surface.
Refs: iss-2609202053570475, iss-2609251235119402
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609202053570475 Assisted-by: Claude:claude-opus-5-5
A capture without --found-at is legitimate and stays so, but it was written silently, and it is the shape all nine misfiled installer records of iss-2609120511058115 had: the path guard refuses a path-shaped found_at absent from the checkout and has nothing to judge when there is none. The verb now says, on stderr, that the record names no location in this checkout, and --json carries no_location; the exit code and the record written are unchanged, and a capture naming a location says nothing of the kind. Refs: iss-2609231156260287, iss-2609120511058115 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609231156260287 Assisted-by: Claude:claude-opus-5-5
findRecordFile, the existence probe behind resolve --intent/--spec and promote --intent, treated every bucket read failure as an empty bucket. An unreadable bucket was answered "not found in the store", and a symlinked bucket was followed out of the store, so a resolve could stamp provenance naming an intent that lives outside the checkout. An absent bucket stays soft; a symlinked or non-directory bucket is refused as path-unsafe, and any other read failure is returned as itself. Refs: iss-260 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-260 Assisted-by: Claude:claude-opus-5-5
An unrecognised --severity, --category or --source was refused as "malformed frontmatter: invalid source ..." — frontmatter the caller never wrote, from a flag the message did not name. capture now judges the three request members first, before anything is written, and returns a FieldValueError naming the member, the value and the accepted set; the CLI renders it as "--source "ci-signal" is not accepted; accepted values: ..." with exit 2. Refs: iss-2608290810037524 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2608290810037524 Assisted-by: Claude:claude-opus-5-5
restampField refused a restamp on a record with no origin but accepted one beside any origin at all, so a record carrying an out-of-vocabulary origin was restamped into a pair no command writes. It now asks provenance.ParseOrigin and refuses naming the value. The converse — a valid origin with no production_mode, completed into the pair by a restamp — is kept and pinned by a test, and stated in spc-56 and the command page as a deliberate write inside the lint's declared residual. Refs: iss-2608300941548519 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2608300941548519 Assisted-by: Claude:claude-opus-5-5
Refs: iss-2609251823551349 Refs: iss-2609251823555125 Refs: iss-2609251823559111 Refs: iss-2609251823560369 Assisted-by: Claude:claude-opus-5-5
Brings v0.11.0, the verb consolidation, the help sentences, and the guard and site fixes onto the capture lane. Two files conflicted, resolved hunk by hunk: commands/capture.md takes main's sentence description and block and keeps the lane's defer in the argument hint; cli.go keeps main's relink-error line ahead of the lane's ledger-naming render for resolve and wontfix. Assisted-by: Claude:claude-opus-5-5
The help-sentence manifest reached this branch with the merge of main, and its gate refused `abcd capture defer`, the one visible verb the lane added: TestEveryVisibleVerbCarriesItsSentenceEverywhere and TestTheFrameworkCommandsAreTheOnlyExemption both named it. The verb now takes its one sentence from internal/core/surface/sentences.go in the house shape and drops its cobra Short; the generated CLI reference and the surface snapshot are regenerated from it. Assisted-by: Claude:claude-opus-5-5
Merging main (85a10e1) into the capture lane, whose fixes grow internal/core/capture and its tests, grew the tree-bounded reading objects. Each window is re-measured by dry run over a clean clone of the merged tip 68deae0 against the rule the entries state, the smallest ten-thousand boundary leaving at least one per cent headroom. Widening measures 1,040,187, past its 1,040,000 declaration, so it moves to 1,060,000 (1.90 per cent); entailment measures 344,124, 1.71 per cent under 350,000, so its declaration stays and only its measured fields move; detection measures 1,049,223, 0.07 per cent under its 1,050,000 declaration, so it moves to 1,060,000 (1.03 per cent). Comparative is bounded by the widening run it is handed, not by the tree, and is exempt by name. Refs: iss-2609251455354719 Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
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.
With this change, the capture ledger says what it skipped, what it has not committed and which ledger it wrote to, keeps every write inside the checkout, and
abcd capture defercarries a major or critical record past one release cut.capturestatus andlistcount every record they skip and name the layer that refused it. An unreadable record bucket is reported as unreadable, not read as empty.os.Rooton the ledger's base. A symlinked lock, a symlinkedopen/or a record swapped for a symlink is refused, and nothing is written outside the checkout. Reading and disposition records are written the same way. The ledger-root marker walk is deleted: discovery goes through git and refuses a marker git will not answer for.capture promoteno longer panics on an orphan draft that has no grounds. Its remedy is the whole command, in a delimiter the text cannot close. A lost promote race names the duplicate draft.impactreaches the same verdict as the bare word in capture and in the record gate.captureand its status board say when a record is not yet committed. Every ledger verb names the checkout and branch whose ledger it addressed, on stderr, and in aledgermember in--json. A capture with no--found-atsays it names no location in this checkout.abcd capture defer <iss-N> --after <vX.Y.Z> --reason <text>writesdeferred_afteranddeferral_reason, and the release cut honours them. It refuses a minor or nitpick record, a record that is not open, an empty reason and an--afterthat is no release tag. The verb takes its help sentence from the surface manifest.--severity,--categoryand the rest) names the flag and its accepted set.Review: Fable 5.1, one round, SHIP. Its four follow-ups are captured and open: iss-2609251823551349, iss-2609251823555125, iss-2609251823559111, iss-2609251823560369. The branch was then merged with main at 85a10e1. Two files conflicted and were resolved hunk by hunk.
Reading-corpus change. The windows are recalibrated at the merged tip (68deae0): widening 1,040,000 to 1,060,000, detection 1,050,000 to 1,060,000, and entailment's measured fields only.
Resolves: iss-258
Resolves: iss-260
Resolves: iss-2608261133218490
Resolves: iss-2608290810037524
Resolves: iss-2608300941548519
Resolves: iss-2608301206073609
Resolves: iss-2608301244450106
Resolves: iss-2609012037143368
Resolves: iss-2609020154474224
Resolves: iss-2609090947359464
Resolves: iss-2609100508570527
Resolves: iss-2609120452071388
Resolves: iss-2609181223260994
Resolves: iss-2609202053570475
Resolves: iss-2609231156260287
Resolves: iss-2609251208394294
Refs: iss-2609120511058115
Refs: iss-2609251151293535
Refs: iss-2609251235119402
Refs: iss-2609251455354719
Refs: iss-2609251823551349
Refs: iss-2609251823555125
Refs: iss-2609251823559111
Refs: iss-2609251823560369
Assisted-by: Claude:claude-opus-5-5