* docs(designs): honest work and gate integrity plan (autoplan-reviewed) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(e2e): review-gate-integrity rule case registered in touchfiles and the PR profile Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(honesty): claims-need-evidence kernel, no_claim card field, CEO value test, implementer dispatch rules, ACCEPTANCE_EDITED, Honest Work ethos Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(gate-diff): gate-edit detector lib + CLI, patch fixtures, calibration script, tests_ran in gstack-evidence Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(gate-diff): RH-15? only on CI/config floors after calibration Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(review,ship): Gate Integrity category, GATE_SCAN_BLOCK, one-way gate disposition, actor stamping, ZERO-RUN lanes, draft-while-open Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: re-measure parity and context budgets on the integrated tree Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(touchfiles): gate-diff fixtures and helpers select review-gate-integrity Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: archaeology lint wording, paid-file census, harness-version non-behavioral record Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pin review Step 3.5 rename, plan-path placeholder, mktemp template, per-stream evidence tail Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: ACCEPTANCE_EDITED fence substitutes the <plan-path> placeholder Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(gate-diff): shallow boundary commits report no_base instead of a whole-tree inventory; recalibrate Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
50 KiB
Design: Honest work and gate integrity across the base skills
Status: plan, autoplan complete (CEO → DX → Eng); awaiting approval
Created: 2026-10-09
Source: the just-say-no-to-process-porn-and-ceremony skill (SKILL.md, REWARD-HACKING-CATALOG.md, HONESTY-PROTOCOL.md, ENFORCEMENT.md, three worksheets), evaluated against gstack's preamble, /review, /ship, /qa, /plan-ceo-review, /plan-eng-review, /spec, /retro.
Problem
The source skill names a family of agent pathologies with stable IDs: an agent
weakens the gate instead of the code (deletes or skips a test, regenerates a
golden, widens a timeout, adds eslint-disable, commits with --no-verify),
presents a mock or fixture as live proof, claims a command it never ran, closes
work by editing the spec, or builds ledgers and dashboards that nothing
consumes. Its evidence is real multi-agent sessions on a beads/NTM swarm; the
gstack-native incidents that match it are cited per workstream below.
gstack already enforces most of the honesty half: /ship Step 16's "no
completion claims without fresh verification evidence", the evidence ledger,
/review's "Verification of claims", the Completion Status Protocol, the
Claimed Limitations directive, the plan-completion audit's Linked to #N
rule (RH-9), measure.md's "never raise a budget, lower a threshold, add a
retry or skip a case to get a pass" (RH-15, SM-11), the testing specialist's
"never accept buggy-output goldens or discarded valid red tests" (RH-3) and
"Negative Assertions" category, and /ship's "never rewrite history" (RH-17).
What gstack does not have:
- Gate-integrity review is scattered, unnamed, and absent from the core
pass that small diffs get. The clauses above live in a specialist that
only runs on diffs over 50 lines, in
/ship's measure loop, and in/qa. Nothing in the core/reviewchecklist, and nothing mechanical, lists the gate edits in a diff (deleted or skipped tests,.only, a timeout bumped from 5s to 60s,@ts-ignore, a regenerated snapshot,[skip ci]) so that a human dispositions each one. The checklist even suppresses "eval threshold changes" outright (review/checklist.md, Suppressions). - No No-Claim line. Plan test criteria name a positive assertion and a wrong result it rejects (CEO Section 6 step 1) but never state what a green result does not prove, the line that makes proof-class inflation a violation of the item's own text.
- Zero-run green is invisible. The evidence ledger records exit codes. A
suite that selected nothing and exited 0 is recorded as a pass.
gstack-observed: AGENTS.md validation discipline, "Bun can silently ignore
a nonexistent file selector and pass the remaining files";
describe.skipplaceholders earning zero selected-case credit. - Execution honesty is implied, not stated. The preamble says a claimed limitation needs evidence; it does not say a claimed execution must have happened, that a mock never stands in for a required live check, that a checked null result is a successful result, that conclusion-changing failures are disclosed, or that stderr is never silenced in an evidence command. gstack-observed: CLAUDE.md "'Pre-existing' without receipts is a lazy claim".
- No value test for process artifacts in plans.
/plan-ceo-reviewchallenges scope but has no question for "this plan adds a ledger / audit matrix / meta-report about the work: who uses it, what decision changes, why is this the smallest version, when does it retire?" - Implementing children inherit narrow success criteria.
/spec --executepipes the spec toclaude -p;/shipStep 7 generates tests and Step 9.4 auto-fixes through children. The source's dispatch rules (never "make the tests pass"; a child's report is a claim; the parent re-checks what the child touched) are absent.
The control is the human disposition, not the detector. In a solo
session the agent that weakened the gate is the agent running /ship twenty
minutes later. A detector that reports to its own author is disclosure, not
control. Everything below is built so that each gate edit reaches a human,
by name, with its justification, in the PR body; the detector exists to make
that listing complete and cheap, and must never become a verdict.
What is universal and what is not
Adopt (universal to any agent writing code against tests), in durability order: the No-Claim line and planted negative in acceptance criteria; dispatch rules for implementing children; disclosure of every gate edit at a human ASK with its reason landing in the PR body; execution honesty, failure disclosure, truthful null results and the stderr rule; zero-run green and positive observables beyond exit code (PL-1, PL-2); the gate-integrity pattern catalog (RH-1, 2, 3, 4, 5, 10, 12, 13, 14, 15, 16) as the engine behind the listing; the value test for process artifacts; the auditor's posture as a self-report at publication.
Do not adopt (swarm- and tracker-specific): the beads/br tracker
encoding, canary-the-enforcement (its universal lesson, "show the claimed
control fires", is kept as the detector's calibration run), the joint-freeze
protocol, SM-2/3/6 ready-pool mechanics, refusal-farming redirects, cass
session mining, NTM references, the three worksheets as standalone
artifacts, the AGENTS.md "durable law" offer (gstack's AGENTS.md is the
durable law), the USER/ENABLER/PROCESS commit classification (it needs a
project-specific definition of "user" an agent cannot infer from paths, and
gstack's own product would read as PROCESS).
No new skill. The agent most in need of an honesty audit is the least
likely to invoke one; /review and /ship are the only surfaces guaranteed
to see the diff, so prevention is folded into them. (A new skill would also
cost catalog bytes, but that is not the reason.) The retrospective "what did
we conceal or waste over the last week" job is real and distinct; it gets a
range mode on the detector (W1) and is otherwise a follow-on, not this PR.
Workstreams
Each workstream names its consumer, the gate it strengthens, the gstack-observed defect that justifies it (or "speculative: adopted on the source's evidence"), and its retirement condition.
W1. Gate-edit detector (lib/gate-diff.ts, bin/gstack-gate-diff)
Consumer: /review Step 3.5/4, /ship core checklist (new item 2.5), Steps
7, 9.4, 16 and 19 (PR body). Gate: pre-landing review and publication.
Observed defect: review/checklist.md suppression of threshold changes;
AGENTS.md "do not lower thresholds, increase model budgets, skip cases, or
rejudge a failure to manufacture a pass" (written after RH-15-class
incidents); the per-pattern rows are adopted on the source's evidence.
Retirement: when the host's review tooling lists gate edits natively, or
when calibration (below) shows findings are skipped more than they are read.
Why a tool and not prose. gstack-diff-scope already flags
SCOPE_TESTS and SCOPE_CONFIG; prose alone could say "read every hunk in
those files with the gate question". The model skips or rationalizes that
step under context pressure, and the PR body needs counts. The tool
produces the inventory and the tags; it never judges and never suppresses.
Three levels, kept apart everywhere (tool output, review prose, PR body):
| level | what | who sees it | human action |
|---|---|---|---|
| listed | every hunk in a test, CI, runner/lint-config, snapshot or golden path (the floor) | counted in GATE_SUMMARY:; full listing in --out file |
none |
| read | listed hunks that carry a tag, or that remove lines (deletion-bearing), capped at 50 with the cap disclosed | the agent reads each with the gate question | none unless it becomes a finding |
| finding | a read hunk the agent judges a relaxation not justified by a measurement or decision visible in the diff | ASK (W2) | one decision per finding or coherent group |
Measured on gstack's own last 40–49 merged PRs (two reviewers, path-floor proxy): median 23–45 listed hunks per PR, p75 over 100, max about 1,300; deletion-bearing median 16. Mandatory disposition per listed hunk is therefore unworkable; only findings reach a human. Added tests, renamed tests, and snapshots updated alongside their source are inventory, never findings.
Floor: own matcher, not gstack-diff-scope's. diff-scope's test globs
are root-anchored and case-sensitive (test/*, *_test.*) so
src/test/java/…, Tests/FooTests.swift and FooTest.kt are never tests,
and its CONFIG class covers every *.yml and lockfile. W1 ships its own
matcher: any-depth, case-insensitive **/test/**, **/tests/**,
**/spec/**, **/__tests__/**, *.test.*, *.spec.*, *_test.*,
*_spec.*, test_*.py, *Test.{java,kt,scala}, *Tests.swift,
*_test.go, *_test.exs; a content backstop (a hunk in any file whose
removed lines match a test-declaration idiom is listed even when the path
did not match); an explicit CI/runner/lint-config list (.github/workflows/,
.gitlab-ci.yml, Jenkinsfile, vitest.config*, jest.config*,
pytest.ini, setup.cfg, tox.ini, bunfig.toml, .eslintrc*,
eslint.config*, tsconfig*.json, .rubocop.yml, golangci*.yml);
snapshot/golden paths (__snapshots__/, *.snap, *.golden,
testdata/). Candidate = committed diff + staged + unstaged + untracked
(the same union /ship uses), with rename detection (-M). A project adds
paths through a CLAUDE.md ## Gate Integrity block (Extra test paths:,
Snapshot paths:), following the ## Test Coverage precedent; the
effective classification is printed in the coverage line. No off switch in
v1 (see Review record).
Tags: best-effort labels on top of the floor.
| id | what it tags | signal |
|---|---|---|
| RH-1 | test removed or disabled | removed test(/it(/def test_/func Test/@Test; added .skip, .only, xit, xdescribe, @pytest.mark.skip, #[ignore], t.Skip(, @Disabled, @Ignore, XCTSkip, @tag :skip |
| RH-3 | snapshot changed without its owning test | __snapshots__/<name>.snap or <name>.snap whose conventional owner (<name> test file) has no hunk in the candidate. *.golden, testdata/ and project Snapshot paths: are tagged RH-3? (owner unresolved: listed and read, never inferred). No general source-ownership inference in v1 |
| RH-4 | placeholder committed | added todo!(), unimplemented!(), raise NotImplementedError, throw new Error("not implemented"), pass # TODO in non-test code |
| RH-12 | environment sniffing in product code | added process.env.CI, NODE_ENV === 'test', RAILS_ENV == 'test', PYTEST_CURRENT_TEST, GITHUB_ACTIONS outside test paths |
| RH-13 | suppression pragma added | added eslint-disable, @ts-ignore, @ts-expect-error, # type: ignore, # noqa, #[allow(, // nolint, rubocop:disable, @SuppressWarnings |
| RH-14 | gate bypass | commits in range with [skip ci]/[ci skip]; added continue-on-error: true, allow_failure: true, --no-verify in scripts or hooks (job-removal detection cut from v1: a removed workflow job is listed by the floor and read) |
| RH-15 | gate value relaxed | in test/CI/config files, a numeric literal changed on a removed/added line pair with the same key inside one hunk, where the key is a gate keyword and the direction relaxes it: timeout/retry/retries/tolerance/epsilon/delta/maxDiff/jitter up, or threshold/min_score/minimum/coverage/required down (direction-agnostic detection, direction shown); or an exact-count assertion relaxed to a bound (toBe(3) → toBeGreaterThan(0)). Unpaired values (new file, reordered multi-number lines) are tagged RH-15? and read; cross-hunk pairing is out of v1. The tool prints any citation it finds beside the hunk (commit text naming a ship-measure report, or a gstack-shortcut(dec-*) marker resolved through gstack-decision-search); it never suppresses on it, because ship-measure reports are gitignored and commit text is forgeable; the human decides |
| RH-16 | exception swallowed | added catch {}, catch (e) {}, except: pass, rescue nil, _ = err in non-test code (overlaps slop-scan's empty-catch rule; gate-diff lists it under the RH id, slop-scan keeps its fix) |
RH-2 (proof-class inflation) and RH-5 (tautological tests) are prose
checks in W2 and W5, not patterns. RH-17 (history rewriting) is not
detectable from a final diff and is covered by /ship's existing "never
rewrite history"; the coverage line says so rather than pretending.
CLI contract. gstack-gate-diff [base-ref] [--format table|jsonl] [--commit <sha>] [--help]. (--out, --paths and --range from the DX
draft are cut: the artifact path is tool-owned, a child's self-reported
path list is both a pathspec-injection surface and a self-report, and the
only history need is calibration, which --commit <sha> covers by scanning
one commit's first-parent delta without the working tree.) Positional
base defaults the way gstack-diff-scope does (origin/HEAD → main →
master); an already-qualified ref is used as given. stdout: one
GATE_SUMMARY: listed=N read=N tagged={RH-1:2,RH-15:1} unmatched=N coverage=<patterns> languages_unlisted=<…> candidate=<sha+wtree> line, then
the read-level listing (bounded; the full listing goes to --out).
--format jsonl emits one object per listed hunk (id, path, level,
tags, old, new, citation); id is the tool-computed identity
(path pair + tag + normalised changed lines, with a structural suffix for
duplicates), never a caller-supplied hash. Candidate model: one
comparison, resolved merge-base SHA → the working tree as it stands,
including non-ignored untracked files as additions (git diff -M --no-color --no-ext-diff --no-textconv <merge-base> plus untracked), with
the candidate fingerprint from gstack-wtree recorded so a concurrently
changing tree is detected, never the committed + HEAD + untracked
concatenation gstack-diff-scope uses for filenames. Binary, symlink,
submodule and undecodable paths are named unsupported coverage, not zero
hunks; CRLF is normalised for tag matching only.
Admission. A hunk is listed when its path matches the floor or any
content tag matches (so an eslint-disable or empty catch added in
product code is admitted through RH-13/RH-16); package.json scripts,
pyproject.toml and setup.cfg tool sections join the config list. The
read level is ordered tagged-first, then deletion-bearing, bounded by 50
hunks and 200 KB; GATE_SUMMARY reports eligible, inspected,
unread. When anything is unread the result is partial, rendered
Gate edits: partial (K of M eligible read), reported as missing coverage
like UNAVAILABLE, and never as none detected.
Exit contract. 0 with or without hunks; 2 only when the base is
unresolvable (GATE_ERROR=no_base ref=<ref> with the concrete recovery:
git fetch <remote> <branch> from the detected remote, or "deepen the
shallow history") or the tool itself failed. Unmatched paths are reported
in GATE_SUMMARY: unmatched=N and listed, never an exit 2. The template
block that calls it branches on the exit explicitly, never
2>/dev/null || true:
if GATE_OUT=$(~/.claude/skills/gstack/bin/gstack-gate-diff <base> 2>&1); then
printf '%s\n' "$GATE_OUT"
else
GATE_EXIT=$?; printf '%s\n' "$GATE_OUT"
case "$GATE_EXIT" in
2) echo "Gate integrity: UNAVAILABLE — $(printf '%s\n' "$GATE_OUT" | grep -m1 '^GATE_ERROR=')" ;;
*) echo "Gate integrity: UNAVAILABLE — helper exit $GATE_EXIT (stale install? run /gstack-upgrade)" ;;
esac
fi
The block is one resolver constant (GATE_SCAN_BLOCK) rendered into
/review Step 3.5 and /ship's core checklist so the two cannot drift; it
uses ctx.paths.binDir and the runtime-root prelude like every other
helper call (the ~/.claude/... path above is the Claude-host rendering),
and if assignment; then … else … fi because a bare VAR=$(cmd); RC=$?
exits an errexit shell before the branch. The full listing is written by
the tool itself to
$GSTACK_STATE_ROOT/projects/<slug>/gate-diff/<candidate-fingerprint>.jsonl
(mode 0600, never synced, never inside the working tree: an untracked file
under the repo would flip /review's review_binding to changed and make
/ship loop at Step 11.5), and prints that path in GATE_SUMMARY:.
UNAVAILABLE is reported as missing coverage in the final report and PR body
(the same way an unverified outside review is), does not flip COMPLETED,
and is never rendered as none detected.
Calibration (free) before W2 ships. A small unshipped script runs
--commit <sha> over gstack's last 50 first-parent merge commits and over
two external fixture diffs (a snapshot-heavy frontend repo, a Rails repo)
and publishes per PR: listed, eligible, tagged by id. I hand-read the
tagged hunks of 10 PRs and record for each whether a reviewer would want
to read it. Tags below 50% on that hand read are demoted to inventory
before merge. Agent-level signal ("findings the agent raised, kept,
restored, left open") is not simulated before ship: it is collected
after ship from the persisted records, which is the only honest source
for it; that is the retirement/promotion input for the tags.
Tests: fixtures under test/fixtures/gate-diff/ as patch text plus a
builder that materialises each into a temporary git repo (no nested .git
in the tree), one planted case per tag, plus controls: a test file moved
(rename: listed, untagged), an added test (listed, untagged), a snapshot
updated with its owning test (listed, untagged), a test deleted alongside
its deleted source (listed and tagged RH-1: tags are factual; the
agent's judgment, not the tool, makes it a non-finding), a spec/ skill
directory with no *_spec.*/*.spec.* inside (not a test path: the floor
treats spec/ as tests only when such files exist there). Adversarial fixtures: a forged "measured"
comment (tagged RH-15 with the citation shown, never suppressed), a golden
regenerated beside an unrelated source edit (tagged RH-3), a Kotlin
@Ignore under src/test/kotlin/ (listed and tagged by the own matcher),
a new untracked vitest.config.ts with a raised timeout (listed via the
untracked union). Pure-function core (lib/gate-diff/{classify,tags,git,render}.ts, each
under the 800/150 module-size ratchet) tested directly; the bin is a thin
CLI with --help, argument-error and errexit-block tests.
W2. Gate Integrity in /review
Consumer: /review core pass (every diff size), /ship core checklist.
Gate: pre-landing review. Observed defect: the "eval threshold changes"
suppression hides RH-15 in the one skill that reads every diff. Retirement:
with W1.
review/checklist.md: new Pass 1 CRITICAL category Gate Integrity, written in the three levels: read every read-level hunk W1 listed with one question, does this relaxation silence the failure it was added for, or is it justified by a measurement or decision visible in this diff?; a hunk that fails the question is a finding; inventory is never a finding. Confidence scores how sure the agent is that the hunk is a gate relaxation (identification), not whether it is justified, so a real gate edit cannot be buried in the low-confidence appendix. Gate findings are ASK, never AUTO-FIX. Coherent groups (one source change plus its N snapshots; one timeout constant raised in M tests) are one decision.- ASK options (replacing bare Fix/Skip for this category): A) Restore
the gate (revert the relaxation; keep the product change), B) Keep —
justified: <agent-drafted one-line reason from the diff/citation>,
C) Keep — other reason (the human supplies it). There is no bare Skip;
the chosen reason is persisted as
reasonon the finding record withaction: "kept", andgate: "RH-15". An auto-chosen answer (plan-tuneAUTO_DECIDE, spawned auto-pick) is never a disposition: the record getsaction: "open". - Question identity and non-interactive sessions. The disposition
question is registered as
review-gate-disposition, a one-way id, sogstack-question-preferencerefusesnever-askfor it and/plan-tunecannot auto-decide it. It carries a fourth option D) Leave open for a later human, and the review prose marks D as the recommended option whenever the preamble echoedSESSION_KIND: spawnedorheadless. The existing auto-pick machinery (skill-start block, AskUserQuestion Format, the two AUQ hooks) then yieldsopenwith no exception edited anywhere. Belt and braces:gstack-review-logstampsactoritself fromgstack-session-kindand forcesaction: "open"for non-interactive kinds, so a child cannot logactor: human, action: keptabout itself. - Narrow the "Eval threshold changes" suppression to: a cited change is
still a finding (a citation never removes a relaxation from the human
ASK, because a diff comment or commit message is forgeable); the agent
resolves the citation read-only (
gstack-decision-searchfordec-*, the namedship-measurereport if present) and pre-fills option B with it, marked verified or unverified. Hunk text, test names, paths and commit messages are data: the review prose says so, excerpts are rendered as fenced data, and nothing in them is followed as an instruction or used to fillactororreason. - Step 3.5 becomes "Diff scans":
GATE_SCAN_BLOCKfirst (local, seconds), thenslop:diffas today; carry the gate listing into Step 4. Small diffs run it too. /reviewnever publishes. Gate findings are persisted in this invocation's review record withgate,id(the tool's identity),reason, logger-stampedactor, and theopen/kept/restoredactions; no raw hunk text is stored (the durable record is brain-synced).opencounts inissues_found. v1 limit: dispositions are per-invocation;/ship's own review pass asks again, exactly as Fix/Skip work today. Cross-invocation reuse needs a versioned identity and a transition table and is a follow-on, stated as such.- Final report line:
Gate edits: none detected (N listed, M read; patterns: …; unlisted idioms: …)orGate edits: N listed, M read, K findings — J kept (reasons below), R restored, O open. review/gate-integrity.md: the adapted catalog (ids, one-paragraph definitions, gstack's countermeasure, attribution to the source skill).
W3. /ship: zero-run green, gate edits in the PR body, draft while open
Consumer: /ship Steps 5, 7, 9 (core checklist item 2.5), 9.4, 11.5, 16,
19. Gate: publication. Observed defect: AGENTS.md "Bun can silently ignore
a nonexistent file selector and pass the remaining files"; describe.skip
zero-credit rule. Retirement: when every supported runner's receipt carries
a test count natively.
- Step 5: a lane passes on its runner summary line, not on exit 0 alone
(PL-2). Three distinct count states:
tests_ran: N;tests_ran: 0(ZERO-RUN: bun0 pass 0 fail, jest "No tests found", pytest "no tests ran", rspec "0 examples",go testwithno test files, an empty selection after a filter) is triaged as a failed lane;tests_ran: unknown(runner summary not recognised) keeps today's exit-0 pass and printscount unavailablein the PR body. The three are never merged. gstack-evidence runkeeps a bounded tail of the streamed output (the saved log is capped at 2 MB and would truncate the summary) and parses it intotests_ranfor the supported runners only: bun (reusingshard-engine's classifier; executed = pass + fail, so an all-skip run is ZERO-RUN), jest/vitest, pytest, rspec, and Go's zero-only case (every package[no test files]); the last summary per runner wins. Everything else isunknown.checkreports ZERO-RUN as STALE-equivalent and unknown as non-blocking. The TRANSPARENCY INVARIANT holds:run's exit is always the child's; detection lives in the receipt and incheck.- After Step 7's test-generation child returns and after Step 9.4's parent
fixes, run the full
GATE_SCAN_BLOCKagain (no child-supplied path list: a self-reported list is the self-report problem); a child's green is a claim until the parent has read its gate hunks. Step 7's child prompt carries the implementer rules (W7); its last-line JSON contract is preserved unchanged (it has nosummaryfield) and the child's gate-edit disclosure uses the prose the protocol already permits before the JSON. - Step 16 stage 5, the folded honesty inventory, two lines: for each gate finding, its disposition, reason and actor; if none, the coverage line. Adopt the auditor's posture for this report: describe the session's own gate edits as evidence to examine, not positions to defend.
- Draft while open. With any
opengate finding, a new PR is created withgstack-post pr-create --draft(GitHub and GitLab both supported by the existing publisher); the early-PR finalisation consults the gate state before marking ready. For an existing ready PR there is no silent conversion (gh pr ready --undois GitHub-only and a label fallback does not make a PR draft): interactive sessions get one question, A) resolve the open items now, B) publish ready with theGate edits: N openbanner (recorded as a human-authorised publication); spawned and headless sessions stop before pushing to a ready PR and report. If disclosure itself fails, the ship reports the failure and leaves the local open-items report; no fabricated success. A later agent never infers approval from the PR existing. - Step 19 PR body: under
## Pre-Landing ReviewtheGate edits:line with kept reasons; under## Test Coveragethe per-lanetests rancount orcount unavailable.
W4. Preamble: Claims Need Evidence (tiers 2–4, including terse)
Consumer: every skill that claims evidence. Gate: the skill's completion
report. Observed defect: CLAUDE.md "'Pre-existing' without receipts is a lazy
claim"; /ship's existing unverified rules exist because reports said
"passed" for runs that did not happen. Retirement: never while gstack reports
evidence; wording trimmed if the context-budget ratchet shows it is the
marginal cost.
Extend generate-evidence-directive.ts from "Claimed Limitations Need
Evidence" to a small honesty kernel, rendered for every tier and for terse
(it is a correctness rule, not prose style; terse currently renders the
directive as '', so the budget delta is the whole kernel, about 140
tokens, and test/fixtures/context-budget.json is refreshed in the same
commit):
- A claimed limitation needs the verbatim error, documented statement or live probe (unchanged).
- A claimed execution happened and its result was observed; name the command and the revision or content fingerprint. Never cite a command whose stderr was silenced.
- Name the evidence kind you have (static read, unit test, fixture or replay, live run, production) and never substitute one for a kind the question requires: a mock is not a live check, a static grep is not a reachability proof. Reuse is valid when the consumed inputs are unchanged, as Step 16 already defines it; a new commit alone does not invalidate evidence.
- Disclose any failure or missing coverage that would change the reader's conclusion; "done, unverified" and "done" are different reports.
- A checked null result ("ran X, inspected Y, found no material issue") is a successful result; an unsupported positive claim is worse than silence. Agreement between agents, or several reads of one source, is one datum.
Completion Status Protocol: DONE means "supported by evidence valid for the
final consumed inputs; reuse and anything not independently verified are
named."
Composition: the kernel renders through generatePreamble for tiers 2–4
and for terse (the tier-1 extension is cut: /browse and the other
tier-1 skills have no evidence claim to make; /benchmark, which does,
moves to tier 2 as a follow-on). Honest size: the draft bullets above are
about 250–290 tokens, not 140; the shipped kernel is trimmed to ≤ 200 by
dropping the reuse sentence in favour of "reuse rules: Step 16". Size is
measured on generated output per tier and host before and after; the
context-budget.json refresh moves ~49 skills and is committed with the
kernel; the parity baseline may need one documented rebase. The stderr
rule is scoped to commands whose output is cited as evidence; the existing
2>/dev/null || true idioms that feed a cited result (Step 3.5's scans)
are rewritten to branch on exit instead, and optional discovery probes keep
theirs.
W5. No-Claim line in plans, specs and test cards
Consumer: /plan-eng-review Test Plan Artifact, /plan-ceo-review Section
6, /spec Section 10 and its archived spec (which is the spawned agent's
prompt), /ship Step 7's Value: test card, /autoplan (inherits the
sections). Gate: plan approval and /ship's plan-completion audit. Observed
defect: speculative, adopted on the source's evidence (ENFORCEMENT.md calls
it the single highest-leverage line). Retirement: if two releases of
plan-completion audits show the line is never cited in a
changed/partial classification.
- One vocabulary, not two. gstack's test card already has
protects=(the positive observable),fails_when=(the planted wrong result),why_new=,seam=, owned byscripts/resolvers/test-value.ts(VALUE_CARD_FIELDS,renderValueCard) and pinned bytest/test-value-bar.test.ts. W5 addsno_claim=at that owner as an optional fifth field: cards gstack generates from now on carry it, four-field cards from older prompts stay valid in the machine check, and everyrenderValueCardcaller and the static specialist copy are inventoried in the same commit. It adds the field to that card and uses the same four-plus-one names in the Test Plan Artifact and/specacceptance criteria, so a plan item, its spec criterion and the test that lands carry one description.no_claimis one sentence: what green here does not prove and which fixtures or mocks stand in for what. Two examples are rendered in the templates:no_claim=provider acceptance (mocked provider; unit only)andno_claim=none beyond protects, so the latter does not become reflexive filler. Never asked of the user; authored by the reviewer. Ano_claimsentence never waives a required live check or an unresolved test-method decision. /spec's acceptance template drops the literal3. Tests written and passing(the exact dispatch W7 forbids) in favour of criteria written in the card vocabulary.- CEO Section 6 step 1 already asks for "a wrong result it rejects"; it
gains the
no_claimsentence. - RH-10 (spec-editing as progress) in
/ship's plan-completion audit: the parent diffs the plan's acceptance-bearing sections as they stand in the working tree (the audit already includes uncommitted work) against a baseline: theorigin/<base>version when the plan exists there, else its first commit on the branch, elsebaseline unavailable(plans outside the repo). An item whose acceptance text changed gets a new classificationACCEPTANCE_EDITED, reported besidedone/changed/…and never folded intoCHANGED(which today means "same goal achieved" and passes the gate); the PR body names it as a scope change, and the original obligation is still audited. Limit stated in the skill: an item rewritten in the same commit that introduced the plan is invisible.
W6. Value test for process artifacts in /plan-ceo-review
Consumer: /plan-ceo-review Section 1 (Architecture) question list. Gate:
plan approval. Observed defect: speculative for product plans; gstack's own
history of ledgers (evidence, decision, review log, telemetry) is the
motivating case. Retirement: if the question never changes a plan in two
releases.
One added question in Section 1, not a new 0D decision: when a plan adds an artifact about the work (status report, audit matrix, certificate, meta-report, conformance check nothing consumes), ask: who uses it, what decision or delivered behavior changes because it exists, why this is the smallest useful version, and when it retires. An explicit user request answers the first two and is recorded as provenance; the last two are still answered. A credible, explicitly identified preventive risk is a valid answer; a past incident is not required. Manufacturing a code consumer to pass the question is the pathology, not an answer.
Carve-out, stated in the text: product observability (dashboards, alerts, runbooks for the shipped system) is launch scope under Prime Directive 5 and is never a process artifact; user-facing docs and agent-consumed instructions are product. The question applies to artifacts about the work, not about the system.
W7. Dispatch rules for implementing children
Consumer: /spec's archived spec (piped to claude -p), /ship Step 7
test-generation child. Gate: the parent's acceptance of a child's work.
Observed defect: speculative; gstack's specialist schema already requires
path/severity and defines NO FINDINGS as a complete empty result.
Retirement: with W5.
- Implementer rules, one constant in
scripts/resolvers/dispatch-rules.tsrendered into/spec's template (so they travel inside the spec that is the prompt) and into Step 7's child prompt: acceptance criteria are theprotects / fails_when / no_claimprofile; never "make the tests pass"; any test, validator, CI, tolerance or suppression change is reported on its own line in your summary with its justification; real code and real tests land in the same change; a reported command names the command and its observed result. /spec --executesetsGSTACK_SESSION_KIND=spawnedon theclaude -pspawn so the child's gstack skills take the spawned branch (open, draft) instead of degrading tointeractiveand emitting a prose brief to nobody.- Reviewer-side change: none. The CEO draft's "
NO FINDINGSnames what you examined" contradicted the specialists' "NO FINDINGSand nothing else" sentinel that the parent parses; it is dropped. - Step 9.4 is parent work, not a child; it is covered by W3's post-fix scan.
W8. Docs and tests
review/gate-integrity.md(W2) is the catalog;ETHOS.mdgains a short Honest Work paragraph linking to it. TASTE DECISION (see Review record): ship the ETHOS paragraph now or hold one release.README.md/AGENTS.mdskill tables: no new skill; the/reviewand/shipone-liners gain "gate integrity".- Tests, free: W1 fixtures and unit tests;
test/review-workflow-claritystructural checks for the Gate Integrity category and the narrowed suppression; prompt-structure tests for the No-Claim tokens in the Test Plan Artifact and the/specimplementer section; preamble render test for the kernel at every tier andterse;test/context-budget-ratchetfixture refresh;test/parity-suite; SKILL.md regeneration for every touched template. - Evals: no new paid lanes. The existing diff-selected PR gate
(
eval:bg:pr) runs because templates and the preamble are prompt files. TASTE DECISION: add onegate-tier,rule-kind E2E case (a fixture branch with a planted.skipand a widened timeout;/reviewmust list both as gate findings and route them to the one-way ASK, never AUTO-FIX; asserted on native question/edit events, not prose), registered in all threetest/helpers/touchfiles-data.tsmaps andPR_PROFILE_CASE_IDSwith a hermetic bin, versus relying on the selected existing evals alone.
NOT in scope
- A new
/honesty-auditskill, or a/reviewaudit mode over a time window (follow-on; W1's--rangeis the only piece shipped). - Tracker encoding, canaries, joint-freeze, swarm ready-pool mechanics, refusal-farming redirects, USER/ENABLER/PROCESS retro classification.
- Blocking on RH-3 golden regeneration (listed and read, never auto-blocked: gstack's own SKILL.md goldens regenerate on every template edit by design; for product snapshot tests the PR-body line makes it visible).
- Cross-invocation reuse of gate dispositions;
--paths/--range; RH-3 source-ownership inference; cross-hunk RH-15 pairing; RH-14 job removal; exact Go test counts; converting an existing ready PR to draft; the tier-1 kernel; a project off switch. - A second round "tuning the gate-integrity category". Defects found in the detector get fixed; categories do not get redesigned (the source's meta-trap rule).
What already exists (and is reused, not duplicated)
/shipStep 16 evidence freshness and ledger (gstack-evidence): W3 addstests_ran, not a new ledger.ship/sections/measure.md: the measure-then-fix loop is gstack's native RH-15 and SM-11 countermeasure; W2's "carries the measurement" means aship-measurereport or equivalent./review"Verification of claims"; CEO Section 6 "a wrong result it rejects";Linked to #Nnever auto-close (RH-9); outside reviewunverifiedis missing coverage, never a pass;/ship"never rewrite history" (RH-17).- Testing specialist: "never accept buggy-output goldens or discarded valid red tests", "Negative Assertions" (about added absence assertions; W2 is about removed or relaxed gates; both stay), the test value bar.
- slop-scan's empty-catch rule (RH-16 cites it rather than re-detecting the
fix);
gstack-diff-scope's path classes, union-of-changes and exit contract (W1 reuses the classes and copies the contract). - Specialist schema (
path,severity,NO FINDINGS) and the plan-completion audit child's "report only" contract.
Failure modes
| Path | Realistic failure | Coverage |
|---|---|---|
| repo with no tests dir | zero listed, exit 0, reads as clean | coverage line always printed: none detected (0 listed; patterns …; paths checked …); a bounded null, not an error |
| shallow CI checkout / absent base | base unresolvable | exit 2 → Gate integrity: UNAVAILABLE (no_base ref=…) with the concrete fetch/deepen command; reported as missing coverage, never none; COMPLETED unaffected |
| Java/Kotlin/Swift layout | diff-scope's root-anchored globs miss src/test/java |
W1's own any-depth matcher + content backstop; languages_unlisted in the coverage line |
| unknown language skip idiom | listed, untagged | floor lists it; coverage line names the gap; no false none |
| test-heavy PR (median 23–45 listed hunks) | per-hunk disposition drowns the human | three levels: only findings reach ASK; groups are one decision; read cap 50 disclosed |
| RH-15 over-fires on legitimate tuning | ASK fatigue | calibration before CRITICAL; cited changes are read, not findings; kept reasons land in PR body |
| forged "measured" comment or injected test name | a relaxation vanishes before ASK, or excerpt text steers the agent | citations never exclude a finding; resolved read-only and marked (un)verified; excerpts are fenced data; actor stamped by the logger |
| detector artifact written in the repo | review_binding flips to changed; /ship loops at 11.5 |
artifact lives under the state root, 0600, path printed |
| 1,300-hunk diff | 50 read, late .skip unread, "none" printed |
tagged-first ordering; partial (K of M eligible read); never none |
| removed credential in a hunk | leaks into the synced review record or PR body | records store id/path/tag/reason only; snippets pass the existing scanner with a content withheld marker; PR body through the scanned publisher |
plan-tune never-ask / AUTO_DECIDE on the gate question |
gate self-waives interactively | review-gate-disposition is a one-way id; preference tool refuses never-ask; auto-chosen = open |
spawned /ship (e.g. /spec --execute) |
auto-Fix reverts a legitimate edit, or auto-Skip waives the gate | GSTACK_SESSION_KIND=spawned set on spawn; option D recommended → auto-pick yields open; logger forces open for non-interactive kinds regardless |
headless /ship with a PR surface |
blocks on any .skip |
same as spawned; BLOCKED only with no writable PR destination; disclosure failure reported, never fabricated |
| existing ready PR gains an open finding | ships as ready silently | interactive: explicit human choice (resolve / publish-with-banner, recorded); spawned/headless: stop and report |
/review answer not reused by /ship |
human asked twice | accepted v1 limit, same as Fix/Skip today; reuse is a follow-on |
| child reports a partial touched-path list | its CI edit escapes the scan | parent runs the full scan, never a child-supplied list |
| preamble growth trips context-budget ratchet | CI red | measured per tier/host; fixture refreshed for the retained delta only |
| unknown runner summary | tests_ran: unknown |
exit-0 pass kept; count unavailable in PR body; never conflated with ZERO-RUN |
tests_ran parser bug |
flips a green lane red | detection in receipt/check only (TRANSPARENCY INVARIANT); check says which state it saw |
2>/dev/null || true idiom copied for the gate scan |
"could not look" swallowed | GATE_SCAN_BLOCK constant branches on exit; shared by /review and /ship |
| plan rewritten in its introducing commit | RH-10 check blind | limit stated in the skill text |
Delivery order (one PR, staged commits, one owner per shared file)
- Contracts and free fixtures: candidate model, coverage/partial
semantics, disposition states,
no_claimschema attest-value.ts, publication rules; gate-diff patch fixtures and builder. - Detector and counts:
lib/gate-diff/*,bin/gstack-gate-diff,gstack-evidencetests_ran, the calibration script and its 10-PR hand read. Independent of 3 and 4. - Review/ship integration: checklist category,
GATE_SCAN_BLOCK,review-gate-dispositionone-way id with option D, logger-stampedactor/forcedopen, record fields, draft-while-open in the publisher, Step 7/9.4 rescan, Step 16 lines, PR body lines,ACCEPTANCE_EDITED. - Prose and acceptance: kernel (W4),
no_claimin templates (W5), W6 question, W7 dispatch rules and/specspawn env,review/gate-integrity.md, ETHOS paragraph (if approved), regen of every SKILL.md and the four host goldens, context-budget and parity refreshes, touchfiles + PR profile entries, the paidrulecase, thenbun run testonce on the frozen tree andeval:bg:pr.
Steps 2 and 3 can run in two worktrees; Step 4 has one owner. ship/ and
the preamble are each touched by exactly one of them at a time.
Rough size
Human team ~3 weeks; AI-assisted 3–4 days with the v1 cuts above (both
Eng voices independently sized the DX draft at 5+ days as written). Real
code: lib/gate-diff/* + CLI ~800–1,000 LOC, their fixtures and tests
~400–600, logger/registry/evidence/publisher ~250–350; ~600 lines of
template prose across 12 files; regen and fixture refreshes; one paid
case; a free calibration script. One PR, shippable at each staged commit. One session, not a project
(ENFORCEMENT.md: "adapt the templates; don't redesign them").
Review record
CEO phase (autoplan, SELECTIVE EXPANSION; native Claude + outside GPT-6 Astra)
Both voices: problem real, fold-in right, universal/swarm split roughly right, plan failed its own creation gate and left spawned/headless undefined.
Decisions applied (P1 completeness, P5 explicit, P6 action):
- Observed-defect and retirement condition per workstream, citing gstack incidents (native 1.3 critical).
- Detector split into discovery / disposition / publication; file-class
floor + pattern tags; coverage line always printed;
-Mrename detection; controls listed-not-tagged; adversarial fixtures; RH-4 added; RH-2 moved to prose; RH-17 to "already exists"; range mode; calibration on 50 merged PRs before CRITICAL (native 1.2, 3.1, 3.4, 4.7, 2.6; outside 2). - Spawned/headless disposition defined (native 3.2 high).
- W6 scoped to artifacts about the work, with the Prime Directive 5 carve-out, value-test wording, no incident requirement, manufactured consumers banned, one Section 1 question (native 4.1; outside 3).
- W4 kernel extended (failure disclosure, no substitution, correlated agreement, evidence reuse) and rendered for every tier including terse; no total proof ordering; DONE redefined around final consumed inputs (outside 4, 5; native 2.3).
- W7 reviewer form cut to one sentence; parent-side check moved to Step 7/9.4 children; implementer rules travel in the spec (native 4.3, 2.x).
- W5 RH-10 mechanism and limit specified; No-Claim allowed to read "none beyond the positive" (native 4.8; outside "no four ceremonial fields").
- Old W8 (retro classification) cut (native 4.4; outside 6).
- Folded honesty inventory as two lines in Step 16 stage 5 with the auditor's posture (native 4.5, 6).
- Problem statement softened and "already exists" completed with
measure.md(native 2.1, 4.2). - Workstreams reordered by durability (native 5.1).
Taste decisions carried to the final gate:
- ETHOS "Honest Work" paragraph now (names are the deterrent) vs hold one release (ETHOS is identity, not a changelog). Recommendation: ship a short paragraph now.
- One new paid
gate-tier E2E case for the Gate Integrity category vs existing selected evals only. Recommendation: add the one case; prose that does not change behavior is ceremony by this plan's own standard. - Retrospective audit packaging (
/review --gate-audit <since>vs a/honesty-auditskill) — deferred as follow-on; no decision needed now.
User challenges: none. Both voices agree with the user's direction (adapt into base skills, no new skill).
DX phase (autoplan, DX POLISH; native Claude + outside GPT-6 Astra)
Both voices measured gstack's own history and found the CEO-phase floor
unworkable as a disposition unit (median 23–45 listed test/CI hunks per
PR); both found the spawned/headless rule unenforced by the code that
actually chooses answers; both found the NO FINDINGS sentence
contradictory and the /review-publishes-draft line out of /review's
remit.
Decisions applied (P5 explicit, P3 pragmatic, P1 completeness):
- Three levels, listed / read / finding; only findings reach ASK; groups are one decision; confidence scores identification (native 4.1, outside DX-01, DX-05).
- ASK options Restore / Keep-justified (agent-drafted reason) / Keep-other;
no bare Skip; record gains
gate,reason,actor,open/kept(native 4.2; outside DX-01, DX-07). review-gate-dispositionregistered one-way sonever-askandAUTO_DECIDEcannot waive it; spawned block exempts it;/specsetsGSTACK_SESSION_KIND=spawned(native 3.1; outside DX-06)./reviewpersists,/shippublishes; draft-while-open covers new, early and existing PRs; disclosure failure reported (outside DX-07).- Own test-path matcher + content backstop + explicit config list +
untracked union; CLAUDE.md
## Gate Integritypaths block; CLI and exit contract specified;--rangewalks commits;--pathsfor children;GATE_SCAN_BLOCKshared constant with explicit exit branching; gate scan before slop (native 2.3, 1.1, 1.2; outside DX-02, DX-03, DX-04). - Tool never suppresses RH-15; shows citation; human decides (native).
- Three count states for zero-run (outside DX-08).
no_claim=folded into the existing test card vocabulary;/specdrops "Tests written and passing" (native, outside duplication check).- W7 reviewer sentence dropped; Step 9.4 is parent work (native; outside DX-10).
- Kernel composition and sizing made explicit; stderr rule scoped (outside DX-09).
- Calibration redefined per PR as listed/read/findings/decisions, including test-scoped PRs and two external fixture repos (native, outside DX-05).
Not adopted: a project-level off switch or Mode: listing-only in v1
(both voices lean against a silent off; revisit after calibration); handing
large-diff gate reads to the testing specialist (keeps the category in the
core pass that small diffs get); renaming the tool.
Taste decisions carried to the final gate: tool name (gstack-gate-diff,
matching the gstack-* helper family, vs gstack-diff-gates); whether to
offer Mode: listing-only later; the read cap (50).
Eng phase (autoplan; native Claude + outside GPT-6 Astra)
Both voices: Go as one PR, not as written. Agreed: the DX draft had become
a cross-workflow state machine (disposition reuse, draft conversion of
ready PRs, child path lists, ownership inference) sized as a 600-LOC
helper; the detector's .gstack/tmp artifact would flip the review
binding; actor/reason were self-attested; RH-15 missed lowered
thresholds (the motivating suppression) and RH-3 had no owner definition;
the citation-only exclusion let a forged comment remove a finding before
the human control; Step 7 has no summary field; CHANGED already means
"same goal achieved"; the kernel is ~250–290 tokens; the paid case needs
three touchfile maps plus the PR profile.
Decisions applied (P5 explicit, P3 pragmatic, P4 DRY, P6 action):
- Artifact under the state root, 0600, unsynced; records store identity, not hunks; snippets redacted through the existing scanner.
- Logger stamps
actorand forcesopenfor spawned/headless; option D "Leave open" recommended in those sessions, so no auto-pick exemption is edited anywhere (native 1.2/1.3 chosen over editing four hook surfaces). - RH-15 direction-agnostic with per-key relax direction, same-hunk pairs,
RH-15?for unpaired; RH-3 conventional__snapshots__owner only,RH-3?otherwise; tags stay factual on controls. - Admission = floor OR tag; bounded read with
partialcoverage state. - Citations never exclude a finding; resolved read-only, pre-fill option B.
- One candidate model (merge-base → working tree + untracked), hardened git flags, named unsupported path kinds; tool-computed identities.
- Full rescan after children; Step 7 JSON untouched;
ACCEPTANCE_EDITEDas a new classification;no_claimoptional attest-value.ts. - Kernel tiers 2–4 + terse, ≤ 200 tokens;
/benchmarkto tier 2 later. - Free calibration (tag precision + 10-PR hand read); agent-level signal collected post-ship from records.
- Paid case:
rulekind,gatetier, three maps + PR profile, hermetic bin. - Cuts: cross-invocation reuse,
--paths,--range, RH-14 job removal, RH-3 inference, cross-hunk RH-15, Go counts, ready→draft conversion, tier-1 kernel. - Sizing 3–4 AI days; four staged commits with single owners.
Taste decisions carried to the final gate: ETHOS paragraph now vs later;
the paid rule case; open counting in issues_found (plan says yes);
read cap 50 / 200 KB.