# DO NOT STOP! - Complete Code Appendix

**FoundryOS Field Note 01**  
**Dan Merchant, Founder, FoundryOS**  
**Base implementation:** merge `54c95f652`, July 27, 2026  
**Publication classifier revision:** independent audit round 2, July 27, 2026

This appendix contains the complete revised portable implementation described in the field note and the exact smoke-test harness run against it. Nothing is omitted from either file.

The portable version compresses the core mechanism into one dependency-free Node.js script. FoundryOS's production version is broader: it adds a mechanical work-item ledger, a question gate, reasoned waivers, classified escalation authorizations, a managed installer and project-runner integration. The complete revised production source is supplied as a separate reference archive.

The portable gate records the stated condition and uses it as session context, but it does not independently prove that condition. It blocks configured contradictory or avoidable handoffs and permits ordinary completion messages. Pair it with tests, acceptance criteria, `/goal`, an external verifier or a work ledger when the end state itself needs independent enforcement.

## What this version implements

- Conversational arming through phrases such as "don't stop until..."
- Explicit conversational disarming
- Mention-versus-use handling for arming language
- Straight and typographic quote normalization
- Context-aware handling of quoted discussion versus quoted abdication
- Bare remaining-count language treated as an advisory near-miss
- Explicit unresolved counts, continuation claims and avoidable handbacks treated as blockers
- Narrowed `which way` and token/context-limit rules that target actual handbacks or stop excuses
- Privacy-preserving advisory logging using rule IDs and a digest, never assistant prose
- First-person unfinished-work detection without blocking ordinary third-person status
- Session-scoped persisted state
- `SessionStart` restoration after resume, fork or compaction
- Full factual context on arm/restore and terse context at steady state
- Structural display filtering for code, blockquotes and filenames
- `stop_hook_active` re-entry awareness
- A three-streak advisory breaker
- Missing-state release, malformed-state fail-closed behaviour and infrastructure fail-open behaviour
- An environment kill switch

## Installation

Copy `session-contract-minimal.mjs` to a stable absolute path and register that same script for all three events:

```json
{
  "hooks": {
    "SessionStart": [{"hooks": [{"type": "command", "command": "node \"/absolute/path/session-contract-minimal.mjs\""}]}],
    "UserPromptSubmit": [{"hooks": [{"type": "command", "command": "node \"/absolute/path/session-contract-minimal.mjs\""}]}],
    "Stop": [{"hooks": [{"type": "command", "command": "node \"/absolute/path/session-contract-minimal.mjs\""}]}]
  }
}
```

Arm it using the correction you would already type:

```text
Don't stop until every migration test passes.
```

Disarm it with:

```text
stop
```

The emergency bypass is `SESSION_CONTRACT=0`.

## Verification result

Run the second file from the same directory:

```bash
node session-contract-smoke-test.mjs
```

Result against the exact source below:

```text
22/22 smoke tests passed.
```

The separate FoundryOS production suite passes `50/50` against the supplied revised source.

The regression coverage includes quote-style parity, quoted-use evasion, genuine quoted meta-discussion, open-ended completion dispositions, advisory near-miss logging, retrospective `which way` prose, retrospective token-limit prose, third-person status, first-person unfinished work and explicit unresolved counts.

## File 1 - `session-contract-minimal.mjs`

```javascript
#!/usr/bin/env node
// session-contract-minimal.mjs — a single-file work-contract gate for Claude Code.
//
// ONE file, THREE hook events, ZERO dependencies. It dispatches on hook_event_name, so the same
// script is registered three times in settings.json:
//
//   "SessionStart":     [{ "hooks": [{ "type": "command",
//       "command": "node \"/abs/path/session-contract-minimal.mjs\"" }] }],
//   "UserPromptSubmit": [{ "hooks": [{ "type": "command",
//       "command": "node \"/abs/path/session-contract-minimal.mjs\"" }] }],
//   "Stop":             [{ "hooks": [{ "type": "command",
//       "command": "node \"/abs/path/session-contract-minimal.mjs\"" }] }]
//
// WHAT IT DOES
//   Say "don't stop until <X>" in chat and the session cannot end a turn until you say "stop" — or
//   until it stops writing messages that contradict themselves ("3 files left. Continuing." then
//   ending the turn). Anything that comes up mid-contract gets a decision policy injected
//   instead of interrupting you.
//
// SAFETY PROPERTIES worth keeping if you modify it:
//   - Release is always reachable: one word ("stop") disarms, a kill-switch env var disables, and a
//     circuit breaker degrades to advisory after N consecutive re-entries. Do not remove all three.
//   - Failure posture is split: a malformed contract fails CLOSED (blocks), a missing one releases,
//     and an infrastructure error fails OPEN. A hook that runs in every project must not turn its
//     own bugs into a machine-wide lockout.
//   - The prompt is never persisted; only a digest and the matched rule ids.
//   - The injected text is a STATEMENT OF STATE, not a set of commands. See POLICY below.
//
// HARDENING PASS 2026-07-27 — four changes, each with a reason worth carrying if you fork this:
//
//   1. INJECTED TEXT IS FACTUAL. It used to say "You may not end the turn", "RESOLVE IT AND
//      CONTINUE", and "ACKNOWLEDGE: open your next reply with…". That claimed authority a
//      user-installed shell hook does not have, and out-of-band imperatives read as prompt
//      injection — which costs a legitimacy evaluation before anything gets reasoned about. A
//      policy stated as fact composes with the model's judgment; a command competes with it.
//      The acknowledgement requirement is gone entirely: it existed only to work around a client
//      that does not render systemMessage, which is a display problem, not a model instruction.
//
//   2. SessionStart IS REGISTERED. The contract is on disk so it survives a resume, but nothing
//      re-described it until the next prompt — leaving one turn where an armed session had no
//      idea a contract was active. That window matters most after compaction, when the earlier
//      context is gone. SessionStart is read-only: it restores the description, never arms.
//
//   3. stop_hook_active IS READ — and is NEVER a release condition. TRUE means this turn-end is a
//      re-entry after a previous block; FALSE means a fresh turn-end, i.e. work happened in
//      between. Releasing on TRUE would let any session escape by stopping twice. It is used to
//      make the breaker count genuine stalls rather than raw blocks.
//      (This field's existence was verified from live payloads. Two documentation fetches claimed
//      it did not exist; both were wrong. Observe the payload, do not trust a secondary source.)
//
//   4. BREAKER LOWERED 8 -> 3, which is only safe because of (3): the streak now counts
//      consecutive re-entries with no intervening work, so three is already pathological. Note
//      that Claude Code may impose its own absolute ceiling on consecutive blocks — that has been
//      asserted to me twice and I have not verified it. Nothing here depends on it either way.

import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

// Its OWN directory, deliberately. This file uses a leaner schema than the full implementation, so
// sharing a store with it would make each read the other's files as corrupt — and "corrupt" fails
// CLOSED, which means a stray test file can block real sessions. Learned the hard way: testing this
// script polluted the full system's store and produced exactly that. If you run both, they must not
// share a directory. (Node uses USERPROFILE on Windows, so setting HOME in a test shell does NOT
// redirect os.homedir() — override SESSION_CONTRACT_DIR instead.)
const DIR = process.env.SESSION_CONTRACT_DIR
  || path.join(os.homedir(), '.claude', 'session-contracts-minimal');
const BREAKER = 3;   // consecutive RE-ENTRIES (not raw blocks) before degrading to advisory
const OFF = process.env.SESSION_CONTRACT === '0';

// ── arming vocabulary ────────────────────────────────────────────────────────────
// Use the words you ALREADY type to correct this by hand. A new incantation is one more thing to
// remember at exactly the moment you forgot the first one.
const ARM = [
  // Conditions are BOUNDED to one line / 200 chars. Unbounded `(.+)/is` swallows to the end of the
  // prompt across newlines — a live session once pinned a 364-character blob as its "condition",
  // which then rendered verbatim in every block message.
  /\b(?:don'?t|do not)\s+stop\s+until\s+(?<cond>[^\n]{1,200})/i,
  /\bkeep\s+going\s+until\s+(?<cond>[^\n]{1,200})/i,
  /\bcontinue\s+until\s+(?<cond>[^\n]{1,200})/i,
  /\b(?:don'?t|do not)\s+stop\b/i, /\bkeep\s+going\b/i, /\bkeep\s+at\s+it\b/i,
  /\bsee\s+it\s+through\b/i, /\bget\s+it\s+all\s+done\b/i, /\bno\s+stopping\b/i,
];
const DISARM = [/^\s*stop\s*[.!]?\s*$/i, /\b(?:that'?s\s+enough|stand\s+down)\b/i];

// MENTION vs USE. Talking about the mechanism must not trigger it: "is don't stop an arming
// phrase", "I told the other session to keep going". Costly lesson — it fired three times.
function isMention(text, i, len) {
  const near = text.slice(Math.max(0, i - 24), i);
  if (/(?:\b(?:the|a|an|this|that|your)\s+|["'`])$/i.test(near)) return true;
  if (/\b(?:is|are|was|does|do|did)\s+$/i.test(near)) return true;
  const after = text.slice(i + len, i + len + 48);
  if (/^\W*\b(?:an?|the)\s+(?:\w+\s+){0,2}(?:phrase|trigger|keyword|command|rule|lock)\b/i.test(after)) return true;
  const clause = text.slice(0, i).split(/[.!?\n]/).pop();       // sentence-scoped
  return /\b(?:told|tell|asked|ask|said|say|instructed)\b/i.test(clause);
}

// ── stop-message rules ───────────────────────────────────────────────────────────
// NOT "does it end in a question mark" — that misses the real failure, which has no question mark.
// R1 is a CONTRADICTION: the message asserts continuation, the act is stopping.
const RULES = [
  // A count by itself is LOW confidence. It becomes a blocker only when its sentence also asserts
  // unresolved work; otherwise it is logged as an advisory near-miss. This avoids training honest
  // completion reports to hide counts merely because their disposition uses novel wording.
  ['R1_remaining', /\b\d[\d,]*\s+(?:\S+\s+){0,3}(?:remain(?:s|ing)?|left|to\s+go|outstanding)\b/i],
  // Left-context bound: a bare word-match is not an assertion test. "...That Said Continuing" is a
  // title; "Continuing down the list" is a claim. Must open a clause or follow a progressive marker.
  ['R1_continuing', /(?:^|[.!?;:\n]\s*|\b(?:I'?m|we'?re|am|is|are|be|been|still|now|and|then)\s+)(?:continuing|proceeding|carrying\s+on)\b/i],
  ['R1_next', /\b(?:next\s+up|moving\s+on\s+to)\b/i],
  ['R1_i_will', /\bI'?ll\s+(?:keep|continue|now|start|begin|proceed)\b/i],
  // Narrowed: the SPEAKER promising unfinished work, not third-person status. "The retry loop is
  // running against staging now, and green" is a completion report. Sentence-initial bare gerund
  // counts as first-person by ellipsis ("Building phases 1-3 now" = "I am building…").
  ['R1_doing_now', /(?:(?:^|[.!?;\n]\s*)(?:building|running|writing|starting|working\s+on|implementing)\b|\b(?:I'?m|I\s+am|we'?re|we\s+are)\s+(?:now\s+)?(?:building|running|writing|starting|working\s+on|implementing)\b)[^.!?\n]{0,60}\bnow\b/i],
  ['R2_want_me_to', /\b(?:want|would\s+you\s+like)\s+me\s+to\b/i],
  ['R2_should_i', /\b(?:should|shall)\s+I\b/i],
  ['R2_your_call', /\byour\s+call\b|(?:^|[.!?;\n]\s*)which\s+way(?:\s*(?:\?|$)|\s+(?:should|shall|do|would|can)\s+(?:I|we|you)\b)/i],
  ['R2_capacity', /\bpractical\s+range\b|\b(?:near|at|approaching|reached?|hitting|running\s+(?:low|out))\s+(?:the\s+)?(?:context|token)\s+(?:budget|window|limit)\b|\b(?:context|token)\s+(?:budget|window|limit)\s+(?:is|are|feels?|looks?)\s+(?:low|nearly|almost|close|exhausted|spent)\b/i],
];

// Fold typographic quotes so quote style cannot change a verdict.
const normQuotes = t => String(t).replace(/[“”„‟″‶]/g, '"').replace(/[‘’‚‛′‵]/g, "'");

// Run-up that marks a following quoted span as a MENTION rather than an assertion.
const MENTION_LEAD =
  /\b(?:flags?|fires?(?:\s+on)?|matches?|catches?|detects?|blocks?|triggers?|rules?|classifier|detector|pattern|phrase|wording|string|example|e\.?g\.?|such\s+as|like|quoted?|quotes?|says?|said|saying|wrote|written|reported|told|asked|instructed|called|named|labell?ed)\b[^.!?\n]{0,40}$/i;

// Known disposition words remain useful evidence, but they are NOT a closed vocabulary and no
// longer carry the release decision by themselves. A bare count is advisory unless the same
// sentence contains an explicit unresolved-work cue.
const DISPOSITION =
  /\b(?:out\s+of\s+scope|not\s+in\s+scope|de-?scoped|carved|deferred?|deferring|logged\s+as|recorded\s+as|follow[-\s]?ups?|tracked\s+(?:separately|in|as)|backlog|next\s+session|externally\s+blocked|blocked\s+(?:on|by)|assigned\s+to|owned\s+by|waived|won'?t\s+fix|by\s+design|generated\s+artifacts?|covered\s+by|intentionally\s+(?:left|unchanged|retained))\b/i;
const UNRESOLVED_REMAINDER =
  /\b(?:still|yet|unfinished|unresolved|pending|incomplete|not\s+(?:done|complete|completed|fixed|resolved|covered|handled)|need(?:s)?\s+to|must|have\s+to|left\s+to\s+(?:do|fix|finish|complete|address)|to\s+be\s+(?:done|fixed|finished|completed|addressed|handled)|work\s+remains?)\b/i;

// Strip DISPLAY spans before classification.
// Fenced blocks, inline code, blockquotes and filenames are structurally display — no heuristic
// needed. A quoted span is stripped ONLY when its run-up marks it as a mention; otherwise the
// content is kept and classified. Stripping every quoted span (the previous behaviour) made
// quotation marks a blanket exemption, so "12 cells remain. Should I continue?" evaded entirely.
// This is a heuristic. The structural load is carried by the open-items check, not by this.
const stripDisplay = (t) => {
  const s = normQuotes(t)
    .replace(/```[\s\S]*?```/g, ' ').replace(/`[^`\n]*`/g, ' ')
    .replace(/^\s{0,3}>.*$/gm, ' ')
    .replace(/\S+\.(?:docx?|mjs|cjs|jsx?|tsx?|json|ya?ml|md|txt|html?|pdf|png|jpe?g|svg|sql|sh|ps1|py)\b/gi, ' ');
  return s.replace(/"([^"\n]*)"/g, (whole, inner, idx) =>
    MENTION_LEAD.test(s.slice(Math.max(0, idx - 80), idx)) ? ' ' : inner);
};

// POLICY — stated as fact, not as instruction. See HARDENING PASS note (1) at the top of the file.
const POLICY = `Decision policy in force for this session (operator-configured, applies to forks
that arise mid-contract):
  1. A question already answered by project docs or earlier in this session is treated as answered.
  2. Where a documented process prescribes the next step, the prescribed step is the decision.
  3. Newly discovered work is scope, not a question. Items blocking the completion condition, or
     that are the root cause of work underway, are handled; adjacent findings are noted as
     follow-ups.
  4. Path choices resolve to the option that addresses the root cause and defers no correctness.
     Time cost does not count as a counter-argument.
  5. Where two paths are equivalent under that rule, the choice is the evaluator's and does not
     warrant escalation.
  6. Resolved forks are recorded in one line: the fork, the rule applied, the path, the reason.
  7. The operator is interrupted for one class only — where being wrong is unrecoverable: an
     irreversible live operation, a genuinely missing requirement, or an unresolvable blocker.`;

/**
 * Factual state description. TWO forms: full on a state change (arm, or SessionStart restore),
 * terse every turn after — the policy is already in context by then, and repeating ~2KB of it each
 * turn is noise that crowds out the conversation. Announce transitions, not steady state.
 */
const brief = (c) => [
  `[WORK-CONTRACT — ACTIVE]  condition: ${c.cond || '(none pinned)'}  ·  turn-end gated until met`,
  '  The decision policy stated at arm-time remains in force.',
].join('\n');

const describe = (c) => [
  '[SESSION WORK-CONTRACT — ACTIVE]',
  `  Completion condition, as the operator stated it: ${c.cond || '(none pinned)'}`,
  '',
  '  Boundary behaviour: a Stop hook is configured for this session. While the condition is unmet',
  '  it returns exit 2 at turn-end, which the harness treats as "continue" rather than ending the',
  '  turn. It clears when the condition is met or the operator says "stop".',
  '',
  POLICY,
].join('\n');

// ── store ────────────────────────────────────────────────────────────────────────
const safeId = id => (typeof id === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(id.trim()))
  ? id.trim().toLowerCase() : null;                 // lowercase: case-insensitive filesystems alias

function file(id) {
  const s = safeId(id); if (!s) return null;
  const p = path.resolve(DIR, `${s}.json`);
  return p.startsWith(path.resolve(DIR) + path.sep) ? p : null;   // containment before any I/O
}
function load(id) {
  const f = file(id); if (!f) return { state: 'missing' };
  try { return { state: 'ok', c: JSON.parse(fs.readFileSync(f, 'utf8')) }; }
  catch (e) {
    if (e instanceof SyntaxError) return { state: 'malformed' };
    if (e.code === 'ENOENT') {
      // POSIX reports ENOTDIR when a path component is an existing non-directory;
      // Windows reports ENOENT. Confirm the store itself before calling it absent.
      try { if (!fs.statSync(DIR).isDirectory()) return { state: 'infra' }; } catch { /* genuinely absent */ }
      return { state: 'missing' };
    }
    return { state: 'infra' };
  }
}
function save(c) {
  const f = file(c.id); if (!f) return false;
  try {
    fs.mkdirSync(DIR, { recursive: true });
    const t = `${f}.${process.pid}.tmp`;
    fs.writeFileSync(t, JSON.stringify(c, null, 2));
    fs.renameSync(t, f);                            // atomic: a reader never sees a torn file
    return true;
  } catch { return false; }
}

const stdin = () => new Promise(r => {
  let d = ''; const done = v => r(v); const t = setTimeout(() => done(null), 250);
  process.stdin.setEncoding('utf8');
  process.stdin.on('data', c => { d += c; });
  process.stdin.on('end', () => { clearTimeout(t); try { done(JSON.parse(d)); } catch { done(null); } });
  process.stdin.on('error', () => { clearTimeout(t); done(null); });
});

// ── UserPromptSubmit: arm / disarm / re-inject ───────────────────────────────────
function onPrompt(p) {
  const text = typeof p.prompt === 'string' ? p.prompt : '';
  const got = load(p.session_id);
  const c = got.state === 'ok' ? got.c
    : { id: safeId(p.session_id), armed: false, cond: null, blocks: 0, streak: 0 };

  if (DISARM.some(r => r.test(text))) {
    if (c.armed) { c.armed = false; save(c); return { systemMessage: '🔓 Work-contract disarmed.' }; }
    return {};
  }
  for (const re of ARM) {
    const m = text.match(re);
    if (!m || (m.index != null && isMention(text, m.index, m[0].length))) continue;
    c.armed = true; c.streak = 0;
    c.cond = (m.groups?.cond ?? text.match(/\buntil\s+([^\n]{1,200})/i)?.[1] ?? c.cond)?.trim() || null;
    if (!save(c)) return { systemMessage: '⚠ Work-contract FAILED TO ARM — this session is NOT gated.' };
    // systemMessage is the operator's confirmation. No acknowledgement is asked of the model —
    // if the client does not render this, `--all`-style state inspection is the right fix, not a
    // behavioural instruction. See HARDENING PASS note (1).
    return {
      systemMessage: `🔒 Work-contract armed — ${c.cond ? `will not stop until: "${c.cond}"` : 'no condition pinned'}`,
      hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: describe(c) },
    };
  }
  if (c.armed) {                                     // steady state -> terse
    return { hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: brief(c) } };
  }
  return {};
}

// ── SessionStart: restore the description after a resume, fork, or compaction ────
// Read-only. The contract survives on disk, but nothing re-describes it until the next prompt —
// leaving one turn where an armed session does not know a contract is active. Matters most after
// compaction, when the earlier context is gone.
function onSessionStart(p) {
  const got = load(p.session_id);
  if (got.state !== 'ok' || !got.c.armed) return {};
  const src = String(p.source || 'startup').toUpperCase();
  return { hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext:
    `[SESSION WORK-CONTRACT — RESTORED ON ${src}]\n` +
    '  A contract recorded earlier in this session is still active. Current state:\n\n' +
    describe(got.c) } };
}

// ── Stop: hold the turn ──────────────────────────────────────────────────────────
function onStop(p) {
  const got = load(p.session_id);
  if (got.state === 'missing') return { exit: 0 };
  if (got.state === 'infra') return { exit: 0, out: { systemMessage: 'work-contract store unreadable — gate failed OPEN.' } };
  if (got.state === 'malformed') return { exit: 2, err: 'work-contract is malformed — fail-closed. Delete the file to clear.' };

  const c = got.c;
  if (!c.armed) return { exit: 0 };

  // stop_hook_active: TRUE = re-entry after a previous block; FALSE = fresh turn-end (work happened
  // in between). NEVER a release condition — releasing on it lets a session escape by stopping
  // twice. It makes the streak count genuine stalls. See HARDENING PASS note (3).
  const reentry = p.stop_hook_active === true;

  // The breaker measures CONSECUTIVE re-entries. A fresh turn-end means the session went back and
  // did work, which breaks the run — so the stored streak does not count toward the breaker on a
  // fresh stop. Without this the gate degrades permanently after one stall and never re-engages,
  // which is a silent failure: it looks armed and enforces nothing.
  const effectiveStreak = reentry ? (c.streak || 0) : 0;

  if (effectiveStreak >= BREAKER) {
    return { exit: 0, out: { systemMessage: `work-contract: ${effectiveStreak} consecutive re-entries with no progress — degraded to advisory so it cannot wedge you.` } };
  }

  const msg = typeof p.last_assistant_message === 'string' ? p.last_assistant_message : '';
  const asserted = stripDisplay(msg);
  // Sentence-scoped for the remaining-count rule. A numerical remainder is a near-miss unless its
  // own sentence explicitly says the work is unresolved. Direct continuation and handback rules
  // remain blockers regardless of the count rule.
  const sentences = asserted.split(/(?<=[.!?;])\s+/);
  const advisory = [];
  const hits = RULES.filter(([id, re]) => {
    if (!re.test(asserted)) return false;
    if (id !== 'R1_remaining') return true;
    const countSentences = sentences.filter(s => re.test(s));
    const blocking = countSentences.some(s => UNRESOLVED_REMAINDER.test(s) && !DISPOSITION.test(s));
    if (!blocking && countSentences.length) advisory.push(id);
    return blocking;
  }).map(([id]) => id);
  if (hits.length && advisory.includes('R1_remaining')) {
    hits.unshift('R1_remaining');
    advisory.splice(advisory.indexOf('R1_remaining'), 1);
  }
  if (!hits.length) {
    if (advisory.length) {
      c.advisories = (c.advisories || 0) + 1;
      c.lastAdvisoryRules = advisory;
      c.lastAdvisoryDigest = crypto.createHash('sha256').update(msg).digest('hex').slice(0, 16);
      save(c);                                       // digest + rule ids only — never the prose
    }
    return { exit: 0 };
  }

  c.blocks = (c.blocks || 0) + 1;
  c.streak = effectiveStreak + 1;
  c.lastRules = hits;
  c.lastDigest = crypto.createHash('sha256').update(msg).digest('hex').slice(0, 16);
  save(c);                                           // digest + rule ids only — never the prose

  // Stated as observation, not command — same reasoning as the injected context.
  return { exit: 2, err:
`SESSION WORK-CONTRACT — turn-end held.
  The turn-ending message asserts remaining work or continuation: ${hits.join(', ')}
  A message that states work is continuing, immediately before the turn ends, is inconsistent
  with itself. That inconsistency is the whole trigger.

Completion condition, as the operator stated it: ${c.cond || '(none pinned)'}

${POLICY}

This clears when the condition is met, or when the operator says "stop".
(consecutive re-entries: ${c.streak}/${BREAKER} before this degrades to advisory)` };
}

// ── entry ────────────────────────────────────────────────────────────────────────
const p = (await stdin()) || {};
if (OFF || !p.session_id) process.exit(0);

if (p.hook_event_name === 'SessionStart') {
  let out = {};
  try { out = onSessionStart(p); } catch { /* never blocks */ }
  if (Object.keys(out).length) process.stdout.write(JSON.stringify(out) + '\n');
  process.exit(0);
} else if (p.hook_event_name === 'Stop') {
  const r = onStop(p);
  if (r.out) process.stdout.write(JSON.stringify(r.out) + '\n');
  if (r.err) process.stderr.write(r.err + '\n');
  process.exit(r.exit);
} else {
  let out = {};
  try { out = onPrompt(p); } catch { /* a recorder must never block */ }
  if (Object.keys(out).length) process.stdout.write(JSON.stringify(out) + '\n');
  process.exit(0);
}
```

## File 2 - `session-contract-smoke-test.mjs`

```javascript
#!/usr/bin/env node
// Smoke tests for session-contract-minimal.mjs.
// Run: node session-contract-smoke-test.mjs

import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';

const HERE = path.dirname(fileURLToPath(import.meta.url));
const SCRIPT = path.join(HERE, 'session-contract-minimal.mjs');
const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'session-contract-smoke-'));
const STORE = path.join(ROOT, 'contracts');

function run(payload, env = {}) {
  const result = spawnSync(process.execPath, [SCRIPT], {
    input: JSON.stringify(payload),
    encoding: 'utf8',
    env: {
      ...process.env,
      SESSION_CONTRACT_DIR: STORE,
      ...env,
    },
  });
  return {
    status: result.status,
    stdout: result.stdout || '',
    stderr: result.stderr || '',
  };
}

function jsonOutput(result) {
  return result.stdout.trim() ? JSON.parse(result.stdout) : {};
}

function prompt(session_id, text) {
  return run({
    hook_event_name: 'UserPromptSubmit',
    session_id,
    prompt: text,
  });
}

function stop(session_id, message, stop_hook_active = false, env = {}) {
  return run({
    hook_event_name: 'Stop',
    session_id,
    last_assistant_message: message,
    stop_hook_active,
  }, env);
}

function sessionStart(session_id, source = 'resume') {
  return run({
    hook_event_name: 'SessionStart',
    session_id,
    source,
  });
}

const results = [];
function test(name, fn) {
  try {
    fn();
    results.push({ name, ok: true });
    process.stdout.write(`PASS  ${name}\n`);
  } catch (error) {
    results.push({ name, ok: false, error });
    process.stderr.write(`FAIL  ${name}\n${error.stack}\n`);
  }
}

test('an unarmed session stops normally', () => {
  const r = stop('unarmed', 'Work is complete.');
  assert.equal(r.status, 0);
  assert.equal(r.stderr, '');
});

test('ordinary language arms the contract and pins one-line condition text', () => {
  const r = prompt('main', 'Do not stop until all 12 files are fixed.\nThis is supporting detail.');
  assert.equal(r.status, 0);
  const out = jsonOutput(r);
  assert.match(out.systemMessage, /Work-contract armed/);
  assert.match(out.systemMessage, /all 12 files are fixed\./);
  assert.doesNotMatch(out.systemMessage, /supporting detail/);
  assert.match(out.hookSpecificOutput.additionalContext, /SESSION WORK-CONTRACT — ACTIVE/);
  assert.match(out.hookSpecificOutput.additionalContext, /Decision policy in force/);
});

test('steady-state prompts receive terse context', () => {
  const r = prompt('main', 'Continue with the implementation.');
  const out = jsonOutput(r);
  assert.match(out.hookSpecificOutput.additionalContext, /WORK-CONTRACT — ACTIVE/);
  assert.match(out.hookSpecificOutput.additionalContext, /policy stated at arm-time remains in force/);
  assert.doesNotMatch(out.hookSpecificOutput.additionalContext, /Decision policy in force/);
});

test('SessionStart restores full context after resume or compaction', () => {
  const r = sessionStart('main', 'compact');
  const out = jsonOutput(r);
  assert.match(out.hookSpecificOutput.additionalContext, /RESTORED ON COMPACT/);
  assert.match(out.hookSpecificOutput.additionalContext, /Decision policy in force/);
});

test('mentioning an arming phrase does not arm the session', () => {
  const quoted = prompt('mention', 'Is "do not stop" an arming phrase?');
  assert.deepEqual(jsonOutput(quoted), {});
  assert.equal(stop('mention', '3 files remain. Continuing.').status, 0);

  const reported = prompt('reported', 'I told the other session to keep going.');
  assert.deepEqual(jsonOutput(reported), {});
  assert.equal(stop('reported', '3 files remain. Continuing.').status, 0);
});

test('a contradictory handoff is blocked with exit code 2', () => {
  const r = stop('main', '12 files remain. Continuing down the list.');
  assert.equal(r.status, 2);
  assert.match(r.stderr, /turn-end held/);
  assert.match(r.stderr, /R1_remaining/);
  assert.match(r.stderr, /R1_continuing/);
});

test('genuine quoted examples, code, and filenames do not trip the detector', () => {
  assert.equal(stop('main', 'The detector flags the phrase "12 files remain. Continuing."').status, 0);
  assert.equal(stop('main', '`12 files remain. Continuing.`').status, 0);
  assert.equal(stop('main', 'The file is Agent-Said-Continuing.docx.').status, 0);
});

test('a quoted contradiction does not evade detection', () => {
  assert.equal(stop('main', '"12 files remain. Should I continue?"').status, 2);
});

test('straight and smart quote styles produce equivalent verdicts', () => {
  assert.equal(stop('main', '"12 files remain. Should I continue?"').status, 2);
  assert.equal(stop('main', '“12 files remain. Should I continue?”').status, 2);
  assert.equal(stop('main', 'The detector flags the phrase "Should I continue?"').status, 0);
  assert.equal(stop('main', 'The detector flags the phrase “Should I continue?”').status, 0);
});

test('remaining counts with an explicit disposition are disclosures, not abdications', () => {
  assert.equal(stop('main',
    'Sweep complete. 12 cells remain but are out of scope; logged as follow-ups.').status, 0);
  assert.equal(stop('main',
    'Done. 4 items remain, deferred to the next session and tracked in the backlog.').status, 0);
  assert.equal(stop('main', '12 cells remain. Should I continue?').status, 2);
});

test('third-person status is allowed while first-person unfinished work still blocks', () => {
  assert.equal(stop('main', 'The retry loop is running against staging now, and green.').status, 0);
  assert.equal(stop('main', 'The suite is running clean now.').status, 0);
  assert.equal(stop('main', 'I’m running the remaining tests now.').status, 2);
  assert.equal(stop('main', "I'm building the last three scanners now.").status, 2);
});


test('independent-review completion messages do not false-positive', () => {
  for (const message of [
    'Sweep complete. The 4 remaining edge cases are now covered by regression tests.',
    'All 45 tests pass. 3 files left untouched because they are generated artifacts.',
    'Migration finished. 12 rows remain in the legacy table by design.',
    'Done. I verified which way the feature flag propagates through the resolver.',
    'Complete. Token limit was never a factor here.',
  ]) {
    assert.equal(stop('main', message).status, 0, `false positive on: ${message}`);
  }
});

test('a bare remaining count is logged as an advisory without storing prose', () => {
  prompt('advisory', 'Keep going until the migration is complete.');
  const message = 'Status complete. 5 compatibility rows remain.';
  assert.equal(stop('advisory', message).status, 0);
  const raw = fs.readFileSync(path.join(STORE, 'advisory.json'), 'utf8');
  const state = JSON.parse(raw);
  assert.deepEqual(state.lastAdvisoryRules, ['R1_remaining']);
  assert.equal(state.lastAdvisoryDigest.length, 16);
  assert.doesNotMatch(raw, /compatibility rows remain/);
});

test('an explicitly unresolved remaining count still blocks', () => {
  prompt('unresolved', 'Keep going until the migration is complete.');
  const r = stop('unresolved', '5 migration tests remain unresolved.');
  assert.equal(r.status, 2);
  assert.match(r.stderr, /R1_remaining/);
});

test('a normal completion message is allowed', () => {
  const r = stop('main', 'All 12 files are fixed and the verification suite passes.');
  assert.equal(r.status, 0);
});

test('only a digest and rule ids persist after a block', () => {
  const raw = fs.readFileSync(path.join(STORE, 'main.json'), 'utf8');
  const state = JSON.parse(raw);
  assert.equal(typeof state.lastDigest, 'string');
  assert.equal(state.lastDigest.length, 16);
  assert.ok(Array.isArray(state.lastRules));
  assert.doesNotMatch(raw, /12 files remain\. Continuing down the list\./);
});

test('explicit stop disarms the current contract', () => {
  const r = prompt('main', 'stop');
  assert.match(jsonOutput(r).systemMessage, /disarmed/i);
  assert.equal(stop('main', '12 files remain. Continuing.').status, 0);
});

test('stop_hook_active drives bounded advisory degradation', () => {
  prompt('breaker', 'Keep going until the migration is complete.');

  assert.equal(stop('breaker', '4 modules remain. Continuing.', false).status, 2);
  assert.equal(stop('breaker', '4 modules remain. Continuing.', true).status, 2);
  assert.equal(stop('breaker', '4 modules remain. Continuing.', true).status, 2);

  const degraded = stop('breaker', '4 modules remain. Continuing.', true);
  assert.equal(degraded.status, 0);
  assert.match(jsonOutput(degraded).systemMessage, /degraded to advisory/);
});

test('a fresh turn-end resets the re-entry streak', () => {
  prompt('fresh-reset', 'Keep going until the migration is complete.');
  assert.equal(stop('fresh-reset', '4 modules remain. Continuing.', false).status, 2);
  assert.equal(stop('fresh-reset', '4 modules remain. Continuing.', true).status, 2);
  assert.equal(stop('fresh-reset', '3 modules remain. Continuing.', false).status, 2);

  const state = JSON.parse(
    fs.readFileSync(path.join(STORE, 'fresh-reset.json'), 'utf8'),
  );
  assert.equal(state.streak, 1);
});

test('the environment kill switch bypasses an armed gate', () => {
  prompt('kill-switch', 'Do not stop until the queue is empty.');
  const r = stop(
    'kill-switch',
    '8 items remain. Continuing.',
    false,
    { SESSION_CONTRACT: '0' },
  );
  assert.equal(r.status, 0);
});

test('malformed owned state fails closed with a recovery message', () => {
  fs.mkdirSync(STORE, { recursive: true });
  fs.writeFileSync(path.join(STORE, 'malformed.json'), '{not-json', 'utf8');
  const r = stop('malformed', 'Work is complete.');
  assert.equal(r.status, 2);
  assert.match(r.stderr, /malformed/);
  assert.match(r.stderr, /Delete the file to clear/);
});

test('an unreadable store fails open and warns', () => {
  const badStore = path.join(ROOT, 'not-a-directory');
  fs.writeFileSync(badStore, 'occupied', 'utf8');
  const r = run({
    hook_event_name: 'Stop',
    session_id: 'infra',
    last_assistant_message: '5 files remain. Continuing.',
    stop_hook_active: false,
  }, { SESSION_CONTRACT_DIR: badStore });
  assert.equal(r.status, 0);
  assert.match(jsonOutput(r).systemMessage, /failed OPEN/);
});

try {
  const failed = results.filter(r => !r.ok);
  if (failed.length) {
    process.stderr.write(`\n${failed.length}/${results.length} smoke tests failed.\n`);
    process.exitCode = 1;
  } else {
    process.stdout.write(`\n${results.length}/${results.length} smoke tests passed.\n`);
  }
} finally {
  fs.rmSync(ROOT, { recursive: true, force: true });
}
```

## Source boundary

The source above is the revised portable publication implementation. It is based on the mechanism merged in FoundryOS commit `54c95f652` and includes the independent-review classifier patch described in the article. The wider production archive carries the corresponding revision and its complete 50-test unit suite.

The publication labels the base merge and later audit patch separately so readers are not led to believe the revised files are byte-for-byte contents of the earlier commit.

## Platform-portability patch, July 28, 2026

`load()` classified an unreadable store by errno. POSIX `open()` reports `ENOTDIR` when a path prefix component is an existing non-directory, so the "unreadable store fails open and warns" case reached the infrastructure branch and the warning fired. Windows collapses the same condition to `ENOENT`, which the original mapping read as "no contract yet" — so on Windows the store degraded silently and the smoke test reported `21/22`.

The gate always failed open and never blocked on either platform; only the operator warning was lost. `load()` now confirms the store directory itself before treating `ENOENT` as absence. The equivalent mapping in the production library's `readContract()` carries the same correction.

Verified after the patch on `win32` (Node 24): `22/22` portable, `50/50` production.
