Development record
Publishing A Release Is One Atomic, Refusable `ship`
Superseded by adr-37 on 2026-09-20, on the product thinker's ruling: publishing moved into CI behind the changelog-driven gate (adr-37), versions are derived (adr-31) and the semantic gate runs before the tag (adr-52), so every criterion here is met in another shape or moot; a review on 2026-09-20 found no criterion still open.
Retroactive intent record for spc-80. spc-80 builds the Tier B (publishing) half of the itd-67 consolidated PRD — see itd-67, whose grilled PRD defines both tiers. This record exists so the spec resolves to an intent under
planned//shipped/; the two-key grandfather marks that it did not pass through its own grill (the grilled provenance lives on itd-67).
Press Release
abcd publishes a new plugin release with one command — or refuses, loudly.
shipcomputes the bump tier from the last launch baseline, writes the version and changelog into the curated release, prunes superseded payload files, stages the delta for confirmation, and then publishes branch + tag atomically. If nothing changed, it refuses. If the repo is not the one it expects, it refuses. If the push fails, nothing half-lands: a recoverable marker preserves the local commit andmainis never force-reset.
Why This Matters
Tier A (itd-67) makes the abcd repo installable and versioned; without
Tier B every subsequent release is a hand-run of renders, bumps, changelog
edits, prunes, and pushes — each a chance to publish a half-state. The
release is a single-repo curated cut from packaging (adr-28),
not a mirror push. ship makes that cut a single audited act with every
safety refusal built in.
What's In Scope
- Bump-tier computation from the
.launch/launch-report.jsonbaseline (phase-complete since baseline → minor; otherwise patch; explicit--versionalways wins and is the only major path) - First-release bootstrap: an absent baseline seeds
v0.1.0and prepares the initial baseline record - No-change refusal on the canonical pre-bump payload digest (
--forceto override) - Repo identity check (marketplace name, plugin name, remote slug, protected branch) before any write
- Overlay write + tracked-manifest prune, payload-scoped only, with traversal/symlink-escape defenses
- Auto-changelog from git tags + the launch report, idempotent
- Staged-delta confirmation, non-TTY refusal without
--yes - Atomic branch+tag push or fail-closed with a recoverable
failed-release.jsonmarker; stale-verdict revalidation immediately before promotion
What's Out of Scope
- The payload resolution itself (spc-78's render manifest is consumed, never re-derived)
- The pre-flight gate suite (spc-79 owns it;
shipinvokes it) - Release retention pruning of prior tags/Releases (itd-70)
Scope Conditions
None stated.
Acceptance Criteria
BDD format, per
itd-1-acceptance-gates. These gates are checked byintent-fidelity-reviewerwhen this intent moves toshipped/.
- Given no baseline exists in the repo, when
shipruns, then the seeded version isv0.1.0, every surface is treated as added, and the initial baseline record is prepared. - Given the pre-bump payload digest equals the baseline's, when
shipruns without--force, then it refuses as a no-change release and publishes nothing. - Given the repo's marketplace name, plugin name, remote slug, or
protected branch does not match the launch config, when
shipruns, then it refuses before any write. - Given a prune entry resolving outside the payload root (absolute,
..-traversal, or symlink escape), when the overlay+prune stage runs, then the entry is rejected and nothing outside the payload root is deleted. - Given
--publishin a non-TTY session without--yes, whenshipreaches confirmation, then it refuses to publish. - Given the atomic branch+tag push fails partway, when
shipexits, then the protected branch is never force-reset and a recoverablefailed-release.jsonmarker preserves the local release commit. - Given the gate verdict was computed against different manifest bytes or
git state, when
shiprevalidates immediately before promotion, then the stale verdict is rejected and nothing is pushed.
Audit Notes
Populated by intent-fidelity-reviewer when intent moves to shipped/.
Frontmatter
| id | itd-72 |
|---|---|
| type | intent |
| lifecycle | superseded |
| kind | standalone |
| severity | major |
.abcd/development/intents/superseded/itd-72-launch-ship-tier-b-publishing.md
open on GitHub ↗ · commit history ↗
References4
- adr-28One repository, a curated release artifact — no dev→public mirrorrelated
- itd-65abcd Refuses To Publish A Payload Until The Full Ship-Time Pre-Flight Gate Suite Passes, Not Just The Secret Scanbuilds on
- itd-66abcd Renders The Exact Public Payload, Proves The Excludes Never Leak, And Smoke-Tests The Installed Surface Before Any Snapshotbuilds on
- 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 Versionblocked by
Referenced by2
mentions9
- adr-31The release version is derived from the intents in it, never authored
- adr-34Intent lifecycle and phase scheduling are orthogonal axes
- adr-52The semantic gate sits on the wrong side of the tag
- 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-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-2609300751191426Rulings CF1 and CF2 of 2026-09-30 are not built: the blocked pre-start check still refuses a blocker whose supersession chain ends at an accepted ADR, and a blocker reclassified as a discipline. The person ruled, through the run's interviewer on 2026-09-30: 'CF1 blocker replaced by an accepted ADR: COUNTS AS SETTLED (the waiting intent may start)' and 'CF2 blocker reclassified as a discipline: COUNTS AS SETTLED (the waiting intent may start)'. At base 85d0bb8eb, followBlocker in internal/core/intent/startcheck.go stops an ADR successor with the problem 'adr-N is not an intent: a decision replaced the blocker, and nothing on the record says that settles the edge', and startBlockedRow counts only a final record in shipped/ as settled, so a chain ending in disciplines/ blocks its dependants for ever. Lane supersededBlocker asked both questions rather than building them; no live edge hits either shape today (itd-72 is superseded by adr-37 and blocks nothing), so nothing is refused wrongly yet.
- iss-261025221426017904-launch.md:64, 179-187, 704-707 and 814-821 call commit/tag/publish and the release commit-message format a design target of itd-72, but itd-72 is superseded by adr-37 (.abcd/development/intents/superseded/itd-72-launch-ship-tier-b-publishing.md:20,28), so no decided target stands behind them
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-70Drawn-out intent