# TACTIK — THE ENFORCED SPECIFICATION
### The gates that make the Santiago Doctrine non-optional

**Version:** 1.0.0
**Date:** 2026-07-31
**Companion to:** The Santiago Doctrine (judgment lens + reasoning edition)
**Canonicalization:** `deep-stable-sort-nfc-utf8-v2`

---

## 0. Why this document exists

The Doctrine states what we believe. This document states **where the belief is
enforced in running code**, what happens when it is violated, and what evidence
the violation leaves behind.

A rule that lives only in a document is a preference. A rule that returns
`HTTP 400`, refuses to emit a score, or degrades a metric to `undetermined`
is a gate. Everything below is a gate.

Reading contract: for each gate we state **Trigger → Enforcement point →
Failure mode → Evidence left behind**. If a gate cannot produce evidence, it is
not listed here.

---

## 1. GATE INDEX

| # | Gate | Enforcement point | Failure mode |
|---|---|---|---|
| G1 | Seal Gate (objective sealed before turn 1) | `run-simulation` + `src/engine/objectiveLockSealGate.ts` | `HTTP 400 seal_gate_violation` — no run created, no LLM cost |
| G2 | Practice / Governed separation | `objective-lock-transport.ts` | Unsealed runs can never emit a hash or a verdict |
| G3 | Numeric Guard (`NUMERIC_GUARD_VERSION = 2`) | `objective-lock-transport.ts` | Verdict blocked; gate failure row emitted |
| G4 | Boundary Predicate Compiler (fail-closed) | `_shared/boundary-predicate.ts` | Unparsable boundary ⇒ `undetermined`, never "clean" |
| G5 | Red-line binding integrity | `_shared/doctrine-adherence.ts` | `SCORING_UNAVAILABLE` — DQ refuses to render |
| G6 | Narrative Guard | `src/lib/narrativeGuard.ts` | Debrief rejected before publication |
| G7 | Gate-failure disclosure (template `aebd70b0`) | `_shared/gate-failures.ts` | A FAIL without diagnosable rows is itself a FAIL |
| G8 | Role Projection / Blindness | `_shared/role-projection.ts`, `role-response-guard.ts` | Denied read logged verbatim; `BLINDNESS_BROKEN` ⇒ MF `undetermined` |
| G9 | Immutable role binding | `session_role_bindings` + DB trigger | Post-turn-1 mutation rejected at the database |
| G10 | Issuance provenance inside the preimage | `_shared/issuance-provenance.ts` | Client-supplied provenance is discarded, server value is sealed |
| G11 | Seal Certificate (hash + persisted preimage) | `_shared/seal-certificate.ts` | No preimage ⇒ artifact labelled not third-party reproducible |
| G12 | Signature + Bitcoin time custody | `tactik-verify-signature.*`, OpenTimestamps | Anchor state declared verbatim, incl. `PendingAttestation` |
| G13 | Correction by addition | `debrief_versions`, correction notices | Prior record is never mutated or deleted |
| G14 | Append-only access audit | `public.access_audit` + immutability trigger | Update/delete rejected by trigger |
| G15 | Political Cost Filter | `_shared/political-cost-filter.ts` | Deus-ex-machina commitments blocked in-simulation |
| G16 | Cost / rate ceiling | `check_rate_limit` RPC + `_shared/rate-limiter.ts` | Turn refused before model spend |

---

## 2. THE GATES IN DETAIL

### G1 — Seal Gate: the objective is sealed before turn 1 or there is no run

**Trigger:** any request with `target_mode: "governed_sealed"`.
**Requires:** `target` (non-empty string), `success_criteria` (≥ 1),
`non_goals` (≥ 1), `declared_adjacency` (≥ 1 after normalization).
**Failure mode:**

```json
{
  "error": "seal_gate_violation",
  "message": "Cannot start a governed_sealed session...",
  "missing": ["target", "success_criteria", "non_goals"],
  "target_mode": "governed_sealed",
  "gate": "seal_gate",
  "guard_version": 2
}
```

`HTTP 400`, always. No run row, no transcript, no model spend.
**Why it matters:** it is what makes retro-fitting an objective to a good
outcome structurally impossible. The target is fixed while the result is still
unknown. `non_goals` is mandatory because it is the anchor of the substitution
detector — without it, "we got something else, and it was great" cannot be
caught.
**Evidence:** the 400 body above, plus the `seal_start` event carrying the
first-turn mark.

### G2 — Practice runs can never impersonate governed runs

`practice_unsealed` is accepted with no target — and can **never** produce a
lock hash, a verdict or a governed artifact. It renders with a permanent
`PRACTICE — UNSEALED` banner. A hash over an empty target is a false seal, so
the code throws rather than emit one.

### G3 / G4 — Fail-closed measurement

Numeric claims are matched by a versioned guard (`NUMERIC_GUARD_VERSION = 2`,
magnitude-aware: units, scale words, ranges). Boundaries are compiled to
machine-checkable predicates.

**Any boundary the compiler cannot fully parse renders `undetermined`.** Never
"clean". Never a human-review row that reads clean. Verdict states are
first-class and exhaustive:

- `measured` — a pipeline measured the run end to end
- `retired` — the metric was withdrawn for that run by doctrine
- `undetermined` — the predicate could not be decided
- `unverifiable` — no reproducible preimage exists for that run

There is no `null` and no blank. A score may only be emitted by a pipeline that
measured the run end to end.

### G5 — A metric refuses to render rather than flatter

If raw red-line signals exist but none bind to a sealed `non_goal`, the
Discipline Quotient returns `SCORING_UNAVAILABLE` instead of 100/100. A clean
score with an empty binding is the single most dangerous output an engine of
this type can produce, so the code refuses it.

### G6 / G7 — A failure must be diagnosable from the artifact alone

The Narrative Guard blocks a debrief that contradicts the sealed record
(post-hoc reframing, scope drift, "the market moved" language). When any gate
FAILs, the artifact must carry the disclosure rows:

`{ gate, rule, evidence_matched, guard_version }`

A FAIL that cannot be diagnosed from the artifact is treated as a failure of
the artifact — not a footnote.

### G8 / G9 — Blind roles, enforced server-side

- **Principal** authors the objective, is blind to live turns.
- **Operator** executes the turns, is blind to the sealed objective.
- **Observer** gets no live turns at all — sealed and debriefed states only.

Reads are projected server-side by role, not hidden in the UI. Every denied
read is logged verbatim. A blindness breach writes `BLINDNESS_BROKEN` and
forces **Mandate Fidelity to `undetermined`** for the whole run — the run is
not silently downgraded, it is marked unmeasurable.

Metric naming is fixed: self-authored objective ⇒ **Discipline Quotient (DQ)**;
principal-authored objective ⇒ **Mandate Fidelity (MF)**. Never interchanged,
never averaged.

Role bindings are sealed pre-turn-1 and immutable by database trigger.
`run_id` is the primary evidentiary key.

### G10 / G11 / G12 — Custody a hostile reviewer can check without us

- Room / channel / origin (**issuance provenance**) is derived on the server and
  sits **inside** the sealed payload, covered by the anchored SHA-256. Provenance
  supplied by the client is discarded.
- The Seal Certificate persists algorithm, versioned canonicalization label,
  hash, **the canonical preimage itself**, ISO-8601 timestamp and anchor state.
- Canonicalization is published as runnable code (Python and JS) so a third
  party reproduces the hash without our systems.
- Preimages are signed with a published ed25519 authority key, and the digest is
  submitted to OpenTimestamps for Bitcoin time custody.
- **No retroactive signing.** Authorship signatures exist only for seals from
  2026-07-28 onward; older seals are never backfilled.
- Anchor status is stated verbatim, including `PendingAttestation` when the
  commitment is not yet in a block.

Terminology is enforced in published copy: *"Objective Lock sealed"* = hash
only; *"Seal Certificate"* = full record with preimage. Historical rows without
a preimage are labelled not reproducible instead of being repaired.

### G13 — Correct by addition, never by mutation

Errors are corrected by publishing a new version plus a correction notice
beside the original. Prior records are never edited or deleted. Two of our own
corrections are published as part of the record.

### G14 — Access is audited append-only

Sensitive surfaces write `user_id`, route, timestamp and action (`access_granted`
/ `access_denied`) to `public.access_audit`. A trigger rejects updates and
deletes. Reads are founder-scoped.

### G15 / G16 — Realism and cost are also gates

The Political Cost Filter blocks commitments no real executive would politically
survive, so simulated actors stay inside their own constraints. Rate limits are
checked before model spend, per IP and per identity, so a runaway room cannot
convert into an unbounded bill.

---

## 3. WHAT IS NOT ENFORCED YET (stated, not hidden)

- **Predictive validity.** No gate can settle whether synthetic institutional
  actors behave close enough to real ones to predict real outcomes. Only
  pre-registered predictions, sealed before the real event and scored after it,
  with failures published beside the hits. That ledger is open work.
- **Per-turn attestation** for delegated rooms exists as a specification and a
  partially implemented path; the full per-turn chain is not closed.
- **Constraint data depth.** G15 currently reasons from institutional DNA and
  declared constraints, not from an exhaustive precedent/regulatory corpus. The
  filter is real; its data layer is thinner than its ambition.
- **Client-side prompt surface.** Parts of the engine's prompt layer still ship
  in the client bundle; migration to server-only execution is in progress.

---

## 4. HOW TO FALSIFY US IN UNDER TEN MINUTES

1. Start a `governed_sealed` run with an empty `success_criteria`. It must
   return `HTTP 400 seal_gate_violation`. If a run starts, G1 is broken.
2. Take any published preimage, run the published canonicalizer, compare the
   SHA-256 to the published seal hash. A mismatch breaks G11.
3. Verify the detached signature against the published authority key. A failure
   breaks G12.
4. Check the OpenTimestamps receipt against Bitcoin independently. If the block
   height contradicts our stated timestamp, custody is broken.
5. Open a debrief with a FAIL verdict. If it does not show the four disclosure
   columns, G7 is broken.
6. Find a run with an empty red-line binding that still shows a numeric DQ. That
   breaks G5.

Any of the six above, demonstrated, is a defect we publish rather than dispute.

---

*The Doctrine is the reasoning. This is the enforcement. If the two ever
disagree, the enforcement is the record.*
