fix: preflight runs on CI's toolchain, and the audit and intent-lock chain - #735
Merged
Merged
Conversation
The verdict ingest wrote 39 shipped intents back with no final newline (the replace-in-place path dropped the separator), so each ends on the last byte of its review block. Every other record writer ends its file with a newline. Each file gains exactly that one byte; no line changes. Refs: iss-2609231011136579 Assisted-by: Claude:claude-opus-5-5
The audit's review block is read and written in one place, readReviewBlocks, and every question the ingest asks of a record goes through it. - Liveness: a marker counts only on a live line of the live Audit Notes section, through condition.AuditNotes over mdrecord.Mask (the one fence-and-comment rule). A marker-shaped line in a fence, a comment span or another section is an example, not state, so it can neither solicit a verdict nor be reused as the parked receipt. appendToAuditNotes finds its section the same way, so the writer never appends under a fenced example the reader cannot see, and condition.ReadDispositions reads live lines only. - Extent: every renderer closes its block on a `<!-- abcd-review-end receipt=rcp-… -->` line, and the block ends there, so prose written below a block survives a replacement and an identical re-ingest stays a noop. A block written before the closing line existed is read by its known extent: an OWED stub is its marker and one sentence, and any other block keeps the rule it always had (next marker, heading or end of section) less a trailing run of link-reference definitions, which no renderer writes. An identical re-ingest over such a block is still a noop, so no tree record is rewritten to gain the line. - Final newline: the record every write returns ends in a newline. - Citations: a rendered block (verdict, re-ingest or dead letter) whose prose cites a record id that names no record is REFUSED, naming the id and its line, with nothing written, where the repository's record-lint arms prose_citation_resolves over the intent store. Refusal over sanitising: the verdict schema cannot mark an id illustrative, and termsafe neutralises syntax, never a citation's meaning, so a rewrite would be a second sanitiser guessing what the auditor meant. The check is the gate's own reading (lint.UnresolvedProseCitationsInRecord, which shares the per-line loop with the rule), registered by the front doors through intent.SetProseCitationGate because lint's tests import intent; an unregistered gate refuses the ingest. The brief's intent surface row and commands/intent.md state the closing line, the liveness rule and the citation refusal. Watched red on a scratch archive of d850f06: TestIngestedRecordEndsIn ANewline (no-newline stub, dead letter), TestFirstIngestKeepsLinkRefs BelowTheOwedStub, TestReplacementKeepsLinkRefsBelowALegacyBlock, TestReingestKeepsAHumanNoteBelowTheBlock, TestOnlyALiveMarkerCounts, TestAFencedMarkerIsNotASolicitation, TestIngestRefusesAnUnresolvable Citation, TestAppendLandsInTheLiveAuditNotes, TestReadDispositionsReadsOnlyLiveBlocks and the CLI's TestIntentAuditIngestRefusesAnUnresolvableCitation. TestEveryTreeReviewBlockRoundTrips holds every intent record in the tree to the byte pattern's reading and to a byte-identical write-back. Refs: iss-2609231011136579, iss-2609231036448320, iss-2609251451432601 Refs: iss-2609251451434656, iss-2609020529185438 Assisted-by: Claude:claude-opus-5-5
…tion gate Found sweeping the verdict ingest's siblings: the consistency ingest and the reading ingest write host-delegated prose into stores prose_citation_resolves reads, with no check before the write. Left for its own lane: capture cannot import core/lint, so the fix is the same front-door seam, and each writer decides whether it refuses the payload or the one finding. Refs: iss-2609261835118276 Assisted-by: Claude:claude-opus-5-5
The fix in 5596495 gives the intent audit one live, bounded reader and writer for the review block: records end in a newline, a verdict citing an id that names no record is refused before any write, the block's extent is bounded so trailing link references and prose below it survive, and only a marker on a live line of Audit Notes is state. Resolves: iss-2609231011136579 Resolves: iss-2609231036448320 Resolves: iss-2609251451432601 Resolves: iss-2609251451434656 Resolves: iss-2609020529185438 Assisted-by: Claude:claude-opus-5-5
…the PATH toolchain Refs: iss-2609261850045839 Assisted-by: Claude:claude-opus-5-5
The format gate captured `GOTOOLCHAIN=go<v> go env GOROOT` with stderr merged into stdout, so on a machine without the declared toolchain cached the fetch's "go: downloading ..." line became the first line of the captured root. The executable test on that two-line path failed and the gate refused, blaming the network, on the run where the fetch had just succeeded. The resolution moves out of the Makefile into scripts/pinned-toolchain.sh, which prints the GOROOT on stdout and nothing else and lets go's own progress and errors pass through on stderr. It refuses (exit 2) and names the skew for a fetch that failed, and separately for a root that holds no go or gofmt, or a go that reports another release. `make fmt-check` and `make fmt` run the gofmt under the root it prints. The resolver's branches are driven against a stub go in TestPinnedToolchainResolver*; the progress case failed on the extracted merged-stream capture before the streams were separated. Refs: iss-2609090951287096 Assisted-by: Claude:claude-opus-5-5
…dout alone Resolves: iss-2609090951287096 Assisted-by: Claude:claude-opus-5-5
make preflight built, vetted and tested on the go on PATH (go1.27.1 on the machine that found it) while CI builds and tests with the release go.mod declares (go 1.26.7). On 2026-09-26 pull request 728 passed preflight and failed CI on a test whose assertion depended on go 1.27's encoding/json error wording. preflight now exports GOTOOLCHAIN=go$(GO_TOOLCHAIN_VERSION), read from go.mod by the same line the format gate uses, to every Go step it makes: its build, vet, test and race lines and each go run and go test of its prerequisites. The go on PATH switches to the declared release and puts its bin first on PATH for what it runs, so a go a test execs is the declared one too. fmt-check, CI's format gate, joins preflight second, straight after the load check: its resolver, scripts/pinned-toolchain.sh, is the one resolver, and it refuses, naming the skew, before any gate runs on a toolchain that cannot be fetched. That also closes iss-46's "gofmt missing from make preflight" parity gap: preflight runs every gate CI's check job runs. Gates whose behaviour changes: preflight (pinned toolchain for every Go step; fmt-check as a prerequisite). AGENTS.md, CONTRIBUTING.md, the install guide, the pre-push hook header and the Makefile comment say so. TestPreflightRunsTheDeclaredToolchain failed on the missing export and on fmt-check's absence from the prerequisite list before this change. Refs: iss-2609261850045839, iss-46 Assisted-by: Claude:claude-opus-5-5
…lchain Resolves: iss-2609261850045839 Assisted-by: Claude:claude-opus-5-5
make preflight ran record-lint without -agent-diff, so agent_contract's
unbumped-edit check (a changed agent prompt bumps its prompt_version and
adds its agents/CHANGELOG.md entry) was a no-op locally and armed only in
CI, which passes -agent-diff "${BASE_SHA}...HEAD". Pull request 606 passed
three green preflights and was refused in the merge queue for exactly
that check.
The record-lint target now passes -agent-diff origin/main...HEAD, the
merge-base range CI's step passes from its base, and needs origin/main
as lint-issues and lint-decisions already do. Gate whose behaviour
changes: record-lint, standalone and under preflight.
In a scratch clone with an agent prompt edited and its version unbumped,
the old recipe printed no blocker and the new one refuses with
agent_contract. TestPreflightArmsRecordLintAsCIDoes failed on the
unarmed recipe before this change.
Refs: iss-2609021152026246
Assisted-by: Claude:claude-opus-5-5
…I does Resolves: iss-2609021152026246 Assisted-by: Claude:claude-opus-5-5
… rule Three of iss-46's lint scope holes, each mechanical: - links_resolve read .abcd/development and .abcd/work (record-lint) and docs/ and README.md (docs-lint), and nothing else: 59 committed markdown files, the root prose, the agent prompts, the plugin command pages and the READMEs, had relative links no gate checked. docs-lint's links_resolve now walks them through extra_roots. They carry no broken link today; a planted one in AGENTS.md is refused. TestEveryCommittedMarkdownFileHasItsLinksChecked derives its roster from git ls-files, so a markdown file added anywhere joins by existing or is named in its exemption list with the reason (eval fixtures, the templates ahoy writes into other repositories, the symlinked mirrors). It failed with 59 files against the old config. - persona_registry ran over the design record alone. docs-lint now arms it against the same roster and severity (TestDocsLintArmsThePersonaRule, red against the old config); docs/ carries no unregistered persona today, and a planted one is refused. The docs-lint seed withholds it: a prepared repository has no persona roster to read. - CONTRIBUTING.md documented how to activate the committed hooks but not that the pre-commit name guard depends on the per-machine banlist, and warns and lets the commit through without one. Gate whose behaviour changes: docs-lint (wider link scope, persona rule), in preflight and CI alike. Refs: iss-46 Assisted-by: Claude:claude-opus-5-5
iss-46 named five lint scope holes. Four are closed in this branch: the link check walks every committed markdown file, docs-lint arms the persona rule, preflight runs fmt-check and an armed record-lint on CI's toolchain, and CONTRIBUTING.md documents the hooks' banlist dependency. The fifth, a warn-baseline ratchet in record-lint with a scope matrix beyond links, is a design that needs a ruling (which warn rules freeze, where the baseline lives, how it only shrinks), so the record stays open with deferred_after: v0.11.0 and that reason. Refs: iss-46 Assisted-by: Claude:claude-opus-5-5
When a competitor lands a record's resolution while a merge-queue entry waits, the record is terminal at the entry's base and enters nothing across the range, so RS001 refuses the entry's trailer, rightly. The diagnosis probed head..base for the base-side commit that placed the record, and in the queue the base is an ancestor of the head, so that walk is always empty and the only message selectable was "already sat in resolved/ before this branch diverged ... Drop the trailer": history that never happened, and a remedy aimed at a trailer that was right when it was written. A new probe, landed_while_waiting, answers in exactly that shape: when the base is an ancestor of the head it walks from the declaring commit's own fork point to the base, and a base-side commit that placed the record there is named as the competitor's landing, with rebase-and-reconcile as the remedy. A record terminal before the branch was cut keeps the message it has, in the queue and out of it. RS005 drew the same stale-branch split for the intent store and had the same blind spot; it takes the same probe. Gate whose behaviour changes: check-issue-resolution.sh commits (RS001, RS005 refusal text only; every verdict is unchanged). The queue cases in check-issue-resolution-cases.sh failed against the old probe. Refs: iss-2609091433422134 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609091433422134 Assisted-by: Claude:claude-opus-5-5
TestDocsLintHarnessNameGate loads the real .abcd/docs-lint.json into a temporary repository, and a configured tree that does not resolve is a load error. Widening links_resolve's extra_roots and arming persona_registry in docs-lint made the fixture fail to load for want of those trees and the persona roster. It now creates each extra root and a roster from the config it loads, so a new root needs no edit here. Refs: iss-46 Assisted-by: Claude:claude-opus-5-5
Two banlist tests lint a fixture repository with the real docs-lint config, and the config's new links_resolve extra roots and persona roster did not exist there, so the load refused. A helper creates every tree the config reads beyond its roots and name_roots, read from the config itself. Refs: iss-46 Assisted-by: Claude:claude-opus-5-5
The verdict ingest (the review-drainA1 flag), the fidelity-review emit and the related-issue/link frontmatter writes each rewrite an intent record as a read-modify-write outside withIntentMintLock. Refs: iss-2609261935343851 Refs: iss-2609261935407925 Refs: iss-2609261935407995 Assisted-by: Claude:claude-opus-5-5
IngestVerdictBytes resolved the receipt, judged the record and wrote it outside withIntentMintLock, so two ingests on one intent, or an ingest beside a condition disposition, each wrote the bytes they read and the later erased the earlier with both exiting 0. The receipt resolution, every check on the record and the write(s) now run in one hold (ingestLocked); reingestVerdict and deadLetter are reached only from there, so the dead-letter's two writes land in the same hold. A repository with no intent store is refused without the lock, which would otherwise create the store in a refusal. The tests land a whole verb in the window through the lock seam rather than on the wall clock: a verdict ingested first makes the second a reported replacement; a malformed verdict never dead-letters over a verdict that landed; a condition disposition survives the ingest. Refs: iss-2609261935343851 Assisted-by: Claude:claude-opus-5-5
…ore lock Resolves: iss-2609261935343851 Assisted-by: Claude:claude-opus-5-5
emitAuditWith, reached from the spec-close ship move, the bundle close, `intent audit` and the audit drain, read a shipped intent, parked the OWED stub and wrote the record back outside withIntentMintLock, so a condition disposition or verdict ingest landing between its read and its write was erased. The read, the marker judgement and the writes now run in one hold (emitLocked); every caller reaches it after any hold of its own is released, so the lock is never taken twice. Refs: iss-2609261935407925 Assisted-by: Claude:claude-opus-5-5
… lock Resolves: iss-2609261935407925 Assisted-by: Claude:claude-opus-5-5
… lock AddRelatedIssue (the intent half of `capture promote --intent`, any bucket) and Link (`intent link`) rewrote the intent's frontmatter as a read-modify-write outside withIntentMintLock, so a hold, a disposition, an ingest or a second edge landing between the read and the write was erased. Both now read and write in one hold, and AddRelatedIssue judges the existing list on the bytes read there rather than on the corpus, so two edges written to one intent both stand. Refs: iss-2609261935407995 Assisted-by: Claude:claude-opus-5-5
…tore lock Resolves: iss-2609261935407995 Assisted-by: Claude:claude-opus-5-5
Left open by the fix2-drainA1 sweep: the fix needs an exported seam for another package to take the intent store lock, the gap relink.Repoint already records. Refs: iss-2609261941039204 Refs: iss-2609261254247117 Assisted-by: Claude:claude-opus-5-5
The consistency request carries no findings shape, the Role 2 sibling of the fidelity request's missing verdict shape; and the fidelity request's delivered line still leaves the diff range to the host, the third addendum of the scope-condition record, which needs a design call on attestation. Refs: iss-2609262011046013 Refs: iss-2609262011091645 Refs: iss-2609181121301638 Refs: iss-2609181121305984 Assisted-by: Claude:claude-opus-5-5
The verdict ingest returns one struct for every outcome, so a dead-lettered verdict's JSON carried zero-valued criteria counters beside a conditions count, which a lane took for a rollup. Its JSON now states what the ingest recorded (`recorded`: verdict, quarantine or nothing) and carries the acceptance rollup and the disposition split only beside a recorded verdict, where a zero is a count. A quarantine keeps the one split it did record, every scope condition untested, as `conditions_untested`, so the agreement with the record that iss-2608300927241768 asked for still holds. The Go fields are unchanged, so the text render and every in-process reader are untouched. A re-emit on an OWED receipt rewrote the request and reported only already_owed, which read as nothing happened. The emit result now names the act beside the state (`request_written`), and `request_path` is the request this emit wrote: a terminal receipt, whose emit writes nothing, names none rather than a path to a request it did not write (the same honesty rule from the other side). The text render says `request rewritten:` on an owed re-emit and `no request written` on a terminal one. Refs: iss-2609190337545165 Refs: iss-2609190337598356 Refs: iss-2608300927241768 Assisted-by: Claude:claude-opus-5-5
…d the shape The request listed the acceptance criteria as "numbered ac-1..ac-K" without printing K, and never named the scope-condition identities the verdict must dispose: they reached the auditor only as HTML comments in the record, and a miscount quarantined a verdict. The request now prints K (the bullet count the ingest judges against) and carries a Scope Conditions block listing every condition under its cond- identity with its text, read through ParseClaims, the reader the ingest's coverage check uses; a conditionless intent is told its list is empty, and an unstamped condition is listed as one. It also carried no verdict shape, which lived only in the bundled agent definition, so a reviewer working from the request learned it from refusals. A Verdict shape section now states it, rendered by reflection from the verdict struct the ingest decodes with DisallowUnknownFields: the schema itself, never a second copy, so a field added to the struct moves the shape and the prompt_hash with it. The ingest's --verdict-json help names the section. The dry-run validator the second record's addendum offers as an alternative remedy is not added: the record names either remedy as sufficient. Both blocks sit in the hashed prompt body, which stays a pure function of the receipt and the record, so the ingest recomputes prompt_hash as before. The auditor definition (0.4.1) names both blocks. The record's third addendum, the delivered range the host still supplies, is captured apart because it needs an attestation design call. Refs: iss-2609181121301638 Refs: iss-2609181121305984 Refs: iss-2609262011091645 Assisted-by: Claude:claude-opus-5-5
The Role 2 sibling of the fidelity request's missing verdict shape: the consistency request stated the classes and the rubric, while the findings shape lived only in the agent definition. It now carries a Findings shape section rendered from the payload struct the consistency ingest decodes, through the same reflective renderer the fidelity request uses; a finding shows both of its ends because the ingest requires exactly two. The section sits in the hashed prompt body, which stays a pure function of the scope and the corpus. Refs: iss-2609262011046013 Assisted-by: Claude:claude-opus-5-5
… rollup Resolves: iss-2609190337545165 Assisted-by: Claude:claude-opus-5-5
…ting inside the ledger A capture transition's repoint waited up to five seconds for the intent store's lock inside the ledger lock, whose own budget is also five seconds, so a long intent hold failed a third process's ledger writer with allocator contention; migrate --apply nested the same way. And an intent verb's repoint rewrote linking ledger records under the intent lock alone, erasing a ledger writer that landed between its read and its write. intent.WithLedgerThenMintLock is now the one pair acquisition: ledger first, then the intent lock asked for only briefly, and on contention the ledger lock is let go and rested before the next attempt, until the intent lock's budget; fn runs once, with both held. The transition repoints in a fresh pair after its move's hold is released, as the intent verbs do, and migrate --apply runs under the pair. The capture package registers its ledger lock with the intent package from init (intent cannot import it), and every intent verb's repoint takes it through the pair. A tree with no ledger takes no ledger lock and grows none; a ledger with no lock registered refuses the repoint, reported as the verb's relink error, rather than rewrite it unlocked. Refs: iss-2609262218059995 Refs: iss-2609262143209970 Assisted-by: Claude:claude-opus-5-5
…dges the plan again Embark classified every target path, then wrote the set with no lock, so an intent or an issue created at a planned target between the two was replaced without a conflict. The write now runs under the target's ledger lock (only when it creates an issue record, since taking it plants a ledger) and then its intent store's lock, through intent.WithLedgerThenMintLock, and every planned write is classified again under them: a record that landed in the window is a conflict and refuses the whole write, as any other conflict does. The command page and the embark brief chapter state it. Refs: iss-2609262143265180 Refs: iss-2609262218306589 Assisted-by: Claude:claude-opus-5-5
…n crashed attempt The reading ingest's prose-citation refusal is returned unrecorded, so the run stays parked for its re-worded ingest, but it skipped the rollback refuse() performs on every other refusal past the identity point: records an earlier, interrupted attempt at the same run id left in the ledger stood until some later commit path swept them, although a refused run leaves no reading records. It now calls rollbackThisRun as refuse() does; the rollback writes no outcome, so the re-worded run is still admitted. The comments that said every refusal from the identity point on is recorded name the exception, the reading chapter states the rollback, and the decision log records the departure. Refs: iss-2609261835118276 Assisted-by: Claude:claude-opus-5-5
…dger to other writers Resolves: iss-2609262218059995 Assisted-by: Claude:claude-opus-5-5
…the ledger lock Resolves: iss-2609262143209970 Refs: iss-2609262218309668 Assisted-by: Claude:claude-opus-5-5
… intent lock Resolves: iss-2609262143265180 Assisted-by: Claude:claude-opus-5-5
…ledger lock Resolves: iss-2609262218306589 Assisted-by: Claude:claude-opus-5-5
Refs: iss-2609262257227538 Assisted-by: Claude:claude-opus-5-5
…derives from it acquireFlock doubled its backoff after the sleep while it was under 100ms, so 80ms became 160ms and the waiter's real poll ceiling was 160ms, while the pair acquisition's ledger rest, written as 150ms, claimed to outlast a 100ms ceiling. A ledger writer sleeping 160ms could miss the whole window the pair left the ledger free. The ceiling is now one exported constant, fsutil.LockPollCeiling (100ms), and the poll is doubled and held at it, never past it. The pair's rest is 2 x LockPollCeiling, derived rather than restated. Two tests pin the guarantee: the poll never sleeps past its ceiling and reaches it, and the rest outlasts the ceiling by at least half again. Refs: iss-2609262257227538 Assisted-by: Claude:claude-opus-5-5
…ck poll's ceiling Resolves: iss-2609262257227538 Assisted-by: Claude:claude-opus-5-5
… of three A link repoint rewrote spec records with no spec lock, and no spec writer took one: the spec store's only lock was the mint's. spec close renamed a spec open/ -> closed/ while a repoint that had read it at open/ wrote it back there, leaving one record in both status folders, and a spec edit landing between a repoint's read and its write was erased. The mint's flock on specs/ becomes the spec store's one lock, and every spec writer takes it: the mint (Create and its siblings), Close (the load and the rename are one critical section under it), and a new spec.Discard, which the refused plan and bundle plan use to take back the spec they minted instead of a bare os.Remove. Writers outside the package — every link repoint (intent's repointUnderLock, capture's repointMovedIssue), a lifeboat embark and capture's migrate apply — get it through intent.WithLedgerThenMintLock, which becomes the three-lock acquisition: ledger, then intent, then spec. It keeps its retry rule for the third lock: each inner lock is asked for within pairIntentTry, and on contention for either every held lock is released for the rest before the next attempt, so no earlier lock is held while a later one is waited on. The name is kept, not renamed, to leave its five call sites untouched. Lock order, stated beside the spec lock, the acquisition and the intent lock: ledger -> intent -> spec. The spec lock is innermost; plan's mint nests it inside the intent lock, close, discard and a remainder mint take it alone, and the spec package imports neither earlier lock's package. None is reentrant; no chain takes the spec lock twice (the acquisition's callbacks run relink or embark writes only, and plan's mint and discard run in sequence). Tests, each watched red on a scratch copy of the base with the new API exported but no writer taking it: a repoint racing spec close leaves the spec in closed/ alone (intent and capture sides); a spec edit racing a repoint is kept; the three locks are taken ledger -> intent -> spec with a repoint holding all three; close and discard wait for the lock; the acquisition leaves the ledger and intent locks free while it waits for the spec lock. Refs: iss-2609262218309668 Assisted-by: Claude:claude-opus-5-5
…c store's lock Resolves: iss-2609262218309668 Assisted-by: Claude:claude-opus-5-5
… with CI Assisted-by: Claude:claude-opus-5-5
…er the queue cap Assisted-by: Claude:claude-opus-5-5
The 9 MiB history capture starts no goroutine of ours, but the identity probe's git calls do start os/exec's pipe-copy goroutines, so "one goroutine" was not literal; the comment, the skip message and the resolution text say "no goroutine of ours". The prose prefilter's blanking replaces bytes with spaces rather than removing them; the argument holds because a space can spell no word, and the comment says so. Wording only, from the ciFast review. Refs: iss-2609261924541555 Assisted-by: Claude:claude-opus-5-5
…; fix/drain-spec-lock) Assisted-by: Claude:claude-opus-5-5
Measured on a clean no-local clone of 4b715ad with a dry-run assemble per position; each window is the smallest ten-thousand boundary with at least one per cent headroom (ceil(tokens * 1.01 / 10000) * 10000): widening 1,301,580 tokens -> 1,320,000 (was 1,280,000) entailment 376,527 tokens -> 390,000 (was 380,000; 0.9% headroom) detection 1,310,616 tokens -> 1,330,000 (was 1,290,000) The comparative entry is not a tree measurement and is unchanged. Refs: iss-2609251455354719 Assisted-by: Claude:claude-opus-5-5
spec.Close (and spec.Discard) on a tree with no spec store plant an empty store before failing, against the rule the store states beside WithStoreLock. Found by the drainSpec review. Refs: iss-2609262342345159 Assisted-by: Claude:claude-opus-5-5
Close and Discard took the store lock through withStoreLock, whose ensureDir creates .abcd/development/specs/ in order to lock it, so on a tree with no spec store Close planted an empty store before refusing the id as not found, and Discard planted one to remove nothing. That is the very thing the store's own rule beside WithStoreLock forbids. Both now check for the store first, through the same storeExists reading WithStoreLockWithin uses: Close refuses the id as not found and Discard has nothing to remove, and neither creates the store. Unlike WithStoreLockWithin they do not run their body unlocked, because with no store there is nothing for it to do. TestAWriterOnATreeWithNoSpecStorePlantsNone was watched RED on a scratch copy of the previous tip (both subtests: the store was planted) and is GREEN here. Refs: iss-2609262342345159 Assisted-by: Claude:claude-opus-5-5
… store Resolves: iss-2609262342345159 Assisted-by: Claude:claude-opus-5-5
Brings integ6 (#730, ciFast already merged here, so it arrives as already-merged), integ7 (#731), integ8 (#732) and integ9 (#733) onto integ/land-10. Conflicts, by hunk: - internal/core/capture/workflow.go: an import-block collision only; both the chain's intent import and main's issuerecord import are kept. - .abcd/config/reading-presets.json: the window hunks take this branch's figures for now; they are re-measured at the merged tip in a follow-up. Auto-merged: DECISIONS.md, commands.md, cli.go, alloc.go, reading.go, create.go, ingest.go, 05-intent.md, 23-reading.md and the command pages. commands.md and surface.json regenerate with no drift. docs-lint extra_roots still names the root CONTRIBUTING.md and SECURITY.md: both are at the repository root on main, so they stay. Assisted-by: Claude:claude-opus-5-5
Close and Discard checked that the spec store existed and then took its
lock, and the lock creates an absent store. A store removed between the
two was re-planted empty: Close then failed "not found" over a fresh
.abcd/development/specs/, and Discard returned nil over one.
withStoreLock now takes a mode. The mint and the three-lock path keep
createStore; Close and Discard take storeMustExist, in which the open
that takes the lock is the existence check and an absent store is
refused with errStoreAbsent before anything is created. Close maps it to
"not found" and Discard to success, as before, and the separate
pre-check is gone. beforeStoreLock is a test seam that runs as the lock
is entered.
TestAStoreRemovedBeforeTheLockIsNotReplanted removes the store through
that seam. Both subtests were red on a git-archive copy carrying the
seam without the mode ("re-planted the removed spec store") and are
green here.
Refs: iss-2609262342345159
Assisted-by: Claude:claude-opus-5-5
Measured on a clean `git clone --no-local` of b727b96 with a dry-run assemble per position; window = ceil(tokens * 1.01 / 10000) * 10000. - widening 1,322,032 tokens / 5,089,824 bytes: 1,320,000 -> 1,340,000 - entailment 382,812 / 1,473,830: 390,000 kept (1.88% headroom) - detection 1,331,068 / 5,124,612: 1,330,000 -> 1,350,000 comparative is unchanged: it has no tree measurement. 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.
Integration branch 10: reviewed lanes land as one PR, with one window recalibration and one preflight. The audit/lock chain is four lanes that built on one another, merged as one merge of its tip. ciFast already landed with integration branch 6 (#730); this branch carries the same commits, so they merge as already merged and its records are not declared again here.
drainGate (preflight gate parity).
make preflightruns its Go steps on the toolchaingo.moddeclares, through one resolver that refuses rather than falling back to the Go on PATH, so a test that passes locally also passes on CI's toolchain. Preflight arms record-lint over the branch's own range, as CI does. docs-lint checks the links of every committed markdown file and arms the persona rule. RS001 and RS005 name a merge-queue collision instead of blaming history from before the branch diverged. The warn-baseline ratchet that remains of the lint-scope record needs a ruling and is deferred past v0.11.0 on the record.ciFast (the macOS check job under the queue's cap; already on main through #730). The race-instrumented time of the cli package roughly halves with every assertion kept. The 9 MiB history capture and the surface-prose check run in the uninstrumented lane only, on both CI legs. The prose check skips a command path whose words the prose never spells. Record-id filename grammars compile once per family. No workflow, timeout, required job name or ruleset changes.
The audit/lock chain (drainA1, drainA2, drainL, drainSpec). An intent's review block has one bounded reader and one writer. A re-ingest keeps link references and human notes below the block, and markers count only on live lines. The fidelity and consistency requests state the condition identities, the count and the exact verdict or findings shape, and the emit and ingest results say what they did. A stale-policy refusal names the re-emit. The verdict, consistency and reading ingests refuse agent prose that cites a record id that does not resolve. Every writer of ledger, intent and spec records takes its store's lock through one seam, in one order: ledger, then intent, then spec. A waiting writer never holds an earlier lock while it waits for a later one. That covers the verdict ingest, the review emit, related-issue edges, spec links, every repoint, capture migrate, embark, spec close and discard, and plan's rollback.
Done at the tip. Wording from the ciFast review: the race-skipped capture starts no goroutine of ours, which is not the same as one goroutine, and the prose prefilter's blanking replaces bytes with spaces. A finding from the drainSpec review is fixed: spec close and discard planted an empty spec store on a tree without one. It was captured, fixed test-first and resolved here. The cold-reading windows are recalibrated at the tip (see Re-merge).
Operational. Once this lands, a fidelity-audit or consistency request written before it cannot have its verdict ingested. That covers the request files in any checkout's local tier and every OWED marker on main, which at this tip are six: itd-63 (rcp-3c9fb4ba9770), itd-131 (rcp-8a6673e9bc8a), itd-2609150819432059 (rcp-97519c4308ad), itd-76 (rcp-595934bbc552), itd-2609212137116617 (rcp-1887e5f574ef) and itd-2609212130146198 (rcp-68f6cce6a701). Each needs a re-emit before any verdict for it is ingested. The request now carries the verdict shape, so its prompt hash moves. Re-emit the request (
abcd intent audit <itd>, orabcd intent consistencyfor the consistency pass), then run the review again. The ingest refusal says this.Re-merge. Main after integration branches 6 to 9 (#730 to #733) is merged in. Two files conflicted, both by hunk: an import block in the capture workflow, where both sides' imports are kept, and the reading presets, which are re-measured. docs-lint's link check still names the root CONTRIBUTING.md and SECURITY.md, because both are at the repository root on main. The drainSpec LOW is closed properly: a spec store removed between close's or discard's decision to lock it and the lock itself is no longer re-planted empty, because the lock for those two writers refuses an absent store rather than creating one (tested through a seam that removes the store at the lock, red before the fix). No intent-record writer arrived on main outside the lock seam: main's new code reads intent and spec records and writes none. Windows at the re-merged tip: widening 1,340,000 (measured 1,322,032), entailment 390,000 kept (382,812), detection 1,350,000 (1,331,068).
Reviews: drainGate SHIP (one LOW accepted: its commits from the docs-lint widening to the RS001 resolution are red until the last test commit, and main keeps merge commits). ciFast SHIP (two wording findings, fixed at the tip). drainA1 SHIP (review and re-review). drainA2 SHIP. drainL SHIP, and its fix round verified SHIP. drainSpec SHIP (one LOW, fixed at the tip).
No intent ships here.
Resolves: iss-2609020529185438
Resolves: iss-2609021152026246
Resolves: iss-2609090951287096
Resolves: iss-2609091433422134
Resolves: iss-2609181121301638
Resolves: iss-2609181121305984
Resolves: iss-2609190337545165
Resolves: iss-2609190337598356
Resolves: iss-2609231011136579
Resolves: iss-2609231036448320
Resolves: iss-2609251451432601
Resolves: iss-2609251451434656
Resolves: iss-2609261254247117
Resolves: iss-2609261835118276
Resolves: iss-2609261850045839
Resolves: iss-2609261935343851
Resolves: iss-2609261935407925
Resolves: iss-2609261935407995
Resolves: iss-2609261941039204
Resolves: iss-2609262011046013
Resolves: iss-2609262104070666
Resolves: iss-2609262143209970
Resolves: iss-2609262143265180
Resolves: iss-2609262218059995
Resolves: iss-2609262218306589
Resolves: iss-2609262218309668
Resolves: iss-2609262257227538
Resolves: iss-2609262342345159
Refs: iss-46
Refs: iss-2609262011091645
Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5