# RI-1-002 — Publish-gate negative controls (SDLC-D-034 second half) - Task: RI-1-002 (docs/release-integrity workstream, PRD item RI-N1), issue ref #1275 - Branch: `test/ri-050-publish-gate-negative` (base `origin/next` @ d8e0aec9 = PR #1277, RI-1-001) - Budget: worker estimate ~45K tokens; keep scoped to the two test files + scratchpad. ## Objective Checked-in negative-control tests that PROVE the publish gate fails when it must: 1. Broken mandatory check blocks every publish step (structural DAG proof from `.woodpecker/publish.yml`). 2. Bypass shapes fail the checker: missing edge, hidden effect (non-`publish` name), detached verify, always-pass verify (`failure: ignore` / `success` override), conditional verify (`when`). 3. Exact-commit identity: no HEAD-moving step between verify and publish effects; legitimate re-checkout requires verify to re-run after it. 4. `verify-release.mjs` composition control: a SUBSET stage list fails the composition check. ## Plan - NEW `scripts/publish-gate-structure.test.mjs` — self-contained structural checker (`assertPublishGateBlocksOnVerify`) + positive control on the real pipeline + one negative-control test per bypass shape (S1–S6, documented in file header) + positive control for the legitimate re-checkout shape. - EXTEND `scripts/verify-release.test.mjs` — refactor the stage-mirror test body into `assertStagesMirrorCi(stages, ci)`; add negative control dropping each stage one at a time (subset must throw). ## Conventions confirmed - Root `test:checkout` = `node --test scripts/*.test.mjs` → new file auto-joins `pnpm test`. - Test-enumeration guard population is `*test*.sh` under `packages/mosaic/framework/tools/` only → unaffected. - Root eslint covers only `**/*.{ts,tsx}` → .mjs files need Prettier style only (printWidth 100, singleQuote, semi, trailingComma all). - Do NOT touch docs/TASKS.md, docs/release-integrity/TASKS.md, docs/scratchpads/. ## Progress log - [x] Base verified: publish.yml `verify` step + verify-release.mjs present; HEAD contains origin/next. - [x] Wrote scripts/publish-gate-structure.test.mjs - [x] Extended scripts/verify-release.test.mjs (mirror fn + subset negative control) - [x] Gates: node --test scripts (31 tests pass), prettier clean on touched files, pnpm typecheck PASS, pnpm lint PASS, pnpm format:check PASS - [x] Committed ff585b88 + pushed, PR #1305 → next (no conflicts). Stopped before merge per task instruction. ## Evidence - `node --test scripts/verify-release.test.mjs scripts/publish-gate-structure.test.mjs` → 31 tests, 0 fail. - Mutation sanity: temporarily removing the `verify` edge from build-gateway in publish.yml → structure test goes red (verified manually during dev, then reverted). - Gates run from repo root on this worktree; results in Progress log. ## Risks / notes - Effect detection (`isPublishCommand`) is deliberately over-broad (any npm/pnpm/yarn command mentioning `publish`, any kaniko/docker-push/`--destination`) — fail-closed: a false positive forces justification, a false negative is the actual hazard. - `git fetch` flagged as HEAD-moving even though fetch alone doesn't move HEAD — fail-closed on the classic `fetch && reset` pair.