Development record
abcd Renders The Exact Public Payload, Proves The Excludes Never Leak, And Smoke-Tests The Installed Surface Before Any Snapshot
Press Release
/abcd:launchgains a real payload render with a leak-proof default-deny filter, a parity diff against the previously published release, and an installed-surface smoke test — so a maintainer sees EXACTLY what would be published, provably without.abcd/leaking, and with every shipped/abcd:*command, skill, and hook confirmed to resolve and import from the rendered snapshot. The payload is the curated release excluding.abcd/**(adr-28); the brief's § 2 payload manifest is default-deny (include-list + hard.abcd/**exclusion), but nothing yet MATERIALISES that manifest and proves the exclusions hold, diffs it against the last published release, or verifies the rendered plugin actually loads. This intent builds that: render → prove-no-leak → parity-diff → consumer-surface smoke, all read-only, so the release is trustworthy before promotion.
"Before I publish the release I want to see the actual file list, be certain none of my development knowledge or flow state rode along, and know the plugin still works once it's just the shipped files," said a maintainer. "A preview I have to trust isn't enough — render it and prove it."
Why This Matters
The pre-flight gate suite ([[itd-65-launch-preflight-gate-suite]]) decides whether the payload is CLEAN; this intent decides whether the payload is CORRECT and COMPLETE — the other half of a trustworthy release. adr-28 makes the wholesale .abcd/** exclusion deliberate packaging policy (the curated release artifact carries plugin code, never the .abcd/** project-knowledge store); but policy without a test is a hope. A materialised render with an asserted default-deny filter turns "we exclude .abcd/" into "we PROVE no .abcd/** path is in the rendered tree." The parity diff against the previously published release shows precisely what a snapshot would change (and catches accidental deletions under clean/overlay modes). The installed-surface smoke test catches the failure an in-repo test cannot see: a command/skill/hook that works in the full tree but is broken once only the shipped files remain. Together they make a release a verified operation, not a leap.
What's In Scope
- A read-only payload RENDER: materialise the § 2 include-manifest into a temp tree, applying
.gitignorepatterns and the default-deny exclude set (.abcd/,.specstory/,memory/). - A leak-proof assertion: the rendered tree contains ZERO
.abcd/**paths, and honours the.abcd/launch.allowallowlist contract (never promotes any.abcd/**line, per adr-28). - A parity diff between the rendered payload and the previously published release: added / changed / removed files, so the operator previews the exact snapshot delta before promotion.
- An installed-surface smoke test: from the rendered snapshot, load
plugin.json+marketplace.jsonand assert every declared/abcd:*command, skill, and hook resolves, and every shipped Python entrypoint imports. - All read-only w.r.t. the dev repo (temp-tree writes only, removed after) — matches the side-effect-free posture of the spc-64 (predecessor store) gate.
- Canonical payload resolution (grill Q3): itd-66's render is the SINGLE resolver of "the payload" (include-manifest + default-deny +
.gitignore+ symlink-resolve). [[itd-65-launch-preflight-gate-suite]]'s gate suite scans exactly this resolved output and never re-resolves — so render and gate can never disagree on what is being shipped. This makes itd-66 (render) a dependency of itd-65 (gate): render → gate. - Layered leak defense (grill Q2): the render asserts no excluded PATH in the tree AND resolves symlinks (a payload symlink targeting
.abcd/fails the assertion); embedded.abcd/.flowCONTENT that rode along inside a shipped file is caught by itd-65's secret/PII/identity content scan. Structural exclusion here; content cleanliness there. - Parity baseline (grill Q1): the diff targets the previously published release at a configured ref (default: the latest release tag). An absent/empty prior release yields an all-added diff (valid first-launch); a wrong/missing configured baseline is a hard error, never a silent empty diff.
- Deep-smoke isolation (grill Q4/Q5): the deep installed-surface smoke imports every shipped Python entrypoint, resolves every declared command/skill/agent/hook, and renders each command's help/frontmatter (short of full behavioral invocation). Imports run in an ISOLATED SUBPROCESS (cwd = temp render tree, guarded/minimal env) so any module-level side effect lands in the throwaway tree, preserving the read-only-w.r.t.-dev-repo guarantee. This deep check is the tier [[itd-67-installable-versioned-plugin]]'s light smoke is later upgraded to call.
What's Out of Scope
- The pre-flight security/PII/marker gate suite (that is [[itd-65-launch-preflight-gate-suite]] — this intent is RENDER + PARITY + SMOKE).
- Actually pushing / mirror-mode execution / version bump + marketplace changelog write (brief §§ 3–4 — the promotion act itself; a later intent or the ship graduation).
- Re-including any
.abcd/**path into the payload — adr-28 forbids it; the render must enforce, not relax, that. - A full end-to-end publish dry-run to a real remote — the smoke test loads the rendered tree locally, it does not clone/push.
Scope Conditions
None stated.
Delivery Status
Reviewed against the v0.9.0 tree on 2026-09-20. Delivered and cited: every
include root rendered and the record namespace structurally excluded
(TestAbcdNamespaceStructurallyExcluded, TestNestedDeniedNamespaceExcluded,
TestBundleShipsEveryPluginSurface); a denied-rooted include refused
(TestAbcdCannotBeReincluded); a symlink into the namespace refused
(TestSymlinkToRepoRootDoesNotLeakDenied); no residue in the dev repo
(TestRenderPayloadLeavesSourceTreeUnversioned); the gate consuming the
render's own resolution (TestPayloadTreeImplementationsResolveIdentically);
the light smoke tier resolving commands, skills and hooks. Moot: the
Python-import clause (nothing shipped imports) and the seeded first baseline
(the first release is manual by the brief's bootstrap exception). Open, and
scoped by spc-2609201955277614: the file-level parity diff and the deep smoke
tier.
Both delivered on 2026-09-25; the Decisions below cite them.
Decisions
- 2026-09-25 — the Python-import clause of the smoke criterion (fourth) is
moot, and the rest of it stands. Source: spc-2609201955277614 (Summary,
piece 2: "the original criterion's Python-import clause is moot (nothing
shipped imports) and is recorded as such on the intent") and the Delivery
Status review above (2026-09-20). The payload's include roots carry no Python
and no other imported entrypoint (
hooks/andscripts/ship shell), so "every shipped Python entrypoint imports" has nothing to assert; the criterion text is unchanged. Stands and is met: every declared command, skill and hook resolves (the light tier), and every declared command, skill and agent page LOADS, rendered in an isolated subprocess rooted at a materialised copy of the payload, so a broken page fails (SmokeDeepandRenderPageHelpininternal/core/launch/deepsmoke.go, the hiddenabcd launch smoke-pageschild ininternal/surface/cli/launch_deep.go;TestSmokeDeepCatchesAPageThatResolvesButDoesNotLoad,TestLaunchDryRunDeepSmokeRunsInAnIsolatedSubprocess,TestLaunchShipRunsTheDeepTierAndParity). The tier is opt-in on the preview (--deep-smoke) and always on in the cut, per the spec's Approach. - 2026-09-25 — how the parity criteria are met (spc-2609201955277614 piece
1). The third criterion:
PayloadParityininternal/core/launch/parity.golists every payload path added, changed or removed with its SHA-256, on every preview and in the cut (its JSON, its render and its pre-flight report) (TestParityAgainstARenderAtTheTagReportsEveryChange,TestLaunchDryRunReportsTheParityDiff). The seventh: no previous tag, or a tag that declared no payload, is all-added with the reason stated (TestParityWithNoPreviousReleaseIsAllAdded,TestParityAgainstATagThatShippedNoPayloadIsAllAdded); a baseline that cannot be read is a named refusal, never an empty diff, and a configured--baselinethat is not a release tag exits 2 by name (TestParityWithAnUnreadableBaselineIsANamedRefusal,TestLaunchDryRunConfiguredBaseline). The fifth still holds with both pieces in: the render at the tag and the deep tier write only private temporary trees they remove. - 2026-09-25 — the baseline is read from the disk unless the operator asks
for the network. The spec reads the previous payload "from the tag's release
asset when present and from a fresh render at that tag otherwise";
adr-38 lets the
network answer only an explicit ask. So the default baseline is a fresh render
at the tag in a private clone of the checkout's own objects, and
--fetch-baseline(preview and cut) reads the tag's published plugin archive first: every fetch announced on stderr and named in the report, the archive refused unless the release's ownchecksums.txtvouches for its digest, and a release publishing no archive falling back to the render at the tag, saying so (TestParityAgainstTheReleaseAssetVerifiesAndDiffs,TestParityReleaseAssetChecksumMismatchFailsClosed,TestParityReleaseAssetAbsentFallsBackToARenderAtTheTag,TestLaunchDryRunFetchBaselineReadsTheVerifiedReleaseAsset). The two manifests the release stamps are compared as canonical JSON with their version keys removed, since the render re-marshals them and the version bump is the cut's own report; against an archive, the catalog the archive omits by construction is named as not compared.
Acceptance Criteria
Given-When-Then per the itd-1 discipline.
- Given the § 2 include-manifest, when the payload is rendered, then the rendered tree contains every include root and ZERO paths under
.abcd/,.specstory/, or legacymemory/. - Given a
.abcd/launch.allowline pointing at a.abcd/**path, when the render applies the allowlist, then that line is refused / never promoted (adr-28), and the render records the refusal. - Given a rendered payload and the previously published release, when the parity diff runs, then it reports added/changed/removed files accurately, so the operator sees the exact snapshot delta.
- Given a rendered snapshot, when the installed-surface smoke test runs, then every
/abcd:*command, skill, and hook declared in the manifest resolves and every shipped Python entrypoint imports — a broken shipped entrypoint FAILS the test. - Given the whole render + parity + smoke flow, when it runs, then it makes no change to the dev repo (temp-tree only) and leaves no residue — including deep-smoke imports, which run in an isolated subprocess rooted at the temp tree.
- Given a payload symlink pointing into
.abcd/, when the render's leak assertion runs, then it resolves the symlink and FAILS (a symlink is not an escape hatch around path exclusion). - Given no previously published release (or an empty one), when parity runs, then it produces an all-added diff (first-launch) rather than refusing; given a wrong configured baseline, it errors clearly.
- Given the resolved payload manifest, when itd-65's gate suite runs, then it consumes THIS render's resolution and does not independently re-resolve the payload.
Prior Art
- adr-28 — the packaging policy (
.abcd/**never ships) this intent turns from policy into a proved assertion. - [[itd-65-launch-preflight-gate-suite]] — sibling: its gate suite consumes this render's resolved payload and never re-resolves (render → gate).
spc-78-launch-payload-render-parity-smoke— the predecessor implementation's spec for this contract, carried as design input per the brief's delivery-state provenance note. Two of its implementation decisions diverge from this repo's brief: its render consumed a.abcd/config/launch-payload.jsonconfig rather than the.abcd/launch.allowallowlist, and its smoke discovered surfaces by directory convention rather than manifest declaration. The brief (04-surfaces/04-launch.md) remains canonical here; the deltas are weighed at spec time (see Open Questions).
Open Questions
- Predecessor delta (spc-78): adopt a config-file payload override (
.abcd/config/launch-payload.json) and directory-convention smoke discovery, or keep the brief's.abcd/launch.allow+ manifest-declared surfaces? Adjudicate at spec time; a brief change needs its own edit, not a silent divergence. - Does the render reuse the walk/filter logic already in the native launch capability (its walk-root routine and the include/exclude sets), promoting it from preview-only to a real materialiser, or is it a fresh module the dry-run then reuses?
- What is the parity baseline when there is no previously published release or it is on a different ref — treat all-added, or require a configured baseline?
- How deep does the smoke test go — manifest resolution + import only, or also a minimal invocation of each command's help surface?
- Should the render assert against the SAME resolved manifest the pre-flight suite ([[itd-65-launch-preflight-gate-suite]]) scans, so the two never diverge on what "the payload" is?
Audit Notes
Fidelity review — receipt rcp-04706634ba21 (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:a64cc13441a073183a99f59e2760d86ac7ec242999c85bf400b4ef087c82525d Input attestations: diff:internal/core/launch at main 811fba17 (git ls-tree -r; the render, parity and deep-smoke modules as delivered through 2026-09-25)@sha256:3a6ae83973dc3e803e7fa3c69deb981dc4b1790082324964db59f244d046210f;
Acceptance rollup: MET 6 · MET_WITH_CONCERNS 2 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: the bundle's denied set names .abcd, .specstory and memory beside .git, .flow and .work, a test proves the namespace is structurally excluded at every depth, and another proves every plugin surface the manifests declare is in the bundle evidence: internal/core/launch/bundle.go:35 — "".git": {}, ".abcd": {}, ".flow": {}, ".work": {}, ".specstory": {}, "memory": {}," evidence: internal/core/launch/bundle_test.go:78 — "func TestAbcdNamespaceStructurallyExcluded" evidence: internal/core/launch/bundle_denyseg_test.go:10 — "func TestNestedDeniedNamespaceExcluded" evidence: internal/core/launch/payload_completeness_test.go:40 — "func TestBundleShipsEveryPluginSurface"
- ac-2 — MET_WITH_CONCERNS: an include naming a denied namespace is refused at load with the pattern named, and even a hand-forced include slice cannot promote a .abcd path; the concern is the name: the criterion says
.abcd/launch.allow, and the delivered allowlist is the includes list of.abcd/config/launch-payload.json, which the canonical launch brief names, so the delta the intent's open question deferred to spec time was settled by the brief rather than by the spec evidence: internal/core/launch/includes.go:149 — "return preflight("include pattern names a denied namespace segment %q: %q", seg, pattern)" evidence: internal/core/launch/includes_test.go:70 — "func TestAbcdCannotBeReincluded" evidence: .abcd/development/brief/04-surfaces/04-launch.md:335 — ".abcd/config/launch-payload.json, over one tree. Everything not named is" - ac-3 — MET: PayloadParity lists every path added, changed or removed against a render at the previous tag with its SHA-256, and the preview reports it evidence: internal/core/launch/parity.go:186 — "func PayloadParity(repoRoot string, bundle Bundle, in ParityInput) ParityReport {" evidence: internal/core/launch/parity_test.go:66 — "func TestParityAgainstARenderAtTheTagReportsEveryChange" evidence: internal/surface/cli/launch_parity_test.go:52 — "func TestLaunchDryRunReportsTheParityDiff"
- ac-4 — MET_WITH_CONCERNS: the light tier resolves every declared command, skill and hook and the deep tier loads every declared page in an isolated subprocess so a page that resolves but does not load fails; the Python-import clause is moot because nothing shipped imports, recorded on the intent on 2026-09-25 with the criterion text unchanged evidence: internal/core/launch/deepsmoke.go:373 — "func SmokeDeep(root string, run PageRunner) DeepSmokeReport {" evidence: internal/core/launch/deepsmoke_test.go:165 — "func TestSmokeDeepCatchesAPageThatResolvesButDoesNotLoad" evidence: internal/surface/cli/launch_parity_test.go:449 — "func TestLaunchShipRunsTheDeepTierAndParity" evidence: .abcd/development/intents/shipped/itd-66-launch-payload-render-parity.md:80 — "the Python-import clause of the smoke criterion (fourth) is"
- ac-5 — MET: the render leaves the source tree unversioned and the deep smoke runs in an isolated subprocess rooted at a materialised temporary copy that is removed evidence: internal/core/launch/render_test.go:94 — "func TestRenderPayloadLeavesSourceTreeUnversioned" evidence: internal/surface/cli/launch_parity_test.go:390 — "func TestLaunchDryRunDeepSmokeRunsInAnIsolatedSubprocess"
- ac-6 — MET: a symlink to the repository root inside the payload does not leak the denied namespace: the test plants .abcd/secret.txt and asserts the render fails to carry it evidence: internal/core/launch/bundle_test.go:177 — "func TestSymlinkToRepoRootDoesNotLeakDenied"
- ac-7 — MET: no previous release, or a tag that shipped no payload, is all-added with the reason stated; an unreadable baseline is a named refusal and a configured baseline that is not a release tag exits 2 by name evidence: internal/core/launch/parity_test.go:113 — "func TestParityWithNoPreviousReleaseIsAllAdded" evidence: internal/core/launch/parity_test.go:138 — "func TestParityWithAnUnreadableBaselineIsANamedRefusal" evidence: internal/core/launch/parity.go:173 — "func ValidateBaselineTag(repoRoot, tag string) error {" evidence: internal/surface/cli/launch_parity_test.go:83 — "func TestLaunchDryRunConfiguredBaseline"
- ac-8 — MET: the two payload-tree implementations the render and the gate walk are proved to resolve identically over the repository, so the gate reads the render's resolution and cannot disagree with it evidence: internal/core/launch/installsurface_test.go:263 — "func TestPayloadTreeImplementationsResolveIdentically"
Gap audit:
- honoured:
- render, prove-no-leak, parity diff and installed-surface smoke, all read-only with respect to the checkout evidence: internal/core/launch/render_test.go:94 — "TestRenderPayloadLeavesSourceTreeUnversioned" evidence: internal/core/launch/parity.go:186 — "func PayloadParity("
- the baseline is read from disk unless the operator asks for the network, and a fetched archive is refused unless the release's own checksums vouch for it evidence: .abcd/development/intents/shipped/itd-66-launch-payload-render-parity.md:112 — "the baseline is read from the disk unless the operator asks" evidence: internal/core/launch/parity.go:268 — "func (rep *ParityReport) fillFromAsset("
- diverged:
- the allowlist is
.abcd/launch.allow; delivered as the includes list of.abcd/config/launch-payload.json, which the canonical brief names, so the predecessor delta was settled by the brief and not by a spec-time adjudication evidence: .abcd/development/brief/04-surfaces/04-launch.md:18 — "(.abcd/config/launch-payload.json) has nothing to preview, and the preview" evidence: .abcd/development/intents/shipped/itd-66-launch-payload-render-parity.md:152 — "Predecessor delta (spc-78)" - every shipped Python entrypoint imports; nothing shipped is Python, so the clause is moot by the intent's own 2026-09-25 decision evidence: .abcd/development/intents/shipped/itd-66-launch-payload-render-parity.md:80 — "the Python-import clause of the smoke criterion (fourth) is"
- the allowlist is
- missing: (none)
Frontmatter
| id | itd-66 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | critical |
.abcd/development/intents/shipped/itd-66-launch-payload-render-parity.md
open on GitHub ↗ · commit history ↗
References1
- adr-28One repository, a curated release artifact — no dev→public mirrorrelated
Referenced by7
- adr-33Launch phase ownership is tiered; the phase index is the sole ownership sourcerelated
- iss-2610262045091104itd-39 extends a Python hook, hooks/prompt_router_hook.py; itd-66 says the payload has no Python and hooks/ ships shellrelated
- iss-2610262045278708itd-3 ships the prompt hook as a Python file in hooks/; itd-66 says hooks/ ships shell and the payload carries no Pythonrelated
- itd-65abcd Refuses To Publish A Payload Until The Full Ship-Time Pre-Flight Gate Suite Passes, Not Just The Secret Scanblocks
- 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 Versionbuilt on by
- itd-72Publishing A Release Is One Atomic, Refusable `ship`built on by
- spc-2609201955277614launch-payload-render-parityimplemented by
mentions13
- adr-34Intent lifecycle and phase scheduling are orthogonal axes
- adr-38Implicit checks are disk-only — the network answers only an explicit ask
- dcn-26070800000000132026-07-08 — Predecessor spc-N artefacts inside intents (do-not-implement
- dcn-26072100000000012026-07-21 — itd-66 (`launch-payload-render-parity`) is DEFERRED to a follow-up
- 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-2609251902439938A checkout without the previous release's tag reads as a first launch: launchParityInput (internal/surface/cli/launch_deep.go) leaves the baseline empty when no release tag is listed, and PayloadParity (internal/core/launch/parity.go) reports every payload path as added with exit 0. A git clone --no-tags of a tree whose CHANGELOG.md dates v0.10.0 reports parity source none and 130 added; a clone --depth 1 reports the retention refusal for a shallow checkout beside the same first-launch diff. The configured-baseline hard error (itd-66 AC7) is defeated in the commoner shape: a fork, a mirror, or tags never pushed.
- 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.
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-16`/abcd:audit` Is the Umbrella; Conversation/Edit Chain Is Its Default Application
- spc-11installable-versioned-plugin
- spc-64The read-block eval falsifies the firewall