Development record
abcd Refuses To Publish A Payload Until The Full Ship-Time Pre-Flight Gate Suite Passes, Not Just The Secret Scan
Press Release
/abcd:launch shipgains the complete Phase-5 pre-flight gate suite the brief specifies: on top of the spc-64 (predecessor store) secret + PII scan, it adds the custom-regex identity layer (home-dir paths, real emails, GitHub usernames), marker-block sanity,plugin.json+marketplace.jsonvalidation, dirty-tree refusal, and the warn-fail documentation and hook-compliance checks — each hard-failing (or warn-failing) exactly as the brief's § 1 pins. Todaylaunchis a dry-run/render-only stub: it runs the spc-64 (predecessor store) gate for real but renders every other gate as "(not yet implemented)". That means a real promotion would ship with those gates inert — precisely the invisible risks abcd exists to catch. This intent graduates the dry-run's "not yet implemented" lines into a real, runnable, fail-closed gate suite so a publish is blocked on a finding, not merely previewed.
"The dry-run already tells me a home-directory path or a broken plugin.json would be a problem," said a maintainer. "But 'would' isn't 'does' — ship has to actually hard-fail on it. I don't want to hand-audit the payload before every snapshot; the gate suite should."
Why This Matters
abcd's whole thesis is routing the risks a non-expert cannot see to a fail-closed gate ([[itd-62-pluggable-safety-gate]]). Its OWN publish path is the highest-stakes instance of that: a launch cuts a curated release from the single repo — packaging that excludes .abcd/** (adr-28) — and publishes it, where a leaked home-dir path, real email, or committed secret is irreversible. The canonical launch brief (04-surfaces/04-launch.md § 1) already specifies the full gate suite; spc-64 (predecessor store) built the secret/PII floor; but the custom-regex identity layer, marker-block sanity, plugin/marketplace validation, and dirty-tree refusal are still stubs. Until they are real, launch ship cannot honestly claim to gate a promotion — and the project standards (no home-dir paths, no real emails, no usernames in file content) have no enforcement at the one moment they matter most. This closes that honesty gap the same way spc-74 closed the doc-fidelity one: make the built reality match what the surface implies.
What's In Scope
- The custom-regex identity layer over the resolved payload: home-dir paths (
/Users/...,/home/...), real emails, GitHub usernames from git config — hard-fail (per brief § 1). - Marker-block sanity over shipped Markdown — hard-fail on malformed.
plugin.jsonparse +marketplace.jsonreference cross-check — hard-fail.- Doc-history gate — hard-fail on change-history / rationale-for-change
narration in shipped doc bodies (the
abcd-cli/CLAUDE.md"docs describe present state, never change history" rule, enforced at ship). On a finding the gate extracts the flagged passage and OFFERS to auto-append it to the changelog owned by [[itd-67-installable-versioned-plugin]] — so the fix is one confirmation, not a manual rewrite. Reuses the spc-74doc_fidelitysurface as the detector. - Dirty-tree refusal unless
--allow-dirty; git-inferable-metadata scan (dates/authors/versions in file content, per project standards). - Warn-fail gates: hook-compliance, documentation auditor over
docs/. - A single fail-closed orchestrator that runs the suite against the § 2 payload include-manifest and writes the pre-flight report (
.abcd/logbook/launch/<ts>/preflight.{json,md}), returning non-zero on any hard-fail — the Phase-5shipbehaviour, distinct fromdry-run's always-exit-0 preview. The orchestrator RUNS ALL gates and collects ALL findings before returning a verdict (report-everything, one fix pass), with a single ordering constraint: doc-history reroute runs before the dirty-tree gate (see below). - Gate composition + tiering (grill Q2/Q4/Q6): the custom-regex identity layer is a SIBLING gate the orchestrator composes (spc-64, predecessor store, keeps its pinned gitleaks+pii.py engines); the identity layer flags only leaks of the LOCAL git identity (user.name/user.email, dev-repo remote URLs) with the public org handle allowlisted, never arbitrary handles. The suite is built as a callable unit so CI and pre-commit can invoke the SAME checks earlier (advisory/blocking per tier), while
launch shipholds the authoritative hard-fail. - Doc-history detector (grill Q3/Q5): a LAYERED detector — deterministic narrow patterns (past→present transitions: "used to X", "changed from X to Y", "no longer X", "migrated from", "renamed X to Y") run always, local-first, never hard-failing on bare present-tense "now"/"previously"; an OPTIONAL oracle/LLM pass (reusing the spc-27 oracle + spc-64 fail-closed-on-unavailable precedent, both predecessor store) adjudicates ambiguous hits when a backend is available, falling back to surface-for-confirmation when not. Auto-reroute stages its own changelog + doc edits as one fix transaction; the dirty-tree gate runs AFTER reroute resolution so a clean staged fix is not mistaken for unexpected dirt.
- Reuse of the unmodified upstream scanners (gitleaks ≥ 8.18.0 pinned per spc-64 (predecessor store); wrap, never fork) per the wrap-only rule.
What's Out of Scope
- The payload render / mirror-mode / versioning machinery (that is the sibling launch work [[itd-66-launch-payload-render-parity]] — this intent is the GATES only).
- Replacing the spc-64 (predecessor store) secret/PII engine or adopting Presidio as the wired engine (a separate recorded decision per brief § 1 / spc-64 (predecessor store) C1a).
- Forking or reimplementing any scanner — configure and wrap the trusted ones.
- Publishing anything: this intent decides go/no-go; it never pushes.
Scope Conditions
None stated.
Delivery Status
Reviewed against the v0.9.0 tree on 2026-09-20. Delivered and cited: the
identity hard-fails on a home path, a real email and a username, naming the
file (internal/adapter/scanner/identity.go, TestRenderPayloadSecretRefuses,
TestShipBlocksOnSecret); the scanner failing closed when unavailable
(TestZeroCoverageRefuses); a local identity flagged while an org handle
passes (TestOtherIdentitiesArmMatchersAndHandleStaysPublic); a clean payload
exiting 0 (TestShipCleanWouldPublish); the manifest half of the marker
criterion (smoke.go). Moot: the auto-append of narration into the changelog,
because the changelog is derived (adr-37); the reroute-not-dirt criterion,
because no reroute exists. Scoped by spc-2609201955279019 and delivered with
it on 2026-09-25: the marker-block check, the change-narration detector, the
dirty-tree refusal, the two warn-fail rows, the report file, the multi-gate
test (the evidence for each is under ## Decisions).
Decisions
- 2026-09-25 — two criteria are superseded in part by the derived
changelog, and the rest of each stands. The changelog is derived from the
records: adr-37
makes the dated CHANGELOG heading the release instrument, and its
2026-08-26 amendment records that the ingest composes that section from the
records that shipped, behind the completeness bijection (first cut this way:
v0.5.1, dcn-2608160000000008). A gate cannot append prose to
a changelog nothing hand-writes, so:
- The doc-history criterion (third). Superseded: the offer to auto-append
the flagged passage, and "the change is recorded in the changelog" as a
condition the gate checks — the change reaches the changelog through its
record, not through the gate. Stands and is met: the gate HARD-FAILS on a
change-narration sentence in a shipped doc body and names it
(
change-narrationrow;narrationFindingsininternal/core/launch/gates.go;TestNarrationGateHardFailsOnAChangeConstruct), and the ship proceeds only once the doc describes present state. - The reroute criterion (last). Superseded whole: with no auto-append there
is no reroute and nothing it stages. The ordering it protected stands in
another form and is met: the cut runs the dirty-tree gate before it writes
anything, and the render after its own writes skips it, so the cut's own
output is never read as dirt (
DirtySkipingates.go;TestLaunchShipRefusesADirtyTreeUnlessAllowed).
- The doc-history criterion (third). Superseded: the offer to auto-append
the flagged passage, and "the change is recorded in the changelog" as a
condition the gate checks — the change reaches the changelog through its
record, not through the gate. Stands and is met: the gate HARD-FAILS on a
change-narration sentence in a shipped doc body and names it
(
- 2026-09-25 — how the remaining criteria are met (spc-2609201955279019).
Marker blocks:
marker-blockrow,TestMarkerBlockGateRefusesAMalformedBlock. Dirty tree:dirty-treerow andabcd launch ship --allow-dirty, the override recorded in the pre-flight report with every path it carried (TestDirtyTreeGateRefusesUnlessAllowed,TestLaunchShipRefusesADirtyTreeUnlessAllowed). Warn tier: thedocumentation-auditorrow runs the docs-lint engine the front door measures, thehook-compliancerow checks hook executability, handler commands and timeouts, and"strict_warnings": truein.abcd/config/launch-payload.jsonis the configured strict tier (TestWarnRowsSurfaceWithoutBlocking). Report: every preview and every cut that renders a payload writespreflight.{json,md}under.abcd/.work.local/logs/launch/<ts>/, the tier iss-73 settled (TestWritePreflightReportLandsInTheLocalTier,TestLaunchDryRunWritesThePreflightReport). Run-all-collect-all:TestSuiteReportsEveryGateInOnePassplants findings in four gates and finds all four in the preview, the render refusal and the written report. Bare present-tense "now"/"previously":TestNarrationGatePassesPresentTense.
Acceptance Criteria
Given-When-Then per the itd-1 discipline.
- Given a resolved payload containing a home-dir path, real email, or GitHub username, when the ship gate suite runs, then it HARD-FAILS (non-zero) and names the finding with its file and match, never a silent pass.
- Given a malformed marker block or an unparseable
plugin.json/ danglingmarketplace.jsonreference in the payload, when the suite runs, then it hard-fails with the specific defect. - Given a shipped doc body containing change-history or rationale-for-change narration ("previously X, now Y", migration notes), when the doc-history gate runs during ship, then it HARD-FAILS and offers to auto-append the flagged passage to the [[itd-67-installable-versioned-plugin]] changelog; the ship proceeds only once the doc describes present state and the change is recorded in the changelog.
- Given a dirty working tree, when ship runs without
--allow-dirty, then it refuses; with--allow-dirtyit proceeds and records the override. - Given a doc-auditor or hook-compliance concern, when the suite runs, then it WARN-fails (surfaced, non-blocking unless configured strict).
- Given gitleaks is absent or older than the pinned floor, when the suite runs, then it fails closed (never a regex fallback), consistent with spc-64 (predecessor store).
- Given a fully clean payload, when the suite runs, then it exits 0 and writes the pre-flight report.
- Given a payload with multiple independent findings across different gates, when the suite runs, then it reports ALL of them in one pass (run-all-collect-all), not just the first hard-fail.
- Given the identity gate, when it scans, then it flags a leaked LOCAL git identity (maintainer's personal name/email/handle) but NOT the allowlisted public org handle appearing in install docs.
- Given a doc using bare present-tense "now"/"previously" without a change construct, when the doc-history gate runs, then it does NOT hard-fail; a genuine "changed from X to Y" / "no longer" construct DOES.
- Given the doc-history auto-reroute writes the changelog and stages it, when the dirty-tree gate then runs, then it treats the reroute's staged fix as expected (not dirt) because reroute is ordered before it.
Open Questions
- ~~Should the custom-regex identity layer live inside the spc-64 (predecessor store) gate module (extending its config) or as a sibling gate the orchestrator composes? (Reuse vs separation.)~~ Answered by this intent's own scope (grill Q2/Q4/Q6, "Gate composition + tiering"): a sibling gate the orchestrator composes, with spc-64 (predecessor store) keeping its pinned engines; delivered as
internal/adapter/scanner/identity.go(Delivery Status). - ~~What is the exact GitHub-username source — git config
user.name/user.email, remote URLs, or a maintained denylist — and how are legitimate org handles in docs distinguished from leaked personal ones?~~ Answered by the same scope bullet: the LOCAL git identity (user.name/user.email, the dev-repo remote URL), never a denylist of arbitrary handles, with the public org handle allowlisted; delivered and pinned byTestOtherIdentitiesArmMatchersAndHandleStaysPublic. - Does the documentation auditor reuse the existing doc-scout machinery, or is it a launch-specific pass? Open. The
documentation-auditorrow runs the deterministic docs-lint engine today; whether the host-delegateddocumentation-auditoragent (brief/05-internals/01-agents.md) joins it is not ruled. - Where does
--allow-doc-warningssit relative to a strict CI invocation of the same suite? Open. No such flag ships: a warning refuses nothing unless the repository sets"strict_warnings": true, and whether a per-run override of that setting is wanted is not ruled.
Audit Notes
Fidelity review — receipt rcp-28b4f73968a9 (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:d1dc999aa4e96dcd17f1bf6b2596d9c3612dbd635f617f29130bd14cbb0b4c1c Input attestations: diff:44eaaf64^..811fba17 -- internal/core/launch internal/surface/cli/launch.go internal/surface/cli/launch_gates_test.go (the gate suite from its feat commit to main)@sha256:fc86184375dafd5ef674ae77e5910e15c2193c9d82a928f9600c68d0bef89811;
Acceptance rollup: MET 6 · MET_WITH_CONCERNS 4 · NOT_MET 1 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: the identity scanner classifies the caller's home path, the other-home shape, the git identity and the local username as findings, the render precheck refuses on a finding in an included file before any write, and the ship path blocks on it naming the file evidence: internal/adapter/scanner/identity.go:44 — "kindHomeSelf = "home_path_self"" evidence: internal/adapter/scanner/identity.go:72 — "func ProbeIdentity(repoRoot string) Identity {" evidence: internal/core/launch/render_scan_test.go:18 — "func TestRenderPayloadSecretRefuses" evidence: internal/core/launch/dryrun_test.go:52 — "func TestShipBlocksOnSecret"
- ac-2 — MET: the marker-block row hard-fails a malformed block naming it, and the lockstep check refuses a marketplace entry with no source, an unparseable sourced manifest, or a name the sourced manifest does not carry evidence: internal/core/launch/gates.go:306 — "func markerBlockFindings(bundle Bundle) []GateFinding {" evidence: internal/core/launch/gates_test.go:61 — "func TestMarkerBlockGateRefusesAMalformedBlock" evidence: internal/core/launch/lockstep.go:118 — "marketplace lists %q but the sourced manifest is named %q — the install id would not resolve" evidence: internal/core/launch/render_test.go:222 — "func TestRenderPayloadRefusesAnUnstampableMarketplace"
- ac-3 — MET_WITH_CONCERNS: the change-narration row hard-fails a change construct in a shipped doc body and names it; the offer to auto-append the passage to the changelog and the changelog-recorded condition are not delivered, superseded by the derived changelog (adr-37) as the intent's 2026-09-25 decision records, and the hard tier's reach is the subject of the open finding iss-2609251827286563 evidence: internal/core/launch/gates.go:749 — "func narrationFindings(bundle Bundle) []GateFinding {" evidence: internal/core/launch/gates_test.go:184 — "func TestNarrationGateHardFailsOnAChangeConstruct" evidence: .abcd/development/intents/shipped/itd-65-launch-preflight-gate-suite.md:98 — "Superseded: the offer to auto-append" evidence: .abcd/work/issues/open/iss-2609251827286563-the-launch-gate-suite-s-change-narration.md:1 — "the-launch-gate-suite-s-change-narration-gate-hard-fail-tier"
- ac-4 — MET_WITH_CONCERNS: the dirty-tree row refuses a dirty tree and --allow-dirty proceeds with the override and its paths in the report, on the core and through the CLI; the concern is the open finding that the render path hardcodes the gate to DirtySkip (iss-2609251827294854), so one entry point states a policy it does not apply evidence: internal/core/launch/gates.go:941 — "func dirtyTreeGate(repoRoot string, policy DirtyPolicy) (GateSummary, []string, string) {" evidence: internal/core/launch/gates_test.go:265 — "func TestDirtyTreeGateRefusesUnlessAllowed" evidence: internal/surface/cli/launch_gates_test.go:73 — "func TestLaunchShipRefusesADirtyTreeUnlessAllowed" evidence: .abcd/work/issues/open/iss-2609251827294854-renderpayload-hardcodes-the-dirty-tree.md:1 — "renderpayload-hardcodes-the-dirty-tree-gate-to-dirtyskip-for"
- ac-5 — MET_WITH_CONCERNS: the documentation-auditor and hook-compliance rows are warn tier, surfaced as warnings and refusing only under strict_warnings in the include config; the concern is the open finding that the hook-compliance row fails open on one shape (iss-2609251827104081) evidence: internal/core/launch/gates.go:181 — "if req.Policy.StrictWarnings {" evidence: internal/core/launch/gates.go:1003 — "func hookComplianceGate(bundle Bundle) GateSummary {" evidence: internal/core/launch/gates_test.go:330 — "func TestWarnRowsSurfaceWithoutBlocking" evidence: .abcd/work/issues/open/iss-2609251827104081-the-launch-gate-suite-s-hook-compliance.md:1 — "the-launch-gate-suite-s-hook-compliance-row-fails-open-on-a"
- ac-6 — MET_WITH_CONCERNS: the suite fails closed when the scanner is unavailable or leaves an included file uncovered, never publishing; the premise of the criterion does not hold as written, because gitleaks is not the engine: the 2026-07-24 ruling made the native pattern scanner the default engine with no shell-out and gitleaks an optional stronger one, so there is no pinned-floor check and the fail-closed property attaches to scanner availability and coverage instead evidence: internal/core/launch/dryrun.go:241 — "if scan.Unavailable {" evidence: internal/core/launch/dryrun.go:247 — "// Fail closed on the coverage gap: any include-selected file the scanner" evidence: internal/core/launch/dryrun_test.go:109 — "func TestZeroCoverageRefuses" evidence: internal/adapter/scanner/finding.go:6 — "gitleaks/trufflehog shell-out) — so it is fully testable and reusable across" evidence: dcn-2607240000000004 — "native patterns are the default engine, gitleaks is the stronger"
- ac-7 — MET: a clean payload would publish with exit 0 and every preview or cut that renders a payload writes preflight.json and preflight.md under the local tier evidence: internal/core/launch/dryrun_test.go:78 — "func TestShipCleanWouldPublish" evidence: internal/core/launch/gates_test.go:464 — "func TestWritePreflightReportLandsInTheLocalTier" evidence: internal/surface/cli/launch_gates_test.go:34 — "func TestLaunchDryRunWritesThePreflightReport"
- ac-8 — MET: the suite runs every gate and collects every finding before returning, and the test plants findings in four gates and finds all four in the preview, the render refusal and the written report evidence: internal/core/launch/gates.go:163 — "func runGateSuite(req suiteRequest) suiteResult {" evidence: internal/core/launch/gates_test.go:402 — "func TestSuiteReportsEveryGateInOnePass"
- ac-9 — MET: the identity probe arms matchers from the local git identity and its other scopes while the public org handle passes as a public handle evidence: internal/adapter/scanner/identity.go:230 — "func isPublicHandle(name string, id Identity) bool {" evidence: internal/adapter/scanner/identity_scopes_test.go:111 — "func TestOtherIdentitiesArmMatchersAndHandleStaysPublic"
- ac-10 — MET: bare present-tense now/previously passes and a changed-from / no-longer construct hard-fails, each pinned by its own test evidence: internal/core/launch/gates_test.go:221 — "func TestNarrationGatePassesPresentTense" evidence: internal/core/launch/gates_test.go:184 — "func TestNarrationGateHardFailsOnAChangeConstruct" evidence: internal/core/launch/gates.go:552 — "func noLongerNarrates(sentence string, at []int) bool {"
- ac-11 — NOT_MET: promised: a doc-history auto-reroute that stages a changelog fix, ordered before the dirty-tree gate; delivered: no reroute exists, so nothing is staged and nothing is ordered before the gate; the intent's 2026-09-25 decision records the criterion as superseded whole by the derived changelog, and the cut's own writes are kept out of the dirty read by DirtySkip instead evidence: .abcd/development/intents/shipped/itd-65-launch-preflight-gate-suite.md:106 — "Superseded whole: with no auto-append there" evidence: internal/core/launch/gates.go:89 — "// DirtySkip leaves the gate out. It exists for the render a cut runs AFTER"
Gap audit:
- honoured:
- a publish is blocked on a finding, not merely previewed: the render refuses before any write on the same verdict the preview reports evidence: internal/core/launch/dryrun.go:236 — "(PrecheckPayload) fails closed on exactly the same scan verdict the dry-run and"
- run-all-collect-all with the pre-flight report written to the local tier evidence: internal/core/launch/gates_test.go:402 — "TestSuiteReportsEveryGateInOnePass" evidence: internal/core/launch/gates_test.go:464 — "TestWritePreflightReportLandsInTheLocalTier"
- the identity layer is a sibling gate flagging the local identity only, with the org handle allowlisted evidence: internal/adapter/scanner/identity_scopes_test.go:111 — "TestOtherIdentitiesArmMatchersAndHandleStaysPublic"
- diverged:
- the doc-history gate offers to auto-append the flagged passage to the changelog; delivered as a hard-fail only, the append superseded by the derived changelog (adr-37) and recorded on the intent evidence: .abcd/development/intents/shipped/itd-65-launch-preflight-gate-suite.md:98 — "Superseded: the offer to auto-append"
- gitleaks at a pinned floor as the secret engine, failing closed when absent; delivered as a native pattern scanner with no shell-out, failing closed on unavailability and coverage, per the 2026-07-24 ruling that the intent record does not itself name evidence: dcn-2607240000000004 — "2026-07-24 — itd-28 gitleaks sign-off: Stage 2 runs through the scanner" evidence: internal/adapter/scanner/finding.go:6 — "gitleaks/trufflehog shell-out)"
- the optional oracle/LLM adjudication of ambiguous narration hits; delivered as a deterministic layered detector only, the oracle pass absent and the auditor-agent question left open on the intent evidence: internal/core/launch/gates.go:807 — "func narrationConstruct(sentence string) string {" evidence: .abcd/development/intents/shipped/itd-65-launch-preflight-gate-suite.md:151 — "Does the documentation auditor reuse the existing doc-scout machinery"
- missing:
- the doc-history auto-reroute ordered before the dirty-tree gate; nothing reroutes, recorded as superseded whole evidence: .abcd/development/intents/shipped/itd-65-launch-preflight-gate-suite.md:106 — "Superseded whole: with no auto-append there"
- the review's five gate-suite findings captured on 2026-09-25 remain open, two of them on gates this audit marks with concerns (hook-compliance fails open; the render path's DirtySkip) evidence: .abcd/work/issues/open/iss-2609251827104081-the-launch-gate-suite-s-hook-compliance.md:1 — "the-launch-gate-suite-s-hook-compliance-row-fails-open-on-a" evidence: .abcd/work/issues/open/iss-2609251827294854-renderpayload-hardcodes-the-dirty-tree.md:1 — "renderpayload-hardcodes-the-dirty-tree-gate-to-dirtyskip-for"
Frontmatter
| id | itd-65 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | critical |
.abcd/development/intents/shipped/itd-65-launch-preflight-gate-suite.md
open on GitHub ↗ · commit history ↗
References3
- adr-28One repository, a curated release artifact — no dev→public mirrorrelated
- itd-66abcd Renders The Exact Public Payload, Proves The Excludes Never Leak, And Smoke-Tests The Installed Surface Before Any Snapshotblocked by
- itd-67abcd Is An Installable, Versioned Claude Code Plugin Whose Repo Is Its Own Marketplace And Whose Every Launch Bumps, Tags, And Publishes A New Versionbuilds on
Referenced by6
- adr-33Launch phase ownership is tiered; the phase index is the sole ownership sourcerelated
- iss-2610262036509587itd-65 calls its change-narration detector the `doc_fidelity` surface; the docs chapter uses doc-fidelity for the gate that checks the brief against the shipped surfacerelated
- iss-2610262045222030itd-124 keeps 'audit' for checking a promise and 'lint' for checking form; itd-65 calls its docs-lint row the documentation-auditorrelated
- iss-2610262045380381itd-67 makes the changelog the reroute target for itd-65's doc-history gate; itd-65 supersedes the reroute wholerelated
- itd-72Publishing A Release Is One Atomic, Refusable `ship`built on by
- spc-2609201955279019launch-preflight-gate-suiteimplemented by
mentions25
- adr-34Intent lifecycle and phase scheduling are orthogonal axes
- adr-37Releases are changelog-driven — rolling `[Unreleased]` is the release decision, and automation tags exactly that commit
- dcn-26072400000000042026-07-24 — itd-28 gitleaks sign-off: Stage 2 runs through the scanner
- dcn-26081600000000082026-08-16 — the v0.5.1 release gate ruled PROMOTE ×2 at content commit
- dcn-26092500000000182026-09-25 — The launch change-narration gate's hard tier refuses where the words cannot tell narration from present state, rather than passing (fix round 2 of lane launchgates, on iss-2609251940304726; the itd-65 acceptance criteria stand as written: "previously X, now Y" and a genuine "no longer" hard-fail). "no longer" is refused whatever its subject, except as a comparative ("no longer than") or in a relative clause over "is"/"are" ("files that are no longer present"); "renamed … to" is refused whenever a "to" follows before the next punctuation mark, except the present purpose form "is/are renamed to match"; "previously … now" is refused when "previously" opens its clause or a change verb stands within three words (counting words only, not punctuation or determiners); "used to" is refused as the past habit, including after a time adverbial ("until v0.6 the dry-run used to skip the tags"). The residual trade, refused although it states present state: a present passive rename other than "to match" ("the staged file is renamed to its final name"); a reduced or copula-less relative "no longer" ("files no longer present are skipped", "a seam that no longer exists"); a clause-opening "previously" beside an unrelated "now". Each takes the docs-lint escape on its line, which every finding names. Survey at the change: 0 findings in the 15 shipped doc bodies; over every Markdown file in the tree the finding locations go from 74 to 405 (333 added, all in records, research notes, agent instruction files and the changelog, outside the gate's scope; 2 dropped, each a "renamed" with no "to" after it). Rejected: a fixed list of change-subject nouns (round 1), which passed "the registry can no longer be edited" and "it was renamed to abcd lint", so a genuine narration reached a release with neither tier catching "used to" or "renamed to" (implementer of lane fix2-launchgates).
- dcn-26092500000000192026-09-25 — The launch change-narration gate's hard tier closes its heuristic rounds with fix round 3 of lane launchgates; every gap it leaves is a residual recorded here, not another round, and the itd-65 acceptance criteria stand as written. Round 3 passes a "no longer" in a clause a subordinator opens (until, once, when, whenever, if, unless, after, before, while, whose: "retry until the error no longer appears", "branches whose upstream no longer exists"; iss-2609252045148890), and refuses the "previously … now" form split at a sentence end when one sentence opens with "previously" and the next in the same paragraph opens with "now" ("Previously, the ledger was a flat file. Now it is a folder."; iss-2609252045147575). The subordinators end a clause for the "no longer" reading alone, because "until" and "before" also open the time adverbial the "used to" reading reads through ("until v0.6 the dry-run used to skip the tags"). The residuals, each checked against the gate at the change. Passed although they narrate: "today" as the contrast word ("Previously the record lived in the log; today it lives in the ledger."); "used to" after a bare plural subject or an adverbial outside the lexicon ("Older versions used to print a banner.", "Versions before 0.6 used to write the receipt.", "Back then the tool used to print a banner.", "The dry-run, until v0.6, used to skip the tags."); a subordinator that opens a prepositional phrase or a subject's relative clause rather than the clause holding the "no longer" ("After the upgrade the verb no longer writes a receipt.", "An intent whose criteria the reviewer cannot enumerate can no longer be planned."); and the split form when "previously" does not open the first sentence or a paragraph break falls between the two ("The default was previously JSON. Now it is YAML."). Refused although it states present state: an "-ed" word within two words of a "previously" that modifies a verb ("Reviewers previously assigned are now listed first."), beside the present-state refusals the 2026-09-25 entry above names. The docs-lint present-tense tier and review are the backstop for each passed form, and the docs-lint escape, which every finding names, for each refused one. Survey at the change: 0 findings in the 15 shipped doc bodies; over every Markdown file in the tree the finding locations go from 422 to 410, 12 dropped and none added, every drop a "no longer" after a subordinator in a record, research note, brief chapter or plugin command page, one of them a genuine narration the residual above names (implementer of lane fix3-launchgates).
- dcn-26092500000000282026-09-25 — The build's open-question check reads the record's own settled convention and stays fail-closed (lane implementer, autonomous run A, fix round 1 of the loop lane, on iss-2609252053576085; the lane's first ruling, that every list item under `## Open Questions` is a question whatever it says, lived only in its report and refused intents the record had settled). `intent.OpenQuestions` recognises exactly two markers, learned from the 12 planned intents that carry list items there. A section whose first non-blank line is an italic `_All resolved …_`, with at most one count word (`_All four resolved …_`), is settled whole (itd-111, itd-93); `_All but one resolved_` is not. An item explicitly marked resolved or deferred is not a question: a bold span opening with the word (`**Resolved — …**`, `**Deferred**`, `**Deferred to a follow-up intent**`, `**explicitly deferred**`, `**explicit deferral**`) or the word as a label (`resolved:`, `RESOLVED:`, `Deferred:`), anywhere in the item including its continuation lines. Everything else stays a question: an item led `**Open` whatever it goes on to say (itd-60's "Open, and not gating scope"), an item that only points to another record (itd-76's "travel with itd-126"), `**Out of scope …**`, and a question that merely mentions deferral. With the rule, itd-111 and itd-93 pass the check; itd-7, itd-60, itd-65, itd-66, itd-76, itd-82, itd-117, itd-131, itd-2609170822093401 and itd-2609211116005482 still refuse, each on an item the record does not mark settled. Relabelling those items is the product thinker's call through the planning interview, not the loop's.
- iss-16itd-66 carries a NON-CANONICAL / do-not-implement banner while being itd-65's declared blocker; reconcile the intent body with the native spec that owns the render contract
- iss-20the launch cluster (itd-65/66/67 critical, itd-72 major) is scheduled in no phase Scope section — the silent-stall pattern itd-78's lint should catch; schedule or record why not
- iss-73Relocate memory-lint logbook output and scanner skip-paths from .abcd/logbook/ (a retired location per iss-36) to .abcd/.work.local/logs/, the gitignored runtime-artefact tier. Maintainer adjudication of iss-56 (2026-07-12): runtime artefacts belong in .work.local/logs/, not a tracked dir. Sites: internal/core/memory/lint.go writes .abcd/logbook/memory/lint dirs; internal/adapter/scanner/scanner.go defaultSkipFragments references .abcd/logbook/pii-scan/ and audit-history/. Fix both plus tests; once the binary no longer writes there the .abcd/logbook retired-location ban (iss-36/iss-56) can be armed. Actionable fix behind iss-56 adjudication.
- iss-2609251827104081The launch gate suite's hook-compliance row fails open on a hooks config that does not parse: hookComplianceGate (internal/core/launch/gates.go) skips an unparseable hooks file with a comment claiming the smoke refuses it, and the smoke does not, because hookCommandEntries (installsurface.go) also drops an unparseable hooks document silently and the light tier checks existence only. A plugin shipping a hooks.json the host cannot parse registers no hooks on any install, while the row reads 'ran, 0 concern(s)'. itd-65 AC5 promises the concern is surfaced. Remedy: a hooks config that is present and cannot be read or parsed is a finding of the row, and the resolver refuses it as it refuses an unparseable plugin manifest.
- iss-2609251827286563The launch gate suite's change-narration gate (hard-fail tier) refuses present-state prose: its constructs in internal/core/launch/gates.go flag 'The token used to authenticate the request is read from the environment.' (a participle 'used to'), 'Files that are no longer present in the tree are skipped.' (a state, not a change of abcd), 'The output is renamed to match the tag.' (a present operation), and 'Now, as previously noted, the report lists every gate.' (previously and now with no change verb, which itd-65 AC10 says must not hard-fail; the AC10 test covers the two words only in separate sentences). The finding text never names the docs-lint escape. A doc sentence that narrates nothing refuses the cut, and on a hand-pushed tag the release workflow's archive verify refuses after the tag exists. Remedy: 'used to' only as the past-habit construction, 'no longer' and 'renamed' only beside a change subject naming abcd or its behaviour, a change verb required beside previously/now, and the escape named in the finding.
- iss-2609251827294854RenderPayload hardcodes the dirty-tree gate to DirtySkip for every caller (internal/core/launch/render.go), with a comment asserting the caller ran the gate earlier, which RenderPayload cannot know: the two live callers (the ship's post-write render, CI's launch archive --verify) are covered today, but any new direct caller (itd-72's publish step) inherits no dirty gate. The render-path precheck also hands the documentation audit nil, so its doc-auditor row claims 'no .abcd/docs-lint.json' for a repository that has one. Remedy: a DirtyPolicy on PayloadRenderRequest whose zero value refuses a dirty tree, each live caller passing its policy explicitly, and a render-path doc-auditor row that says it was not measured rather than that the config is absent.
- iss-2609251940304726The launch gate suite's change-narration gate (hard-fail tier) misses genuine narration after its round-1 narrowing: 'Previously, the ledger was a flat file; now it is a folder.' passes because changeVerbBesideEither counts ',' and 'the' as tokens, so 'was' falls outside its three-word window (itd-65 AC3 names 'previously X, now Y' as the hard-fail case); 'The registry can no longer be edited by hand.' and 'The scanner no longer skips fenced blocks.' pass because 'no longer' fires only beside a fixed list of subject nouns (AC10 says a genuine 'no longer' DOES hard-fail); 'It was renamed to abcd lint.' passes for the same reason; and 'Until v0.6 the dry-run used to skip the tags.' passes because pastHabitUsedTo requires the subject phrase to open its clause. Docs lint has no token for 'used to' or 'renamed to', so the last two shapes are caught by neither tier. Remedy: count word tokens only in the previously/now window, restore 'no longer' and 'renamed to' as hard fails exempting only the relative-clause and comparative forms ('files that are no longer present', 'no longer than') and the purpose form ('is renamed to match'), and read a past-habit 'used to' after a time adverbial.
- iss-2609252045147575The launch gate suite's change-narration hard tier passes itd-65 AC3's canonical form when it is split at a sentence end: 'Previously, the ledger was a flat file. Now it is a folder.' carries 0 findings, because the previously/now construct reads one sentence at a time and needs both words in it. Remedy: when a sentence opens with 'now' and the sentence before it in the same paragraph opens with 'previously', refuse the pair as 'previously … now', located at its first sentence.
- iss-2609290448510918The predecessor-store qualifier sweep reaches past the six intents iss-2609261536147903 named: outside drafts/ and those six, 192 citation sites across about forty intents name a spc-N at or below spc-70 that is not the citing intent's own spec, with no '(predecessor store)' on the line. Some are live cross-references (the cold-reading family's intents cite each other's live specs), and some are the predecessor store's (itd-4, itd-6, itd-29, itd-47 and itd-49 describe pre-rebuild work in its terms), so each site needs the same reading against the live spec it collides with; the specs charter's Two spc-N Namespaces section is the rule. Found while sweeping the pattern of iss-2609261536147903 in lane drainDrift3; a census script over intents/{planned,shipped,disciplines} reproduces the count.
- iss-2610021502084298No check meets the retired role word in an intent's press release at the record: the docs-lint rule roles/retired-role-word roots at the docs, the command pages and the rules, so a shipped or planned intent's press release carrying it is refused only when the site composer renders it as the landing page's hero quote, which is how itd-60's first MET audit broke the site-render gate (iss-2610021446271464). After the R5 reword of itd-60, thirteen shipped press releases still carry the word (itd-65, itd-66, itd-67, itd-69, itd-73, itd-74, itd-93, itd-102, itd-111, itd-114, itd-125, itd-2609111003026787, itd-2609212137129937), so the next MET audit ingested for any of them refuses the render again on a branch that only recorded an audit; the product thinker's ruling R5 (2026-10-02) accepted that a future slip breaks the build and did not ask for this check. Related to iss-2610020731591808, which names the brief, the principles and the personas registry as swept but unguarded.
- iss-2610262033189165itd-67 sends change-narration stripped from a doc into the changelog; 04-launch says rephrase the doc and never move the sentence into the changelog
- iss-2610262042174713itd-67 builds the changelog from git tags, commit and spec history and the launch report, and lets a gate append to it; the release entry composes it from records that reached a terminal folder.
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-28Spec-Tied Reviews Live Next To The Spec They Reviewed
- itd-62abcd Routes The Invisible Risks An Amateur Cannot See To A Real, Fail-Closed Gate That Wraps A Trusted Scanner
- spc-27the-registry-cannot-wave-its-hands-every-surface-file-carrie
- spc-64The read-block eval falsifies the firewall
- spc-2609211955339422setup-wizard-explains-installs