---
cuppy_hub: cuppy
cuppy_path: reference/governance/engineering-standard.md
cuppy_section: docs
cuppy_doc: FGHG-Z5CZ
cuppy_doc_uuid: 3156b217-9e66-4b00-959c-dcfb8d1d239a
cuppy_permalink: https://cuppy.io/cuppy/d/FGHG-Z5CZ
cuppy_version: 1
cuppy_updated: 2026-10-17T06:32:45.511Z
cuppy_canonical: https://cuppy.io/cuppy/docs/reference/governance/engineering-standard
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 3d63e9e11d017409f9a8baf72cf2c7b37b2258730afc7f4a90c612cd13396942
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---

# 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:

| Instrument | Authority of record | Relationship |
|---|---|---|
| Development standard | `commander/commander-dev/docs/governance/CMD-DEV-STD-001.md` v1.1 | adopted; this is cuppy's local binding |
| Release standard | `.../CMD-REL-STD-001.md` | adopted in part; §8.5 states what is blocked |
| Single-operator posture | `auth/docs/governance/SINGLE-OPERATOR-GOVERNANCE.md` | adopted in its compensating-controls form (§5) |
| Invariants and acceptance criteria | `docs/governance/INVARIANTS.md` | cuppy's own |
| Executable invariant status | `apps/api/src/gates/gateCells.ts` | cuppy's own; the only place a status may change |
| Incident ledger | `docs/governance/LESSONS-LEARNED.md` | cuppy's own |
| Identifier registry | `docs/governance/ID-REGISTRY.md` | cuppy'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**:

| Defect | As a **regression** in a known route | As a **new** unguarded route |
|---|---|---|
| L1 traversal | **Caught** — `path-confinement.test.ts`, a gate | **Not caught.** The controls that would (FEAT-019's route-enumerating drill, FEAT-017's auth floor) are both **red** |
| L3 email leak | **Caught** — `redact.test.ts`, a gate | **Not caught.** Nothing asserts every reader route passes through the masker; §6's parity sweep is prose |
| L2 dev-mode production | **Caught** by a live fatal boot assertion — the one unambiguous win | the 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 round | **Not caught by any gate.** R3 is the control and its enforcement is a *warning* | — |
| L5 47 fixture commits | **Caught** — `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

| Word | Meaning |
|---|---|
| **gate** | A check that fails a run: inside `scripts/gate.sh`, inside the suite it calls, or a hook that exits non-zero. |
| **written** | The mechanism exists but fails nothing today — CI with no remote, a script no path calls. Named with what makes it enforcing. |
| **planned** | No mechanism exists. The work item that creates one is named. |
| **prose** | Judgment, 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.

| # | Rule | Mechanism | Word |
|---|---|---|---|
| C1 | Two trees, one change — until cutover | `gate.sh` step 6 diffs the declared source roots; `inv:trees-identical-until-cutover` | gate |
| C2 | Verify through the public hosts | `restart.sh` curls both hosts and refuses on a non-200/non-JSON | gate *(conditional on C3)* |
| C3 | Restart only by the script | — nothing prevents a bare `pm2 restart` | prose |
| C4 | Git discipline — path-scoped staging, no unasked commits | — | prose |
| C5 | Roles from tokens and the session cookie only | `inv:role-never-from-a-request-header` — **RED** | planned (FEAT-018) |
| C6 | Caller paths through `confinedPath`/`confinedFile` | `path-confinement.test.ts`; `inv:storage-path-confined`, `inv:identifier-is-never-a-path` | gate |
| C7 | No addresses or secrets in output | `redact.test.ts`; `inv:redaction-fails-closed`, `inv:no-address-below-admin-on-any-channel`; `gate.sh` step 5 | gate |
| C8 | Research-only gate on the Phase 1+ redesign | — | prose |
| C9 | The gate is green before any restart | `gate.sh`, called by `restart.sh` step 1 | gate *(conditional on C3)* |
| C10 | Closure evidence, with outputs | §2 R5 | prose |

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.
2. **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.
3. **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.
4. 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;
4. verify closures **behaviourally**: re-run each original reproducer against the **public host**, after first
   establishing which tree and which build is serving;
5. disposition every finding as fixed / scheduled-with-owner / recorded — **never silently dropped**;
6. 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**:

| # | Check | Why it exists |
|---|---|---|
| 1 | `npm run typecheck`, all three workspaces | the root script called workspace scripts that did not exist and silently failed; fixed during source-tree reconciliation (FEAT-001) |
| 2 | `npm run build` | the repository could not build at all until 2026-10-11 |
| 3 | `npm test`, which is also where the gate matrix runs | the matrix refuses a coverage claim whose backing test was renamed, moved or detached |
| 4 | the suite left **no tracked change and no new commit** | the suite wrote 47 commits into this history (L5) |
| 5 | no shipped default token outside the config chain; no secret-shaped file tracked | `change-me-admin-token` is public knowledge |
| 6 | the two trees identical **across the declared source roots** | a one-tree fix is a system regression by construction |
| 7 | every changed code work item has an impact record (R1) | the artifact existed without the gate |
| 8 | **warns** 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

| Artifact | Path | Written by | Binds from |
|---|---|---|---|
| Pointer of record | `docs/sprints/next-task.txt` | every session | now |
| Dynamic state | `CURRENT_WORK.md` | every session | now |
| Impact record | `work/impact/<ITEM-ID>.json` | implementing agent | adoption, forward, code items only |
| Vet prompt | `work/vet/PROMPT.md` | committed template | adoption |
| Vet record | `work/vet/<SET-ID>.md` | independent clean-context agent | adoption, forward |
| Session / delivery note | `tracking/updates/YYYY-MM-DD-<slug>.md` | implementing agent | now |
| Feature / bug record | `tracking/features\|bugs/<ID>.md` | implementing agent | now |
| Invariants | `docs/governance/INVARIANTS.md` | any agent; rules only, never status | adoption |
| Gate cells (status) | `apps/api/src/gates/gateCells.ts` | by commit only | adoption |
| Incident ledger | `docs/governance/LESSONS-LEARNED.md` | the session that closed the incident | adoption |
| ID registry | `docs/governance/ID-REGISTRY.md` | the commit that mints the ID | adoption |
| Runbook | `docs/runbooks/RUNBOOK-<subject>.md` | the session that learned the failure mode | adoption |
| Release note | `tracking/releases/CHANGELOG.md`, `.../vX.Y.Z.md` | `scripts/release.sh` only | §8 |
| Restart log | `tracking/restart-log.txt` | `scripts/restart.sh` only | adoption |

**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.
2. **The gate is green, honestly.** Exit 0, with skips and rotten findings surfaced rather than counted. Exit 2
   is not green.
3. **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.
3. **Subagents never inherit the model.** Inheritance binds at agent creation and persists for that agent's
   life, including across every later resume.
4. 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.
5. 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.
6. 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.
2. **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.
3. **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.
4. **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.

| Value | Posture |
|---|---|
| `NODE_ENV` on a host declaring public hosts | **not 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` hop | **must 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 defaults | **not configurable** on a public deployment — fatal boot assertion plus `gate.sh` step 5 |
| redaction floor (owner email, key-shaped tokens) below admin | **not disableable** below platform scope |
| rate-limit identity | **not 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 caps | soft config **with a code ceiling**, named default at the head of the chain |
| `COMING_SOON*` and the preview token | soft 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

| Rule | Reason | Word |
|---|---|---|
| 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 ones | an 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 reason | gate 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 **forbidden** | the round-one masker failed **open** on any exception; it now fails closed with a correlation id | gate (`redact.test.ts`) |
| Secrets never appear in chat, git, a document, an error, a log, or a dev-only response field | this project leaked an email with output hygiene as its only control | gate (step 5; `redact.test.ts`) |
| `.gitignore` and the image exclude `*.pem *.key *.crt *.p12 *.pfx .env secrets/`, asserted by the gate | an untracked-by-luck secret is not a control | gate (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 note | one 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-hand | prose |

**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.

| # | Rule | Why it is cheap now | Lands |
|---|---|---|---|
| F1 | Every schema change is a **numbered, additive-first migration**; a destructive default is operator-hand | nothing is applied by hand and every change is reversible | Phase 1 |
| F2 | **Bootstrapping and verification code runs AFTER the migration deploys, never before** | a sibling paid for this ordering three times | Phase 1 |
| F3 | A failed migration **exits non-zero** rather than serving a half-applied schema; the boot assertion "a migration is pending" refuses the start | a server serving half a schema is the worse outcome, and a restart applies no migrations by itself | Phase 1 |
| F4 | Objects 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 generate | Phase 1 |
| F5 | **One secret provider, fail-closed**: every secret resolves through one accessor that **throws rather than defaulting** | the live counter-example is `lib/config.ts:67` (residual R5) | Phase 1 |
| F6 | A documented maximum rotation age with a startup warning; defer a KMS, keep the provider seam | a key nothing tracks the age of can never be rotated | Phase 1 |
| F7 | Every input constraint is declared **in the tool schema**, so rejection happens at the transport before the handler runs | a server-side bound leaks the validator's internals; a declared one returns a clean protocol error | Phase 1 |
| F8 | A response distinguishes "nothing needed redaction" from "redaction was off" | an agent consuming a hub cannot otherwise tell an honest answer from a degraded one | Phase 1 |
| F9 | Queue claiming, lease tables and transaction-scoped advisory locks, as a **closed toolkit with a required citation** | these are PostgreSQL primitives that do not exist in SQLite; naming them now beats case-by-case judgment later | Phase 1 |
| F10 | Free text is constrained for **rendering safety**, not only length: bidi overrides and control characters rejected | hub content is third-party text rendered to a reader who then makes a trust decision; a bidi override makes two distinct records render identically | Phase 3 |
| F11 | A 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 mechanism** | a 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`;
7. 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.
2. **Three quoted counts, all wrong,** in the document that forbids quoting counts. Removed; the rule now
   carries its own author as the example (§4).
3. **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".
4. **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.
5. **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.
6. **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.
7. **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.
8. **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.
9. **`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.
10. **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.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 18 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/cuppy/
<!-- /cuppy -->
