유대선
프로젝트로
·기술 회고·3

Deterministic gates that block the launch: release readiness and scope integrity

Three parallel workers built pure, local-only gates — a release-readiness scanner and a scope-integrity decision model — that refuse to call Talkak shippable until owner and legal facts are resolved. Honest caveat: the readiness gate is a manual script, not yet CI-wired.

The fastest way to ship a bad launch is to let an eager agent "polish until green." So before the next Talkak build I wanted the opposite reflex: a deterministic gate that refuses to declare the product shippable while real owner/legal facts are still unresolved. Three parallel workers landed that on main on 2026-06-17 — REL02 (trust-claim cleanup), REL03 (readiness gate), and SIG01 (scope-integrity model).

REL03 — a scanner that reports blockers instead of inventing facts

packages/shared/src/releaseReadinessGate.ts is a pure function: it takes file contents in, emits a ReleaseReadinessGateReport out. No filesystem, no network, no LLM. The CLI wrapper scripts/release-readiness-gate.mjs reads a fixed list of release surfaces (landing pages, launch-plan.md, latest.json, tauri.conf.json, the entitlement files) and feeds them in.

It scans for the things that quietly poison a launch:

Run today, the gate does exactly what it should: pnpm release:readiness exits 1 with 5 blockers, 0 warnings, 2 passes (--dry-run prints the same report but exits 0). The repo is correctly not release-ready. The point of the gate is to make that verdict mechanical and unbluffable rather than a vibe.

The honest caveat: it is a manual script, not CI

This is where I have to stay honest with myself. The two workflows in .github/workflows (ci.yml, regression.yml) run cargo and pnpm tests and Biome — neither calls release:readiness. A git grep for the gate across .github returns nothing. So today the gate is a deterministic command a human must remember to run, not an enforced merge blocker. Calling it a "CI gate" would be exactly the kind of overclaim REL02 was cleaning up. It is a gate in logic, not yet in wiring; promoting it to a required check is a one-line follow-up.

REL02 — the claim ledger that feeds the scanner

REL03's blocklist isn't arbitrary; it's the codification of REL02's work. docs/product/release-trust-claim-ledger-2026-06-17.md classifies every public claim as proven / partial / planned / unsupported, and REL02 made the narrow landing-copy edits to pull the unsupported ones (broad "fact-checks/remembers everything", "the product wrote this page", current managed-AI behavior). A sharp friction surfaced here: Biome 1.9.4 processed 0 HTML files — it doesn't lint HTML at all, so the landing edits got no automated lint coverage. Worth knowing before trusting "biome passed" on a copy change.

SIG01 — feedback is feedback, not a requirement

packages/shared/src/scopeIntegrity.ts is the same shape: a pure combiner, buildScopeIntegrityDecisionGateReport, fusing structured feedback, blueprint-diff state, task-packet non-goal checks, and sycophancy/scope-critic output. Its core rule is the anti- sycophancy discipline I keep relearning: user pressure and medium critic-risk do not silently become requirements — they raise a captain-review reason. It blocks only on real conditions: blueprint conflict, ADR-required changes, non-goal violations, an explicit critic block, or high scope risk. It is phase-1 shared model only — no Mission Control UI, no merge-queue, no GraphStore persistence, no git scanner (callers pass changed files in).

Where it stands

44 shared tests for REL03, 39 for SIG01, both typecheck-clean. The gates are pure, isolated, and deterministic — the cheap, boring kind of correctness. The remaining work is wiring (CI enforcement) and the facts the gates exist to wait for: owner-confirmed price, one Merchant-of- Record, aligned versions, and notarization state of the actual artifact. The gate's whole value is that it won't let me pretend those are done.