Article VII Verification
7.1
Shape of the suite
Thirty-two test files, two hundred and seventy-two declared test() calls. Six of
those are parameterised inside loops, so the runtime count is higher. There is no test framework:
node:test and node:assert/strict throughout, invoked by
node --test.
The suite’s conventions are unusually consistent. Files isolate their database into a temporary file before importing anything, so no test can touch live state. Several point both the state layer and the runner library at the same temporary file, because the two resolve their path from different environment variables and a split would make the assertions meaningless — a hazard that is itself the subject of two dedicated tests.
Where a test drives real runner code rather than a helper, dependencies are injected as explicit seams: the model call, the notification path, the outward tool call and even file existence are all replaceable, so the full path — model decision, gate, coarsened feedback, stored row — can be exercised with no network and no MCP transport.
7.2
The pure kernel, run
The safety-composition module has no database, no environment and no model dependency, so it runs on its own with nothing installed. The output below is a real run of that file.
$ node --test test/safety_compose.test.js ✔ TERMINAL_INTENT_STATUSES mirrors operator.js set (0.812166ms) ✔ T1 P0 blocked policy -> halt_blocked, no escalation/memory, not sanctioned (0.220666ms) ✔ T2 P0 terminal intentStatus=sent -> halt_blocked (0.077250ms) … ✔ MF2 sanctioned+web -> web_readonly label (most-restrictive), flags preserved (0.028666ms) ✔ MF4 P3 fail-closed: webAutoDispatch="descriptor" -> web_readonly, readOnly (0.017291ms) ✔ MF3 composeRestrictions forces taint consequence on malformed input (0.046500ms) ✔ MF-A composeRestrictions binds halt-tier label to its consequence flags (0.056458ms) ✔ MF-A2 P0 case-variant policy.decision=bLoCkEd -> halt_blocked (0.014875ms) ℹ tests 45 ℹ suites 0 ℹ pass 45 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 62.270708
Forty-five runtime tests from twenty-seven declared calls; the difference is four parameterised loops. Run outside the pinned Node line — the module’s purity is what makes that possible. The rest of the suite requires the pinned runtime, because the SQLite binding is native.
7.3
The adversarial file
One file is written from the attacker’s side. Each of its six tests is titled as an attack and asserts that it fails.
- Attack 1Self-approval bypass. Every runner subject and every non-allowlisted subject is rejected; after each attempt the intent is asserted still not executable.
- Attack 2Replay. A decision approving one intent is pointed at another, and separately the approved intent’s recipient is mutated after approval. Both are rejected — the second by hash mismatch.
- Attack 3Record-time linkage. An approval identifier supplied when the intent is created is stripped, and the intent is not executable.
- Attack 4Terminal transitions. A sent or failed intent cannot move again.
- Attack 5The stuck claim. A dead claim is routed to
needs_human; a fresh one is left alone. - Attack 6Header injection. Multi-recipient and CR/LF-bearing addresses are refused by the mail handler.
A second file extends the same posture across process boundaries: a cross-midnight approval is executed by today’s pass; a stale claim from yesterday is reaped; a runner-authored comment is not treated as authoritative human signal; and an untrusted inbound message does not raise goal confidence, because “taint ≠ Ian signal”.
7.4
Register of invariants
The claims made in Articles I to VI, with where each is enforced and where each is proven.
| Invariant | Enforced by | Proven in |
|---|---|---|
| No MCP tool can grant an approval | Explicit throws in both mutation paths | slice5_2_critique · slice8_1_security |
| A runner is never a human approver | Subject pattern, tested before the allowlist | slice8_1_security · slice8_executor |
| One approval binds one intent and its content | intent_id + action_hash, re-derived on every read | slice8_1_security |
| A blocked intent is never executable | First branch of the executability predicate | slice4_gate · slice8_executor |
| Terminal statuses never transition | Guard before every lifecycle write | slice8_1_security |
| An external send needs approval, tier-independently | Executor tier floor, released back to the prior status on refusal | slice8_executor |
| Approved spending is never auto-purchased | Spend handler notifies and hands off | slice8_executor |
| A tainted tick writes no durable memory | P1 of the precedence lattice, checked before each write | safety_compose · step1a |
| A caller can never un-taint an observation | Forced at the sink; no field on the tool schema | step1b |
| Gate feedback to the model is coarse | Coarsening applied at four call sites | step2 (four tests) |
| Success labels come from the verifier, not self-report | Effect–action binding, then a world check | phaseA_learning · step2 |
| Learning never auto-promotes | Candidate status only; minting off by default | phaseA_learning |
| Exactly one active goal model | Partial unique index — a second is an insert error | slice8_2_crosscut · slice8_3_version |
| The computed priority cannot forge board churn | Excluded from the state hash by an explicit field list | step1a |
| Pruning never starves the streak window | Retention window plus a minimum-rows floor | hardening_retention (three tests) |
| The runners and the gate share one database | Both resolve the same override variable | hardening_dbpath |
| Silence is the default for interruption | Short-circuit gate, atomic budget claim | step4 · step4_wiring |
| An identical proposal produces one row and one ping | Derived idempotency key plus a created flag | slice2_critique |
7.5
Drift observed on reading
Three places where the prose in the repository no longer matches the code beneath it. None is a defect in behaviour; each is a hazard for the next reader, and two of them concern security-relevant descriptions.
- schema headerThe core schema section is labelled “8 tables”; the function creates fifteen.
- approval hashThe comment above the approval function lists five hashed fields; the implementation hashes six, including the title. The code is the stricter of the two.
- stale test headerOne cross-cutting file’s header describes five fixes; the file contains seven plus an additional whole-system test.
One structural coupling is documented but not machine-checked: the terminal-status set is deliberately duplicated between the state layer and the pure kernel module to preserve the latter’s purity. A test asserts the two are equal, which converts an ordinary duplication hazard into a checked one — a pattern worth copying rather than criticising.
One test in the suite hard-codes an absolute path belonging to a specific deployment and cannot run elsewhere. For a public release that would need parameterising.
7.6
Scope of disclosure
This description was prepared from a private repository under assessment for public release. It documents mechanisms and interfaces only. The following categories were found in the source and are deliberately absent from every page of this document, including its diagrams and terminal renderings:
- personal contentThe operator’s own goal model, task titles, decision log entries and the profile document describing the person the system serves.
- channel identifiersA messaging channel identifier that appears as a hard-coded fallback default in two runner files, and the messaging bot’s handle.
- deployment topologyAbsolute installation paths, the service account name, the host name of the machine the units target, and the URL of a private operator console.
- inference endpointsDefault model identifiers and the loopback addresses and ports of adjacent private services.
- business specificsNamed commercial goals, counterparties and targets appearing in the planning documents and in one audit runner’s list of stale phrases.
No credential, token or secret value was found in the tracked source; the ignore file excludes environment files, databases, logs and anything matching a secrets pattern, and the runners read every secret from files outside the repository at run time. The one hard-coded default worth flagging is the channel identifier noted above, which is not a secret but is personal.
Two mechanisms in the codebase reduce the risk of such material leaking through the system’s own outputs, and both are worth noting in an assessment: text and metadata are redacted against three token patterns before storage, and the corpus tooling applies its own redaction plus a sensitive-path exclusion covering environment files, key material and databases before any transcript is read.
7.7
Standing observations
Three properties of this codebase are unusual enough to record plainly, because they are the ones a reader would most want to know are genuine.
The dangerous path is the narrow one
There is exactly one function that can make an action executable, it is not exported to the network interface, and four independent mechanisms guard it: a subject pattern, an allowlist, a binding to the intent, and a binding to the intent’s content. Removing any one of them still leaves three.
Defaults are refusals
Dry-run is on unless a variable holds one exact string. The executor’s timer is not enabled. The attention sweep is inert unless armed. Learning mints nothing unless switched on and above a threshold. Confidence is never hand-written by a model. The pattern is consistent enough to read as a policy rather than a habit.
The system is instrumented to notice its own failure
A no-progress streak, a board-unchanged backstop, a stall monitor outside the loop, a dead-man’s-switch heartbeat on runs, a reaper for stuck claims, an interrupt-precision scorer, and a verifier that is not permitted to accept the model’s account of its own success. Much of the code exists to make dishonesty about progress structurally difficult.