/release-readiness — Release Decision Composer
You compose the sprint's release evidence into a single decision. You do not run any of the upstream skills; you read the artifacts they already saved and emit a status that gates /ship. The conductor's phase_graph puts you between the upstream phases and /ship, so this skill is the last thing that runs before delivery.
This is the release-readiness skill from the compliance-release stack. PR 2 of the Custom Stack Examples v1 round wires the real composer logic; this PR (PR 1) ships the skill structure so the static contract validates.
Process
0. Resolve paths (host-agnostic)
NANOSTACK_ROOT="${NANOSTACK_ROOT:-$HOME/.claude/skills/nanostack}"
SKILL_DIR="${SKILL_DIR:-$HOME/.claude/skills/release-readiness}"
1. Resolve upstream evidence
NANOSTACK_ROOT="${NANOSTACK_ROOT:-$HOME/.claude/skills/nanostack}"
"$NANOSTACK_ROOT/bin/resolve.sh" release-readiness
The resolver returns phase_kind: "custom" and upstream_artifacts with five keys: review, qa, security, license-audit, privacy-check. Each value is either a path to the artifact JSON or null if no artifact exists for that upstream.
2. Compose the decision
SKILL_DIR="${SKILL_DIR:-$HOME/.claude/skills/release-readiness}"
"$SKILL_DIR/bin/summarize.sh"
The helper reads each upstream artifact through bin/find-artifact.sh --verify and maps each to a check entry. Per-check status:
MISSINGfor any upstream whose artifact is absent.TAMPEREDfor an artifact whose stored hash does not match the recomputed hash (evidence: "integrity_failure") or whose.integrityfield is absent (evidence: "missing_integrity"). A release gate cannot trust evidence it cannot verify; an attacker who can modify the file can delete the field as easily as mutate the hash, so missing integrity is treated as the same risk class as a bad hash.BLOCKEDwhen the upstream'ssummary.statusisBLOCKED.WARNwhen the upstream'ssummary.statusisWARN, or when no status is declared (artifact present but unannotated).OKwhen the upstream'ssummary.statusisOKand integrity verifies.
Rollup is monotonic worst-case:
- Any
BLOCKED,TAMPERED, orMISSINGper-check entry forces the rollup toBLOCKED. - Otherwise, any
WARNper-check entry rolls up toWARN. - Otherwise,
OK.
3. Save the artifact
NANOSTACK_ROOT="${NANOSTACK_ROOT:-$HOME/.claude/skills/nanostack}"
"$NANOSTACK_ROOT/bin/save-artifact.sh" release-readiness \
'{"phase":"release-readiness","summary":{"status":"...","headline":"...","checks":[...],"next_action":"..."},"context_checkpoint":{"summary":"Release readiness composed upstream evidence."}}'
4. Headline
[release-readiness] BLOCKED: privacy note missing and QA evidence absent.
Prefix the status (OK, WARN, BLOCKED) and surface the most actionable next step. The composer's job is to tell the user what to do, not just to print a verdict.
Gotchas
- This skill never runs
/ship, never opens a PR, never commits, never deploys. It only composes evidence into a decision. - Missing upstreams are explicit. If
qahas no artifact, the rollup isBLOCKEDfor "QA evidence missing" (notOKwith a quiet gap). The whole point of the gate is to surface that exactly. - Tampered upstreams are explicit. An artifact whose stored hash does not match the recomputed content, or whose
.integrityfield is absent, becomesTAMPERED(notOK). A release gate cannot afford to trust evidence it cannot verify. - The status rollup is monotonic: once any upstream is
BLOCKED,TAMPERED, orMISSING, the composer cannot soften the rollup toWARN. The user must explicitly resolve the failure. WARNrollups still allow/ship(the composer does not auto-block), but the artifact records the warning and the next-action so the team has a paper trail.