Engineering standard

The engineering standard this project is built under: the per-item cycle, the machine gates, the autonomy boundary and the security controls.

markdownreferencev1FGHG-Z5CZgovernancestandardgatesautonomysecurity

Updated 2026-10-17

CUP-DEV-STD-001 — cuppy development governance

Status: PROPOSED for owner lock. Drafted and adversarially vetted 2026-10-11. Agents do not ratify standards. Binding as working practice from drafting; the lock is the owner's act and nothing below claims it has happened. Version 1.0.

Scope. Every change to the cuppy platform repository (.) and the production tree it deploys (.-app-factory/src): code, configuration, schema, documentation, tracking records, and the public hosts cuppy.io and semarchy.cuppy.io. Out of scope: the content of the redesign specification in tracking/roadmap/ (owner-reviewed research; §5 holds the research-only gate), the other projects on this host, and the vhosts and DNS of the sibling sites.

Normative keywords are RFC 2119.

Inheritance — canonical by reference, never copied. One authority per concern:

InstrumentAuthority of recordRelationship
Development standardcommander/commander-dev/docs/governance/CMD-DEV-STD-001.md v1.1adopted; this is cuppy's local binding
Release standard.../CMD-REL-STD-001.mdadopted in part; §8.5 states what is blocked
Single-operator postureauth/docs/governance/SINGLE-OPERATOR-GOVERNANCE.mdadopted in its compensating-controls form (§5)
Invariants and acceptance criteriadocs/governance/INVARIANTS.mdcuppy's own
Executable invariant statusapps/api/src/gates/gateCells.tscuppy's own; the only place a status may change
Incident ledgerdocs/governance/LESSONS-LEARNED.mdcuppy's own
Identifier registrydocs/governance/ID-REGISTRY.mdcuppy's own

Inherited text is not reproduced. A second copy of a rule is a second source of truth, and cuppy has paid for that class already: mcp/handler.ts:199 hard-codes a version that drifted from package.json, and .github/workflows/ci.yml restates the checks instead of calling the script that owns them.

Precedence. Where this document and an inherited instrument disagree for cuppy work, this document wins, and the disagreement MUST appear in §9. An unrecorded divergence is a defect.


Why this exists, measured

In the seven days to 2026-10-11 this project shipped, to a live public site: an unauthenticated path traversal that streamed the production database to anonymous callers through four res.download routes; a production process running with NODE_ENV=development, so x-semarchy-role: owner granted owner to any caller; the owner's email returned to anonymous callers on reader routes; a first remediation round an independent review found incomplete in four places; and a test suite that wrote 47 commits into this history because both file stores accepted an ancestor git work tree as their own.

Every one was already forbidden by prose. Rules 5, 6, 7 and 9 of the previous CLAUDE.md named the exact classes that shipped anyway. cuppy's problem was never the absence of rules. It was the absence of an enforcement column. Each incident, its cause with a file reference, and the rule it produced is in LESSONS-LEARNED.md. A rule here without a scar there, or in a sibling's ledger, SHOULD be deleted at the next vet. There is no separate provenance section: the reason is attached to each rule, where a reader deciding whether to keep it will look.

The enforceability census

Seventeen rules in this document carry the word gate. Every other rule — the large majority — is judgment. The seventeen: C1, C2, C6, C7, C9 (§0); R1 (§2); §7.1; §7.2; rows 1, 3 and 5 of §7.3; the confinement half of §7.4; all four gated rows of §7.5; and rule 6 of §5A. Every rule in §7.8 is explicitly not among them.

Their entire mechanical surface is eight steps in one script, plus the security-class test files the suite runs. Of the seventeen, sixteen fire only when a human or an agent chooses to run one of two scripts — gate.sh, or restart.sh for C2 — and CI has never run, because this repository has no remote. Exactly one control here fires without being invoked: the PreToolUse hook in §5A.

That ratio is the honest state of the project, printed up front so no later session reads this document as a description of a controlled system. The highest-value change available to cuppy is not another rule in this file — it is a git remote, which turns the invoked checks into automatic ones. Configuring it is operator-hand.

Would this standard have caught the week's defects?

work/vet/SET-002-phase0-hardening.md already answers this for the hardening set — "two of four" — including the most important sentence in cuppy's governance to date:

"no control in this project yet looks for a class of defect nobody has written a test for; the closest thing is the adversarial vet, which is a person-or-agent process, not a gate."

This standard adds one distinction that record does not draw, and it moves the answer down:

DefectAs a regression in a known routeAs a new unguarded route
L1 traversalCaught — path-confinement.test.ts, a gateNot caught. The controls that would (FEAT-019's route-enumerating drill, FEAT-017's auth floor) are both red
L3 email leakCaught — redact.test.ts, a gateNot caught. Nothing asserts every reader route passes through the masker; §6's parity sweep is prose
L2 dev-mode productionCaught by a live fatal boot assertion — the one unambiguous winthe header trust path is still representable and inert only because NODE_ENV is correct; inv:role-never-from-a-request-header is red
L4 incomplete fix roundNot caught by any gate. R3 is the control and its enforcement is a warning—
L5 47 fixture commitsCaught — gate.sh step 4, a gate—

Two classes caught as regressions, one caught by a boot assertion with a latent path behind it, and the class that produced the incomplete-fix round — the one that needed a second reviewer — is governed by prose and a warning. That is this standard's real measure, stated before any rule rather than after.


§1. Enforcement vocabulary

WordMeaning
gateA check that fails a run: inside scripts/gate.sh, inside the suite it calls, or a hook that exits non-zero.
writtenThe mechanism exists but fails nothing today — CI with no remote, a script no path calls. Named with what makes it enforcing.
plannedNo mechanism exists. The work item that creates one is named.
proseJudgment, recorded in an artifact. Accepted risk, stated as such.

Nothing here may be described as enforced unless its word is gate. A rule whose word is prose is the rule that will fail next.


§0. The engineering bar, with its enforcement column

The bar is the ten absolutes in CLAUDE.md, which is the wording of record. They appear here only as identifiers mapped to mechanisms.

#RuleMechanismWord
C1Two trees, one change — until cutovergate.sh step 6 diffs the declared source roots; inv:trees-identical-until-cutovergate
C2Verify through the public hostsrestart.sh curls both hosts and refuses on a non-200/non-JSONgate (conditional on C3)
C3Restart only by the script— nothing prevents a bare pm2 restartprose
C4Git discipline — path-scoped staging, no unasked commits—prose
C5Roles from tokens and the session cookie onlyinv:role-never-from-a-request-header — REDplanned (FEAT-018)
C6Caller paths through confinedPath/confinedFilepath-confinement.test.ts; inv:storage-path-confined, inv:identifier-is-never-a-pathgate
C7No addresses or secrets in outputredact.test.ts; inv:redaction-fails-closed, inv:no-address-below-admin-on-any-channel; gate.sh step 5gate
C8Research-only gate on the Phase 1+ redesign—prose
C9The gate is green before any restartgate.sh, called by restart.sh step 1gate (conditional on C3)
C10Closure evidence, with outputs§2 R5prose

Four observations this table exists to force:

  1. C3 is prose, and C2 and C9 are conditional on it. A script is not a gate if nothing requires its use. An

agent can type pm2 restart semarchy and bypass the gate, the public-host verification and the restart log in one command. What would close it is a PreToolUse hook on Bash matching pm2 *(restart|stop|delete|reload) outside the script — the shape of §5A's hook. Planned, not written: a Bash-matching hook needs a false-positive pass first. Until then the three most operationally important rules here rest on compliance.

  1. C5 is red, and it is the rule that already failed. No test can assert that a request header confers

nothing, because the suite's own helper (apps/api/test/helpers.ts:9) authenticates by setting x-semarchy-role under NODE_ENV=test. The suite depends on the trust path whose production absence it would need to prove. FEAT-018 is a negative-principal test constructing the app in production mode.

  1. C4, C8 and C10 are prose and will decay. Accepted: C4 and C8 constrain judgment rather than program

behaviour, and C10's evidence is only meaningful to a reader.

  1. A rule with no row here does not exist.

§2. The per-work-item cycle

R1 — Impact record, scoped to what it can pay for

A work item whose diff touches apps/, scripts/, config/, a schema or a dependency manifest MUST commit a JSON impact record at work/impact/<ITEM-ID>.json. Schema of record: work/impact/TEMPLATE.json. Three layers:

  • L1, decision safety — twelve lenses, only the binding ones surfaced, plus adversarialNotes and a

proposed verdict: path-confinement · authn-bypass · hub-isolation · information-disclosure · fail-open-defaults · env-mode-drift · injection · rate-and-abuse · secrets · idempotency · observability · testability.

  • L2, blast radius — eleven axes, each Touched: <what> or None: `api · mcp_tools · hub_content ·

storage_schema · auth_session · fs_paths · redaction · tests_and_gates · public_hosts · skew · docs`.

  • L3, operational — only where it binds: perf · cost · backup · runbook · secrets · supply-chain.

Plus the keys the existing FEAT-001 record invented, which two trees make load-bearing: reload_required, operator_hand_remaining, rollback, alongside standards, config_chain, tests, gate_cells, verdict.

A documentation-only item gets no record file — one docs: line in the session note instead. This diverges from the inherited R1's "no exceptions, including docs-only items" and is recorded as §9 C8. Reason: cuppy is a documentation platform whose docs commits will outnumber code commits several to one. A rule demanding a JSON file for a typo fix is the rule the project silently stops obeying in week three, and a ledger containing a selectively-obeyed rule is worse than one with a narrower rule that holds.

Reason for the record at all. Each L1 lens is a class this project has shipped or is one change away from shipping. The L2 axes make "no sprawl" a measurement: a change touching more axes than its intent requires is leaking scope by definition, and skew is an axis because cuppy's callers are coding agents holding cached tool catalogs. JSON, because a record a script can read is a record a gate can check. Enforcement: gate — gate.sh step 7 fails when a tracking record changed in the working tree or HEAD has no impact record and the same range touched code. Binding: forward from adoption. FEAT-002–FEAT-005 are grandfathered (residual R4). Retroactive red cells make the gate red on day one with nothing to do about it, which teaches bypass.

R2 — Verdict gating, and who owns the verdict

No work item MAY start while a predecessor on its dependency track carries a verdict of amend, reject or pending. proceed-with-mitigation unlocks the successor only after the mitigation commit lands. Parallel-track items MAY batch into one session note per dependency layer, each keeping its own record.

**The verdict in an impact record is the implementing agent's proposal. The binding verdict is the vet record's (R3).** An agent writing proceed in its own record and then proceeding is self-approval with an extra file in the way. The inherited standard does not draw this distinction because it assumed an advisor at the other end of the delivery note; cuppy has no advisor, so the distinction must be in the text.

Reason. The absence of a verdict reads as permission unless the standard says otherwise, and that is how one bad seam becomes five dependent bad seams. FEAT-007 is already correctly blocked on FEAT-006. Enforcement: prose. It becomes a gate when the pointer's in-flight block is machine-readable.

R3 — Post-set re-vet by an independent clean-context agent

After each set of related items, a re-vet pass MUST run without an operator prompt and MUST:

  1. read the set's actual diff, hunk by hunk — never the implementing agent's report;
  2. walk at least the twelve L1 lenses, and the L2 axes against the whole live system;
  3. look back over the previous three sets in work/vet/ for interaction regressions, invariants weakened,

assumptions invalidated, and unswept siblings of earlier findings;

  1. verify closures behaviourally: re-run each original reproducer against the public host, after first

establishing which tree and which build is serving;

  1. disposition every finding as fixed / scheduled-with-owner / recorded — never silently dropped;
  2. commit the verdict to work/vet/<SET-ID>.md.

A set without its vet record is red and the next set does not unlock.

The re-vet agent MUST be a fresh subagent in a clean context, dispatched read-only, with no access to the implementing session's claims, and MUST NOT be the implementing agent reflecting on its own work.

Its prompt MUST be work/vet/PROMPT.md, used as committed. An implementing session that writes its own reviewer's brief has reproduced the second-identity problem one layer up: it chooses what the reviewer looks at. The dispatching session may append the set id and the diff range, nothing else. *This is the correction that makes R3 an honest substitute for a human reviewer rather than a longer way to agree with oneself.*

Reason. L4 bought this rule: three fixes were item-level correct and system-level incomplete, and what found that was an independent review reading the modules. Cuppy's one operator does not read every diff, so the independent pass is the review. The three-set look-back exists because per-item review is blind to the interaction between adjacent sets, and a seven-phase plan is nothing but adjacent sets. Step 4 is not optional while two trees exist: a reproducer run against the repo tree while pm2 serves the other one is right-looking evidence about the wrong run. Enforcement: written, and the limit is the point. gate.sh step 8 warns when the newest vet record is older than the newest commit touching apps/ or scripts/. It cannot fail, because the re-vet follows deployment by design — both existing vet records were written after their sets were live — so a hard fail would block the restart that precedes the vet. The forcing function is R3 itself, administered by the next session reading the warning. No script can judge a vet's quality and this one does not pretend to.

R4 — The delivery note is the session note

Each set ships tracking/updates/YYYY-MM-DD-<slug>.md carrying exactly: item ids with commit SHAs; the L2 axes touched; every deviation from the plan, however small, called out explicitly; verification evidence as output, not claim; any docs-only docs: lines; open questions and assumptions; the one-line verdict. Reason. The "deviation however small" clause catches the incomplete-fix class — the four round-one gaps were all small deviations nobody wrote down. Bound to tracking/updates/, which this project already writes, rather than the inherited docs/comms/ path nobody will create: a named authority with no artifact behind it is worse than no authority. Enforcement: prose.

R5 — Closure evidence, three parts and an identity check

A fix is closed only when its record carries (a) the deployed build identity read from the public host, (b) the schema/migration state at verification time, (c) the reproducer's own output, or the exact replayable request an outside party can issue. And the evidence MUST be verified to be about the thing it claims — check the output's own host, build and ids against the claim before publishing. "Verified" as free text is banned. Reason. An artifact carrying the right version string and the wrong run is worse than none, because it survives scrutiny while being false — and with two trees and one public host, cuppy generates that artifact by accident every time an agent curls loopback. Enforcement: prose, reinforced by restart.sh, which prints the output an agent must paste.


§3. Machine gates: what "green" means today

One command. bash scripts/gate.sh, the only authoritative list of checks. .github/workflows/ci.yml MUST call it rather than restate its steps — it currently restates them, the same two-lists-of-one-fact class that produced the version drift, and its "no new commit" half is a shell no-op that can never fail.

Green means all eight steps pass in the repository tree:

#CheckWhy it exists
1npm run typecheck, all three workspacesthe root script called workspace scripts that did not exist and silently failed; fixed during source-tree reconciliation (FEAT-001)
2npm run buildthe repository could not build at all until 2026-10-11
3npm test, which is also where the gate matrix runsthe matrix refuses a coverage claim whose backing test was renamed, moved or detached
4the suite left no tracked change and no new committhe suite wrote 47 commits into this history (L5)
5no shipped default token outside the config chain; no secret-shaped file trackedchange-me-admin-token is public knowledge
6the two trees identical across the declared source rootsa one-tree fix is a system regression by construction
7every changed code work item has an impact record (R1)the artifact existed without the gate
8warns when the newest vet record is older than the newest code commit (R3)a skipped vet is how the incomplete-fix class survives

Step 6 is an allowlist, and that is a correction rather than a detail. It enumerates the source roots the running service is built from — apps/api/src, apps/web/src, apps/web/public, packages/shared/src, plus the workspace manifests — and compares only those. It does not subtract a growing list of exceptions from "everything".

Two reasons, the first being this standard's own §7.4: *an allowlist is the only form that stays correct when something new appears.* An exclusion list silently begins comparing every directory a later phase adds. This was measured, not predicted. Run verbatim against the two trees on 2026-10-11, the first draft's exclusion-list comparison returned four differences — CLAUDE.md, CURRENT_WORK.md, QUICKSTART.md and a diverged README.md — none of which is compiled into anything the service serves. So step 6 was red before a single new governance file landed; restart.sh refuses on a red gate (C9); and the net effect of adopting the standard would have been that cuppy could not restart at all, including to deploy the next security fix. The allowlist comparison, run against the same two trees at the same moment, returns identical.

A gate that blocks the fix for the incident it was written for is not respected; it is bypassed, and then the policy is gone. The allowlist makes governance artifacts, scripts, docs and tracking repo-only by declaration, which is the truth, and the gate green on landing.

The comparison lives in one file, scripts/lib/tree-diff.sh, sourced by gate.sh, restart.sh and preflight.sh. Three copies of one exclusion list was a §6 one-authority-per-concern violation inside the governance tooling itself. That file also records, with reasons, what is deliberately not compared: config/ and storage/ are runtime-mutable — the application writes them, so production legitimately diverges from the repo the moment anyone saves a connection — and dist/ is build output, whose real property ("prod's build is not stale against prod's source") restart.sh checks directly instead.

Step 7's coverage limit, disclosed rather than widened. It fires only when a tracking record and code change in the same range, so the second and later commits of a feature — code with no tracking-record edit — pass it silently. Widening it would false-red every ordinary follow-up commit, which teaches bypass. The honest position: step 7 catches the opening of an undocumented work item, not every commit inside one, and R1 covers the rest as prose.

What green does NOT mean:

  • CI is written, not enforcing. It has never run; there is no remote. It becomes gate when the owner

configures one — operator-hand, and the highest-value unblock in this document.

  • There is one test tier. Everything in apps/api/test/ is in-process, isolated storage, no network, no

database service. No contract tier boots the real server against a real database. Planned, Phase 1.

  • A skipped or crashed run is not a pass. Exit 2 means the gate could not run: never a pass, never a fail.
  • A rotten or false-positive finding is surfaced, never worked around — not with --no-verify, not by

re-running until green. If a check is wrong, fix the check.

  • The gate takes minutes, running typecheck, a full build and the whole suite serially. During a live

incident that is a hazard, not a safeguard, which is why §5 carries a break-glass path rather than leaving an agent to invent one under pressure.


§4. The artifact ledger

ArtifactPathWritten byBinds from
Pointer of recorddocs/sprints/next-task.txtevery sessionnow
Dynamic stateCURRENT_WORK.mdevery sessionnow
Impact recordwork/impact/<ITEM-ID>.jsonimplementing agentadoption, forward, code items only
Vet promptwork/vet/PROMPT.mdcommitted templateadoption
Vet recordwork/vet/<SET-ID>.mdindependent clean-context agentadoption, forward
Session / delivery notetracking/updates/YYYY-MM-DD-<slug>.mdimplementing agentnow
Feature / bug record`tracking/features\bugs/<ID>.md`implementing agentnow
Invariantsdocs/governance/INVARIANTS.mdany agent; rules only, never statusadoption
Gate cells (status)apps/api/src/gates/gateCells.tsby commit onlyadoption
Incident ledgerdocs/governance/LESSONS-LEARNED.mdthe session that closed the incidentadoption
ID registrydocs/governance/ID-REGISTRY.mdthe commit that mints the IDadoption
Runbookdocs/runbooks/RUNBOOK-<subject>.mdthe session that learned the failure modeadoption
Release notetracking/releases/CHANGELOG.md, .../vX.Y.Z.mdscripts/release.sh only§8
Restart logtracking/restart-log.txtscripts/restart.sh onlyadoption

Cost ceiling, as a rule. A code work item costs three files: its tracking record, its impact record, a share of the set's session note. A set costs one more: its vet record. A docs-only item costs one line. Anything that would raise those numbers must first retire something. Reason: a ledger demanding five files per item is abandoned by week three, and an abandoned ledger is indistinguishable from no governance while still costing the tokens to read.

Pointer hygiene. The current state block plus at most the two previous; older blocks roll into tracking/updates/. Every session is told to read it, so an unbounded ledger is a per-session token tax. prose.

Stale documents are retired loudly, never silently and never by deletion — a dated STALE — RETIRED banner at line 1 naming the replacement. An agent that reads a confident, wrong document acts on it, and this repository's docs/PASS*.md files describe a surface that has moved, several still showing x-semarchy-role as a live auth path. prose, with the sweep as residual R7.

Counts are derived, never quoted. Test, tool, route and version numbers are read from the system (npm test, /api/health, grep -c over the catalog) and MUST NOT be restated in a governance document as fact. Where a document needs a number, it names the command. Reason, not hypothetical: the first draft of this standard quoted "223 MCP tools", "260 routes" and "24 PASS* files". Measured 2026-10-11: the catalog held 217 tool definitions, api.ts registered 261 routes, and there were 23 PASS* files. Three numbers, three wrong, in the document that forbids quoting them — one lifted from its own list of examples of stale numbers. The rule survives its author. prose, and the only exception is a dated incident narrative, where "136 MB on 2026-10-10" is history rather than a claim about the present.


§5. Autonomy, and what is operator-hand

An agent proceeds without an operator turn only when all three hold:

  1. Reversible. git revert undoes it; no destructive migration, external publication or production

mutation; and it is inside the scoped work item or its written spec.

  1. The gate is green, honestly. Exit 0, with skips and rotten findings surfaced rather than counted. Exit 2

is not green.

  1. No open state-source disagreement on the touched seam.

Criterion 3 is load-bearing and is its own rule:

**A disagreement between the repository, the production tree, the pointer, CURRENT_WORK.md, the running pm2 process and the database is itself the defect. PAUSE and surface it. Do not pick the source that lets the work continue.**

Reason. Two of three defects were state-source disagreements, not coding errors: the file said production and the process said development, because dotenv never overwrites an injected variable (L2). "Two trees, patch both" is a licensed, permanent disagreement of the same kind, which is why the cutover is Phase 0's first task. Three live instances are named in residual R10. gate for the tree half (step 6), prose for the rest.

Standing agent authority (no operator turn needed)

Commit each finished slice to the working branch; run the gate; build; write and update tracking, impact, vet and session records; author and republish documentation artifacts; run read-only probes against the public hosts; dispatch read-only audit subagents; mint and delete its own short-lived test fixtures.

An irreversible act is never committed and deployed in the same session. The commit lands; the deploy waits for a later turn. Reason: it is the one half of the inherited cooling-off control that costs nothing. The other half — a mandatory timed delay — is rejected (§9 C3): the sibling that built it opened a work item to walk it back after four ceremonies cost an hour of serialized waiting.

Operator-hand — landed as prepared and pending, with a runbook

An agent principal is never an approver. Where a control expects a second reviewer who does not exist, the compensating path is honest attestation in the record + the act surfaced to the operator + the act left pending or reversible — never a fabricated second identity, and never "reviewed and approved" where one agent reviewed itself. The wording:

review: "independent clean-context agent pass (work/vet/PROMPT.md), read-only, run <n>; no human reviewer — single-operator compensating controls, SoD infeasible (org size 1). NIST SP 800-53r5 AC-5; ISO/IEC 27001 A.5.3; SOC 2 CC3–CC7."

Reason for the citation and not just the candour. Fabricating a second approver identity is a control deficiency and a misleading one to an auditor; the same standards that require separation of duties also provide for its infeasibility in a one-person organization, through documented compensating controls. Cuppy is about to publish audit artifacts and a benchmark whitepaper describing real defects on a live public service, so the attestation must carry the clause that makes it defensible rather than merely honest.

The following are the operator's hand regardless of what the permission system allows: pm2 restart or stop outside restart.sh (customer-visible on a live public site); the pm2 cutover to the checkout (runbook prepared, not executed); DNS, vhosts and certificates (shared host, three other sites front the same proxy); .env changes including COMING_SOON* and the preview token (a product decision and a credential); secrets creation or rotation; any write to storage/app.db outside the application; destructive migrations and any non-additive-first schema change (§7.8 F1); writing another project's production database; configuring a git remote, pushing, tagging, publishing (publication is the operator's, local commits are the agent's); any change to the authorization seam; hub publication or unpublication once hubs exist (irreversible from a consumer's view).

Break-glass: the incident path

A live security incident MAY need a restart sooner than the gate can finish. Inventing a path under pressure is how a script gets bypassed permanently, so the path is written in advance:

bash scripts/restart.sh --break-glass --reason "<incident, one line>"

Its scope is the gate's latency and nothing else. It skips only step 1. Every other refusal still applies, and each costs seconds rather than minutes: the two-tree source parity check, the build-staleness check, the production-mode assertion read from the process, the public-host verification, the L1 traversal re-probe, the log append. The line is marked BREAK-GLASS. gate.sh MUST be run once the incident closes, and the next vet record MUST disposition every break-glass line since the previous one — an undispositioned break-glass is a finding in its own right.

Two-tree parity is never skipped, even here, and the distinction is the point: if the trees differ because an agent has patched only one, the remedy is to patch the other — seconds of cp — not to deploy half a fix. Shipping a one-tree fix during an incident is exactly how a fix reads as complete while the hole stays open, which is L4's class. Break-glass buys time against a slow check; it never buys permission to skip a control.

Reason. C9 plus a minutes-long gate is a deadlock whose only exit is an agent typing a bare pm2 command, losing the public-host verification and the reproducer re-probe at exactly the moment they matter most. Break-glass drops the expensive check, keeps every cheap one, and leaves a reviewed record. prose for the decision to use it; gate for everything it does not skip.

The authorization seam is frozen

lib/auth.ts, lib/access.ts, lib/comingSoon.ts, the cuppy_session cookie path, lib/bootAssertions.ts and the NODE_ENV resolution are operator-hand; agents treat the seam as read-only outside an owner-released work item.

Never code around a refusal. A 401, 403, 428 or a boot assertion that blocks an agent is the control working. A mitigation that weakens the guard is by definition operator-hand.

Reason. An agent that can edit the seam can unblock itself through the security control, and this is the exact seam whose misconfiguration granted owner to anonymous callers. prose today; planned to add a gate.sh step failing when a diff touches these files without an owner-released item id in the commit trailer.

No history rewriting, with its one recorded exception

Corrections land as forward commits referencing what they fix. Exception, recorded: on 2026-10-11 the branch was rebased onto 9bf78d3 to drop 47 test-authored commits, after taking the safety branch backup-before-history-cleanup. That is the sanctioned shape — safety branch first, reason in LESSONS-LEARNED L5 — and the only one. prose.


§5A. Model-tier governance

  1. Orchestration — triage, adversarial vetting, synthesis, release decisions — stays in the main loop.
  2. Every subagent dispatch MUST set model explicitly: opus for analysis, code and vetting; sonnet for

mass or mechanical work; haiku for the cheapest fan-out. At most four concurrent.

  1. Subagents never inherit the model. Inheritance binds at agent creation and persists for that agent's

life, including across every later resume.

  1. A subagent MUST NOT be resumed across a governance boundary (a vet round, a deploy, an acceptance).

Re-dispatch fresh with a self-contained prompt.

  1. A subagent that dies mid-event on a model, credit or limit error is a PAUSE, not a retry-in-place: verify

what actually landed (git log, git status, the built artifacts, the running build) before relaunching.

  1. Never run subagent work on the orchestration tier. Degrade opus → sonnet → haiku → the main loop; never

up-tier.

Reason. Rules 3 and 4 cost a sibling a release event: a runner agent created early in a session silently held the then-current main-loop model and died on its credit limit during release preflight. For cuppy the same failure mid-deploy leaves a public site half-patched across two trees. Enforcement: gate — .claude/hooks/block-orchestration-tier-subagent.sh, a PreToolUse hook on Agent|Task that exits 2 on an orchestration-tier model. The only control here that fires without being invoked. Its coverage limit is in the script: it sees an explicit model only, never an inherited one — which is how the incident happened. Rules 1, 3, 4, 5, 6 are prose and are repeated in CLAUDE.md for that reason.


§5B. Token-budget governance

  1. State the estimate before the spend. Before any fan-out of three or more subagents, or any single

long-running agent: worker count, each worker's tier, a rough token estimate, in the thread, so the operator can veto before it runs rather than after.

  1. Always leave it recoverable. Every subagent's output MUST be committable on its own, and the orchestrator

commits in slices as results land rather than batching to the end. A limit can terminate any agent at any instant; the test of the design is whether that costs a re-run or costs the work.

  1. Cheapest instrument that answers the question. A targeted grep over re-reading a file; one test file

while iterating and the full gate once at the end; the main loop over a subagent for a single lookup. Re-running a green gate to feel sure is waste, not rigour.

  1. Observable proxies, not an unreadable meter, and check the scope is not already covered before a

fan-out. The plan meter is not readable from inside this host, so the agent gates on what it can see: workers already spawned, whether a 429 has occurred, elapsed wall-clock, and whether a committed record already answers the question. After any 429: finish and commit what is open, no new agents, checkpoint the pointer, say plainly what remains.

Reason. Rule 2 is the only one that actually saved the work when a 429 terminated a sibling's worker mid-task. Rule 4 replaces that project's "ask the operator for the meter", whose stated fallback — assume the last quarter — would forbid most fan-outs every time and therefore be ignored: a rule practice must violate to function is a rule that decays. A duplicate fan-out is the most expensive single mistake available and the check against it costs a thousand tokens. Enforcement: prose, by construction; no hook can read a budget. The control is that the estimate is stated in the thread before the spend.


§6. Sweep and parity discipline

Capability parity is a mandatory sweep lens. Pass 1 of any sweep inventories each capability across all five surfaces: routes/api.ts (REST) ↔ mcp/tools.ts + mcp/handler.ts (MCP tools) ↔ tier gating in lib/access.ts ↔ lib/redact.ts (response masking) ↔ confinedPath/confinedFile in lib/fsdb.ts (filesystem reach).

Backend-reachable with no gate is a dark-capability finding. Advertised by a description or a tier table but unenforced in the handler is a parity finding, and it is the security case:

The request handler is the only enforcement point. Tool descriptions, tier tables, MCP annotations and the web UI are advertisements. Identity comes from the authenticated boundary — a bearer token or the cuppy_session cookie — never from the request body or a header, and never defaults open when resolution fails. if (ctx?.something) scope(...) is a forbidden shape: absent context means an explicit refusal or an explicit public-safe redaction, chosen in the code and written down.

Reason. All three of this week's defects are findings of this one lens. The traversal was a handler with no gate behind a surface that assumed one. The owner-via-header bug was a client-supplied claim treated as enforcement. The email leak was a redaction-parity gap in a codebase that already had the masker.

Skew is a standing L2 axis. Every contract change states its stale-client and stale-server behaviour. Reason: cuppy's clients are coding agents holding cached catalogs of a hand-rolled JSON-RPC surface pinned to protocol 2024-11-05; a renamed tool or a tightened schema is a silent break for every agent that already listed tools. The live drift between package.json and mcp/handler.ts:199 means a client cannot even tell which surface it has.

Findings define classes, not instances. A finding MUST be swept across every sibling site of its class — grep the pattern across the whole surface — before it may close. Fixing only the cited instance is an incomplete fix that reads as complete. Reason: measured, twice. Path confinement had been reimplemented several times with inconsistent rigour; round one hardened three of four call sites and left projectFileBytes on a startsWith check with no separator. Enforcement: prose, carried as a class swept: line in the commit body and in the vet record.

One authority per concern. Duplicated logic across the REST surface, the MCP handler and the web is a defect even when it works. One confinement helper, one git-root helper, one archive guard, one masker, one identity resolver, one authorization seam — and, after this vet, one two-tree comparison (scripts/lib/tree-diff.sh), because the governance tooling had written the same exclusion list three times. prose; inv:one-version-of-record is the first mechanised instance and is red.

Opportunistic extraction. A sprint that materially modifies a region of an oversized module extracts that region as part of the change. Pure line-count refactors with no functional driver are rejected as churn. Reason: routes/api.ts is over a thousand lines of route registrations; both ossifying and churning it are real risks with agents building seven phases. prose.


§7. Security controls, expressed as testable controls

The rules live in INVARIANTS.md in a fixed five-field shape — Immutable because (the attack, with the incident), AC+ (the control works), AC− (the bypass is denied — the load-bearing criterion), Verify (the method), Std (a formal anchor). The status of each lives only in gateCells.ts and changes only by commit. A specification that carries status becomes a ledger and rots.

7.1 The AC− rule. Every security invariant MUST carry a negative acceptance criterion. A positive test proves a feature exists; only the negative test proves the bypass is closed. An invariant with only an AC+ is incomplete. Reason: the round-one path-confinement test never varied the root, which is precisely why round one shipped a hole that a sibling-prefix and a symlink both walked through. gate (the matrix refuses a green cell with no backing test that references it).

7.2 Red is a schedule, not an omission. A control cuppy cannot yet test is written down anyway, as a cell with status: 'red' and a named blocker, surfaced as it.todo in every run. An invariant false as built is marked FALSE-as-built with its file and line, and is the defect-to-fix rather than an omitted statement. Reason: every one of this week's defects was nominally forbidden by prose. A predicate in prose is a wish; a red cell with a named blocker is a schedule. gate.

7.3 Floors and non-representability

Security-critical values are not configuration. A floor a config could relax is not a floor. Where a value should not exist at all, it is removed from the type rather than defaulted off.

ValuePosture
NODE_ENV on a host declaring public hostsnot configurable — boot assertion exits non-zero. Fatal, because the state is exploitable by someone who knows no secret
role from x-semarchy-role / x-forwarded-host / the first x-forwarded-for hopmust be non-representable in production: the header path deleted from the production code path, not disabled by a flag. Currently flag-conditional — RED (FEAT-018)
ADMIN_TOKEN, DEV_TOKEN equal to their shipped defaultsnot configurable on a public deployment — fatal boot assertion plus gate.sh step 5
redaction floor (owner email, key-shaped tokens) below adminnot disableable below platform scope
rate-limit identitynot configurable — the verified peer only; trust proxy MUST stay off, because this proxy appends the real peer last and Express reads from the right
rate-limit windows, body caps, HSTS age, CSP string, retention window, zip capssoft config with a code ceiling, named default at the head of the chain
COMING_SOON* and the preview tokensoft config, operator-hand

Reason. A config overlay that can relax a control is a hole, and the production-in-development-mode defect is that hole realised: a literal in the wrong file defeated the whole config chain and armed three trust paths. gate for rows 1, 3, 5; planned (FEAT-018) for row 2.

7.4 The named conflict cuppy must resolve in writing

Hub content MUST be publicly readable and the production database MUST NOT be, and one process serves both from one filesystem. Not a theoretical tension — it is how a traversal streamed the database to anonymous callers. The resolution, as a design constraint rather than a judgment call:

Every servable root is an allowlist entry, never an exclusion list, and every path derived from caller input is resolved through confinedFile against a declared root with real-path equality. The database and any key material live outside every servable root.

Verify: a drill that walks every file-serving route with both traversal shapes and asserts a 404 and that the database file is never opened. gate for inv:storage-path-confined; planned (FEAT-019) for the drill that enumerates routes rather than naming the four that were found. *This is the control whose absence means a new unguarded route is still invisible.*

7.5 Error surface and redaction — what binds now

RuleReasonWord
Internal errors reach callers as an id only, and the sanitiser is a deny-list of unrecognised internal errors, never an allow-list of known-bad onesan allow-list leaks every error class it has not met yet; a sibling leaked SQL text, absolute paths and a raw validator dump in sequence for exactly this reasongate for the id (middleware landed in FEAT-004); planned for the deny-list shape
An error path MUST NOT change a redaction decision; a catch that yields the unmasked body is forbiddenthe round-one masker failed open on any exception; it now fails closed with a correlation idgate (redact.test.ts)
Secrets never appear in chat, git, a document, an error, a log, or a dev-only response fieldthis project leaked an email with output hygiene as its only controlgate (step 5; redact.test.ts)
.gitignore and the image exclude *.pem *.key *.crt *.p12 *.pfx .env secrets/, asserted by the gatean untracked-by-luck secret is not a controlgate (step 5)
A credential an agent generates or resets is stored in the same step in a named store outside git at mode 0600, with its existence and location in the session noteone that exists only in a transcript is lost at session end and leaked for as long as the transcript lives. Creating the store is operator-handprose

7.6 Tests may never touch the production store. A run MUST assert its resolved storage path is inside the throwaway test directory and is not the production storage/app.db, failing the whole run otherwise. Anything that writes a real store needs a second explicit opt-in flag beyond the ordinary test environment. Reason. apps/api/vitest.config.ts sets STORAGE_DIR: './.test-storage-mcp' — a relative path resolved against the process working directory, with nothing asserting where it lands. A suite run from the wrong directory writes to a real store, on a project whose production database has already been streamed to the internet once and whose stores are git-backed, which is how 47 commits reached this history. planned — FEAT-020, an assertion in test/global-setup.ts; inv:tests-never-touch-the-production-store is RED with that blocker.

7.7 Concurrency — the part that is portable today. Every concurrency control is row- or key-scoped: no table-level lock, no global mutex around a hub write, nothing that blocks unrelated rows, and every new shared-table writer cites its pattern in a comment. And any read-then-insert of a monotonic key is backstopped by a UNIQUE index at the key's grain, with the racing loser's constraint error surfacing as a typed, retryable refusal that names the retry — never a generic failure, never the raw driver code. Both hold on SQLite, so both bind now. prose.

7.8 Rules that bind at Phase 1, written now because they are free now

Phase 1 moves cuppy from SQLite to PostgreSQL and Phase 3 stands up the first hub. These are the rules that are one paragraph before that work and a migration afterwards. None is enforced; none may be cited as a control. They are here so the first implementer inherits a decision instead of making one under deadline.

#RuleWhy it is cheap nowLands
F1Every schema change is a numbered, additive-first migration; a destructive default is operator-handnothing is applied by hand and every change is reversiblePhase 1
F2Bootstrapping and verification code runs AFTER the migration deploys, never beforea sibling paid for this ordering three timesPhase 1
F3A failed migration exits non-zero rather than serving a half-applied schema; the boot assertion "a migration is pending" refuses the starta server serving half a schema is the worse outcome, and a restart applies no migrations by itselfPhase 1
F4Objects a migration tool cannot see (functions, triggers, policies) are registered in a required-objects list a test asserts, surfaced at boot as `ok \degraded`an object invisible to the tool is silently dropped by a later generatePhase 1
F5One secret provider, fail-closed: every secret resolves through one accessor that throws rather than defaultingthe live counter-example is lib/config.ts:67 (residual R5)Phase 1
F6A documented maximum rotation age with a startup warning; defer a KMS, keep the provider seama key nothing tracks the age of can never be rotatedPhase 1
F7Every input constraint is declared in the tool schema, so rejection happens at the transport before the handler runsa server-side bound leaks the validator's internals; a declared one returns a clean protocol errorPhase 1
F8A response distinguishes "nothing needed redaction" from "redaction was off"an agent consuming a hub cannot otherwise tell an honest answer from a degraded onePhase 1
F9Queue claiming, lease tables and transaction-scoped advisory locks, as a closed toolkit with a required citationthese are PostgreSQL primitives that do not exist in SQLite; naming them now beats case-by-case judgment laterPhase 1
F10Free text is constrained for rendering safety, not only length: bidi overrides and control characters rejectedhub content is third-party text rendered to a reader who then makes a trust decision; a bidi override makes two distinct records render identicallyPhase 3
F11A published-claims tracker: one row per claim, with its evidence and gate cell. The status column changes only on landed evidence, never on intent — and a claim may not rest on a single supporting mechanisma sibling had to write down explicitly that deny-by-default is a mechanism, not the architecture, because one landed control was being read as satisfying a whole standard (NIST SP 800-207). A benchmark over five to ten hubs generates exactly that temptation: one green cell quoted as "isolated"Phase 3, before the whitepaper publishes a number

F11's gate-cell half is gate today; its tracker is not built. A benchmark whose numbers move on intent is the reputational form of a failed certification, which is why it is the one row here with a named deadline.

7.9 Communication register for security work. Work here is authorised hardening of our own code. Write summaries, comments, records and commit messages in a defensive-remediation register: describe what is gated, audited or closed — never an abuse recipe. Prefer "authorization gap" over "privilege escalation", "reachable by a low-privilege caller" over "exploitable", "grant elevated access without authorization" over the escalation verb, "adversarial review" over "red team". Keep standard NIST/ISO vocabulary: least privilege, deny-by-default, break-glass, separation of duties. Reason. Cuppy will publish audit artifacts and a whitepaper describing real defects on a live public service, written by agents. Hardening described in attack language gets refused, flagged or mis-escalated — including by the agents doing the work. prose, placed in CLAUDE.md where it loads automatically.


§8. Release process

Cuppy has no release process. This section creates the smallest honest one and states what is blocked.

8.1 One version of record. The version lives in one file: package.json → version. SemVer 2.0 core only; pre-releases are branches and commits, never versions. No one edits it by hand — not the operator, not an agent. scripts/release.sh is the only writer. Reason. The failure mode is live: package.json and /api/health agree on one number while mcp/handler.ts:199 hard-codes another. inv:one-version-of-record is RED (FEAT-016). gate once the cell is green.

8.2 Two numbers, always. /api/health MUST report both the version of the build on disk and the version the running process loaded, plus which tree the process was launched from. When they differ the state is restart pending. Reason. With two trees and one public host, "which build is serving?" is cuppy's most important operational question and the first one every re-vet must answer; one endpoint change turns the project's most dangerous standing condition into a number a session reads at preflight instead of a premise it assumes. planned, FEAT-016.

8.3 Commit grammar. type(scope): <ID> — summary, Conventional Commits 1.0, with two load-bearing additions: harden(...) for adversarial-pass remediation and security(...) for security-control changes. ! or a BREAKING CHANGE: footer marks a break. The scope list is itself a blast-radius declaration. Every subject carries a registered ID. The body carries the decision or finding that prompted it, the change bullets, the class swept: line, and the evidence lines tests:, gate cells:, verified: <public-host output>. Reason. This repository's git log is its only durable record: no CI has ever run and there is no remote. A week of security remediation is currently invisible in the log because it was filed as fix. One declared type list, in this file, is the list release.sh reads. prose for authoring; planned for the registered-ID check.

8.4 The cut. scripts/release.sh is the only path. It does not exist yet — it is written together with the cutover, because a cut that cannot deploy from a tag has nothing to cut. Its contract, so it is not improvised:

  1. refuse a dirty tree; 2. gate.sh to exit 0; 3. apply pending migrations before anything restarts,

because new schema in front of old code is inert while new code in front of an old schema is the failure direction; 4. write package.json, CHANGELOG.md and vx.y.z.md; 5. one commit release: vx.y.z — <title> containing only those files, and an annotated tag; 6. restart through restart.sh;

  1. verify through the public hosts; 8. write Verify-after-deploy and Rollback last, from real output.

A red gate means no release commit. A failure inside the cut restores the files and halts with nothing advanced. Every entry carries runnable Verify-after-deploy commands against the public host and a Rollback section. Release artifacts are write-once per version. Version level: a breaking marker forces major regardless of volume; a shipped schema migration forces at least minor; volume alone never forces a major. Reason: cuppy's consumers are other people's agents with pinned tool names, so a rename is a major event even when the diff is small.

8.5 What is blocked, and on what. The inherited release standard requires every deployment stage to be a git worktree of the same repository. Cuppy's production tree is not a checkout at all. The release process is therefore BLOCKED on docs/sprints/RUNBOOK-cutover-to-checkout.md, prepared, verified and operator-hand. A blocker, not an adaptation; §9 C2 records it. Adoptable and required now: 8.1 and 8.3, plus creating tracking/releases/CHANGELOG.md with an ## Unreleased heading so a cut has somewhere to land.

8.6 Separate numbering, fenced in advance. The build version, the MCP protocol revision (2024-11-05), the tool-catalog version and per-hub content snapshot versions are four different numbers and are never required to agree. Only the build version is the version of record. Reason: fencing this now costs one paragraph; discovering it across ten hubs costs a synchronisation chore that never ends.


§9. Conflict ledger

An unrecorded divergence is a defect.

C1 — Gate definition. The inherited §3 requires the suite green *with the live contract tier running and skipped: 0 confirmed. Cuppy has one test tier and no contract tier. Cuppy's side:* green is §3's eight steps in the repository tree, with the single-tier limitation stated rather than papered over. The tier arrives in Phase 1; the skipped: 0 half transfers today.

C2 — Release and deployment. The inherited standard requires worktree stages and a promote ladder. Cuppy has neither, and production is not a checkout. Cuppy's side: §8 adopts one-version-of-record, the commit grammar and the changelog now; the cut and the two-number health report are blocked on the cutover and are named residuals. Cuppy does not claim the release standard.

C3 — The second-approver gap, and the cooling-off half. Several inherited controls expect a reviewer who does not exist. Cuppy's side: the sibling position — honest attestation with its standards citation, the act surfaced, the act left pending or reversible; never a fabricated second identity. The substitution for the missing human is mechanical: a fresh clean-context read-only agent running a committed prompt that probes the original reproducer through the public host before reading the implementer's disposition (R3). Self-attestation by the implementing agent is explicitly rejected, which is where cuppy diverges from the inherited R3 as written. The timed cooling-off delay is also rejected — the project that built it opened a work item to walk it back after it cost an hour of serialized waiting in one wave; cuppy keeps only the free half, "never commit and deploy an irreversible act in the same session".

C4 — Lens count. The inherited framework walks 56 lenses across three layers. Cuppy's side: twelve L1 lenses weighted to cuppy's threat model, eleven L2 axes, six L3. Scale the walk, never waive the record — the inherited framework itself says the full walk is overkill for small changes.

C5 — ID registry. One sibling rejects a separate registry as a second source of truth; another adopts one. Cuppy's side: adopt it, because cuppy is about to mint identifiers across seven phases, five to ten vendor hubs and a published benchmark. One line per ID, never a backlog: the content lives in the artifact the row points at. ISSUE-NNN is dropped from the identifier set — tracking/issues/ does not exist and an id class with no directory invites inventing one.

C6 — Boot document. An inherited quickstart is a large file carrying ports, URLs and a hand-typed preflight. Cuppy's side: the boot document carries no instance data; hosts, ports, process names, versions and project ids come from scripts/preflight.sh and the Commander project row, and the facts table names the source instead of the value. A boot document that restates state goes stale within a release, and this project's two quickstarts already disagreed with each other and with the console's resolver (residual R6).

C7 — Enrollment over big-bang. A MUST cuppy cannot satisfy everywhere on day one binds an enrolled set, declared in the gate and additive-only; a surface not yet enrolled is owed the sweep, not exempted, and a surface created under the rule enrolls at creation. Reason: a platform-wide MUST the repository violates everywhere is ignored within a week. This is how FEAT-017's route-auth floor lands.

C8 — Docs-only items get no impact record. The inherited R1 says "no exceptions, including docs-only items". Cuppy's side: a docs: line in the session note (R1). The inherited rule was written for a project whose docs commits are a minority of its history; cuppy is a documentation platform whose agents write docs commits continuously, and the first rule to be quietly dropped is the one demanding a JSON file for a typo. A narrower rule that holds beats a broader one that is abandoned.


§10. Adoption vet of this document

The findings this vet changed are listed first, because a vet that reports only comfort is not a vet.

  1. A deploy deadlock, measured. Step 6 compared the trees by subtracting exceptions from everything. Run

verbatim on 2026-10-11 it returns four differences — CLAUDE.md, CURRENT_WORK.md, QUICKSTART.md, a diverged README.md — so it was already red before any new governance file landed, and restart.sh refuses on a red gate. Adopting the standard as drafted would have made the project undeployable, including for the next security fix. Step 6 is now an allowlist of declared source roots living in one shared helper (§3) — which is what §7.4 already required of every other allowlist here — and it returns identical against the same two trees. §5 gains a break-glass path for the incident case that remains.

  1. Three quoted counts, all wrong, in the document that forbids quoting counts. Removed; the rule now

carries its own author as the example (§4).

  1. An invented phase. Five rules were scheduled into a "Phase 0b" that appears nowhere in the specification,

which has Phase 0 through Phase 6. A standard that schedules work into a phase the build plan does not have is a standard the build plan ignores. All five now name their work item or say "Phase 1".

  1. Self-approval one layer up. R3 required an independent agent but let the implementing session write that

agent's prompt. The prompt is now a committed artifact (work/vet/PROMPT.md), and R2 distinguishes the implementer's proposed verdict from the vet's binding one.

  1. A cost ceiling. The ledger required an impact record for every item including docs-only ones — five

artifacts per item on a documentation platform. Scoped to code items with a one-line alternative for docs, and the ceiling is now a rule (§4) so the next addition must retire something.

  1. C3 downgraded to prose. "Restart only by the script" was presented as a gate. Nothing stops a bare pm2

command, so C2 and C9 are conditional on compliance too. Named, with the hook that would fix it.

  1. The security answer was too generous. L1 and L3 are caught only as regressions in known routes; the

controls for a new unguarded route are red. Stated in the measurement section, before any rule.

  1. An over-narrow allowlist, caught on the rebound. Fixing step 6 created the mirror risk — a false green.

apps/web/public is copied into the served bundle and llms.txt had already drifted there once, so it is now a declared root. config/ is deliberately excluded, with the reason recorded in the helper: the application writes it at runtime, so comparing it would manufacture a red gate out of normal operation.

  1. restart.sh lost a real property when its wrong check was removed. Byte-equal dist across two

independent builds was never a sound assertion, but "prod's build is not stale against prod's source" is — and without it, source parity can be green while pm2 serves pre-patch code. Added as its own step.

  1. Cut for being copied rather than reasoned: the append-then-supersede concurrency mandate (no incident,

no referent, and cuppy's document history already works another way); a provenance section restating reasons already attached to each rule; a twelve-row self-assessment table this list replaces; and a 57-entry adoption inventory that functioned as a trophy case.

Standing assessments, briefly, so the remaining angles are on record rather than restated at length. Adversarial: a docs-only change grants no new write authority, and the §5A hook only ever refuses. Least privilege: the operator-hand list is broader than previous practice, standing authority narrower and explicit. Backward compatibility: no retroactive red cells — R1 and R3 bind forward, and existing conventions are kept rather than migrated. Configuration purity: no runtime value is introduced. Failure modes: red cells fail closed (a missing record blocks the next item) while emitting as it.todo, so a pre-staged gate cannot hold the build hostage; the trade is stated and residual R3 names the forcing function it lacks. Observability: every act leaves a committed artifact and the trail is diffable git history; the two-number health report is the gap. Portability: bash, npm, TypeScript, Vitest, twenty lines of shell — no vendor dependency. Performance: the only runtime cost is the §5A hook, one node parse per subagent dispatch; the gate's own minutes-long runtime is addressed by break-glass rather than ignored. Testability: §3 defines green mechanically as one command, runnable by an agent that did not write it. Maintenance: every rule carries a reason and an enforcement word, so a later session can delete a rule whose scar is gone rather than keeping it out of caution. Sprawl: four prose instruments plus an index, five executable artifacts and one hook, with a hard ceiling of five prose instruments — the sixth replaces one. Standards: RFC 2119 here; per-invariant anchors in INVARIANTS.md (RFC 9110 §11, RFC 6797, CSP L3, sysexits), and NIST SP 800-53r5 AC-5 / ISO/IEC 27001 A.5.3 / SOC 2 CC3–CC7 for §5's attestation.

Token economics, with one disclosure. CLAUDE.md is held under 8 KB, this document is read on demand, and the boot document carries no state so it never needs editing when the project moves. The disclosure: that 8 KB ceiling was raised from the drafted 5 KB by this vet, after the file already exceeded it — which is the R12 move, made in the open rather than quietly. It is argued rather than fitted: the file auto-loads into every session, so a kilobyte here is a kilobyte on every piece of work forever, and the ten absolutes plus the self-approval rule do not compress much below this without dropping a scar. The test of any ceiling is that the next addition must retire something.

And this document remains long — roughly 67 KB — which is a standing finding, not a resolved one. §7.8 consolidated everything that does not yet bind, and the vet twice attempted a general compression and twice converged without real savings, because the length is reasons rather than padding. The next vet's job is deletion: find the rules whose reason restates another's and cut them, rather than adding a twelfth section.

Named residuals — recorded, not absorbed

  • R1. CI exists and has never run: no remote. Every gate in §3 is an invoked command. **Highest-value single

unblock in this document**; configuring it is operator-hand.

  • R2. One test tier. No contract tier boots the real server against a real database; Phase 1.
  • R3. A red gate cell has no deadline. An item can be declared delivered while its own acceptance cell is

red and nothing objects. The forcing function — delivered items must have green cells — is not built.

  • R4. FEAT-002–FEAT-005 have no impact record (grandfathered).
  • R5. SKILL_URL_SIGNING_SECRET is unset in production and warns on every boot. The fallback chain at

lib/config.ts:67 terminates at a shipped public literal when ADMIN_TOKEN is also unset, so this is a fail-open default and not merely a coupling (§7.8 F5). Setting it is secrets rotation: operator-hand.

  • R6. Two boot documents disagreed with each other and with the Commander console's resolver. Verified

2026-10-11: commander-dev's quickstart_paths default is ['.claude/QUICKSTART.md', 'QUICKSTART.md'], first match wins (lib/tunables.js:130), so the console renders .claude/QUICKSTART.md — while both files claimed the root one was rendered. Closed by this set: the substance moves to .claude/QUICKSTART.md and the root file becomes a pointer. A second cause is outside this repository and is not closed by this set: commander-dev's boot-document resolver landed in commit feat(FEAT-081) at 2026-10-11 02:36 UTC, while the running commander-dev process has been up since 2026-10-09 06:25 UTC — so the route the console panel calls does not exist in the build that is serving, and a 404 renders as "not found" over a file that is present. Restarting that project is operator-hand there, not here. A separate correction is owed: RUNBOOK-register-in-commander.md says no project row exists, which is true of the other Commander on this host (which reads the root file, server.js:7930) and false of commander-dev, where the row has existed since 2026-07-20 with git_repo_path set to this repository. Scope the runbook to the Commander it means.

  • R7. The docs/PASS*.md files describe a surface that has moved and several show x-semarchy-role as a

live auth path. They need the §4 retirement banner. Count them; do not quote the count.

  • R8. The rate limiter is in-memory and per process, so pm2 cluster mode stays off until a shared store

exists.

  • R9. CSP carries 'unsafe-inline' on script-src because the coming-soon and activation pages render

inline scripts. Per-response nonces are real work and are not done.

  • R10. Three open state-source disagreements, all §5 criterion-3 defects, all the owner's to resolve.

(a) CURRENT_WORK.md says development is not released while FEAT-002–FEAT-005 are built and deployed. (b) It says the hotfixes "remain uncommitted in this repo" while git history shows them committed. (c) It reports the specification at revision 18 while the newest roadmap commit says revision 19. An agent that reads any of these and acts on it is acting on a false premise. This standard's rule is to pause; these three are the live test of whether that rule is obeyed.

  • R11. The architecture fork this standard assumes closed is open. The pointer records an unresolved owner

decision on where ACTION lives — pure documentation; executable documentation where cuppy emits a ready-to-run request the calling agent executes with its own credential; or the status quo where cuppy holds credentials and runs the call. This document assumes the status quo, and that assumption is load-bearing for §7.4's resolution, §6's five-surface parity lens and the operator-hand list. If the owner picks the emit-a-request option the threat model changes shape — a new invariant is needed (an emitted request MUST never carry a cuppy credential) and parts of §6 narrow. No rule here may be read as pre-deciding that fork, and the first vet after it closes re-opens §6, §7.4 and §5's operator-hand list.

  • R12. Step 5 has one true positive to clear in the adopting commit. Run against the repository on

2026-10-11, the corrected secret check finds scripts/smoke.ts:2 — const devToken = process.env.DEV_TOKEN || 'change-me-dev-token' — a second copy of a shipped default outside the config chain, which is precisely the class the rule forbids. It is a dev smoke script and not in the served path, so the exposure is low; the finding is nonetheless real and the fix is one line (resolve through lib/config.ts, or fail when the variable is unset). It is named here rather than added to the check's exclusion list, because excluding a true positive to make a gate green on landing is the same mistake as step 6's exclusion list, one rule further down. The adopting commit fixes the script; the gate stays strict.


*Every rule above traces to an entry in LESSONS-LEARNED.md or to a sibling's scar recorded there. A rule that cannot name one SHOULD be deleted at the next vet.*


Change log. 2026-10-11: the five planned controls this document numbered FEAT-009 to FEAT-013 are renumbered FEAT-016 to FEAT-020, because the sprint builders had already allocated FEAT-009 (developer Postgres and S1 migrations), FEAT-010 (the gate scripts) and FEAT-011 (content tables) before the collision was noticed, and those records sit on branches under vet. The allocation registry is tracking/features/INDEX.md; an id is taken by adding it there in the same commit as the record.

Read this document as markdown — the same bytes an agent receives.