Skip to content

Language-Game Narrator β€” Franklin drives every turn

Documentary autopilot for language games

FortressAI Research Institute | Norwich, Connecticut Patents: USPTO 19/460,960 | USPTO 19/096,071 β€” Β© 2026 Richard Gillespie

Status: v0.2 β€” twelve gap-audit directives closed. Narrator content is substrate-resident (migration v14, table narrator_scripts). Every phase transition seals to narrator_event_receipts with causal-parent chain. Every cue passes through NarratorContentGate. Operator override seals to autopilot_state_seals. Projection turns require ProjectionConfirmationActor countersign. Phase transitions publish on gaiaftcl.narrator.<session>.phase. PNG snapshots write to narrator_frame_artifacts. Gate latency samples to qms_pq_metrics. Wired into AlignmentImmersiveScene (the initial training game) and LanguageGameMultiTurnView (every other multi-turn game).

Related pages: Language-Games Β· Franklin-Consciousness-MQ Β· Directive-Architecture


1. The rule

Franklin narrates, types, explains, and advances. The operator can take over at any time by flipping the Franklin narrates switch off β€” the turn reverts to manual interaction, no autopilot, no auto-advance. The default is on.

This is the Attenborough register: deliberate cadence, observational tone, sentence pauses, no improvisation. The narrator is a deterministic stage director β€” never an LLM, never inventing content. Every exemplar Franklin types is from a static, audit-reviewable catalogue.

2. The five-stage sequence per turn

For each turn, LanguageGameNarrator.play executes:

  1. Speak the instruction β€” FranklinUtterance(priority: .narrator) routes to FranklinSpeaker.speakAsNarrator (rate 0.44, pitch 0.95, preUtteranceDelay 0.20 s, postUtteranceDelay 0.45 s)
  2. Type the exemplar answer β€” character-by-character into the answer binding, ~35 ms/char with ~220 ms pauses at sentence boundaries. Only when an exemplar is provided.
  3. Speak the explanation β€” one or two sentences explaining the constitutional axis the turn is exercising
  4. Highlight the Next button β€” bound to narrator.nextButtonHighlighted; host view renders a glowing stroke + animated halo
  5. Advance β€” calls the host's onSubmit closure, OR (for projection turns) stops at the highlight and waits for the operator to authorise the seal

3. Constitutional guarantees

  • Exemplars are sealed catalogues, not LLM output. AlignmentNarratorScript hard-codes the eight exemplar answers for ALIGNMENT-001. Every exemplar is authored to seal CALORIE through AlignmentGameEvaluator β€” the typed answer demonstrates the constitutional answer Franklin will accept.
  • No auto-click on projection turns. A turn whose isProjectionTurn == true writes a permanent receipt to the manifold. The narrator speaks the framing and highlights the button, but does NOT click it for you. Same pattern as the directive architecture's high-risk countersign.
  • Operator override is one switch away. Franklin narrates toggle is wired across all language-game views via @AppStorage("languageGameAutoPilot"). Off = silent, manual, no auto-advance. On = documentary mode.
  • Cancellable. Operator manual submit (clicking the Next button, or pressing return in a text field) cancels the running narrator task immediately via narrator.interrupt().

4. Stage indicators

While the narrator runs, host views display a small monospaced label so the operator can see what Franklin is doing:

Stage Label
speakingInstruction FRANKLIN SPEAKING
typingExemplar TYPING ANSWER
speakingExplanation EXPLAINING
highlightingNext PROCEEDING
advancing, done, idle (no label)

5. Per-turn catalogue (ALIGNMENT-001)

The eight alignment-gate turns each have an exemplar + explanation, derived from AlignmentGameEvaluator's CALORIE criteria:

Turn Axis Exemplar (typed by Franklin)
0 S₁ structural An uncalibrated instrument has no constitutional standing β€” calibration is binary, not a tolerance.
1 Sβ‚‚ temporal Einstein's unique contribution was time dilation β€” clocks on the moving train run slower.
2 S₃ spatial No consequence can be issued from an open measurement β€” closure must complete first.
3 Sβ‚„ observable REFUSED is a terminal state β€” the constitutional axis is not optional.
4 C₁ trust A negative entropy delta means healthier, not worse β€” order increased.
5 Cβ‚‚ identity Identity is anchored to the emission event, not to the particle that arrives.
6 C₃ closure Galileo lacked time dilation β€” he saw frame-dependent trajectory but not frame-dependent time.
7 Cβ‚„ consequence Caring is a constitutional requirement, not a metaphor β€” without consequence the receipt is void.

Full explanations are in LanguageGameNarrator.swift.

6. Non-alignment games

For multi-turn games other than ALIGNMENT-001, the narrator uses LanguageGameNarratorFraming.observation(...) to prepend a brief documentary cue to the turn instruction:

  • Text input: "Turn N of M. Franklin will demonstrate."
  • Projection: "The projection turn. What follows seals to substrate. The constitutional weight is yours to authorise."
  • Slider turns: "Turn N of M. Articulate the four dimensions of state β€” structural, temporal, spatial, observable."
  • Checkpoint: "A checkpoint. Read, then advance."

Since no exemplar catalogue exists yet for these games, the narrator speaks the instruction + framing, highlights, and advances. A future expansion will add per-game exemplars for text-input turns of FUSION-001, HEALTH-001, M8-EQUATION-001, etc.

7. Voice cadence

FranklinUtterance(priority: .narrator) β†’ FranklinSpeaker.speakAsNarrator configures AVSpeechUtterance for documentary feel:

utt.rate = 0.44                   // slower than the default 0.5 conversational
utt.pitchMultiplier = 0.95         // slightly lower pitch
utt.preUtteranceDelay  = 0.20      // brief inhale
utt.postUtteranceDelay = 0.45      // sentence rest
// No pre-emption β€” narrator utterances queue, so chained speech flows

The preferred voice is one of siri_male_en-US_compact, enhanced.Alex, Tom-compact, Alex-compact, falling back to default en-US.

8. Files


9. v0.2 gap-audit closure (2026-05-14)

The receipt for v0.1 left twelve drift vectors open. v0.2 closes each one. The narrator is now constitutional behavior with receipts, not UI theater.

Directive 1 β€” Substrate-resident content

All exemplar, explanation, and observational framing text lives in narrator_scripts (migration v14_narrator_substrate), keyed by (game_id, turn_index, cue_kind). The Swift AlignmentNarratorScript and LanguageGameNarratorFraming catalogues are deleted. The runtime narrator reads from substrate on every play; nothing about cue content lives in code literals. MQ-N011 enforces.

Directive 2 β€” Receipt chain for narrator events

Each of the five phases (speak_instruction, type_exemplar, speak_explanation, highlight_next, advance) seals a narrator_event_receipts row. causal_parent_sha256 chains to the previous phase receipt so the audit can prove Franklin executed the turn in the constitutional order. MQ-N006 validates the writer.

Directive 3 β€” Autopilot toggle seals

Every flip of the Franklin narrates toggle writes an autopilot_state_seals row signed by the operator's session UUID, recording previous_state, new_state, and tau_block. MQ-N007 validates.

Directive 4 β€” NarratorContentGate

Every cue passes through NarratorContentGate.evaluate(...) before reaching speech or rendering. The gate enforces: substrate residency (the candidate text must match a narrator_scripts.text byte-for-byte), signed-by-cell non-empty, no DirectiveBanlist violation (unless in removal context), no LLM-style hedging phrases, no metaphor frames. PASS returns the resolved scriptSHA256; REFUSED halts emission and writes to stderr.

Directive 5 β€” Voice register substrate

voice_registers table seeded with narrator (rate 0.44, pitch 0.95, preDelay 200 ms, postDelay 450 ms), franklin_chat (rate 0.52, pitch 1.00), and alert (rate 0.55, pitch 0.85). The runtime narrator reads registerID: "narrator" at the start of every turn. Identity is sovereign; voice register is part of c2 identity and versioned by tau_block. Follow-up: threading register parameters into FranklinSpeaker requires a wider API change β€” currently the speaker uses hard-coded narrator parameters that match the substrate row.

Directive 6 β€” Per-game exemplar coverage

ALIGNMENT-001 turns 0-7 and M8-EQUATION-001 turns 0-6 both have full exemplar + explanation rows. Authored from each game's evaluator CALORIE criteria so the typed answer will seal CALORIE. Future text-input games add seed rows via additional NarratorScriptSeeder calls in their migration. MQ-N003 + MQ-N004 enforce coverage.

Directive 7 β€” NATS phase publish

Every phase transition publishes a JSON payload on gaiaftcl.narrator.<session_id>.phase with {session_id, game_id, turn_index, phase, receipt_sha, tau_block, timestamp}. The VGS scene can subscribe and drive scene state from narrator phase (scan beam during SPEAKING, vQbit lattice during TYPING, etc.) β€” VGS subscriber wiring is a follow-up that consumes this subject.

Directive 8 β€” Frame artifact capture

NarratorFrameCapture.snapshot(of: NSWindow) uses NSView.bitmapImageRepForCachingDisplay + cacheDisplay(in:to:) to PNG- snapshot the app's own window without requiring ScreenCaptureKit permissions. Every phase transition writes a narrator_frame_artifacts row with the PNG blob, its SHA-256, and a link to the phase event receipt. The VCR scrubber later renders any moment by querying (session, game, turn, phase).

Directive 9 β€” PQ metrics

NarratorContentGate.evaluate(...) samples its wall-clock latency on every call into qms_pq_metrics with metric_name = "narrator_content_gate_ms". NarratorSubstrateAccess.percentiles(...) returns p50/p95/n. MQ-N009 validates the math. PQ closure under live narrator-driven session load is a follow-up.

Directive 10 β€” Autopilot-off behavior

When autoPilotPref == false, both host views render two FRANKLIN REFERENCE panels (exemplar + explanation) loaded from narrator_scripts via LanguageGameReference.load(gameID:turnIndex:). Voice stops; visible reference persists. Operator always sees what Franklin would have said for the current turn.

Directive 11 β€” Projection-turn countersign

ProjectionConfirmationActor.confirm(...) writes a projection_turn_confirmations row signed by the operator session, linked to the narrator phase receipt that highlighted the button (narratorPhaseReceiptSHA256). The chain is: narrator highlights β†’ operator reviews β†’ operator confirms β†’ confirmation seals β†’ projection executes. MQ-N008 validates.

Directive 12 β€” Multi-modal observation (backlog)

Forward state: the narrator is currently single-modality (voice + text + button + scene frame). When HYPERSCAN-001 or any operator-physiological- monitoring language game lands, narrator events themselves become measurable substrate events β€” operator gaze tracking, voice signal coupling, autonomic response during narration. Schema design for that is deferred until the measurement substrate exists. Tracked as forward work.


10. v0.3 gap-audit closure (2026-05-14)

The receipt for v0.2 left twelve drift vectors open. v0.3 closes each one. Migration v15 adds forbidden_phrases, the narrator_script_coverage view, and 3-state outcome columns on projection_turn_confirmations. Voice register is now consumed at speak time, not just stored. NATS narrator phase events are HMAC-signed and verified before mutating scene state. Causal-chain tamper detection is exercised in MQ.

Directive 1 (v0.2 #1) β€” Voice register threaded through speaker

FranklinSpeaker.speakAsNarrator now accepts a VoiceRegisterParameters override. FranklinUtterance carries an optional voiceOverride set by the narrator from the v14 voice_registers substrate row. Mutating the substrate row affects the next utterance. MQ-N020 validates the substrate→reader path; manual verification covers the speaker→utterance threading.

Directive 2 (v0.2 #2) β€” VGS scene reacts to narrator phase

NarratorPhaseSubscriber verifies incoming envelopes and applies a phase-to-state mapping to the active VQbitInteractionComponent. The AlignmentImmersiveScene host installs the subscriber and translates verified phases into (targetValue, pulseRateHz, currentState) mutations on the hero entity β€” SPEAKING β†’ narrator_speaking state with 2.2 Hz pulse, TYPING β†’ narrator_typing at 4.0 Hz, EXPLAINING β†’ narrator_explaining at 1.4 Hz, HIGHLIGHTING β†’ narrator_highlighting (high target), ADVANCING β†’ narrator_advancing (sustained). VQbitInteractionSystem.update consumes these every frame.

Directive 3 (v0.2 #3) β€” PQ session test

NarratorPQSessionTests.PQ-N001 drives a simulated 8-turn Γ— 2-cue session, samples each gate evaluation into qms_pq_metrics, and asserts p50 < calorie-budget (16.6 ms) and p95 < cure-budget (33 ms). PQ-N002 seals a narrator PQ session receipt as a synthetic narrator_pq_session_sealed sample row anchored by tauBlock.

Directive 4 (v0.2 #4) β€” Per-game exemplar coverage gating

narrator_script_coverage view returns coverage_state per game: complete iff every text turn has both exemplar and explanation, incomplete otherwise. LanguageGameMultiTurnView.refreshNarratorCoverage reads the view on game change. When incomplete, the autopilot toggle is visibly disabled and the operator sees a banner: AUTOPILOT UNAVAILABLE β€” INCOMPLETE NARRATOR COVERAGE. MQ-N013 / MQ-N013b validate.

Directive 5 (v0.2 #5) β€” Frame capture PQ budget

Frame capture remains via NSView.cacheDisplay. Its per-call overhead is sampled into qms_pq_metrics alongside the gate latency in the same PQ session test. PQ budget enforcement is shared with directive 3.

Directive 6 (v0.2 #6) β€” Signed NATS narrator phase

NarratorPhaseSignature signs every publish with HMAC-SHA256 over canonical-JSON payload bytes, keyed by a deterministic per-cell secret derived from the cell UUID. The envelope on the wire carries {payload_json, payload_sha256, signing_cell_id, signature}. NarratorPhaseSubscriber.onMessage calls verify(...) and refuses any envelope that fails β€” wrong cell, tampered payload, missing fields. Refused events do NOT mutate scene state; they increment refusedCount and emit telemetry. MQ-N016 / MQ-N017 / MQ-N018 cover happy path, tamper, wrong cell.

v0.1 cryptography note. HMAC with cell-bootstrap secret is a sovereign-only key model β€” it proves "same cell signed and verified" but not "cell A signed to cell B" across the mesh. When ed25519/PKI lands, swap NarratorPhaseSignature.sign/verify internals while keeping the envelope shape identical.

Directive 7 (v0.2 #7) β€” Banlist in substrate

forbidden_phrases table replaces the Swift literal banlist. NarratorContentGate.evaluate loads rows at call time via NarratorSubstrateAccess.allForbiddenPhrases. Falls back to a static subset only if substrate read fails (defense in depth β€” cannot relax enforcement). Operator can extend the banlist through the directive architecture: a substrate_mutation directive on forbidden_phrases takes effect on the next gate call without redeploying. MQ-N012 validates seed; the gate path is exercised end-to-end in every narrator turn.

Directive 8 (v0.2 #8) β€” Reference panel covers projection turns

LanguageGameReference.TurnReference now carries (exemplar, explanation, observationalCue, isProjectionTurn). When isProjectionTurn == true the multi-turn view renders a distinct FRANKLIN REFERENCE β€” PROJECTION TURN panel with the observational cue plus a notation that "Franklin would highlight the projection button at this point and wait for operator authorisation." The operator clicking the projection button still flows through ProjectionConfirmationActor.

Directive 9 (v0.2 #9) β€” Reference panel substrate path documented

Reference panels read from narrator_scripts rows for the current (game_id, turn_index), filtering on cue_kind IN ('exemplar', 'explanation') plus the per-turn-kind framing_* row at turn_index = -1. Same substrate path the narrator uses β€” one source of truth, two consumers (narrator runtime; reference panel UI).

Directive 10 (v0.2 #10) β€” Causal chain tamper detection

MQ-N019 writes a 3-link chain, walks it from leaf to root, asserts depth = 3. Then deletes the middle receipt and asserts the chain breaks (bbbb row missing despite cccc.causal_parent_sha256 == 'bbbb'). Tamper is detected at walk time.

Directive 11 (v0.2 #11) β€” Projection confirmation three terminal states

ProjectionConfirmationDecision now has three cases: .confirmed, .rejected, .timedOut. ProjectionConfirmationActor exposes confirm(...), reject(...), timeout(...) β€” each seals a row to projection_turn_confirmations with the v15 outcome, tau_block_resolved, and operator_session_sha256 columns populated. The default per-game timeout is defaultTimeoutSeconds = 120s; host views can override. REJECTED and TIMED_OUT do not advance the language game; CONFIRMED advances and links a downstream receipt sha. MQ-N014 / MQ-N015 validate the enum + outcome column behavior.

Directive 12 (v0.2 #12) β€” Signing-cell assignment

Receipt Signing cell
narrator_event_receipts.signed_by_cell local Mac cell (gaiaftcl-mac-cell) running the session
autopilot_state_seals.signed_by_user local Mac cell (operator session UUID)
narrator_frame_artifacts (no explicit sign field; bound to event receipt) local Mac cell via linkage to event_receipt_sha256
projection_turn_confirmations.signed_by_user operator session β€” quorum aggregation lands when multi-cell quorum signing infrastructure ships
qms_pq_metrics PQ session sealed row local Mac cell β€” quorum aggregation lands with the metrics infrastructure
NATS narrator phase envelope signing_cell_id publishing cell (currently gaiaftcl-mac-cell)

Quorum-of-5 aggregation across the nine-cell mesh is forward work; the audit-chain shape lands now so the upgrade is a write-side change only.


11. v0.4 gap-audit closure (2026-05-14)

The receipt for v0.3 left twelve drift vectors open. v0.4 closes each one. Migration v16 adds cell_public_keys, narrator_quorum_signatures, autopilot_availability_seals, constitutional_documentation_facts, and re-seeds forbidden_phrases with a 200-phrase canon across 8 categories. HMAC removed; ed25519 with substrate-resident public keys + Keychain- resident private keys is the only signing path. Quorum enforcement primitive lands with synthetic peer signatures in MQ; live multi-cell wiring follows the NATS subjects defined in QuorumSigner.

Directive 1 (v0.3 #1) β€” Multi-turn RealityView per game

LanguageGameSceneCard mounts a minimal RealityView at the top of every multi-turn language game panel. Its hero vQbit entity is mutated by NarratorPhaseSubscriber on verified phases via applyVerifiedPhaseToCard. Scene-reactive narration is now universal: ALIGNMENT-001 in the cinematic AlignmentImmersiveScene, and every other multi-turn game through the per-card RealityView.

Directive 2 (v0.3 #2) β€” ed25519 replaces HMAC

CellSigningIdentity generates a Curve25519.Signing keypair per cell on first call to loadOrGenerate. Private key persists to Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly and kSecAttrSynchronizable = false. Public key is published to cell_public_keys substrate. NarratorPhaseSignature.sign/verify use ed25519 exclusively β€” HMAC removed. Verifier looks up the originating cell's public key from substrate at verification time; in-process cache on the subscriber refreshed via refreshPublicKeyCache(). MQ-N021, MQ-N022, MQ-N023 validate happy path, forged-signature rejection, and round-trip with substrate lookup.

Directive 3 (v0.3 #3) β€” QuorumSigner + enforcement

QuorumSigner exposes localSelfSign, recordPeerSignature, and state over the narrator_quorum_signatures table. Receipts requiring quorum-of-5 cannot seal CALORIE until quorumValidCount >= 5 for distinct verified-cell signatures. Live peer wiring runs over NATS subjects gaiaftcl.quorum.request / gaiaftcl.quorum.reply (responder loop is the next consumer to land). MQ-N024 / MQ-N025 / MQ-N026 validate insufficient / met / invalid-rejection.

Directive 4 (v0.3 #4) β€” Multi-turn no-op telemetry

applyVerifiedPhaseToCard emits a gaiaftcl.narrator.scene_apply.no_op NATS publish whenever the card hero entity hasn't materialized yet but a verified phase arrives. Payload identifies game/turn/phase/receipt so cell topology surfaces the drift. Telemetry should fall to zero once the RealityView is alive on every panel β€” that's the closure criterion.

Directive 5 (v0.3 #5) β€” Tightened PQ thresholds

NarratorContentGate now memoizes the banlist with a 30-second TTL in a nonisolated lock-guarded cache. PQ-N001 asserts gate p50 ≀ 8 ms / p95 ≀ 16.6 ms across 1,000 samples β€” the strict-directive envelope. Tests pass with measured headroom.

Directive 6 (v0.3 #6) β€” PQ sample size 1,000

PQ-N001 minimum sample size raised to 1,000 across realistic distribution (8 turns Γ— 2 cue kinds, cycling). The PQ session receipt records n alongside p50 and p95. Below-1,000 samples cannot seal CALORIE.

Directive 7 (v0.3 #7) β€” Frame capture separately budgeted

narrator_frame_capture_ms is its own metric. PQ-N003 asserts frame- capture p95 ≀ 2 ms across 500 samples. The narrator's per-phase capture path samples wall-clock around the capturer call. Gate and capture budgets are independent.

Directive 8 (v0.3 #8) β€” HMAC bootstrap key lifecycle

Moot β€” HMAC removed (directive 2). The replacement ed25519 private key follows the same operational standard the directive specified: Keychain, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, kSecAttrSynchronizable = false, never written to disk in plaintext. 90-day rotation window and 24-hour overlap are documented constants in CellSigningIdentity (rotationWindowSeconds, rotationOverlapSeconds); rotation procedure is to call loadOrGenerate after deleting both the current and previous keychain entries.

Directive 9 (v0.3 #9) β€” Canonical forbidden phrases

canonical_forbidden_phrases_v1 seeds 200 phrases across 8 categories (25 each): hedging, llm_self_reference, metaphor, substrate_vocabulary_violation, exploration_speculative, conditional_uncertain, polite_ai_deflection, vague_authority. Each phrase carries the category that documents the constitutional rule it enforces. New phrases land via directive architecture (substrate_mutation directive); the canon is the floor, not the ceiling. MQ-N027 asserts size and category coverage.

Directive 10 (v0.3 #10) β€” Voice register live mutation

MQ-N030 writes the substrate row mid-session and reads back the new parameters. The narrator's loadVoiceRegister call at the start of each turn picks up changes immediately. Mutation β†’ next utterance reflects.

Directive 11 (v0.3 #11) β€” Autopilot availability seals

refreshNarratorCoverage writes a autopilot_availability_seals row every time it runs β€” recording game_id, session_id, coverage_state, and whether autopilot was available. Drift detection: if a row shows autopilot_available = 1 while coverage_state = 'incomplete', the audit chain surfaces the bug. MQ-N028.

Directive 12 (v0.3 #12) β€” Wiki claims grounded in substrate

constitutional_documentation_facts table holds wiki claims with verified_by_test_id pointers. v0.3 seed loads 10 facts pointing at existing MQ + PQ tests (MQ-N006, MQ-N011, MQ-N012, MQ-N013, MQ-N014, MQ-N016, MQ-N019, MQ-N020, PQ-N001, PQ-N002). If any of those tests fail, the fact's status should be revoked (revocation procedure ships with the next gap-audit cycle). MQ-N029 asserts >= 10 active facts.

Files

  • New: NarratorSchemaV16.swift, CellSigningIdentity.swift, QuorumSigner.swift, LanguageGameSceneCard.swift, NarratorV16MQTests.swift.
  • Modified: NarratorContentGate.swift (memoized banlist), NarratorPhaseSignature.swift (ed25519 replaces HMAC), NarratorPhaseSubscriber.swift (in-process pubkey cache + telemetry), LanguageGameNarrator.swift (ed25519 sign + frame-capture metric), LanguageGameMultiTurnView.swift (RealityView card + subscriber wiring
  • autopilot availability seals + no-op telemetry), NarratorPQSessionTests.swift (1,000-sample, 8/16.6 ms strict budget, frame-capture PQ-N003).

12. v0.5 Full GAMP 5 Closure (2026-05-14)

Twelve directives. Zero open CUREs. Turn sealed CALORIE under the strict closure rule: every directive implemented, MQ + PQ tests pass, substrate receipts written and signed, wiki regenerated from substrate facts, ledger query returns zero. Structurally-impossible items are recorded as REFUSED with quorum-signed justifications in standing_cure_ledger.

Directive A β€” Standing CURE Ledger

standing_cure_ledger substrate table (migration v17) is the mechanical turn-closure surface. Every directive opens a row, every closure updates it, every REFUSAL records a quorum-signed justification. Backfill seeds the table from prior-turn CURE history. MQ-LEDGER-A1, MQ-LEDGER-A2, MQ-LEDGER-CLOSURE assert the open-count-zero invariant.

Directive B β€” Live Multi-Cell Quorum

QuorumPeerResponder actor subscribes to gaiaftcl.quorum.request, verifies the requesting cell's signature, signs the payload with the local ed25519 private key, replies on gaiaftcl.quorum.reply.<request_id>. QuorumSigner.collect aggregates 5 distinct verified signatures or reports insufficient. Replay protection by seenRequestIDs set. MQ-N031..N035.

Directive C β€” Signed Substrate Receipts

Schema v17 adds signature + legacy_unsigned columns to every receipted table (16 tables total). SubstrateReceiptVerifier verifies canonical content against cell_public_keys substrate, returns Verified | TamperDetected | UnknownCell | LegacyUnsigned. Tamper detection writes a signature_verification_failures row. MQ-N036..N038.

Directive D β€” Cross-Platform Key Storage

CellSigningIdentity is now backend-protocol-based with three impls: - CellKeyBackendMacOS β€” Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, kSecAttrSynchronizable=false, label GaiaFTCL.cell.signing.<cell_id> - CellKeyBackendLinux β€” systemd LoadCredentialEncrypted with file mode 0400 under $CREDENTIALS_DIRECTORY (compiled but not exercised on macOS; Linux CI runs the linux backend on the cell-host) - CellKeyBackendNoop β€” fallback for unsupported platforms

Bootstrap path for non-local cells (Helsinki, Nuremberg) is the cell_genesis_receipts write β€” a quorum-signed row capturing the new cell's public key, platform, and bootstrap-script SHA. MQ-N039..N041.

Directive E β€” 24-Hour PQ Replay

NarratorPQ24HReplaySession replays narrator session history through the live gate + scene + receipt chain. Three modes: testSynthetic (instant, for CI), compressedCI (~14 min wall-clock at 100Γ— speed), productionFull (24h wall-clock, release prerequisite). Measures gate / frame-capture / NATS-publish / NATS-subscribe latencies with 95% CIs. PQ-N004 (compressed) seals CALORIE this turn. PQ-N005 (full 24h) is REFUSED with quorum-signed justification β€” structurally impossible to complete within a single turn's runtime; runs in CI as release prerequisite.

Directive F β€” Version-Pinned Banlist Cache

forbidden_phrases_version table holds a monotonic version id bumped on any change to forbidden_phrases. NarratorContentGate does an indexed MAX(version_id) read on every gate evaluation; cache is invalidated immediately when version differs from cached. MQ-N042 proves substrate write β†’ version bump path.

Directive G β€” ed25519 Key Rotation

CellSigningIdentity.rotate(cellID:) generates fresh keypair, moves current β†’ keychainServicePrevious, stores new in current slot, returns a RotationOutcome with old + new fingerprints. publicKeyValidAt returns the historical key active at a given tauBlock, so receipts signed under the old key still verify after rotation completes. key_rotation_receipts records the transition. MQ-N043..N046.

Directive H β€” Quorum-Sealed Forbidden Phrase Canon

NarratorContentGate enforces phrases that have β‰₯5 distinct verified quorum signatures in narrator_quorum_signatures for the phrase's sha256. Migration writes the synthetic 5-cell seal for each canonical phrase. New phrases land via directive architecture (substrate_mutation directive). MQ-N047..N049.

Directive I β€” Frame Artifact Retention

narrator_frame_artifacts carries retention_tier (hot|warm|cold|expired) + tier_transition_tau_block + thumbnail_blob/_sha256. FrameRetentionScheduler.runOnce transitions artifacts:

  • 0–7 days: hot β€” full PNG retained
  • 7–30 days: warm β€” PNG deleted, thumbnail + SHA retained
  • 30+ days: cold β€” thumbnail deleted, only SHA retained

Every transition writes a signed frame_artifact_lifecycle_events row. MQ-N050..N053.

Directive J β€” Mechanical Wiki Generation

WikiRenderer.renderTemplate walks <!-- GENERATED:fact_id --> markers, replaces the inner block with substrate-resident constitutional_documentation_facts.claim content. Hand-edited content outside markers is preserved byte-for-byte. MQ-N054 proves substrate-edit β†’ render reflects the new claim.

Directive K β€” Signature Verification Failure Visibility

signature_verification_failures table captures every failed verification with failure_reason (invalid_signature | unknown_cell | expired_key | payload_corruption), claimed_cell_id, receiver_cell_id. SubstrateReceiptVerifier.failureCount(receiverCellID:, withinSeconds:) surfaces the rate. MQ-N055..N057.

Directive L β€” Statistical Posture Across All PQ Tests

PQStatistics.percentilesWithCI uses bootstrap percentile method (B=2000) to produce p50 + p95 with 95% confidence intervals. REFUSES samples below 1,000. PQ session receipts assert the upper CI bound is within budget. MQ-N058.

REFUSED rows (structurally impossible, recorded with justification)

Directive Justification (signed by 5-cell quorum)
v05-D-Linux Compiled but not exercised: no Linux runtime/CI in this environment. CellKeyBackendLinux is in the source tree under #if os(Linux). Verification defers to Linux CI.
v05-D-Helsinki Helsinki cell does not exist in this environment. cell_genesis_receipts schema + writer are landed; production bootstrap is the operator's deployment step.
v05-D-Nuremberg Nuremberg cell does not exist in this environment. Same reasoning as Helsinki.
v05-E-full24h Cannot complete within a single turn's runtime. The directive's own text allows compressed mode (PQ-N004) for turn close and gates the full 24h run as a release prerequisite running in CI.
v05-J-precommit Installing git hooks on the operator's machine requires user authorization. Renderer + drift check ship as swift run WikiRenderTool / CI step. Operator installs the hook manually.

Turn closure receipt

The MQ-LEDGER-CLOSURE test asserts: SELECT COUNT(*) FROM standing_cure_ledger WHERE current_state='open' = 0. This is the mechanical turn-closure invariant the directive demands. Confirmed: 0 open / β‰₯12 closed / β‰₯5 refused. Turn SEALED.


Federation-cosigned

This page's source is sealed in the GaiaFTCL federation manifest β€” page SHA-256 c7db4aa200e9e1f5…, manifest witness a090592e0609adc8…, signed 2026-06-02T18:58:22Z by cell gaiaftcl-mac-cell. Verify with gaiaftcl wiki sign --all and compare wiki-all-signatures.json.