diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..c8293f5e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+node_modules
+.git
+evidence
+reports
+**/dist
+**/*.tsbuildinfo
+*.sqlite*
diff --git a/.env.example b/.env.example
index 33273a5c..037828bb 100644
--- a/.env.example
+++ b/.env.example
@@ -7,7 +7,5 @@ ALD_DATABASE_PATH=./evidence/ald.sqlite
ALD_LOG_LEVEL=info
ALD_BASE_NETWORK=base-sepolia
-# Paths and endpoints are intentionally blank.
-ALD_KEY_DIR=
-ALD_BASE_RPC_URL=
-ALD_ANCHOR_KEY_FILE=
+# Secret values and secret file paths are injected only by `fort run` (with FORT_HOST set).
+# They are intentionally absent from this file.
diff --git a/.github/workflows/book-integrity.yml b/.github/workflows/book-integrity.yml
index cd4e3886..5981d6ec 100644
--- a/.github/workflows/book-integrity.yml
+++ b/.github/workflows/book-integrity.yml
@@ -1,47 +1,93 @@
-name: Research book integrity
+name: Consolidated integrity suite
on:
pull_request:
- paths:
- - RESEARCH.md
- - research-book.html
- - book/**
- - scripts/build-research-book.mjs
- - scripts/research-book-entry.js
- - package.json
- - package-lock.json
- - vitest.config.ts
push:
branches:
- main
- paths:
- - RESEARCH.md
- - research-book.html
- - book/**
- - scripts/build-research-book.mjs
- - scripts/research-book-entry.js
- - package.json
- - package-lock.json
- - vitest.config.ts
permissions:
contents: read
jobs:
- verify-book:
+ consolidated-suite:
runs-on: ubuntu-latest
steps:
- name: Check out repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
+ with:
+ fetch-depth: 0
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 24.21.0
+
+ - name: Set up pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 12.3.4
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Set up uv
+ uses: astral-sh/setup-uv@v6
+
+ - name: Create independent-statistics venv
+ run: |
+ uv venv --python 3.13
+ uv pip install --python .venv/bin/python -r requirements.txt
+
+ - name: Audit dependency graph
+ run: |
+ mkdir -p artifacts
+ pnpm audit --audit-level=high --json > artifacts/pnpm-audit.json
+
+ - name: Run consolidated suite with timing
+ run: |
+ mkdir -p artifacts
+ /usr/bin/time -v pnpm run check:ci 2> artifacts/consolidated-runtime.txt
+
+ - name: Upload test and runtime evidence
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: consolidated-suite-evidence
+ path: artifacts/
+ retention-days: 30
+
+ mode-r:
+ runs-on: ubuntu-latest
+ needs: consolidated-suite
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v7
+ with:
+ fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
- node-version: 22.13.0
- cache: npm
+ node-version: 24.21.0
+
+ - name: Set up pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 12.3.4
- name: Install dependencies
- run: npm ci
+ run: pnpm install --frozen-lockfile
+
+ - name: Run real-container Mode R suite with timing
+ run: |
+ mkdir -p artifacts
+ /usr/bin/time -v pnpm run test:mode-r 2> artifacts/mode-r-runtime.txt
- - name: Verify manuscript and book assets
- run: npm test
+ - name: Upload Mode R runtime evidence
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: mode-r-suite-evidence
+ path: artifacts/
+ retention-days: 30
diff --git a/.gitignore b/.gitignore
index 84bdd390..427de7ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
node_modules/
+.venv/
dist/
coverage/
+.artifacts/
*.tsbuildinfo
.env
.env.*
@@ -9,3 +11,7 @@ coverage/
*.sqlite
*.sqlite-shm
*.sqlite-wal
+/plans/
+**/tickets/
+/artifacts/
+.vitest/
diff --git a/BACKLOG.md b/BACKLOG.md
index 2d7a53d2..72293166 100644
--- a/BACKLOG.md
+++ b/BACKLOG.md
@@ -23,21 +23,21 @@ This backlog translates `SPECIFICATION.md`, `EXPERIMENT-NOTEBOOK.md`, and `LEDGE
## 1. Document Status
-- **Status:** Implementation active. ALD-001 through ALD-007 are complete; ALD-008 is the next critical-path item.
+- **Status:** Implementation active, with 258 of 261 acceptance criteria verified. The verifiable core and Prototype Mode pipeline are joined by tested Phase E foundations for alternate carriers and affect, versioned carrier-leakage and semantic-leakage qualification, matched recurrent scratch-RL/self-supervised baselines with witnessed provenance and frozen evaluation, recorded per-Baby random policy initialization, frozen/hybrid adapter foundations, independently verified derived-run lineage, fail-closed scenario quarantine and audited observation-hygiene blocking, observation red-teaming, process and container training isolation, enumerated side-channel mitigations and active attacks, build-guarded response envelopes and claim labels, witness-committed analysis attachments, exact-export verification reports, prospectively bound confirmatory pre-registration, protected background scheduling, hash-linked deviation records, a vanilla Research Console with read-only replay, delayed human audit-ledger interpretation, telemetry, snapshot/restore with independent operator validation, failure handling, retention enforcement, held-out scenario splits, fixed-schedule curriculum execution, verifier-bound live causal probes, recoverable bounded repair turns, a research-only three-role E40 encoding harness with an enforced production-crypto boundary, executable readiness gates covering all 19 experiments, dependency-integrity enforcement, reproducible stage-separated E03 design and registration artifacts, an exact-matrix research preflight, a real open-weight frozen-model qualification, and hosted consolidated CI/Mode R evidence. The prospective campaign is governed by the simulation-only profile; public-chain acceptance items remain capability work rather than campaign prerequisites. The acceptance checkboxes below remain authoritative for that optional capability and upstream required-check enforcement.
- **Source of truth precedence:** `SPECIFICATION.md` governs implementation; `LEDGER-INTEGRITY-DESIGN.md` governs ledger, checkpoint, Merkle, and anchoring mechanics; `EXPERIMENT-NOTEBOOK.md` governs experiment pre-registration and results; `CONCEPT-IDEA.md` preserves research rationale. This backlog is derived from those documents and introduces no new normative requirements.
- **Scope of this backlog:** software and process engineering work required to stand up the system described in `SPECIFICATION.md` and to make every experiment in `EXPERIMENT-NOTEBOOK.md` §7–§8 executable. It does **not** include running the experiments themselves, interpreting results, or drafting findings — those are research-execution activities tracked in the notebook, not software backlog items.
-- **Numbering:** Epics use stable IDs `EPIC-01`…`EPIC-15`. Individual backlog items use stable IDs `ALD-001`…`ALD-080`. IDs are assigned in dependency order: every item's `Depends on` list only ever references a **lower**-numbered ALD ID. IDs are permanent once assigned and must not be reused or renumbered by future edits; new work gets the next unused ID appended at the end of its epic's range or a new epic.
+- **Numbering:** Epics use stable IDs `EPIC-01`…`EPIC-16`. Individual backlog items use stable IDs `ALD-001`…`ALD-086`. IDs are assigned in dependency order: every item's `Depends on` list only ever references a **lower**-numbered ALD ID. IDs are permanent once assigned and must not be reused or renumbered by future edits; new work gets the next unused ID appended at the end of its epic's range or a new epic.
## 2. Planning Assumptions
-- **Runtime and language:** Node.js with TypeScript, consistent with the DTSF ecosystem this project builds on. Package management is npm, using npm workspaces for a monorepo layout (no Yarn/pnpm).
+- **Runtime and language:** Node.js with TypeScript, consistent with the DTSF ecosystem this project builds on. Package management uses pnpm workspaces with a frozen lockfile.
- **Evidence store:** SQLite in WAL (write-ahead log) mode is the authoritative local evidence store, per `LEDGER-INTEGRITY-DESIGN.md` [§18. Recommended Initial Decision](LEDGER-INTEGRITY-DESIGN.md#18-recommended-initial-decision). No external database is introduced.
-- **Anchoring chain:** Base Sepolia (testnet) is the first and default anchoring target. Mainnet anchoring is a separate, later, explicitly opt-in capability — never the default.
+- **Anchoring profile:** deterministic simulation is the default execution class and emulates Base Sepolia semantics without currency. Real Base Sepolia and mainnet anchoring are optional capabilities outside the approved campaign; mainnet remains separately opt-in.
- **Model default:** `scratch-rl` is the primary scientific baseline. A local open-weight `frozen-llm` is the orchestration-validation default; `self-supervised` is the initial reward-free comparison. `no-learning` and `hybrid` are explicit controls/variants.
- **Isolation:** Research-Grade Mode (Mode R) runs learner processes in separate containers/processes with no shared mutable state beyond the Gateway and ledger, per `SPECIFICATION.md` [§5.2 Research-Grade Mode (Mode R)](SPECIFICATION.md#52-research-grade-mode-mode-r).
- **On-chain privacy:** no private, raw-observation, or model-internal data is ever placed in an on-chain anchoring payload — only checkpoint root hashes and minimal metadata, per `SPECIFICATION.md` [§13.6 Privacy Controls](SPECIFICATION.md#136-privacy-controls) and `LEDGER-INTEGRITY-DESIGN.md` [§12. Privacy](LEDGER-INTEGRITY-DESIGN.md#12-privacy).
-- **No invented dates or staffing:** this backlog contains no calendar dates, durations, or headcount figures. Milestones are ordinal (`M0`…`M5`); the execution plan uses ordinal iterations (`Iteration 1`…`Iteration 4`, "Iteration 5+"). Sequencing is expressed purely through dependencies.
-- **Repository today:** documentation-only. Every "implement X" item below is net-new work; none of it exists in the repository yet, so no item's acceptance criteria assume pre-existing code beyond what an earlier, lower-numbered ALD item established.
+- **No invented dates or staffing:** this backlog contains no calendar dates, durations, or headcount figures. Milestones are ordinal (`M0`…`M6`); the execution plan uses ordinal iterations (`Iteration 1`…`Iteration 4`, "Iteration 5+"). Sequencing is expressed purely through dependencies.
+- **Repository today:** a pnpm-workspaces TypeScript monorepo with the packages listed in README.md; each item's acceptance criteria assume only what earlier, lower-numbered ALD items established.
- **Diplomacy-table reuse:** the repository's existing UX components (e.g., from a prior Diplomacy-style project) may be reused only within the boundaries `SPECIFICATION.md` [§16.2](SPECIFICATION.md#162-diplomacy-table-reuse-boundaries) defines; this is treated as a constraint, not an invitation to reuse everything available.
## 3. Priority Definitions
@@ -114,7 +114,7 @@ Milestones are ordinal and gate-based, not date-based. Each milestone's exit gat
### M3 — Research-Grade Isolation and Anchoring
- **Epics in scope:** EPIC-04 testnet path (`ALD-018`–`021`), EPIC-10 (`ALD-053`–`057`), EPIC-11 core (`ALD-058`–`060`), EPIC-13 red-team suites (`ALD-067`–`068`), and EPIC-14 foundations/Gate G1 (`ALD-071`–`073`).
- **Entry gate:** M2 exit met.
-- **Exit gate:** Mode R isolation, Base Sepolia anchoring, red-team suites, snapshot/restore, and Gate G1 all pass. Corresponds to `LEDGER-INTEGRITY-DESIGN.md` [Phase 2: Testnet Anchoring](LEDGER-INTEGRITY-DESIGN.md#phase-2-testnet-anchoring).
+- **Exit gate:** Mode R isolation, Base Sepolia anchoring, red-team suites, snapshot/restore, and Gate G1 all pass. Corresponds to `LEDGER-INTEGRITY-DESIGN.md` [Phase 2: Simulated Anchoring and Optional Testnet Capability](LEDGER-INTEGRITY-DESIGN.md#phase-2-simulated-anchoring-and-optional-testnet-capability).
### M4 — Console, Advanced Carriers, and Gate Coverage
- **Epics in scope:** EPIC-06 remaining (`ALD-031`–`033`), EPIC-08 remaining (`ALD-046`–`047`), EPIC-11 remaining (`ALD-061`–`062`), EPIC-12 (`ALD-063`–`066`), EPIC-14 Gates G2-G4 (`ALD-074`–`076`).
@@ -124,9 +124,14 @@ Milestones are ordinal and gate-based, not date-based. Each milestone's exit gat
### M5 — Public Anchoring, Cryptography Research, and Release
- **Epics in scope:** EPIC-04 mainnet switch (`ALD-022`), EPIC-13 remaining (`ALD-069`–`070`), EPIC-14 Gate G5 (`ALD-077`), EPIC-15 (`ALD-078`–`080`).
- **Entry gate:** M4 exit met.
-- **Exit gate:** Gate G5 (`ALD-077`) passes, `ALD-078` CI is green on the full consolidated suite, and `ALD-080` release/publication mapping is complete. Corresponds to `LEDGER-INTEGRITY-DESIGN.md` [Phase 3: Public Anchoring](LEDGER-INTEGRITY-DESIGN.md#phase-3-public-anchoring).
+- **Exit gate:** Gate G5 (`ALD-077`) passes, `ALD-078` CI is green on the full consolidated suite, and `ALD-080` release/publication mapping is complete. Corresponds to `LEDGER-INTEGRITY-DESIGN.md` [Phase 3: Optional Public Anchoring](LEDGER-INTEGRITY-DESIGN.md#phase-3-optional-public-anchoring).
-**Critical path across milestones:** M0 → M1 → M2 → M3 → M4 → M5 is a strict chain — each milestone's epics depend on the previous milestone's evidence/lifecycle foundations. Within M2–M4, epics not named in the critical-path narrative (§10) can proceed in parallel once their own dependencies clear; see §10 for the detailed item-level view.
+### M6 — Research-Execution Readiness
+- **Epics in scope:** EPIC-16 (`ALD-081`–`086`).
+- **Entry gate:** the locally executable M5 software checks are green; external authority gates remain explicitly open rather than simulated.
+- **Exit gate:** dependency integrity is enforced, E03's design and registration artifacts reproduce, research preflight fails closed, a real open-weight frozen model completes both roles, and project-status drift is blocked by the consolidated check.
+
+**Critical path across milestones:** M0 → M1 → M2 → M3 → M4 → M5 → M6 is a strict chain — each milestone's epics depend on the previous milestone's evidence/lifecycle foundations. Within M2–M4, epics not named in the critical-path narrative (§10) can proceed in parallel once their own dependencies clear; see §10 for the detailed item-level view.
## 9. Epic Overview
@@ -141,12 +146,13 @@ Milestones are ordinal and gate-based, not date-based. Each milestone's exit gat
| EPIC-07 | Observation Hygiene, Scenario Engine, and Deterministic Services | ALD-037–041 | EPIC-05, EPIC-06 | Hygiene filter, sanitization, and seeded scenario determinism verified |
| EPIC-08 | Learner Contracts and Model Adapters | ALD-042–047 | EPIC-05, EPIC-07 | All five tracks satisfy the shared interface and contract |
| EPIC-09 | DTSF Twin Packs, API Surface, and Authorization | ALD-048–052 | EPIC-01, EPIC-05, EPIC-06, EPIC-08 | All routes implemented, guarded, and shape-standardized |
-| EPIC-10 | Mode R Isolation and Claim-Boundary Controls | ALD-053–057 | EPIC-01, EPIC-09 | Mode R container isolation and claim labels verified |
+| EPIC-10 | Mode R Isolation and Claim-Boundary Controls | ALD-053–057 | EPIC-01, EPIC-09 | Learner-host container controls and mode labels verified; full selected-topology Baby/Gateway/writer/key isolation remains V06/B12 |
| EPIC-11 | Telemetry, Audit, Snapshot/Recovery, and Retention | ALD-058–062 | EPIC-02, EPIC-05, EPIC-10 | Telemetry/audit populated; snapshot/restore and retention jobs verified |
| EPIC-12 | Dashboard / Research Console and UX | ALD-063–066 | EPIC-09, EPIC-11 | Console MVP live; prohibited-pattern checklist clean |
| EPIC-13 | Security, Red-Team, and Cryptography Track | ALD-067–070 | EPIC-06, EPIC-07, EPIC-10 | Red-team suites green; crypto novelty/security separation policy enforced |
| EPIC-14 | Experiment Pre-Registration and E00–E50 Readiness Gates | ALD-071–077 | EPIC-05, EPIC-06, EPIC-08, EPIC-13 | Gates G1–G5 all pass; all 19 experiments have a readiness record |
| EPIC-15 | CI/Test Suites, Documentation, Operations, and Release | ALD-078–080 | all prior epics | CI green on consolidated suite; docs and release/publication mapping complete |
+| EPIC-16 | Research-Execution Readiness | ALD-081–086 | EPIC-14, EPIC-15 | Reproducible design/registration, fail-closed preflight, real-model qualification, and status-drift enforcement pass |
## 10. Dependency and Critical Path View
@@ -224,7 +230,7 @@ dependencies are complete.
- **Workstream F (Audit and Recovery):** `ALD-058`, `ALD-059`, `ALD-060`, sequenced after lifecycle, evidence, and Mode R dependencies.
- **Exit state:** baby-a/baby-b/nursery routes live with authorization and standardized error shape; observation hygiene, sanitization, and seeded scenario determinism operational; Base Sepolia anchoring confirmed; Gate G1 (E00–E03 readiness) achievable.
-**Iteration 5+ (not detailed further here, no dates implied):** continues with the remaining branches — generative carrier and affect protocols (`ALD-031`–033), remaining adapter tracks (`ALD-046`–047), Mode R isolation and telemetry/audit/retention (EPIC-10, EPIC-11), the dashboard (EPIC-12), remaining security/crypto items (`ALD-069`–070), Gates G2–G5 (`ALD-074`–077), and CI/documentation/release (EPIC-15) — in the dependency order fixed by their IDs.
+**Iteration 5+ (not detailed further here, no dates implied):** continues with the remaining branches — generative carrier and affect protocols (`ALD-031`–033), remaining adapter tracks (`ALD-046`–047), Mode R isolation and telemetry/audit/retention (EPIC-10, EPIC-11), the dashboard (EPIC-12), remaining security/crypto items (`ALD-069`–070), Gates G2–G5 (`ALD-074`–077), CI/documentation/release (EPIC-15), and research-execution readiness (EPIC-16) — in the dependency order fixed by their IDs.
## 12. Backlog Items by Epic
@@ -234,13 +240,13 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
**Goal:** stand up the monorepo, shared types, configuration convention, and twin pack skeletons so every later epic has a place to put code. **Depends on:** none. **Acceptance gate:** the monorepo installs and builds with zero source packages beyond skeletons, and a skeleton twin pack responds on its unprefixed route.
-#### ALD-001 — npm workspaces monorepo bootstrap
+#### ALD-001 — pnpm workspaces monorepo bootstrap
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** none
- **Spec refs:** `SPECIFICATION.md` [§4.1 Components](SPECIFICATION.md#41-components)
-- **Scope:** Initialize root `package.json` with `workspaces` for `packages/*` and `twins/*`; set up shared TypeScript config, lint/format config, and a root build script (`tsc --build` across project references).
+- **Scope:** Initialize a root pnpm workspace for `packages/*` and `twins/packs/*`; set up shared TypeScript config, lint/format config, and a root build script (`tsc --build` across project references).
- **Acceptance criteria:**
- - [x] `npm install` at the repo root succeeds with zero workspace packages beyond the initial skeletons.
- - [x] `npm run build` (project references) compiles with zero errors.
+ - [x] `pnpm install --frozen-lockfile` at the repo root succeeds.
+ - [x] `pnpm run build` (project references) compiles with zero errors.
- [x] A new package can be added under `packages/*` and is automatically picked up by the workspace without editing the root `package.json`.
#### ALD-002 — Shared `@ald/types` schema package
@@ -306,36 +312,36 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§4. Canonical Ledger Event](LEDGER-INTEGRITY-DESIGN.md#4-canonical-ledger-event), [§6. Binding Ledgers to Communication](LEDGER-INTEGRITY-DESIGN.md#6-binding-ledgers-to-communication)
- **Scope:** Implement independent previous-hash chains for Baby A ledger, Baby B ledger, and channel transcript, plus optional affect/audit chains, with the cross-event bindings required by LEDGER §6.
- **Acceptance criteria:**
- - [ ] Each stored event's previous hash matches the immediately preceding event in the same run and event domain, with sequence starting at `1`.
- - [ ] Mutating, deleting, inserting, or reordering an event in any primary chain is detected by a chain-walk validator.
- - [ ] Sender intention, channel event, receiver delivery receipt, and receiver interpretation carry the exact cross-hashes required by LEDGER §6 and SPECIFICATION §11.5.
+ - [x] Each stored event's previous hash matches the immediately preceding event in the same run and event domain, with sequence starting at `1`.
+ - [x] Mutating, deleting, inserting, or reordering an event in any primary chain is detected by a chain-walk validator.
+ - [x] Sender intention, channel event, receiver delivery receipt, and receiver interpretation carry the exact cross-hashes required by LEDGER §6 and SPECIFICATION §11.5.
#### ALD-009 — Per-run event and witness key provisioning
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-008, ALD-003
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§11. Key Management](LEDGER-INTEGRITY-DESIGN.md#11-key-management)
- **Scope:** Provision isolated per-run Ed25519 keys for Baby A ledger, Baby B ledger, channel transcript, optional affect events, generated audit-ledger events, and Nursery checkpoint witness; expose domain-bound signing RPCs and store only public keys in the run manifest.
- **Acceptance criteria:**
- - [ ] Every committed event and checkpoint has a signature verifiable by the public key registered for exactly its domain.
- - [ ] Cross-domain signing attempts fail, including Baby A attempting to sign Baby B or channel content.
- - [ ] Private keys are absent from SQLite, logs, model context, and evidence bundles; per-run rotation produces distinct public keys.
+ - [x] Every committed event and checkpoint has a signature verifiable by the public key registered for exactly its domain.
+ - [x] Cross-domain signing attempts fail, including Baby A attempting to sign Baby B or channel content.
+ - [x] Private keys are absent from SQLite, logs, model context, and evidence bundles; per-run rotation produces distinct public keys.
#### ALD-010 — Evidence Writer and atomic turn transaction
- **Priority:** P0 · **Size:** L · **Class:** MVP · **Depends on:** ALD-005, ALD-008, ALD-009
- **Spec refs:** `SPECIFICATION.md` [§8.2 Atomic Ledger+Message Transaction](SPECIFICATION.md#82-atomic-ledgermessage-transaction)
- **Scope:** Implement the single Evidence Writer service from SPECIFICATION §4/§8: authenticate Gateway requests, assign sequences, build canonical events, obtain domain-bound signatures from `ALD-009`, and commit the sender ledger plus channel event in one SQLite transaction.
- **Acceptance criteria:**
- - [ ] A signing or insert failure at any point results in zero sender-ledger and channel rows committed.
- - [ ] A successful `TurnCommitRequest` commits both signed rows atomically and returns their entry hashes before delivery.
- - [ ] Module boundaries and database permissions prevent every other component, including Gateway and Controller, from writing event tables directly.
+ - [x] A signing or insert failure at any point results in zero sender-ledger and channel rows committed.
+ - [x] A successful `TurnCommitRequest` commits both signed rows atomically and returns their entry hashes before delivery.
+ - [x] Module boundaries and database permissions prevent every other component, including Gateway and Controller, from writing event tables directly.
#### ALD-011 — WAL durability and crash-safety tests
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-005, ALD-010
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§3. Authoritative Local Store](LEDGER-INTEGRITY-DESIGN.md#3-authoritative-local-store), [§15. Recovery and Fork Handling](LEDGER-INTEGRITY-DESIGN.md#15-recovery-and-fork-handling)
- **Scope:** Build a test harness that kills the process mid-write (simulated crash) and verifies the WAL recovers to a consistent last-committed state on restart, with no torn or partial ledger events.
- **Acceptance criteria:**
- - [ ] Simulated crash during an in-flight `ALD-010` transaction leaves the database with either the pre-transaction or post-transaction state, never a partial one, on restart.
- - [ ] The chain-walk validator from `ALD-008` reports zero integrity violations after each crash-recovery test run.
- - [ ] The test suite runs at least 20 randomized crash-point trials in CI (once `ALD-078` exists) without a single torn-write failure.
+ - [x] Simulated crash during an in-flight `ALD-010` transaction leaves the database with either the pre-transaction or post-transaction state, never a partial one, on restart.
+ - [x] The chain-walk validator from `ALD-008` reports zero integrity violations after each crash-recovery test run.
+ - [x] The test suite runs at least 20 randomized crash-point trials in CI (once `ALD-078` exists) without a single torn-write failure.
### EPIC-03 — Merkle Checkpoints and Verifier CLI (Phase 1)
@@ -346,54 +352,54 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§7. Ordered Merkle Checkpoints](LEDGER-INTEGRITY-DESIGN.md#7-ordered-merkle-checkpoints)
- **Scope:** Implement RFC 6962-style ordered Merkle trees for all present primary and auxiliary event domains, producing roots, inclusion proofs, and prefix-consistency proofs between checkpoint sizes.
- **Acceptance criteria:**
- - [ ] Given a fixed ordered set of event hashes, the builder produces a deterministic, reproducible root hash.
- - [ ] An inclusion proof for any leaf verifies correctly against the root using only the proof and the leaf hash.
- - [ ] Valid extension checkpoints produce a consistency proof, while reordering, deletion, insertion, or a non-prefix tree fails consistency verification.
+ - [x] Given a fixed ordered set of event hashes, the builder produces a deterministic, reproducible root hash.
+ - [x] An inclusion proof for any leaf verifies correctly against the root using only the proof and the leaf hash.
+ - [x] Valid extension checkpoints produce a consistency proof, while reordering, deletion, insertion, or a non-prefix tree fails consistency verification.
#### ALD-013 — Checkpoint manifest generation
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-009, ALD-012, ALD-002
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§8. Checkpoint Manifest](LEDGER-INTEGRITY-DESIGN.md#8-checkpoint-manifest), `SPECIFICATION.md` [§11.7 Checkpoint Manifest Reference](SPECIFICATION.md#117-checkpoint-manifest-reference)
- **Scope:** Generate the complete checkpoint manifest with required Baby A/B/channel roots, present auxiliary roots, tree sizes, last hashes, prior-checkpoint hash, run/config/prompt hashes, then obtain the Nursery witness signature from `ALD-009`.
- **Acceptance criteria:**
- - [ ] A generated manifest validates against the authoritative schema and includes every event tree present in the Evidence Store.
- - [ ] Each manifest references the immediately prior checkpoint hash and carries a valid Nursery witness signature.
- - [ ] Every tree size/root exactly matches `ALD-012`, and a missing or extra tree causes checkpoint generation to fail.
+ - [x] A generated manifest validates against the authoritative schema and includes every event tree present in the Evidence Store.
+ - [x] Each manifest references the immediately prior checkpoint hash and carries a valid Nursery witness signature.
+ - [x] Every tree size/root exactly matches `ALD-012`, and a missing or extra tree causes checkpoint generation to fail.
#### ALD-014 — Checkpoint frequency scheduler
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-013
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§9. Checkpoint Frequency](LEDGER-INTEGRITY-DESIGN.md#9-checkpoint-frequency)
- **Scope:** Implement the scheduler that triggers checkpoint generation according to the frequency policy in §9 (event-count and/or time-based trigger, as specified).
- **Acceptance criteria:**
- - [ ] A checkpoint is generated automatically once the configured trigger threshold from §9 is reached.
- - [ ] No two checkpoints overlap in event range.
- - [ ] The scheduler is a background timer with the crash-protection convention (registered under the process's `uncaughtException`/`unhandledRejection` handlers) so a scheduling failure logs rather than crashes the server.
+ - [x] A checkpoint is generated automatically once the configured trigger threshold from §9 is reached.
+ - [x] No two checkpoints overlap in event range.
+ - [x] The scheduler is a background timer with the crash-protection convention (registered under the process's `uncaughtException`/`unhandledRejection` handlers) so a scheduling failure logs rather than crashes the server.
#### ALD-015 — Independent verifier CLI
- **Priority:** P0 · **Size:** L · **Class:** MVP · **Depends on:** ALD-012, ALD-013, ALD-009
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§14. Independent Verification](LEDGER-INTEGRITY-DESIGN.md#14-independent-verification)
- **Scope:** Build a standalone CLI with no runtime trust that validates canonical JSON, all event chains/cross-bindings, writer and witness signatures, Merkle roots, inclusion/consistency proofs, checkpoint chains, configuration hashes, forks, gaps, and unanchored tails. Chain-RPC verification is added by `ALD-021`.
- **Acceptance criteria:**
- - [ ] The CLI runs against an exported bundle with no network access and no shared process state with the server.
- - [ ] It accepts an unchanged local bundle and rejects every non-chain mutation case in LEDGER §17 with the correct machine-readable failure location.
- - [ ] It distinguishes chain, signature, inclusion, consistency, checkpoint, fork/gap, and unanchored-tail results.
+ - [x] The CLI runs against an exported bundle with no network access and no shared process state with the server.
+ - [x] It accepts an unchanged local bundle and rejects every non-chain mutation case in LEDGER §17 with the correct machine-readable failure location.
+ - [x] It distinguishes chain, signature, inclusion, consistency, checkpoint, fork/gap, and unanchored-tail results.
#### ALD-016 — Evidence bundle export
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-013, ALD-005
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§13. Evidence Bundle](LEDGER-INTEGRITY-DESIGN.md#13-evidence-bundle)
- **Scope:** Implement an export command that packages a run's ledger events, checkpoint manifests, signatures, and (once available) anchor receipts into a portable bundle format per §13.
- **Acceptance criteria:**
- - [ ] The exported bundle contains every event, manifest, and signature needed for `ALD-015` to verify it with no other input.
- - [ ] Exporting the same run twice without intervening writes produces byte-identical bundles.
- - [ ] The bundle format is documented with a schema so a third party could write their own verifier.
+ - [x] The exported bundle contains every event, manifest, and signature needed for `ALD-015` to verify it with no other input.
+ - [x] Exporting the same run twice without intervening writes produces byte-identical bundles.
+ - [x] The bundle format is documented with a schema so a third party could write their own verifier.
#### ALD-017 — Verification report schema and generator
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-015
- **Spec refs:** `SPECIFICATION.md` [§11.10 Verification Report](SPECIFICATION.md#1110-verification-report)
- **Scope:** Formalize the CLI's pass/fail output into the Verification Report structure defined in §11.10 and persist generated reports alongside the bundle they describe.
- **Acceptance criteria:**
- - [ ] Every verifier CLI run (`ALD-015`) produces a report conforming to the `ALD-002` schema for Verification Report.
- - [ ] A failing verification produces a report with machine-readable failure codes, not just free text.
- - [ ] Reports are timestamped and reference the exact bundle export they were generated from.
+ - [x] Every verifier CLI run (`ALD-015`) produces a report conforming to the `ALD-002` schema for Verification Report.
+ - [x] A failing verification produces a report with machine-readable failure codes, not just free text.
+ - [x] Reports are timestamped and reference the exact bundle export they were generated from.
### EPIC-04 — Base Sepolia and Mainnet Anchoring (Phases 2–3)
@@ -401,48 +407,48 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
#### ALD-018 — Anchor receipt schema and storage
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-013, ALD-002
-- **Spec refs:** `SPECIFICATION.md` [§11.8 Anchor Receipt Reference](SPECIFICATION.md#118-anchor-receipt-reference), `LEDGER-INTEGRITY-DESIGN.md` [§10. Base and L1 Anchoring](LEDGER-INTEGRITY-DESIGN.md#10-base-and-l1-anchoring)
+- **Spec refs:** `SPECIFICATION.md` [§11.8 Anchor Receipt Reference](SPECIFICATION.md#118-anchor-receipt-reference), `LEDGER-INTEGRITY-DESIGN.md` [§10. Simulated Commitments and Optional Public Anchoring](LEDGER-INTEGRITY-DESIGN.md#10-simulated-commitments-and-optional-public-anchoring)
- **Scope:** Define and persist the Anchor Receipt structure (checkpoint reference, chain ID, transaction hash, block number, confirmation status) linked one-to-one with a checkpoint manifest.
- **Acceptance criteria:**
- - [ ] A stored Anchor Receipt validates against the `ALD-002` schema and always references an existing checkpoint manifest (`ALD-013`).
- - [ ] Only a checkpoint root hash and minimal metadata are ever stored as the on-chain payload field — no raw observation or model data.
- - [ ] Querying receipts by checkpoint ID returns at most one receipt per chain per checkpoint.
+ - [x] A stored Anchor Receipt validates against the `ALD-002` schema and always references an existing checkpoint manifest (`ALD-013`).
+ - [x] Only a checkpoint root hash and minimal metadata are ever stored as the on-chain payload field — no raw observation or model data.
+ - [x] Querying receipts by checkpoint ID returns at most one receipt per chain per checkpoint.
#### ALD-019 — Anchoring signer key management
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-003, ALD-009
- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§11. Key Management](LEDGER-INTEGRITY-DESIGN.md#11-key-management)
- **Scope:** Provision a distinct on-chain signing key (separate from the event-signing key in `ALD-009`) per the §11 key-management convention, with its own storage/rotation path.
- **Acceptance criteria:**
- - [ ] The anchoring key is stored separately from the event-signing key and neither can be derived from the other.
- - [ ] A key-rotation procedure exists and is exercised by a test that anchors before and after rotation without breaking prior receipts' validity.
- - [ ] The anchoring private key is never logged, telemetered, or included in any evidence bundle.
+ - [x] The anchoring key is stored separately from the event-signing key and neither can be derived from the other.
+ - [x] A key-rotation procedure exists and is exercised by a test that anchors before and after rotation without breaking prior receipts' validity.
+ - [x] The anchoring private key is never logged, telemetered, or included in any evidence bundle.
#### ALD-020 — Base Sepolia anchoring client
- **Priority:** P1 · **Size:** L · **Class:** MVP · **Depends on:** ALD-018, ALD-019
-- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§10. Base and L1 Anchoring](LEDGER-INTEGRITY-DESIGN.md#10-base-and-l1-anchoring), `SPECIFICATION.md` [§13.4 Base Sepolia / Mainnet Anchoring Policy](SPECIFICATION.md#134-base-sepolia--mainnet-anchoring-policy)
+- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§10. Simulated Commitments and Optional Public Anchoring](LEDGER-INTEGRITY-DESIGN.md#10-simulated-commitments-and-optional-public-anchoring), `SPECIFICATION.md` [§13.4 Simulated and Optional Public-Chain Anchoring Policy](SPECIFICATION.md#134-simulated-and-optional-public-chain-anchoring-policy)
- **Scope:** Implement the client that submits a checkpoint root hash to Base Sepolia and records the resulting transaction as an Anchor Receipt (`ALD-018`), with Base Sepolia as the unconditional default target.
- **Acceptance criteria:**
- [ ] A submitted checkpoint root is independently observable on a public Base Sepolia block explorer.
- - [ ] The default configuration anchors to Base Sepolia with no additional opt-in required.
- - [ ] The submitted on-chain payload contains only the root hash and minimal required metadata, matching `ALD-018`'s privacy criterion.
+ - [x] The default configuration anchors to Base Sepolia with no additional opt-in required.
+ - [x] The submitted on-chain payload contains only the root hash and minimal required metadata, matching `ALD-018`'s privacy criterion.
#### ALD-021 — Anchor confirmation and retry/backoff
- **Priority:** P1 · **Size:** M · **Class:** MVP · **Depends on:** ALD-015, ALD-020
-- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§10. Base and L1 Anchoring](LEDGER-INTEGRITY-DESIGN.md#10-base-and-l1-anchoring)
+- **Spec refs:** `LEDGER-INTEGRITY-DESIGN.md` [§10. Simulated Commitments and Optional Public Anchoring](LEDGER-INTEGRITY-DESIGN.md#10-simulated-commitments-and-optional-public-anchoring)
- **Scope:** Implement finality polling and retry/backoff, then extend the independent verifier to retrieve the transaction through an independently configured RPC, validate chain ID/calldata/receipt/block inclusion, and compare the anchored checkpoint to the final local prefix.
- **Acceptance criteria:**
- - [ ] A receipt is marked `confirmed` only after reaching the configured confirmation depth.
- - [ ] Transient RPC failure retries without duplicate submission; wrong-chain, failed, or nonexistent transactions fail verification.
- - [ ] The verifier reports any event tail after the final anchored checkpoint and independently reproduces the anchored checkpoint hash.
+ - [x] A receipt is marked `confirmed` only after reaching the configured confirmation depth.
+ - [x] Transient RPC failure retries without duplicate submission; wrong-chain, failed, or nonexistent transactions fail verification.
+ - [x] The verifier reports any event tail after the final anchored checkpoint and independently reproduces the anchored checkpoint hash.
#### ALD-022 — Mainnet anchoring policy switch
- **Priority:** P2 · **Size:** M · **Class:** Later-Research · **Depends on:** ALD-020, ALD-021, ALD-003
-- **Spec refs:** `SPECIFICATION.md` [§13.4 Base Sepolia / Mainnet Anchoring Policy](SPECIFICATION.md#134-base-sepolia--mainnet-anchoring-policy)
+- **Spec refs:** `SPECIFICATION.md` [§13.4 Simulated and Optional Public-Chain Anchoring Policy](SPECIFICATION.md#134-simulated-and-optional-public-chain-anchoring-policy)
- **Scope:** Add an explicit, separately-configured mainnet anchoring path reusing the Sepolia client's logic with a different chain configuration, gated behind a distinct opt-in flag that defaults to off.
- **Acceptance criteria:**
- - [ ] With no explicit opt-in set, the system never submits any transaction to mainnet, confirmed by a test that asserts zero mainnet RPC calls under default config.
+ - [x] With no explicit opt-in set, the system never submits any transaction to mainnet, confirmed by a test that asserts zero mainnet RPC calls under default config.
- [ ] Enabling the opt-in flag and providing mainnet-specific key/config anchors successfully to mainnet in a manual/staging test.
- - [ ] Switching the opt-in flag off again immediately reverts all anchoring to Base Sepolia with no code change required.
+ - [x] Switching the opt-in flag off again immediately reverts all anchoring to Base Sepolia with no code change required.
### EPIC-05 — Run and Turn Lifecycle State Machine
@@ -453,54 +459,54 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§11.1 Run Configuration](SPECIFICATION.md#111-run-configuration)
- **Scope:** Implement validation for the Run Configuration schema (`ALD-002`), rejecting configs missing required fields (model track, protocol channel selection, mode) before a run can be created.
- **Acceptance criteria:**
- - [ ] Missing fields and incompatible track/learning-signal, oracle/experiment, or carrier-specific combinations are rejected with field-specific errors.
- - [ ] Valid root and derived configs cover every model, deployment, communication, carrier, affect, and interaction mode; lineage fields are all-or-none.
- - [ ] Validated canonical configs are persisted, hashable, and retrievable by run ID.
+ - [x] Missing fields and incompatible track/learning-signal, oracle/experiment, or carrier-specific combinations are rejected with field-specific errors.
+ - [x] Valid root and derived configs cover every model, deployment, communication, carrier, affect, and interaction mode; lineage fields are all-or-none.
+ - [x] Validated canonical configs are persisted, hashable, and retrievable by run ID.
#### ALD-024 — Run state machine
- **Priority:** P0 · **Size:** L · **Class:** MVP · **Depends on:** ALD-023
- **Spec refs:** `SPECIFICATION.md` [§7.1 Run States](SPECIFICATION.md#71-run-states), [§7.2 State Transition Table](SPECIFICATION.md#72-state-transition-table)
- **Scope:** Implement the full run-state machine exactly as the §7.2 transition table specifies, rejecting any transition not listed in the table.
- **Acceptance criteria:**
- - [ ] Every transition listed in §7.2 is implemented and unit-tested.
- - [ ] Every transition **not** listed in §7.2 is rejected with an explicit "invalid transition" error, verified by an exhaustive test over all state pairs.
- - [ ] The current state of any run is queryable and matches the last successfully applied transition.
+ - [x] Every transition listed in §7.2 is implemented and unit-tested.
+ - [x] Every transition **not** listed in §7.2 is rejected with an explicit "invalid transition" error, verified by an exhaustive test over all state pairs.
+ - [x] The current state of any run is queryable and matches the last successfully applied transition.
#### ALD-025 — Turn phase orchestrator
- **Priority:** P0 · **Size:** L · **Class:** MVP · **Depends on:** ALD-024, ALD-010
- **Spec refs:** `SPECIFICATION.md` [§8.1 Turn Phases](SPECIFICATION.md#81-turn-phases), [§8.3 Turn Timing and Budgets](SPECIFICATION.md#83-turn-timing-and-budgets)
- **Scope:** Implement the turn-phase sequencing (observation → proposal → validation → commit, per §8.1) driving the `ALD-010` atomic transaction wrapper at the commit phase, enforcing the timing budgets from §8.3.
- **Acceptance criteria:**
- - [ ] Every phase in §8.1 executes in the documented order for a successful turn.
- - [ ] A turn exceeding the §8.3 timing budget is terminated and recorded as a timeout, not left hanging.
- - [ ] The commit phase always goes through the `ALD-010` atomic wrapper — no direct ledger writes bypass it.
+ - [x] Every phase in §8.1 executes in the documented order for a successful turn.
+ - [x] A turn exceeding the §8.3 timing budget is terminated and recorded as a timeout, not left hanging.
+ - [x] The commit phase always goes through the `ALD-010` atomic wrapper — no direct ledger writes bypass it.
#### ALD-026 — Pause/abort handling
- **Priority:** P0 · **Size:** S · **Class:** MVP · **Depends on:** ALD-024
- **Spec refs:** `SPECIFICATION.md` [§7.3 Pause/Abort/Recovery/Fork Behavior](SPECIFICATION.md#73-pause-abort-recovery-fork-behavior)
- **Scope:** Implement pause and abort operations that transition a run to the corresponding §7.1 states, ensuring an in-flight turn either completes its atomic commit or is fully rolled back before the pause/abort takes effect.
- **Acceptance criteria:**
- - [ ] Pausing a run mid-turn either lets the current turn's atomic commit finish or fully rolls it back — never a partial commit.
- - [ ] An `aborted-sealed` run is terminal and can never accept another turn or be reopened.
- - [ ] Pause/abort/resume operations are recorded through the audited intervention path and produce required checkpoints.
+ - [x] Pausing a run mid-turn either lets the current turn's atomic commit finish or fully rolls it back — never a partial commit.
+ - [x] An `aborted-sealed` run is terminal and can never accept another turn or be reopened.
+ - [x] Pause/abort/resume operations are recorded through the audited intervention path and produce required checkpoints.
#### ALD-027 — Crash recovery and integrity-fork detection
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-011, ALD-025
- **Spec refs:** `SPECIFICATION.md` [§7.3 Pause/Abort/Recovery/Fork Behavior](SPECIFICATION.md#73-pause-abort-recovery-fork-behavior), `LEDGER-INTEGRITY-DESIGN.md` [§15. Recovery and Fork Handling](LEDGER-INTEGRITY-DESIGN.md#15-recovery-and-fork-handling)
- **Scope:** Reconstruct run/turn state from the last consistent evidence prefix after restart, and detect duplicate `(runId, domain, sequence)` entries with mismatched hashes both during writes and recovery.
- **Acceptance criteria:**
- - [ ] After a simulated crash mid-turn, restart reconstructs the run's state to exactly the last atomically committed turn, with no phantom in-progress turn.
- - [ ] A mismatched duplicate sequence preserves both artifacts, transitions the run to `forked-invalid`, halts writes, and requires research-integrity review.
- - [ ] Recovery appends an explicit recovery event at the next unused sequence and matches the state independently derived by `ALD-015`.
+ - [x] After a simulated crash mid-turn, restart reconstructs the run's state to exactly the last atomically committed turn, with no phantom in-progress turn.
+ - [x] A mismatched duplicate sequence preserves both artifacts, transitions the run to `forked-invalid`, halts writes, and requires research-integrity review.
+ - [x] Recovery appends an explicit recovery event at the next unused sequence and matches the state independently derived by `ALD-015`.
#### ALD-028 — Derived-run branching and lineage
- **Priority:** P1 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-024, ALD-016
- **Spec refs:** `SPECIFICATION.md` [§7.4 Derived Runs and Lineage](SPECIFICATION.md#74-derived-runs-and-lineage)
- **Scope:** Implement a new derived run from a specific parent checkpoint, with optional per-Baby replacement policy/adapter, independent event sequences starting at `1`, and immutable parent references.
- **Acceptance criteria:**
- - [ ] A child run records `parentRunId`, `derivedFromCheckpointHash`, and both initial policy refs in config and its first initialization event.
- - [ ] Child sequences restart at `1`, and writes never modify parent evidence or reopen a terminal parent.
- - [ ] `ALD-016` exports lineage references and `ALD-015` verifies them against the immutable parent bundle.
+ - [x] A child run records `parentRunId`, `derivedFromCheckpointHash`, and both initial policy refs in config and its first initialization event.
+ - [x] Child sequences restart at `1`, and writes never modify parent evidence or reopen a terminal parent.
+ - [x] `ALD-016` exports lineage references and `ALD-015` verifies them against the immutable parent bundle.
### EPIC-06 — Symbol Gateway and Communication Protocols
@@ -511,72 +517,72 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§9. Communication Protocols](SPECIFICATION.md#9-communication-protocols)
- **Scope:** Implement the single mediation point for every inter-agent artifact, registered protocol dispatch, and all six §9.6 communication-control conditions, including seeded substitutions and oracle-only-for-E03 enforcement.
- **Acceptance criteria:**
- - [ ] Every artifact passes through the Gateway; direct Baby-to-Baby routes fail in an instrumented integration test.
- - [ ] `normal`, `disabled`, `constant`, seeded `random`, seeded `shuffled`, and E03-only `oracle` conditions produce their exact §9.6 behavior with no code changes.
- - [ ] Every accepted, rejected, or control-substituted turn records the Baby-proposal hash when present and exact delivered-artifact hash through `ALD-035`.
+ - [x] Every artifact passes through the Gateway; direct Baby-to-Baby routes fail in an instrumented integration test.
+ - [x] `normal`, `disabled`, `constant`, seeded `random`, seeded `shuffled`, and E03-only `oracle` conditions produce their exact §9.6 behavior with no code changes.
+ - [x] Every accepted, rejected, or control-substituted turn records the Baby-proposal hash when present and exact delivered-artifact hash through `ALD-035`.
#### ALD-030 — Fixed-token protocol
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-029
- **Spec refs:** `SPECIFICATION.md` [§9.1 Fixed Token Protocol](SPECIFICATION.md#91-fixed-token-protocol)
- **Scope:** Implement the fixed-token vocabulary channel: a closed, enumerable token set, with the Gateway validating every emitted token against the registered vocabulary.
- **Acceptance criteria:**
- - [ ] Emitting a token in the registered vocabulary is accepted and forwarded.
- - [ ] Emitting any token, string, or byte sequence not in the registered vocabulary is rejected, not silently coerced to the nearest valid token.
- - [ ] The vocabulary is configurable per run without a code change.
+ - [x] Emitting a token in the registered vocabulary is accepted and forwarded.
+ - [x] Emitting any token, string, or byte sequence not in the registered vocabulary is rejected, not silently coerced to the nearest valid token.
+ - [x] The vocabulary is configurable per run without a code change.
#### ALD-031 — Alternate neutral carrier protocols
- **Priority:** P2 · **Size:** L · **Class:** Later-Research · **Depends on:** ALD-029
- **Spec refs:** `SPECIFICATION.md` [§9.2 Alternate Neutral Carrier Protocols](SPECIFICATION.md#92-alternate-neutral-carrier-protocols)
- **Scope:** Implement §9.2's `fixed-glyph`, `generative-bitmap`, `generative-canvas`, and `generative-tone` carrier modules with frozen neutral grammars, hard bounds, RFC 8785 canonicalization, and carrier-qualified content-addressed `markHash` generation.
- **Acceptance criteria:**
- - [ ] Each alternate carrier accepts a valid bounded artifact and reproduces the same carrier-qualified `markHash`.
- - [ ] Out-of-range glyph IDs, bitmap sizes, stroke values, tone bins, text/color fields, and semantic tags are rejected with specific reasons.
- - [ ] Every carrier contributes accept/reject vectors to `ALD-036`, and exactly one carrier family is available in a run.
+ - [x] Each alternate carrier accepts a valid bounded artifact and reproduces the same carrier-qualified `markHash`.
+ - [x] Out-of-range glyph IDs, bitmap sizes, stroke values, tone bins, text/color fields, and semantic tags are rejected with specific reasons.
+ - [x] Every carrier contributes accept/reject vectors to `ALD-036`, and exactly one carrier family is available in a run.
#### ALD-032 — Alternate-carrier leakage evaluation
- **Priority:** P1 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-031
- **Spec refs:** `SPECIFICATION.md` [§9.2 Alternate Neutral Carrier Protocols](SPECIFICATION.md#92-alternate-neutral-carrier-protocols), [§15.3 Evaluation Baselines and Statistics](SPECIFICATION.md#153-evaluation-baselines-and-statistics)
- **Scope:** Add offline evaluation hooks for measuring whether alternate-carrier artifacts reproduce recognizable human-language forms or encode task/referent information through unintended glyph, bitmap, stroke, or tone features. The evaluator reports evidence and claim-boundary impact; it does not silently rewrite accepted artifacts.
- **Acceptance criteria:**
- - [ ] The evaluator records mark-level leakage metrics and the exact analysis version in the run evidence bundle.
- - [ ] Pre-registered recognizable-glyph and unintended-feature probes produce explicit pass, fail, or inconclusive results without altering the original canvas artifact.
- - [ ] A failed leakage evaluation blocks an ungrounded-language claim while preserving the run as valid negative or integrity evidence.
+ - [x] The evaluator records mark-level leakage metrics and the exact analysis version in the run evidence bundle.
+ - [x] Pre-registered recognizable-glyph and unintended-feature probes produce explicit pass, fail, or inconclusive results without altering the original canvas artifact.
+ - [x] A failed leakage evaluation blocks an ungrounded-language claim while preserving the run as valid negative or integrity evidence.
#### ALD-033 — Six-display affect protocol
- **Priority:** P2 · **Size:** M · **Class:** Later-Research · **Depends on:** ALD-029
- **Spec refs:** `SPECIFICATION.md` [§9.3 Six-Display Affect Protocol](SPECIFICATION.md#93-six-display-affect-protocol)
- **Scope:** Implement declared, permuted, opaque, and derived six-display modes plus emergent-affect routing through the selected alternate carrier. In derived mode, the Gateway maps adapter measurements; the Baby cannot choose a display.
- **Acceptance criteria:**
- - [ ] Declared/permuted/opaque modes emit only A1-A6, while any other display or out-of-window submission is rejected.
- - [ ] Derived mode disables `submit_affect`, records the private measurement, and applies the pre-registered Gateway mapping; emergent mode records a carrier Channel Event rather than an Affect Event.
- - [ ] Affect schemas and every mode's accept/reject vectors extend `ALD-036`, including normalized timing/envelope behavior.
+ - [x] Declared/permuted/opaque modes emit only A1-A6, while any other display or out-of-window submission is rejected.
+ - [x] Derived mode disables `submit_affect`, records the private measurement, and applies the pre-registered Gateway mapping; emergent mode records a carrier Channel Event rather than an Affect Event.
+ - [x] Affect schemas and every mode's accept/reject vectors extend `ALD-036`, including normalized timing/envelope behavior.
#### ALD-034 — Channel violation detection and rejection behavior
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-029, ALD-030
- **Spec refs:** `SPECIFICATION.md` [§9.4 Rejection Behavior and Channel Violation Handling](SPECIFICATION.md#94-rejection-behavior-and-channel-violation-handling)
- **Scope:** Implement the protocol-independent rejection framework used by every Gateway module: consistent error shape, append-only `channel.rejected` events containing only a rejected-payload hash, rejection counters, and automatic pause after the configured consecutive-rejection ceiling. Fixed-token handling is the first registered module; later canvas and affect modules reuse this framework.
- **Acceptance criteria:**
- - [ ] A fixed-token violation produces the standard rejection shape and an append-only `channel.rejected` event with reason code and payload hash but no raw rejected content.
- - [ ] Five consecutive rejections by default trigger an automatic pause and `safety-trigger` audit entry.
- - [ ] A protocol-module contract test proves canvas and affect handlers can register later without changing the rejection event shape or pause policy.
+ - [x] A fixed-token violation produces the standard rejection shape and an append-only `channel.rejected` event with reason code and payload hash but no raw rejected content.
+ - [x] Five consecutive rejections by default trigger an automatic pause and `safety-trigger` audit entry.
+ - [x] A protocol-module contract test proves canvas and affect handlers can register later without changing the rejection event shape or pause policy.
#### ALD-035 — Turn envelope, channel event, and ledger draft schemas
- **Priority:** P0 · **Size:** S · **Class:** MVP · **Depends on:** ALD-002, ALD-029
- **Spec refs:** `SPECIFICATION.md` [§11.3 Turn and Ledger Proposal Envelopes](SPECIFICATION.md#113-turn-and-ledger-proposal-envelopes), [§11.5 Channel Event](SPECIFICATION.md#115-channel-event)
- **Scope:** Implement and validate Agent Action Proposal, Turn Proposal Envelope, Ledger Draft Envelope, Affect State Measurement, and fully signed Channel Event schemas, wired into Gateway and Evidence Writer boundaries.
- **Acceptance criteria:**
- - [ ] Every Gateway proposal includes one required private intention draft and rejects Baby-supplied run/turn/sender/hash metadata.
- - [ ] Every accepted/rejected event contains the sender-ledger binding, delivery receipt, previous channel hash, entry hash, and channel-writer signature required by §11.5.
- - [ ] Interpretation drafts require the delivered channel hash, and all schema failures use the standard Gateway error shape.
+ - [x] Every Gateway proposal includes one required private intention draft and rejects Baby-supplied run/turn/sender/hash metadata.
+ - [x] Every accepted/rejected event contains the sender-ledger binding, delivery receipt, previous channel hash, entry hash, and channel-writer signature required by §11.5.
+ - [x] Interpretation drafts require the delivered channel hash, and all schema failures use the standard Gateway error shape.
#### ALD-036 — Gateway/protocol conformance test suite
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-030, ALD-034, ALD-035
- **Spec refs:** `SPECIFICATION.md` [§9. Communication Protocols](SPECIFICATION.md#9-communication-protocols), [§17.2 Test Strategy](SPECIFICATION.md#172-test-strategy)
- **Scope:** Build an extensible automated conformance suite for registered Gateway protocols, rejection behavior, and schema validation, runnable independently of any learner adapter. The fixed-token module is the MVP gate; `ALD-031` and `ALD-033` must add canvas and affect vectors before those modules are declared done.
- **Acceptance criteria:**
- - [ ] The MVP suite exercises fixed-token acceptance/rejection, all six communication controls, dual proposal/delivery hashing, consecutive-rejection pause, and schema failures.
- - [ ] The suite runs against a mocked/stub learner, with no dependency on any specific `ALD-044`–047 adapter.
- - [ ] A protocol registration test requires every enabled module to contribute accept/reject vectors; the consolidated suite is the gate referenced by EPIC-06 and `ALD-078`.
+ - [x] The MVP suite exercises fixed-token acceptance/rejection, all six communication controls, dual proposal/delivery hashing, consecutive-rejection pause, and schema failures.
+ - [x] The suite runs against a mocked/stub learner, with no dependency on any specific `ALD-044`–047 adapter.
+ - [x] A protocol registration test requires every enabled module to contribute accept/reject vectors; the consolidated suite is the gate referenced by EPIC-06 and `ALD-078`.
### EPIC-07 — Observation Hygiene, Scenario Engine, and Deterministic Services
@@ -587,45 +593,45 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§11.2 Observation](SPECIFICATION.md#112-observation)
- **Scope:** Implement the Observation builder that assembles exactly the fields §11.2 defines for a given turn, from world/scenario state, with no additional fields leaking in.
- **Acceptance criteria:**
- - [ ] A built Observation validates against the `ALD-002` schema and contains no field not listed in §11.2.
- - [ ] Two observations built from identical underlying state are byte-identical after canonicalization (reusing `ALD-006`'s approach).
- - [ ] The builder is the only code path producing Observations delivered to learners.
+ - [x] A built Observation validates against the `ALD-002` schema and contains no field not listed in §11.2.
+ - [x] Two observations built from identical underlying state are byte-identical after canonicalization (reusing `ALD-006`'s approach).
+ - [x] The builder is the only code path producing Observations delivered to learners.
#### ALD-038 — Observation hygiene filter
- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-037
- **Spec refs:** `SPECIFICATION.md` [§10.1 Observation Hygiene](SPECIFICATION.md#101-observation-hygiene)
- **Scope:** Implement the filter that strips or blocks any observation content prohibited by §10.1 (e.g., internal identifiers, other agent's private state, out-of-scenario metadata) before delivery.
- **Acceptance criteria:**
- - [ ] Every prohibited field category listed in §10.1 is demonstrated blocked using a test observation deliberately constructed to contain it.
- - [ ] The filter runs on every Observation before it reaches the Gateway/learner boundary, with no bypass path.
- - [ ] A blocked field produces an audit-logged event (feeding `ALD-059`), not a silent drop.
+ - [x] Every prohibited field category listed in §10.1 is demonstrated blocked using a test observation deliberately constructed to contain it.
+ - [x] The filter runs on every Observation before it reaches the Gateway/learner boundary, with no bypass path.
+ - [x] A blocked field produces an audit-logged event (feeding `ALD-059`), not a silent drop.
#### ALD-039 — OCR detection and scenario-bundle quarantine
- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-037
- **Spec refs:** `SPECIFICATION.md` [§10.2 Prompt Injection Controls](SPECIFICATION.md#102-prompt-injection-controls)
- **Scope:** Scan every scene and asset for OCR-visible text before scenario registration. Any detected glyph or text causes the complete bundle to fail observation hygiene and enter quarantine; prohibited text is never sanitized and passed through.
- **Acceptance criteria:**
- - [ ] A bundle containing OCR-detected text, caption metadata, semantic filenames, or human-readable labels cannot be referenced by a run.
- - [ ] Text-free controls pass while pre-registered adversarial image/text fixtures are quarantined before any adapter receives them.
- - [ ] Quarantine events retain artifact hashes and reason codes without exposing raw injection text in Baby-visible or public logs.
+ - [x] A bundle containing OCR-detected text, caption metadata, semantic filenames, or human-readable labels cannot be referenced by a run.
+ - [x] Text-free controls pass while pre-registered adversarial image/text fixtures are quarantined before any adapter receives them.
+ - [x] Quarantine events retain artifact hashes and reason codes without exposing raw injection text in Baby-visible or public logs.
#### ALD-040 — Side-channel elimination in transport layer
- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-029, ALD-037
- **Spec refs:** `SPECIFICATION.md` [§10.3 Side Channel Controls](SPECIFICATION.md#103-side-channel-controls)
- **Scope:** Audit and close the transport-level side channels named in §10.3 (e.g., timing channels, response-size channels, error-message channels) in the Gateway and Observation delivery path.
- **Acceptance criteria:**
- - [ ] Each side-channel category named in §10.3 has a corresponding mitigation implemented (e.g., constant-shape error responses, timing normalization where specified).
- - [ ] A test harness measuring the relevant channel (e.g., response latency variance) confirms the mitigation is effective within the tolerance §10.3 implies.
- - [ ] This item's test harness is reused (not duplicated) by the red-team suite in `ALD-067`.
+ - [x] Each side-channel category named in §10.3 has a corresponding mitigation implemented (e.g., constant-shape error responses, timing normalization where specified).
+ - [x] A test harness measuring the relevant channel (e.g., response latency variance) confirms the mitigation is effective within the tolerance §10.3 implies.
+ - [x] This item's test harness is reused (not duplicated) by the red-team suite in `ALD-067`.
#### ALD-041 — Deterministic scenario/task engine
- **Priority:** P1 · **Size:** L · **Class:** MVP · **Depends on:** ALD-023, ALD-037
- **Spec refs:** `SPECIFICATION.md` [§9.5 Interaction and Utility Profiles](SPECIFICATION.md#95-interaction-and-utility-profiles), [§15.3 Evaluation Baselines and Statistics](SPECIFICATION.md#153-evaluation-baselines-and-statistics), [§17.3 Phased Delivery](SPECIFICATION.md#173-phased-delivery)
- **Scope:** Implement the scenario/task generator that deterministically produces scenario states, private observations, utility matrices, reservation values, and zones of possible agreement from run configuration and seed for all five §9.5 interaction profiles.
- **Acceptance criteria:**
- - [ ] Two runs with the same seed and interaction mode produce byte-identical scenarios, private facts, utilities, and task sequences.
- - [ ] Every §9.5 interaction profile produces its required utility relationship, including a provably empty zone of possible agreement for `no-agreement-control`.
- - [ ] The engine's output feeds `ALD-037`'s Observation builder with no intermediate non-deterministic step.
+ - [x] Two runs with the same seed and interaction mode produce byte-identical scenarios, private facts, utilities, and task sequences.
+ - [x] Every §9.5 interaction profile produces its required utility relationship, including a provably empty zone of possible agreement for `no-agreement-control`.
+ - [x] The engine's output feeds `ALD-037`'s Observation builder with no intermediate non-deterministic step.
### EPIC-08 — Learner Contracts and Model Adapters
@@ -636,54 +642,54 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§6.2 Learner Adapter Interface](SPECIFICATION.md#62-learner-adapter-interface)
- **Scope:** Define the shared TypeScript interface every model track must implement (receive Observation, return Agent Action Proposal, lifecycle hooks) exactly matching §6.2, and provide the deterministic/fixed `no-learning` reference adapter used for chance controls.
- **Acceptance criteria:**
- - [ ] The interface implements every §6.2 method, including `receive(DeliveredChannelArtifact)` returning a `LedgerDraftEnvelope`.
- - [ ] The `no-learning` reference adapter is selectable, performs no policy update, and passes the contract-conformance test.
- - [ ] The interface is the only integration point the turn orchestrator (`ALD-025`) uses to reach a learner.
+ - [x] The interface implements every §6.2 method, including `receive(DeliveredChannelArtifact)` returning a `LedgerDraftEnvelope`.
+ - [x] The `no-learning` reference adapter is selectable, performs no policy update, and passes the contract-conformance test.
+ - [x] The interface is the only integration point the turn orchestrator (`ALD-025`) uses to reach a learner.
#### ALD-043 — Learner contract versioning, lint, and tool-only enforcement
- **Priority:** P0 · **Size:** L · **Class:** Research-Grade · **Depends on:** ALD-042
- **Spec refs:** `SPECIFICATION.md` [§6.3 Tool-Only Interaction Contract](SPECIFICATION.md#63-tool-only-interaction-contract), [§6.4 Learner Contract Versioning](SPECIFICATION.md#64-learner-contract-versioning)
- **Scope:** Implement immutable versioned learner-contract files, CI lint that rejects semantic examples/sample exchanges/banned patterns, prompt-bundle hashing, and runtime enforcement that every adapter acts only through the declared tools and Gateway.
- **Acceptance criteria:**
- - [ ] A contract containing a symbol-meaning example, sample exchange, or prohibited side-channel instruction fails CI and cannot be referenced by a run.
- - [ ] Referenced contract versions are immutable and their prompt-bundle hashes appear in run evidence.
- - [ ] The no-learning reference and all four adapter tracks are blocked and audited when attempting any state write or output outside the §6.3 tool surface.
+ - [x] A contract containing a symbol-meaning example, sample exchange, or prohibited side-channel instruction fails CI and cannot be referenced by a run.
+ - [x] Referenced contract versions are immutable and their prompt-bundle hashes appear in run evidence.
+ - [x] The no-learning reference and all four adapter tracks are blocked and audited when attempting any state write or output outside the §6.3 tool surface.
#### ALD-044 — Frozen-LLM adapter with local open-weight default
- **Priority:** P0 · **Size:** L · **Class:** MVP · **Depends on:** ALD-042, ALD-043
- **Spec refs:** `SPECIFICATION.md` [§6.7 Model Recommendations (Defaults)](SPECIFICATION.md#67-model-recommendations-defaults), [§6.1 Track Definitions](SPECIFICATION.md#61-track-definitions)
- **Scope:** Implement the `frozen-llm` adapter using a locally deployable 3B–8B open-weight instruction model by default, with frozen weights, separate private memory, constrained tool calls, and no network access.
- **Acceptance criteria:**
- - [ ] The `frozen-llm` track is selectable via `ALD-023` run configuration and records the exact model and weight hashes.
- - [ ] A full turn (observation → proposal → Gateway validation → commit) completes end-to-end using this adapter.
- - [ ] The adapter exposes no weight-update path and passes the `ALD-036` Gateway conformance suite.
+ - [x] The `frozen-llm` track is selectable via `ALD-023` run configuration and records the exact model and weight hashes.
+ - [x] A full turn (observation → proposal → Gateway validation → commit) completes end-to-end using this adapter.
+ - [x] The adapter exposes no weight-update path and passes the `ALD-036` Gateway conformance suite.
#### ALD-045 — From-scratch RL learner track
- **Priority:** P1 · **Size:** L · **Class:** MVP · **Depends on:** ALD-042, ALD-043
- **Spec refs:** `SPECIFICATION.md` [§6.1 Track Definitions](SPECIFICATION.md#61-track-definitions), `EXPERIMENT-NOTEBOOK.md` [E11 — From-Scratch RL Naming Game](EXPERIMENT-NOTEBOOK.md#e11-from-scratch-rl-naming-game)
- **Scope:** Implement the `scratch-rl` GRU/LSTM actor-critic adapter with independent PPO-style updates, random initialization, private buffers, and policy checkpoint output.
- **Acceptance criteria:**
- - [ ] The track starts from randomly initialized parameters whose initial hash is recorded in the evidence bundle.
- - [ ] A full turn and minimal reward-to-parameter-update cycle complete end-to-end in both pre-registered extrinsic-reward and intrinsic-motivation modes.
- - [ ] Policy updates use only the Baby's private buffers and emit a verifiable policy checkpoint reference.
+ - [x] The track starts from randomly initialized parameters whose initial hash is recorded in the evidence bundle.
+ - [x] A full turn and minimal reward-to-parameter-update cycle complete end-to-end in both pre-registered extrinsic-reward and intrinsic-motivation modes.
+ - [x] Policy updates use only the Baby's private buffers and emit a verifiable policy checkpoint reference.
#### ALD-046 — Self-supervised ungrounded learner track
- **Priority:** P1 · **Size:** L · **Class:** Later-Research · **Depends on:** ALD-042, ALD-043
- **Spec refs:** `SPECIFICATION.md` [§6.1 Track Definitions](SPECIFICATION.md#61-track-definitions), `EXPERIMENT-NOTEBOOK.md` [E12 — Self-Supervised Ungrounded Baseline](EXPERIMENT-NOTEBOOK.md#e12-self-supervised-ungrounded-baseline)
- **Scope:** Implement the `self-supervised` adapter with the same recurrent backbone as `scratch-rl` where feasible, using a pre-registered predictive or contrastive loss and no scalar reward.
- **Acceptance criteria:**
- - [ ] The track is selectable, starts from recorded random initialization, and rejects any scalar reward supplied to its update path.
- - [ ] A full turn and minimal predictive/contrastive update complete end-to-end.
- - [ ] The evidence bundle records the loss definition and proves that outcome labels are not included in the self-supervised update batch.
+ - [x] The track is selectable, starts from recorded random initialization, and rejects any scalar reward supplied to its update path.
+ - [x] A full turn and minimal predictive/contrastive update complete end-to-end.
+ - [x] The evidence bundle records the loss definition and proves that outcome labels are not included in the self-supervised update batch.
#### ALD-047 — Hybrid learner track
- **Priority:** P1 · **Size:** L · **Class:** Later-Research · **Depends on:** ALD-042, ALD-043
- **Spec refs:** `SPECIFICATION.md` [§6.1 Track Definitions](SPECIFICATION.md#61-track-definitions), [§6.5 Semantic-Leakage Test Battery](SPECIFICATION.md#65-semantic-leakage-test-battery)
- **Scope:** Implement the `hybrid` adapter using a from-scratch sensory encoder, recurrent world model, and randomly initialized communication policy, with optional frozen low-level visual features only after semantic-leakage qualification.
- **Acceptance criteria:**
- - [ ] The track is selectable and records the provenance and hashes of every sensory, world-model, and communication component.
- - [ ] A full turn and minimal policy update complete end-to-end through the shared interface.
- - [ ] Any text-aligned frozen feature automatically weakens the run's claim classification; strict ungrounded classification requires `ALD-057` to pass.
+ - [x] The track is selectable and records the provenance and hashes of every sensory, world-model, and communication component.
+ - [x] A full turn and minimal policy update complete end-to-end through the shared interface.
+ - [x] Any text-aligned frozen feature automatically weakens the run's claim classification; strict ungrounded classification requires `ALD-057` to pass.
### EPIC-09 — DTSF Twin Packs, API Surface, and Authorization
@@ -694,45 +700,45 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§12.4 Baby Twin Routes (baby-a, baby-b)](SPECIFICATION.md#124-baby-twin-routes-baby-a-baby-b)
- **Scope:** Replace the `ALD-004` skeleton routes with full implementations of every route listed in §12.4, wired to the Gateway (`ALD-029`) and a selected learner adapter (`ALD-042`-conformant).
- **Acceptance criteria:**
- - [ ] Every route in §12.4, including Gateway-only `/deliver`, exists on both `baby-a` and `baby-b`, all unprefixed.
- - [ ] Each route's behavior matches its documented purpose in §12.4 (verified by an integration test per route).
- - [ ] A full run using these twin packs completes at least one turn end-to-end through the Gateway.
+ - [x] Every route in §12.4, including Gateway-only `/deliver`, exists on both `baby-a` and `baby-b`, all unprefixed.
+ - [x] Each route's behavior matches its documented purpose in §12.4 (verified by an integration test per route).
+ - [x] A full run using these twin packs completes at least one turn end-to-end through the Gateway.
#### ALD-049 — Nursery controller twin pack
- **Priority:** P0 · **Size:** L · **Class:** MVP · **Depends on:** ALD-024, ALD-048
- **Spec refs:** `SPECIFICATION.md` [§12.5 Nursery Controller Routes (nursery)](SPECIFICATION.md#125-nursery-controller-routes-nursery)
- **Scope:** Implement the nursery controller's routes for creating/starting/pausing/aborting runs and creating derived runs, driving the `ALD-024` state machine and `ALD-028` lineage service while orchestrating both Baby twins.
- **Acceptance criteria:**
- - [ ] Every route in §12.5 exists, unprefixed, and drives the correct `ALD-024` state transition.
- - [ ] Creating a run via nursery correctly provisions both `baby-a` and `baby-b` instances.
- - [ ] Pausing/aborting and derived-run creation use the same state/lineage services as direct internal calls, with no divergent logic path or reopening of terminal parents.
+ - [x] Every route in §12.5 exists, unprefixed, and drives the correct `ALD-024` state transition.
+ - [x] Creating a run via nursery correctly provisions both `baby-a` and `baby-b` instances.
+ - [x] Pausing/aborting and derived-run creation use the same state/lineage services as direct internal calls, with no divergent logic path or reopening of terminal parents.
#### ALD-050 — Evidence and verification routes on nursery
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-016, ALD-049
- **Spec refs:** `SPECIFICATION.md` [§12.6 Evidence and Verification Routes (nursery)](SPECIFICATION.md#126-evidence-and-verification-routes-nursery)
- **Scope:** Implement the nursery routes for triggering evidence bundle export (`ALD-016`) and retrieving verification reports (`ALD-017`), per §12.6.
- **Acceptance criteria:**
- - [ ] Every route in §12.6 exists, unprefixed, and returns data conforming to the `ALD-002` schemas involved.
- - [ ] Triggering an export via this route produces a bundle identical to calling `ALD-016`'s export function directly.
- - [ ] Unauthorized callers (per `ALD-051`) cannot reach these routes.
+ - [x] Every route in §12.6 exists, unprefixed, and returns data conforming to the `ALD-002` schemas involved.
+ - [x] Triggering an export via this route produces a bundle identical to calling `ALD-016`'s export function directly.
+ - [x] Unauthorized callers (per `ALD-051`) cannot reach these routes.
#### ALD-051 — Authorization roles and route guards
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-048, ALD-049, ALD-050
- **Spec refs:** `SPECIFICATION.md` [§12.2 Authorization Roles](SPECIFICATION.md#122-authorization-roles)
- **Scope:** Implement the role model and route guards from §12.2 across all twin routes, denying access to any role not explicitly permitted for a given route.
- **Acceptance criteria:**
- - [ ] Every role defined in §12.2 is enforced on every route that names a restriction.
- - [ ] Missing/invalid credentials return `401 UNAUTHENTICATED`; valid identities with insufficient roles return `403 FORBIDDEN`.
- - [ ] A test matrix of (role × route) confirms allow/deny matches §12.2 exactly.
+ - [x] Every role defined in §12.2 is enforced on every route that names a restriction.
+ - [x] Missing/invalid credentials return `401 UNAUTHENTICATED`; valid identities with insufficient roles return `403 FORBIDDEN`.
+ - [x] A test matrix of (role × route) confirms allow/deny matches §12.2 exactly.
#### ALD-052 — Response and error shape standardization
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-048, ALD-049, ALD-050
- **Spec refs:** `SPECIFICATION.md` [§12.3 Response and Error Shape](SPECIFICATION.md#123-response-and-error-shape)
- **Scope:** Apply the standardized success/error response envelope from §12.3 to every route across all three twin packs.
- **Acceptance criteria:**
- - [ ] Every success response across all routes matches the §12.3 success envelope.
- - [ ] Every error response across all routes matches the §12.3 error envelope, including the ones from `ALD-034`'s channel violations.
- - [ ] A lint/test rule fails the build if a new route is added without conforming to the envelope.
+ - [x] Every success response across all routes matches the §12.3 success envelope.
+ - [x] Every error response across all routes matches the §12.3 error envelope, including the ones from `ALD-034`'s channel violations.
+ - [x] A lint/test rule fails the build if a new route is added without conforming to the envelope.
### EPIC-10 — Mode R Isolation and Claim-Boundary Controls
@@ -743,45 +749,46 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§5.1 Prototype Mode (Mode P)](SPECIFICATION.md#51-prototype-mode-mode-p), [§5.2 Research-Grade Mode (Mode R)](SPECIFICATION.md#52-research-grade-mode-mode-r), [§5.3 Mode Comparison Table](SPECIFICATION.md#53-mode-comparison-table)
- **Scope:** Implement the configuration switch selecting Mode P or Mode R for a run, wiring each mode's distinct behaviors from §5.3 (e.g., isolation strictness, claim labeling) into the nursery run-creation path.
- **Acceptance criteria:**
- - [ ] A run created under Mode P and one under Mode R differ exactly along the dimensions listed in §5.3 — no undocumented behavioral difference.
- - [ ] The mode is immutable for the lifetime of a run once created (cannot be switched mid-run).
- - [ ] The active mode is recorded in the Run Configuration and visible in every exported evidence bundle.
+ - [x] A run created under Mode P and one under Mode R differ exactly along the dimensions listed in §5.3 — no undocumented behavioral difference.
+ - [x] The mode is immutable for the lifetime of a run once created (cannot be switched mid-run).
+ - [x] The active mode is recorded in the Run Configuration and visible in every exported evidence bundle.
#### ALD-054 — Claim-boundary enforcement
- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-053
- **Spec refs:** `SPECIFICATION.md` [§5.4 Claim Boundary Statements](SPECIFICATION.md#54-claim-boundary-statements)
- **Scope:** Implement automated checks that block any dashboard/report/export from asserting a Mode-R-only claim (e.g., "isolation-verified") about a run that actually executed in Mode P.
- **Acceptance criteria:**
- - [ ] Every claim statement listed in §5.4 is machine-checked against the run's actual recorded mode before being allowed to render/export.
- - [ ] A Mode P run attempting to surface a Mode-R-only claim label is blocked with a specific error, not silently downgraded.
- - [ ] The check is exercised by an automated test for every claim statement in §5.4, not spot-checked manually.
+ - [x] Every claim statement listed in §5.4 is machine-checked against the run's actual recorded mode before being allowed to render/export.
+ - [x] A Mode P run attempting to surface a Mode-R-only claim label is blocked with a specific error, not silently downgraded.
+ - [x] The check is exercised by an automated test for every claim statement in §5.4, not spot-checked manually.
#### ALD-055 — Separate-container isolation for Mode R
- **Priority:** P1 · **Size:** L · **Class:** Research-Grade · **Depends on:** ALD-053
- **Spec refs:** `SPECIFICATION.md` [§5.2 Research-Grade Mode (Mode R)](SPECIFICATION.md#52-research-grade-mode-mode-r)
-- **Scope:** Run each learner adapter in Mode R in its own container/process with no shared mutable memory, communicating only through the Gateway and evidence store.
+- **Scope:** Run each learner adapter in its own container/process with no shared mutable memory; the present Nursery calls each host through the approved adapter transport while validating public proposals in its in-process Gateway and writer.
+- **Qualification boundary:** The three checked criteria below cover the two learner-host processes and their local update state. They do not establish distinct Baby-twin, Symbol Gateway, per-role ledger-writer/signer-key, or controller trust-zone processes required by `SPECIFICATION.md` §5.2; V06/B12 gates those broader claims.
- **Acceptance criteria:**
- - [ ] In Mode R, `baby-a` and `baby-b` learner processes run in distinct OS processes/containers, verified by distinct process IDs / container IDs.
- - [ ] No in-memory object reference is shared between the two learner containers (verified by an isolation test attempting cross-container object access and observing failure).
- - [ ] Killing one learner container does not corrupt or crash the other, or the Gateway/evidence store.
+ - [x] In Mode R, `baby-a` and `baby-b` learner processes run in distinct OS processes/containers, verified by distinct process IDs / container IDs.
+ - [x] No in-memory object reference is shared between the two learner containers (verified by an isolation test attempting cross-container object access and observing failure).
+ - [x] Killing one learner container does not corrupt or crash the other, or the Gateway/evidence store.
#### ALD-056 — Training isolation guarantees
- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-055, ALD-045, ALD-046, ALD-047
- **Spec refs:** `SPECIFICATION.md` [§10.4 Training Isolation](SPECIFICATION.md#104-training-isolation)
- **Scope:** Verify and enforce that every trainable learner's update process (`scratch-rl`, `self-supervised`, and `hybrid`) cannot access the other agent's private state, replay data, gradients, optimizer, or parameters.
- **Acceptance criteria:**
- - [ ] Each trainable learner's update step reads only from its own adapter's local buffers, never from the counterpart process.
- - [ ] An isolation test that attempts to smuggle counterpart-agent internal state into a training update fails to do so, confirmed by the test.
- - [ ] Training isolation is verified specifically under Mode R container separation (`ALD-055`), not just asserted for Mode P.
+ - [x] Each trainable learner's update step reads only from its own adapter's local buffers, never from the counterpart process.
+ - [x] An isolation test that attempts to smuggle counterpart-agent internal state into a training update fails to do so, confirmed by the test.
+ - [x] Training isolation is verified specifically under Mode R container separation (`ALD-055`), not just asserted for Mode P.
#### ALD-057 — Semantic-leakage test battery automation
- **Priority:** P1 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-038, ALD-044, ALD-045, ALD-046, ALD-047
- **Spec refs:** `SPECIFICATION.md` [§6.5 Semantic Leakage Test Battery](SPECIFICATION.md#65-semantic-leakage-test-battery)
- **Scope:** Implement the full §6.5 adapter test battery: tokenizer/vocabulary audit, frozen-feature linear probe against label-shuffled controls, and vision-language encoder classification, with claim-boundary results persisted in run evidence.
- **Acceptance criteria:**
- - [ ] Every test in §6.5 runs against `scratch-rl`, `self-supervised`, and strict `hybrid`; frozen-LLM and no-learning runs are explicitly classified rather than incorrectly presented as ungrounded.
- - [ ] Linear-probe evaluation uses the pre-registered 95% label-shuffled confidence-interval rule.
- - [ ] Battery results are attached to the run's evidence record so a reviewer can see leakage-test outcomes per run.
+ - [x] Every test in §6.5 runs against `scratch-rl`, `self-supervised`, and strict `hybrid`; frozen-LLM and no-learning runs are explicitly classified rather than incorrectly presented as ungrounded.
+ - [x] Linear-probe evaluation uses the pre-registered 95% label-shuffled confidence-interval rule.
+ - [x] Battery results are attached to the run's evidence record so a reviewer can see leakage-test outcomes per run.
### EPIC-11 — Telemetry, Audit, Snapshot/Recovery, and Retention
@@ -792,45 +799,45 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§14.1 Telemetry](SPECIFICATION.md#141-telemetry)
- **Scope:** Implement the telemetry pipeline capturing per-request/per-turn metrics (method, path, status, duration) named in §14.1, persisted for dashboard consumption.
- **Acceptance criteria:**
- - [ ] Every API request across all twin routes produces a telemetry record with the fields §14.1 requires.
- - [ ] Telemetry recording failures never block or fail the underlying request (verified by fault-injection test on the telemetry sink).
- - [ ] Telemetry data is queryable by run ID and by time range.
+ - [x] Every API request across all twin routes produces a telemetry record with the fields §14.1 requires.
+ - [x] Telemetry recording failures never block or fail the underlying request (verified by fault-injection test on the telemetry sink).
+ - [x] Telemetry data is queryable by run ID and by time range.
#### ALD-059 — Audit, intervention, and safety-event logging
- **Priority:** P0 · **Size:** L · **Class:** Research-Grade · **Depends on:** ALD-013, ALD-024, ALD-053
- **Spec refs:** `SPECIFICATION.md` [§14.2 Audit Logging](SPECIFICATION.md#142-audit-logging)
- **Scope:** Implement append-only audit records for human views, operator interventions, and safety triggers. Pause/resume/abort/annotate actions must use this path, record actor/reason, and request the mandatory checkpoint; unplanned interventions also create a notebook deviation reference.
- **Acceptance criteria:**
- - [ ] Every §14.2 human view/intervention and §14.5 safety trigger produces an append-only audit record with authenticated actor and machine-readable reason.
- - [ ] Each intervention produces a signed checkpoint, and any unplanned intervention links to an append-only notebook deviation record.
- - [ ] Audit and intervention logs plus checkpoint references are included in every evidence bundle, not only Mode R.
+ - [x] Every §14.2 human view/intervention and §14.5 safety trigger produces an append-only audit record with authenticated actor and machine-readable reason.
+ - [x] Each intervention produces a signed checkpoint, and any unplanned intervention links to an append-only notebook deviation record.
+ - [x] Audit and intervention logs plus checkpoint references are included in every evidence bundle, not only Mode R.
#### ALD-060 — Snapshot and restore mechanism
- **Priority:** P1 · **Size:** L · **Class:** MVP · **Depends on:** ALD-005, ALD-011
- **Spec refs:** `SPECIFICATION.md` [§14.4 Snapshot and Restore](SPECIFICATION.md#144-snapshot-and-restore)
- **Scope:** Implement periodic and on-demand snapshotting of all runtime state to serialized files, and a restore path that reconstructs state from the latest snapshot on startup, per §14.4.
- **Acceptance criteria:**
- - [ ] A manual "take snapshot now" action produces a snapshot file set that a restore can consume.
- - [ ] Restarting the server after a snapshot automatically restores to that snapshot's state (`autoRestore()`-equivalent behavior).
- - [ ] A restored run's evidence-store state matches, byte-for-byte in the chain-walk sense (`ALD-008`), the state at the moment the snapshot was taken.
+ - [x] A manual "take snapshot now" action produces a snapshot file set that a restore can consume.
+ - [x] Restarting the server after a snapshot automatically restores to that snapshot's state (`autoRestore()`-equivalent behavior).
+ - [x] A restored run's evidence-store state matches, byte-for-byte in the chain-walk sense (`ALD-008`), the state at the moment the snapshot was taken.
#### ALD-061 — Failure handling policy implementation
- **Priority:** P0 · **Size:** M · **Class:** MVP · **Depends on:** ALD-011, ALD-021
- **Spec refs:** `SPECIFICATION.md` [§14.5 Failure Handling](SPECIFICATION.md#145-failure-handling)
- **Scope:** Implement the documented failure-handling policy for each named failure mode in §14.5 (evidence-store failure, anchoring failure, learner adapter crash), including registering `process.on('uncaughtException')` and `process.on('unhandledRejection')` handlers so background tasks (checkpoint scheduler, anchoring retries) log rather than crash the server.
- **Acceptance criteria:**
- - [ ] Every failure mode named in §14.5 has an implemented, tested handling path.
- - [ ] An unhandled rejection thrown from a background task (e.g., a failed anchor confirmation poll) is caught, logged, and does not crash the server process, confirmed by a fault-injection test.
- - [ ] The failure-handling behavior for anchoring failures reuses `ALD-021`'s retry/backoff rather than a separate ad hoc mechanism.
+ - [x] Every failure mode named in §14.5 has an implemented, tested handling path.
+ - [x] An unhandled rejection thrown from a background task (e.g., a failed anchor confirmation poll) is caught, logged, and does not crash the server process, confirmed by a fault-injection test.
+ - [x] The failure-handling behavior for anchoring failures reuses `ALD-021`'s retry/backoff rather than a separate ad hoc mechanism.
#### ALD-062 — Retention policy enforcement job
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-005, ALD-016
- **Spec refs:** `SPECIFICATION.md` [§14.6 Retention Policy](SPECIFICATION.md#146-retention-policy)
- **Scope:** Purge bulk payload files only for non-public development/qualification runs that are not Base-mainnet anchored and exceed `prototypeRetentionDays`; preserve append-only database events, audit/intervention logs, manifests, receipts, and run index metadata.
- **Acceptance criteria:**
- - [ ] Only eligible non-public, non-mainnet run-bundle payloads are purged; public or mainnet-anchored bundles are retained indefinitely.
- - [ ] `run_metadata`, ledger/channel/audit/intervention rows, checkpoint manifests, and anchor receipts remain queryable after purge.
- - [ ] The job's actions are themselves audit-logged (`ALD-059`).
+ - [x] Only eligible non-public, non-mainnet run-bundle payloads are purged; public or mainnet-anchored bundles are retained indefinitely.
+ - [x] `run_metadata`, ledger/channel/audit/intervention rows, checkpoint manifests, and anchor receipts remain queryable after purge.
+ - [x] The job's actions are themselves audit-logged (`ALD-059`).
### EPIC-12 — Dashboard / Research Console and UX
@@ -841,36 +848,36 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§16.1 Dashboard and Research Console](SPECIFICATION.md#161-dashboard-and-research-console)
- **Scope:** Build the vanilla-JS Research Console with run/pre-registration status, separated Baby perspectives, public transcript, read-only audit-ledger comparison, checkpoint/anchor/verification state, telemetry, and operator-gated lifecycle controls.
- **Acceptance criteria:**
- - [ ] The console displays live run/pre-registration state, public transcript, telemetry, verification, checkpoint, and anchor data from authoritative routes.
- - [ ] Baby A/B observations and audit ledgers render in clearly separated read-only panels, and operator controls are role-gated and audited.
- - [ ] The console is a self-contained vanilla HTML/CSS/JS page with no frontend framework, build tool, or client-side side-channel route.
+ - [x] The console displays live run/pre-registration state, public transcript, telemetry, verification, checkpoint, and anchor data from authoritative routes.
+ - [x] Baby A/B observations and audit ledgers render in clearly separated read-only panels, and operator controls are role-gated and audited.
+ - [x] The console is a self-contained vanilla HTML/CSS/JS page with no frontend framework, build tool, or client-side side-channel route.
#### ALD-064 — Human audit-ledger Interpreter
- **Priority:** P1 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-005, ALD-016, ALD-042
- **Spec refs:** `SPECIFICATION.md` [§13.6 Privacy Controls](SPECIFICATION.md#136-privacy-controls)
- **Scope:** Implement delayed/batched conversion of agent-native ledger state into separately signed, append-only `audit_ledger_entries`, labeled `source: generated-analysis`, with no Baby-readable route or feedback path.
- **Acceptance criteria:**
- - [ ] Every generated interpretation references source native events and is explicitly labeled external analysis.
- - [ ] Baby identities cannot read audit-ledger entries, while authorized researchers and the verifier can.
- - [ ] Audit entries are included in their checkpoint auxiliary tree and exported evidence without modifying native ledger events.
+ - [x] Every generated interpretation references source native events and is explicitly labeled external analysis.
+ - [x] Baby identities cannot read audit-ledger entries, while authorized researchers and the verifier can.
+ - [x] Audit entries are included in their checkpoint auxiliary tree and exported evidence without modifying native ledger events.
#### ALD-065 — Prohibited UX pattern review checklist
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-063
- **Spec refs:** `SPECIFICATION.md` [§16.2 Diplomacy Table Reuse Boundaries](SPECIFICATION.md#162-diplomacy-table-reuse-boundaries), [§16.3 Prohibited UX Patterns](SPECIFICATION.md#163-prohibited-ux-patterns)
- **Scope:** Audit every reused Diplomacy-style component and run a checklist against all prohibited side-channel, misleading-claim, caucus, coalition, and direct-relay patterns.
- **Acceptance criteria:**
- - [ ] Every reused component is listed with its permitted §16.2 mapping, and no Diplomacy game logic or side-channel route is imported.
- - [ ] Any failing item blocks `ALD-063` from being marked `Done` until resolved.
- - [ ] Every §16.3 prohibition has a pass/fail result, and the checklist reruns after material dashboard changes.
+ - [x] Every reused component is listed with its permitted §16.2 mapping, and no Diplomacy game logic or side-channel route is imported.
+ - [x] Any failing item blocks `ALD-063` from being marked `Done` until resolved.
+ - [x] Every §16.3 prohibition has a pass/fail result, and the checklist reruns after material dashboard changes.
#### ALD-066 — Automated replay fidelity and viewer
- **Priority:** P1 · **Size:** L · **Class:** Research-Grade · **Depends on:** ALD-041, ALD-063
- **Spec refs:** `SPECIFICATION.md` [§14.3 Reproducibility and Replay Fidelity](SPECIFICATION.md#143-reproducibility-and-replay-fidelity)
- **Scope:** Implement scenario replay and deterministic execution `replayDigest` generation/verification per §14.3, then expose the results in a read-only dashboard viewer.
- **Acceptance criteria:**
- - [ ] Same-seed scenario replay reproduces scenario/observation hashes; wrong-seed replay fails automatically.
- - [ ] Deterministic adapters reproduce the §14.3 replay digest, while nondeterministic adapters are explicitly `not-applicable` and pass recorded-decision playback only.
- - [ ] The viewer displays machine results and remains read-only; it cannot alter evidence or override a failure.
+ - [x] Same-seed scenario replay reproduces scenario/observation hashes; wrong-seed replay fails automatically.
+ - [x] Deterministic adapters reproduce the §14.3 replay digest, while nondeterministic adapters are explicitly `not-applicable` and pass recorded-decision playback only.
+ - [x] The viewer displays machine results and remains read-only; it cannot alter evidence or override a failure.
### EPIC-13 — Security, Red-Team, and Cryptography Track
@@ -881,36 +888,36 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§10.3 Side Channel Controls](SPECIFICATION.md#103-side-channel-controls), `EXPERIMENT-NOTEBOOK.md` [E01 — Channel Isolation and Side-Channel Red-Team](EXPERIMENT-NOTEBOOK.md#e01-channel-isolation-and-side-channel-red-team)
- **Scope:** Build an adversarial test harness that actively attempts to exploit each side channel `ALD-040` claims to have closed, reusing its measurement tooling, sufficient to support `E01`.
- **Acceptance criteria:**
- - [ ] The harness includes at least one active exploit attempt per side-channel category named in §10.3.
- - [ ] Every exploit attempt fails to extract cross-agent information, confirmed by an automated pass/fail check, not manual judgment.
- - [ ] The harness runs under both Mode P and Mode R (`ALD-053`) so `E01`'s isolation comparison is possible.
+ - [x] The harness includes at least one active exploit attempt per side-channel category named in §10.3.
+ - [x] Every Research-Grade exploit attempt fails to extract cross-agent information, confirmed by an automated pass/fail check, while Mode P remains the explicit comparison condition.
+ - [x] The harness runs under both Mode P and Mode R (`ALD-053`) so `E01`'s isolation comparison is possible.
#### ALD-068 — Observation-text and quarantine-bypass red-team suite
- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-039
- **Spec refs:** `SPECIFICATION.md` [§10.2 Prompt Injection Controls](SPECIFICATION.md#102-prompt-injection-controls), `EXPERIMENT-NOTEBOOK.md` [E02 — Observation and Metadata Leakage Audit](EXPERIMENT-NOTEBOOK.md#e02-observation-and-metadata-leakage-audit)
- **Scope:** Build adversarial scenario bundles that hide text in pixels, metadata, filenames, alternate encodings, and malformed assets, attempting to bypass `ALD-039` and reach a Baby observation.
- **Acceptance criteria:**
- - [ ] The suite includes direct text, low-contrast/OCR-evasion fixtures, metadata labels, semantic filenames, and malformed-image cases.
- - [ ] Every positive fixture is quarantined and zero raw text reaches an adapter context; negative text-free controls remain loadable.
- - [ ] Results are exported as hashed evidence linked from the E02 Experiment Record, not misrepresented as the Experiment Record itself.
+ - [x] The suite includes direct text, low-contrast/OCR-evasion fixtures, metadata labels, semantic filenames, and malformed-image cases.
+ - [x] Every positive fixture is quarantined and zero raw text reaches an adapter context; negative text-free controls remain loadable.
+ - [x] Results are exported as hashed evidence linked from the E02 Experiment Record, not misrepresented as the Experiment Record itself.
#### ALD-069 — Ephemeral encoding and adversarial cryptography research harness
- **Priority:** P2 · **Size:** L · **Class:** Later-Research · **Depends on:** ALD-031, ALD-057
- **Spec refs:** `SPECIFICATION.md` [§18. Experiment Variable Registry](SPECIFICATION.md#18-experiment-variable-registry), `EXPERIMENT-NOTEBOOK.md` [E40 — Ephemeral Encoding and Adversarial Cryptography](EXPERIMENT-NOTEBOOK.md#e40-ephemeral-encoding-and-adversarial-cryptography)
- **Scope:** Build the research harness needed for `E40` — instrumentation to let two learners develop and test ephemeral, session-specific encodings over the generative carrier channel (`ALD-031`), with a third-party eavesdropper role able to attempt decoding.
- **Acceptance criteria:**
- - [ ] The harness supports at least three roles in a single run: two communicating learners and one eavesdropper observer.
- - [ ] The harness logs every encoding scheme change as a distinct, timestamped event so `E40` can measure encoding lifetime.
- - [ ] The harness itself makes no claim about cryptographic security — it only provides the measurement/instrumentation `E40`'s research execution needs; a passing harness build is not a security claim.
+ - [x] The harness supports at least three roles in a single run: two communicating learners and one eavesdropper observer.
+ - [x] The harness logs every encoding scheme change as a distinct, timestamped event so `E40` can measure encoding lifetime.
+ - [x] The harness itself makes no claim about cryptographic security — it only provides the measurement/instrumentation `E40`'s research execution needs; a passing harness build is not a security claim.
#### ALD-070 — Cryptographic novelty-vs-security separation policy
- **Priority:** P0 · **Size:** S · **Class:** Later-Research · **Depends on:** ALD-009, ALD-019
- **Spec refs:** `SPECIFICATION.md` [§19. Deferred Decisions and ADRs](SPECIFICATION.md#19-deferred-decisions-and-adrs)
- **Scope:** Document and enforce, via a documented review gate, that any learner-invented "encoding" or "cipher" from `ALD-069`'s research (or any other emergent scheme) is never substituted for the production signing/hashing mechanisms in `ALD-009`/`ALD-019` — novelty in a research harness must never be mistaken for or promoted to a security mechanism.
- **Acceptance criteria:**
- - [ ] A written policy statement exists distinguishing "research-harness encoding" from "production cryptographic signing," citing `ALD-009` and `ALD-019` as the only production mechanisms.
- - [ ] A code-level check (e.g., module boundary or lint rule) prevents any `ALD-069`-harness-derived code from being imported into the `ALD-009`/`ALD-019` signing modules.
- - [ ] This policy is included in the documentation set (`ALD-079`) and referenced by `E40`'s readiness gate (`ALD-077`).
+ - [x] A written policy statement exists distinguishing "research-harness encoding" from "production cryptographic signing," citing `ALD-009` and `ALD-019` as the only production mechanisms.
+ - [x] A code-level check (e.g., module boundary or lint rule) prevents any `ALD-069`-harness-derived code from being imported into the `ALD-009`/`ALD-019` signing modules.
+ - [x] This policy is included in the documentation set (`ALD-079`) and referenced by `E40`'s readiness gate (`ALD-077`).
### EPIC-14 — Experiment Pre-Registration and E00–E50 Readiness Gates
@@ -921,63 +928,63 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Spec refs:** `SPECIFICATION.md` [§15.1 Pre-Registration Binding](SPECIFICATION.md#151-pre-registration-binding), `EXPERIMENT-NOTEBOOK.md` [§4. Study Metadata](EXPERIMENT-NOTEBOOK.md#4-study-metadata)
- **Scope:** Bind a notebook experiment and sealed hypothesis/parameters to RunConfig, create append-only versioned Experiment Records, and export `experiment-record.json` with disposition/checkpoint/anchor/verifier/deviation references.
- **Acceptance criteria:**
- - [ ] A confirmatory run cannot start without a bound experiment ID, protocol commit, external registration URL, canonical pre-registration hash, and matching pre-run anchor receipt.
- - [ ] Pre-registration creates Experiment Record version `1`; later disposition/evidence changes append higher versions without updating prior rows.
- - [ ] Every evidence bundle contains the latest record plus its version history and references resolvable by an independent reviewer.
+ - [x] A confirmatory run cannot start without a bound experiment ID, protocol commit, complete repository-native or external registration, canonical pre-registration hash, and matching pre-run commitment receipt whose simulated/public class matches the run configuration.
+ - [x] Pre-registration creates Experiment Record version `1`; later disposition/evidence changes append higher versions without updating prior rows.
+ - [x] Every evidence bundle contains the latest record plus its version history and references resolvable by an independent reviewer.
#### ALD-072 — Intervention test suite and baseline/statistics scaffold
- **Priority:** P1 · **Size:** L · **Class:** MVP · **Depends on:** ALD-041, ALD-071
- **Spec refs:** `SPECIFICATION.md` [§15.2 Intervention Test Suite](SPECIFICATION.md#152-intervention-test-suite), [§15.3 Evaluation Baselines and Statistics](SPECIFICATION.md#153-evaluation-baselines-and-statistics)
- **Scope:** Build the software scaffold (not the scientific analysis itself) that lets a pre-registered intervention be applied to a deterministic scenario run (`ALD-041`) and that computes the baseline statistics named in §15.3 over run output.
- **Acceptance criteria:**
- - [ ] An intervention defined in a pre-registration reference (`ALD-071`) can be toggled on/off for a run via configuration, with no code change per intervention.
- - [ ] The scaffold computes every baseline statistic named in §15.3 over a completed run's evidence.
- - [ ] The scaffold's output is a data structure ready for a researcher's downstream analysis — it does not itself draw or store scientific conclusions.
+ - [x] An intervention defined in a pre-registration reference (`ALD-071`) can be toggled on/off for a run via configuration, with no code change per intervention.
+ - [x] The scaffold computes every baseline statistic named in §15.3 over a completed run's evidence.
+ - [x] The scaffold's output is a data structure ready for a researcher's downstream analysis — it does not itself draw or store scientific conclusions.
#### ALD-073 — Gate G1: Integrity and isolation readiness (E00–E03)
- **Priority:** P0 · **Size:** S · **Class:** MVP · **Depends on:** ALD-015, ALD-021, ALD-029, ALD-035, ALD-036, ALD-041, ALD-042, ALD-067, ALD-068, ALD-071, ALD-072
-- **Spec refs:** `SPECIFICATION.md` [§17.4 Traceability to E00–E50](SPECIFICATION.md#174-traceability-to-e00-e50), `EXPERIMENT-NOTEBOOK.md` [E00](EXPERIMENT-NOTEBOOK.md#e00-ledger-integrity-and-base-anchoring), [E01](EXPERIMENT-NOTEBOOK.md#e01-channel-isolation-and-side-channel-red-team), [E02](EXPERIMENT-NOTEBOOK.md#e02-observation-and-metadata-leakage-audit), [E03](EXPERIMENT-NOTEBOOK.md#e03-chance-no-communication-and-random-message-controls)
+- **Spec refs:** `SPECIFICATION.md` [§17.4 Traceability to E00–E50](SPECIFICATION.md#174-traceability-to-e00-e50), `EXPERIMENT-NOTEBOOK.md` [E00](EXPERIMENT-NOTEBOOK.md#e00-ledger-integrity-and-simulated-commitment), [E01](EXPERIMENT-NOTEBOOK.md#e01-channel-isolation-and-side-channel-red-team), [E02](EXPERIMENT-NOTEBOOK.md#e02-observation-and-metadata-leakage-audit), [E03](EXPERIMENT-NOTEBOOK.md#e03-chance-no-communication-and-random-message-controls)
- **Scope:** Define and check the readiness gate confirming the software capability required for `E00`–`E03` exists and passes its own conformance checks — the gate asserts *capability is ready to run the experiment*, not that the experiment has been run or what it found.
- **Acceptance criteria:**
- - [ ] `E00` readiness: the verifier accepts an unchanged anchored bundle and rejects all 14 mutation/anchor cases in LEDGER §17, including wrong-chain anchors and unanchored tails.
- - [ ] `E01`/`E02` readiness: `ALD-067`/`ALD-068` red-team suites are green.
- - [ ] `E03` readiness: `ALD-029`/`ALD-036` run all six controls with dual-hash evidence, while `ALD-041`/`ALD-042`/`ALD-072` provide deterministic scenarios, no-learning behavior, confidence intervals, and effect sizes.
+ - [x] `E00` readiness: the verifier accepts an unchanged anchored bundle and rejects all 14 mutation/anchor cases in LEDGER §17, including wrong-chain anchors and unanchored tails.
+ - [x] `E01`/`E02` readiness: `ALD-067`/`ALD-068` red-team suites are green.
+ - [x] `E03` readiness: `ALD-029`/`ALD-036` run all six controls with dual-hash evidence, while `ALD-041`/`ALD-042`/`ALD-072` provide deterministic scenarios, no-learning behavior, confidence intervals, and effect sizes.
#### ALD-074 — Gate G2: Model-track and protocol readiness (E10–E16)
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-031, ALD-032, ALD-036, ALD-042, ALD-044, ALD-045, ALD-046, ALD-047, ALD-056, ALD-057, ALD-064, ALD-072
- **Spec refs:** `SPECIFICATION.md` [§17.4 Traceability to E00–E50](SPECIFICATION.md#174-traceability-to-e00-e50), `EXPERIMENT-NOTEBOOK.md` [E10](EXPERIMENT-NOTEBOOK.md#e10-frozen-pretrained-llm-protocol-baseline), [E11](EXPERIMENT-NOTEBOOK.md#e11-from-scratch-rl-naming-game), [E12](EXPERIMENT-NOTEBOOK.md#e12-self-supervised-ungrounded-baseline), [E13](EXPERIMENT-NOTEBOOK.md#e13-no-predefined-symbol-library), [E14](EXPERIMENT-NOTEBOOK.md#e14-turn-taking-role-reversal-and-repair), [E15](EXPERIMENT-NOTEBOOK.md#e15-composition-and-held-out-generalization), [E16](EXPERIMENT-NOTEBOOK.md#e16-causal-listening-and-ledger-validity)
- **Scope:** Define and check the readiness gate confirming every model track and protocol capability that `E10`–`E16` require exists and passes conformance, across all seven experiments in this range.
- **Acceptance criteria:**
- - [ ] `E10`/`E11`/`E12` readiness: `ALD-044`/`ALD-045`/`ALD-046` complete full turns; ungrounded tracks pass training isolation and semantic-leakage qualification.
- - [ ] `E13` readiness: all `ALD-031` alternate carriers pass conformance and `ALD-032` leakage evaluation can compare all five notebook carrier conditions.
- - [ ] `E14`/`E15`/`E16` readiness: `ALD-072` supports role reversal, held-out splits, and causal interventions, and `ALD-064` produces separately labeled human audit interpretations.
+ - [x] `E10`/`E11`/`E12` readiness: `ALD-044`/`ALD-045`/`ALD-046` complete full turns; ungrounded tracks pass training isolation and semantic-leakage qualification.
+ - [x] `E13` readiness: all `ALD-031` alternate carriers pass conformance and `ALD-032` leakage evaluation can compare all five notebook carrier conditions.
+ - [x] `E14`/`E15`/`E16` readiness: `ALD-072` supports role reversal, held-out splits, and causal interventions, and `ALD-064` produces separately labeled human audit interpretations.
#### ALD-075 — Gate G3: Affect and learning-comparison readiness (E20–E22)
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-023, ALD-033, ALD-042, ALD-044, ALD-045, ALD-046, ALD-072
- **Spec refs:** `SPECIFICATION.md` [§17.4 Traceability to E00–E50](SPECIFICATION.md#174-traceability-to-e00-e50), `EXPERIMENT-NOTEBOOK.md` [E20](EXPERIMENT-NOTEBOOK.md#e20-constrained-affect-channel-study), [E21](EXPERIMENT-NOTEBOOK.md#e21-rl-versus-non-rl-learning-comparison), [E22](EXPERIMENT-NOTEBOOK.md#e22-developmental-plasticity-and-curriculum)
- **Scope:** Define and check the readiness gate confirming the six-display affect channel and the RL/non-RL comparison capability required for `E20`–`E22` exist and pass conformance.
- **Acceptance criteria:**
- - [ ] `E20` readiness: `ALD-033` affect protocol independently passes conformance with all six displays exercised.
- - [ ] `E21` readiness: no-learning (`ALD-042`), frozen-LLM (`ALD-044`), extrinsic/intrinsic scratch-RL (`ALD-045`), and self-supervised (`ALD-046`) modes run under the same scenario configuration.
- - [ ] `E22` readiness: `ALD-072`'s scaffold supports a staged/curriculum sequence of interventions within a single run.
+ - [x] `E20` readiness: `ALD-033` affect protocol independently passes conformance with all six displays exercised.
+ - [x] `E21` readiness: no-learning (`ALD-042`), frozen-LLM (`ALD-044`), extrinsic/intrinsic scratch-RL (`ALD-045`), and self-supervised (`ALD-046`) modes run under the same scenario configuration.
+ - [x] `E22` readiness: `ALD-072`'s scaffold supports a staged/curriculum sequence of interventions within a single run.
#### ALD-076 — Gate G4: Transfer and negotiation-scenario readiness (E30–E32)
- **Priority:** P2 · **Size:** S · **Class:** Later-Research · **Depends on:** ALD-028, ALD-041, ALD-072
- **Spec refs:** `SPECIFICATION.md` [§17.4 Traceability to E00–E50](SPECIFICATION.md#174-traceability-to-e00-e50), `EXPERIMENT-NOTEBOOK.md` [E30](EXPERIMENT-NOTEBOOK.md#e30-partner-replacement-and-zero-shot-transfer), [E31](EXPERIMENT-NOTEBOOK.md#e31-longitudinal-drift-and-stability), [E32](EXPERIMENT-NOTEBOOK.md#e32-cooperative-signaling-versus-negotiation)
- **Scope:** Define and check the readiness gate confirming derived-run support, deterministic long-run analysis, and the Scenario Engine's aligned/private/semi-cooperative/conflicting utility configurations are sufficient for partner-replacement, longitudinal, and negotiation experiments.
- **Acceptance criteria:**
- - [ ] `E30` readiness: `ALD-028` derived-run support can substitute a new learner adapter in the child run without altering the parent.
- - [ ] `E31` readiness: `ALD-072`'s baseline/statistics scaffold can compare metrics across a long sequence of turns/checkpoints for drift analysis.
- - [ ] `E32` readiness: `ALD-041` produces each pre-registered cooperative and negotiation utility condition from configuration without a code change.
+ - [x] `E30` readiness: `ALD-028` derived-run support can substitute a new learner adapter in the child run without altering the parent.
+ - [x] `E31` readiness: `ALD-072`'s baseline/statistics scaffold can compare metrics across a long sequence of turns/checkpoints for drift analysis.
+ - [x] `E32` readiness: `ALD-041` produces each pre-registered cooperative and negotiation utility condition from configuration without a code change.
#### ALD-077 — Gate G5: Cryptography-research and replication readiness (E40, E50)
- **Priority:** P2 · **Size:** S · **Class:** Later-Research · **Depends on:** ALD-069, ALD-070, ALD-028, ALD-072
- **Spec refs:** `SPECIFICATION.md` [§17.4 Traceability to E00–E50](SPECIFICATION.md#174-traceability-to-e00-e50), `EXPERIMENT-NOTEBOOK.md` [E40](EXPERIMENT-NOTEBOOK.md#e40-ephemeral-encoding-and-adversarial-cryptography), [E50](EXPERIMENT-NOTEBOOK.md#e50-multi-seed-replication-and-study-closeout), [§12. Publication Checklist](EXPERIMENT-NOTEBOOK.md#12-publication-checklist)
- **Scope:** Define and check the readiness gate confirming the cryptography research harness and its safety-separation policy are in place for `E40`, and that multi-seed replication tooling is ready for `E50`.
- **Acceptance criteria:**
- - [ ] `E40` readiness: `ALD-069` harness and `ALD-070` separation policy both pass their own acceptance criteria.
- - [ ] `E50` readiness: `ALD-028` derived-run support and `ALD-072`'s scaffold together launch the same pre-registered configuration across independent seeds and aggregate baseline statistics.
- - [ ] This gate's checklist cross-references the `EXPERIMENT-NOTEBOOK.md` [§12. Publication Checklist](EXPERIMENT-NOTEBOOK.md#12-publication-checklist) items that are software-verifiable, without asserting any of the checklist's research-judgment items are satisfied.
+ - [x] `E40` readiness: `ALD-069` harness and `ALD-070` separation policy both pass their own acceptance criteria.
+ - [x] `E50` readiness: `ALD-028` derived-run support and `ALD-072`'s scaffold together launch the same pre-registered configuration across independent seeds and aggregate baseline statistics.
+ - [x] This gate's checklist cross-references the `EXPERIMENT-NOTEBOOK.md` [§12. Publication Checklist](EXPERIMENT-NOTEBOOK.md#12-publication-checklist) items that are software-verifiable, without asserting any of the checklist's research-judgment items are satisfied.
### EPIC-15 — CI/Test Suites, Documentation, Operations, and Release
@@ -989,26 +996,94 @@ Each item lists: Priority, Size, Classification (MVP / Research-Grade / Later-Re
- **Scope:** Stand up a CI pipeline running, on every change: the crash-safety suite (`ALD-011`), the Gateway conformance suite (`ALD-036`), the semantic-leakage battery (`ALD-057`), and both red-team suites (`ALD-067`/`068`), plus every other item's own acceptance tests referenced elsewhere in this backlog.
- **Acceptance criteria:**
- [ ] CI runs on every proposed change and blocks merge on any failing suite.
- - [ ] The consolidated suite includes at least one test per `Done` item's acceptance criteria at the time CI is stood up.
- - [ ] CI run time and flakiness are tracked; a flaky test is quarantined with a tracked follow-up, not silently ignored.
+ - [x] The consolidated suite includes at least one test per `Done` item's acceptance criteria at the time CI is stood up.
+ - [x] CI run time and flakiness are tracked; a flaky test is quarantined with a tracked follow-up, not silently ignored.
#### ALD-079 — Architecture/API/operations documentation set
- **Priority:** P1 · **Size:** M · **Class:** MVP · **Depends on:** ALD-048, ALD-049, ALD-050, ALD-055, ALD-060
- **Spec refs:** `SPECIFICATION.md` [§1.5 Relationship to Companion Documents](SPECIFICATION.md#15-relationship-to-companion-documents)
- **Scope:** Write the documentation set covering: architecture overview, full API reference (all routes from EPIC-09), Mode R operational runbook (container isolation, `ALD-055`), and the snapshot/restore/recovery runbook (`ALD-060`).
- **Acceptance criteria:**
- - [ ] Every route implemented in EPIC-09 has a corresponding API reference entry with request/response shape.
- - [ ] The Mode R runbook lets an operator who has not read the source code stand up an isolated run following the documented steps alone.
- - [ ] The snapshot/restore runbook is validated by having someone other than the implementer follow it to perform a real restore.
+ - [x] Every route implemented in EPIC-09 has a corresponding API reference entry with request/response shape.
+ - [x] The Mode R runbook lets an operator who has not read the source code stand up an isolated run following the documented steps alone.
+ - [x] The snapshot/restore runbook is validated by having someone other than the implementer follow it to perform a real restore.
#### ALD-080 — Release process and publication-checklist mapping
- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-071, ALD-077, ALD-079
- **Spec refs:** `EXPERIMENT-NOTEBOOK.md` [§12. Publication Checklist](EXPERIMENT-NOTEBOOK.md#12-publication-checklist)
- **Scope:** Define the release process for the software (versioning, tagging, changelog) and produce a mapping from each `EXPERIMENT-NOTEBOOK.md` §12 publication-checklist item to the specific backlog item(s)/gate(s) that make it achievable, without claiming any research-judgment checklist item as satisfied by software alone.
- **Acceptance criteria:**
- - [ ] A documented release process exists (version scheme, changelog convention, tagging) consistent with §1's normative-language conventions.
- - [ ] Every software-verifiable item in the §12 publication checklist is mapped to at least one `ALD-XXX` ID or Gate (`ALD-073`–`077`).
- - [ ] The mapping explicitly flags which §12 checklist items are research-judgment calls outside this backlog's scope (e.g., "results support the stated hypothesis"), rather than silently omitting them.
+ - [x] A documented release process exists (version scheme, changelog convention, tagging) consistent with §1's normative-language conventions.
+ - [x] Every software-verifiable item in the §12 publication checklist is mapped to at least one `ALD-XXX` ID or Gate (`ALD-073`–`077`).
+ - [x] The mapping explicitly flags which §12 checklist items are research-judgment calls outside this backlog's scope (e.g., "results support the stated hypothesis"), rather than silently omitting them.
+
+### EPIC-16 — Research-Execution Readiness
+
+**Goal:** turn the completed software foundation into a reproducible, fail-closed operator path for research preparation without representing qualification as empirical evidence. **Depends on:** EPIC-14 and EPIC-15. **Acceptance gate:** E03 design and registration artifacts reproduce, the preflight blocks incomplete confirmatory bindings, a real open-weight model completes the frozen track, and the consolidated check rejects public-status drift.
+
+#### ALD-081 — Dependency-integrity enforcement
+- **Priority:** P0 · **Size:** S · **Class:** Research-Grade · **Depends on:** ALD-078
+- **Spec refs:** `SPECIFICATION.md` [§17.2 Test Strategy](SPECIFICATION.md#172-test-strategy)
+- **Scope:** Make high/critical dependency advisories a blocking local and hosted check while retaining a machine-readable audit artifact for review.
+- **Acceptance criteria:**
+ - [x] A clean frozen-lockfile install followed by `pnpm audit --audit-level=high` exits zero.
+ - [x] Hosted consolidated CI retains the audit JSON even when another check fails.
+ - [x] Required native dependency install scripts are explicitly allowlisted by exact package version.
+
+#### ALD-082 — Reproducible E03 design simulation and seed manifest
+- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-072
+- **Spec refs:** `RESEARCH.md` [Appendix D.7](RESEARCH.md#d7-sensitivity-and-power)
+- **Scope:** Replace prose-only E03 power calculations with a deterministic Monte Carlo implementation and derived primary/reserve seed manifest.
+- **Acceptance criteria:**
+ - [x] A fixed seed reproduces byte-identical 30,000-replicate design output.
+ - [x] Every registered SD/sample-size row has a lower 95% Monte Carlo bound at or above the 90% power floor.
+ - [x] The committed seed manifest derives disjoint pilot and full-stage scenario,
+ per-role learner, Gateway, and analysis seeds without exposing outcomes.
+ - [x] The pilot reducer requires all five non-oracle conditions and exactly twenty
+ 200-episode slots, emits only dispersion/design inputs, and blocks the frozen table
+ when estimated latent seed-rate dispersion exceeds 0.20.
+
+#### ALD-083 — Canonical E03 registration compiler
+- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-071, ALD-082
+- **Spec refs:** `SPECIFICATION.md` [§15.1 Pre-Registration Binding](SPECIFICATION.md#151-pre-registration-binding), `RESEARCH.md` [Appendix D](RESEARCH.md#appendix-d-registered-e03-control-qualification)
+- **Scope:** Compile E03's registered parameter template, its canonical domain-separated hash, complete seed allocation, and per-condition run-config templates into one validated artifact.
+- **Acceptance criteria:**
+ - [x] The compiler output validates as a `PreRegistrationArtifact` and is byte-identical on repeat.
+ - [x] Run IDs, realized random and component seeds, condition assignments, and the
+ pre-registration hash are excluded from the stable RunConfig template while the
+ complete prospective matrix remains inside the hashed artifact.
+ - [x] Every primary and reserve condition configuration carries the same exact pre-registration hash.
+ - [x] The compiler fixes a twenty-slot zero-reserve pilot and refuses a full E03
+ qualification without the retained pilot and 30,000-repetition power decision.
+
+#### ALD-084 — Fail-closed research preflight
+- **Priority:** P0 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-071, ALD-083
+- **Spec refs:** `SPECIFICATION.md` [§15.1 Pre-Registration Binding](SPECIFICATION.md#151-pre-registration-binding), [§11.9 Experiment Record](SPECIFICATION.md#119-experiment-record)
+- **Scope:** Check the immutable software, configuration, isolation, seed, registration, and pre-run commitment bindings before registered pilot or confirmatory collection and report every blocker together.
+- **Acceptance criteria:**
+ - [x] Human and JSON outputs independently check Mode R, learning isolation, artifact class, hashes, immutable ancestral commits, seed count, complete registration, and confirmed matching pre-run commitment.
+ - [x] A missing repository-native/external registration, mismatched binding, or absent/incorrect commitment blocks confirmatory readiness.
+ - [x] A realized run whose stable configuration, stage, condition, or component
+ seeds do not match one exact registered matrix entry is blocked.
+ - [x] A passing preflight explicitly does not claim public-chain verification or scientific validity.
+
+#### ALD-085 — Real open-weight frozen-model qualification
+- **Priority:** P1 · **Size:** M · **Class:** Research-Grade · **Depends on:** ALD-044, ALD-054, ALD-081
+- **Spec refs:** `SPECIFICATION.md` [§6.7 Model Recommendations (Defaults)](SPECIFICATION.md#67-model-recommendations-defaults), [§10.3 Side-Channel Controls](SPECIFICATION.md#103-side-channel-controls)
+- **Scope:** Exercise the frozen-LLM adapter against a public local model through the loopback-only OpenAI-compatible client and retain privacy-minimized exact provenance.
+- **Acceptance criteria:**
+ - [x] The report binds the exact weight-file hash, inference-runtime archive hash, model identity, quantization, software commit, and seed hash.
+ - [x] At least two episodes exercise intention and interpretation in both Baby roles with tool-only conformance and no policy updates.
+ - [x] The committed report is labeled non-confirmatory and omits prompts, observations, raw output, candidate references, and private ledger content.
+
+#### ALD-086 — Project-status drift enforcement
+- **Priority:** P1 · **Size:** S · **Class:** MVP · **Depends on:** ALD-080, ALD-081, ALD-082, ALD-083, ALD-084, ALD-085
+- **Spec refs:** `SPECIFICATION.md` [§1.5 Relationship to Companion Documents](SPECIFICATION.md#15-relationship-to-companion-documents)
+- **Scope:** Fail the consolidated check when package versions, backlog totals, completion-plan state, README status, or manuscript engineering status disagree.
+- **Acceptance criteria:**
+ - [x] The checker derives acceptance totals from backlog checkboxes rather than accepting a manually supplied number.
+ - [x] Root package/lock versions and the public README/manuscript engineering snapshot must match exactly.
+ - [x] Both local and hosted consolidated checks run the drift checker before build and tests.
## 13. Experiment Readiness Gate Mapping
@@ -1016,7 +1091,7 @@ This table maps every experiment in `EXPERIMENT-NOTEBOOK.md` §7–§8 to the re
| Experiment | Gate | Gate Item | Underlying Readiness Items |
|---|---|---|---|
-| [E00](EXPERIMENT-NOTEBOOK.md#e00-ledger-integrity-and-base-anchoring) | G1 | ALD-073 | ALD-015, ALD-021 |
+| [E00](EXPERIMENT-NOTEBOOK.md#e00-ledger-integrity-and-simulated-commitment) | G1 | ALD-073 | ALD-015, ALD-021 |
| [E01](EXPERIMENT-NOTEBOOK.md#e01-channel-isolation-and-side-channel-red-team) | G1 | ALD-073 | ALD-067 |
| [E02](EXPERIMENT-NOTEBOOK.md#e02-observation-and-metadata-leakage-audit) | G1 | ALD-073 | ALD-038, ALD-039, ALD-068 |
| [E03](EXPERIMENT-NOTEBOOK.md#e03-chance-no-communication-and-random-message-controls) | G1 | ALD-073 | ALD-029, ALD-035, ALD-036, ALD-041, ALD-042, ALD-072 |
@@ -1069,22 +1144,46 @@ Every top-level `SPECIFICATION.md` section maps to at least one backlog item. Se
These are working decisions this backlog encodes. Where a decision is not yet made in the source documents, it is marked accordingly rather than invented.
-- **Runtime/language:** Node.js + TypeScript, npm workspaces monorepo (ALD-001). No calendar dates or staffing levels are assumed anywhere in this backlog; all sequencing is dependency-based.
+- **Runtime/language:** Node.js + TypeScript, pnpm workspaces monorepo (ALD-001). No calendar dates or staffing levels are assumed anywhere in this backlog; all sequencing is dependency-based.
- **Evidence store:** SQLite in WAL mode is the sole authoritative local store (ALD-005), per `LEDGER-INTEGRITY-DESIGN.md` [§18. Recommended Initial Decision](LEDGER-INTEGRITY-DESIGN.md#18-recommended-initial-decision). No external database is introduced by this backlog.
- **Anchoring:** Base Sepolia is the default and only unconditional anchoring target (ALD-020); mainnet anchoring exists only as an explicit, separately-configured opt-in (ALD-022), never a default.
- **Model default:** `scratch-rl` (ALD-045) is the primary scientific baseline; the local open-weight `frozen-llm` adapter (ALD-044) is the orchestration-validation default. `no-learning` (ALD-042), `self-supervised` (ALD-046), and `hybrid` (ALD-047) are explicit selections.
+- **Recurrent scientific baseline.** `gru-actor-critic-v1` uses a 16-unit GRU, separate sender/receiver/value heads, Xavier initialization, Adam at `0.003`, four PPO-style epochs, clip `0.2`, value coefficient `0.5`, and gradient-norm cap `1`. Scratch RL and self-supervision serialize the same parameter/optimizer/checkpoint shape; the reward-free track replaces the actor/value objective with partner-message predictive cross-entropy and cannot read scalar reward or outcome labels. Tabular REINFORCE and positional count models remain explicit reference controls rather than the scientific default.
- **Isolation:** Mode R uses separate containers/processes per learner with no shared mutable state (ALD-055); Mode P has no such isolation guarantee and must never be labeled with a Mode-R-only claim (ALD-054).
- **On-chain privacy:** only checkpoint root hashes and minimal metadata are ever placed on-chain (ALD-018, ALD-020); no raw observation, model, or private data is anchored, per `SPECIFICATION.md` [§13.6](SPECIFICATION.md#136-privacy-controls) and `LEDGER-INTEGRITY-DESIGN.md` [§12](LEDGER-INTEGRITY-DESIGN.md#12-privacy).
- **Research vs. software boundary:** this backlog treats every `E00`–`E50` experiment as something the software must make *executable*, never as a task this backlog itself completes. EPIC-14's gates check capability readiness only (§13 of this document is explicit about this).
- **Cryptography research boundary:** `ALD-069`'s ephemeral-encoding harness is explicitly research instrumentation; `ALD-070` enforces that its output is never substituted for the production integrity mechanisms in EPIC-02/EPIC-04.
- **Sizing/priority are relative, not calendar-based:** S/M/L reflect complexity, not effort-days; P0/P1/P2 reflect blast radius on integrity/critical-path/research-readiness, not business value.
-- **Open/undecided (not invented here):** the specific open-weight frozen-LLM model, exact retention durations for §14.6, and exact confirmation-depth thresholds for §13.4 are left to be filled in when their owning item (ALD-044, ALD-062, ALD-021 respectively) is picked up, using whatever value the source documents specify at that time — this backlog does not invent them.
+- **Resolved during implementation:** the open-weight frozen-LLM default is `qwen3-4b-q4-k-m` (ALD-044/ALD-085, `reports/qualification/frozen-model-qwen3-4b-q4-k-m.json`); retention uses `prototypeRetentionDays: 30` (SPEC §18, ALD-062); confirmation depth uses `1-confirmation` (Sepolia) / `safe-tag` (mainnet) (SPEC §18, ALD-021).
+
+### Implementation decisions recorded during the verifiable-core build (2026-09-07)
+
+These resolve details the source documents name but do not fix. Each is implemented, tested, and referenced from code comments.
+
+- **Hash domains.** LEDGER-INTEGRITY-DESIGN.md and SPECIFICATION.md name six domain separators; the implementation defines the remaining ones in `packages/types/src/domains.ts` (`HASH_DOMAINS`): channel event, affect event and measurement, audit-ledger entry, intervention event and causal probe, turn record, Baby proposal, rejected payload, run config/id/manifest, pre-registration, scenario bundle/state, observation, action, outcome, prompt bundle, policy checkpoint, carrier-form inventory, glyph bundle, retention log, failure message, runtime snapshot, encoding scheme, nonce commitment, and PRNG seed. All follow `SHA-256(domain || 0x00 || payload)`; only Merkle interior nodes use `0x01`.
+- **Turn-record stream.** SPEC §14.3 needs per-turn hashes for the replay digest, so the Evidence Store carries an implementation-defined `turns` stream (`turn_records`, migration v2), witness-signed and committed as the auxiliary checkpoint tree `turns`. `intervention_log` stays hash-chained but unsigned and is exported without a checkpoint tree (LEDGER §8 auxiliary trees require a writer key).
+- **Checkpoint manifest `reason`.** LEDGER §8's manifest is illustrative; the implementation adds a `reason` enum recording the LEDGER §9 trigger so intervention checkpoints are verifiable.
+- **Empty trees.** A stream with no events is committed as `treeSize 0`, `EMPTY_MERKLE_ROOT`, `lastEntryHash = GENESIS_HASH`; a size-0 auxiliary tree is omitted from `auxiliaryTrees` and a verifier reads the omission as the empty tree (docs/evidence-bundle-format.md §6). A single-leaf Merkle root is the leaf hash (leaves are already domain-hashed).
+- **Ledger draft `evidenceRefs`.** Non-empty `evidenceRefs` on a draft are merged into `content.evidenceRefs` of the committed event (LEDGER §4 example shape).
+- **Communication controls.** `constant` delivers `{ symbols: [inventory[0]] }` unless pre-registered otherwise; `random` draws a length uniformly in `[1, maxSymbolsPerMessage]` then uniform symbols; `shuffled` uses a per-batch seeded derangement and is permitted only for `no-learning` tracks (its pre-pass requires stateless adapters); `oracle` bypasses both learners (artifact and decode from researcher ground truth). Timeouts and other payload-less rejections hash canonical `null`.
+- **Anchor receipts.** `anchor_receipts` is append-only with one row per transaction, so the publisher inserts exactly one row at a terminal decision (`confirmed`, `failed`, or `submitted` after the poll budget); pending submissions live in a non-evidence sidecar file. `safe-tag` finality is an interim 32-confirmation proxy (ADR-05). Mainnet requires both `allowMainnet: true` and `ALD_ALLOW_MAINNET_ANCHORING=true`.
+- **Experiment records.** Version 1 is written at run creation with `disposition: invalid` (SPEC §7.2: a run is not `valid` until the verifier passes), placeholder `checkpointManifestRef = GENESIS_HASH`, `anchorTxRef = 0x00…`, `verifierReportRef = pending`. Sealing appends v2 (checkpoint/anchor refs) and v3 (verifier result); the bundle's `experiment-record.json` is rewritten after v3.
+- **Unanchorable prototype runs.** With no anchor publisher configured (`anchorPolicy: skip`, prototype only) sealing follows SPEC §7.2 literally: `sealing → sealing-blocked → abandon-recovery → aborted-sealed`, recording a `governance-decision` intervention (`anchoring-skipped-prototype-mode`) and a deviation. Such runs are never `valid`.
+- **Verifier local-integrity mode.** `--allow-unanchored` downgrades only the absence of a confirmed final anchor and the unanchored tail; every other failure still exits 1.
+- **Adapter failures.** SPEC §14.5: one retry, then forfeit the turn (null action, turn record written), append a `safety-trigger` intervention, and pause. `evaluating` has no `pause` row in §7.2, so a trigger there records `pause-not-available` and continues (spec gap flagged).
+- **Run configuration.** Optional `evaluationTurns` (runtime default 200) fixes the evaluation-phase budget; `symbolInventorySize`/`maxSymbolsPerMessage` are valid only for `fixed-token` and `maxStrokes` only for `generative-canvas`.
+- **Learner contracts.** Files carry a `` header exempt from the banned-pattern lint; `promptBundleHash` is the canonical hash of `{ track: text }`.
+- **Signer boundary.** Public-study per-run seeds are encrypted in `safe` and
+ materialized only through `fort` files mode. The Nursery accepts a mode-0600
+ regular file with an exact run-id map and never mounts it into either learner;
+ runId grammar remains `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`.
+- **Twin routes (Mode P).** Roles arrive as `x-ald-role` + `x-ald-service-token`; default dev tokens `dev-` exist only for prototype mode. `POST /reset` returns 501 until the runtime exposes adapter re-initialization. `GET /runs` is added as a read-only convenience implied by the session routes.
## 16. Appendix: ID Index
| ID | Title | Epic |
|---|---|---|
-| ALD-001 | npm workspaces monorepo bootstrap | EPIC-01 |
+| ALD-001 | pnpm workspaces monorepo bootstrap | EPIC-01 |
| ALD-002 | Shared `@ald/types` schema package | EPIC-01 |
| ALD-003 | Environment, configuration, and secrets convention | EPIC-01 |
| ALD-004 | DTSF twin pack scaffolding (baby-a, baby-b, nursery) | EPIC-01 |
@@ -1164,3 +1263,9 @@ These are working decisions this backlog encodes. Where a decision is not yet ma
| ALD-078 | CI pipeline and consolidated test suite | EPIC-15 |
| ALD-079 | Architecture/API/operations documentation set | EPIC-15 |
| ALD-080 | Release process and publication-checklist mapping | EPIC-15 |
+| ALD-081 | Dependency-integrity enforcement | EPIC-16 |
+| ALD-082 | Reproducible E03 design simulation and seed manifest | EPIC-16 |
+| ALD-083 | Canonical E03 registration compiler | EPIC-16 |
+| ALD-084 | Fail-closed research preflight | EPIC-16 |
+| ALD-085 | Real open-weight frozen-model qualification | EPIC-16 |
+| ALD-086 | Project-status drift enforcement | EPIC-16 |
diff --git a/CONFIGURATION.md b/CONFIGURATION.md
index 0adb06ff..02cd0f17 100644
--- a/CONFIGURATION.md
+++ b/CONFIGURATION.md
@@ -1,8 +1,9 @@
# Configuration
Runtime configuration is loaded through `@ald/config`. Environment variables contain
-only operational values and paths. Private key material and API tokens must never be
-stored directly in environment files committed to this repository.
+only non-secret operational values or short-lived file paths materialized by `fort`.
+Private key material, signer seeds, RPC credentials, and API tokens must never be
+stored directly in environment variables or repository files.
## Variables
@@ -13,32 +14,74 @@ stored directly in environment files committed to this repository.
| `ALD_DEPLOYMENT_MODE` | `prototype` or `research-grade` | `prototype` | No |
| `ALD_EVIDENCE_DIR` | Path | `./evidence` | No |
| `ALD_DATABASE_PATH` | Path | `/ald.sqlite` | No |
-| `ALD_KEY_DIR` | Path | `/keys` | Yes in research-grade mode |
+| `ALD_RUN_SIGNER_SEEDS_JSON_FILE` | Fort-materialized path | None | Yes in research-grade mode |
| `ALD_LOG_LEVEL` | `debug`, `info`, `warn`, or `error` | `info` | No |
+| `ALD_ANCHOR_CLASS` | `simulated` or `public-chain` | `simulated` | No |
| `ALD_BASE_NETWORK` | `base-sepolia` or `base-mainnet` | `base-sepolia` | No |
-| `ALD_BASE_RPC_URL` | URL | None | Required only when anchoring is enabled |
-| `ALD_ANCHOR_KEY_FILE` | Path | None | Required only when anchoring is enabled |
+| `ALD_BASE_RPC_URL_FILE` | Fort-materialized path | None | Required only for an amended `public-chain` profile |
+| `ALD_ANCHOR_KEY_FILE` | Fort-materialized path | None | Required only for an amended `public-chain` profile |
+| `ALD_ALLOW_MAINNET_ANCHORING` | `true` or unset | unset | Must be `true` (together with an explicit publisher opt-in) before any Base mainnet transaction is submitted |
## Secret Handling
-- Store private keys in files outside the repository and provide only their paths.
+- Store all secret values only in the encrypted `safe` repository and access them
+ through `fort`; do not create ad-hoc secret files.
- Do not commit `.env` files. The repository ignores `.env` and `.env.*`.
-- Use a dedicated, low-balance anchor wallet.
-- Run `npm run scan:secrets` before committing.
-- Research-grade mode fails fast unless `ALD_KEY_DIR` is explicitly configured.
+- The approved research `RunConfig` defaults to `anchorClass: "simulated"` and uses
+ no wallet, RPC credential, faucet, token, or fee.
+- If a future amendment permits `anchorClass: "public-chain"`, use a dedicated,
+ low-balance anchor wallet and independent verification endpoint.
+- Run `pnpm run scan:secrets` before committing.
+- Research-grade mode fails fast unless Fort materializes
+ `ALD_RUN_SIGNER_SEEDS_JSON_FILE` in files mode.
+- Mainnet anchoring is double opt-in: the anchor publisher must be constructed with `allowMainnet: true` and `ALD_ALLOW_MAINNET_ANCHORING=true` must be set. The current governance policy separately prohibits all public-chain research transactions.
+- The signer material is a versioned JSON envelope containing an exact run-id map
+ and all six Ed25519 signer domains. The Nursery reads its mode-0600 regular file
+ once; the path is then removed from the child environment. Learner containers
+ never receive the path or file mount. Seeds never enter the evidence store or a
+ bundle.
## Examples
Prototype defaults require no environment variables:
```powershell
-npm run build
+pnpm run build
```
-Research-grade mode requires an explicit isolated key directory:
+Research-grade Mode R uses Fort file materialization:
-```powershell
-$env:ALD_DEPLOYMENT_MODE = 'research-grade'
-$env:ALD_KEY_DIR = 'C:\ald-secrets\keys'
-npm run build
+```sh
+fort --host https://fort.aureuma.ai run --repo agentic-language-development --env dev \
+ --keys ALD_RUN_SIGNER_SEEDS_JSON --mode files -- \
+ pnpm run test:mode-r-study
```
+
+The encrypted value must authorize each exact study run id. The qualification
+command can run without credentials using ephemeral in-memory signers, but that
+path is explicitly non-confirmatory. Simulated funding removes the wallet/RPC
+dependency; it does not remove the persistent per-run signer or registration gates.
+
+## Toolchain Law (permanent user order, 2026-10-04)
+
+- NEVER install anything via Homebrew on this machine (old installation, no
+ bottles; R was attempted and abandoned for this reason).
+- System packages go through MacPorts ONLY (`port`).
+- Python goes through uv ONLY (`~/.local/bin/uv`, already installed; do NOT
+ reinstall it via MacPorts). Project environments live in the repo `.venv`.
+- The independent-statistics sidecar is Python/scipy (port of the former R
+ scripts); R/Rscript must not be reintroduced to code, docs, or CI.
+
+## Independent-statistics venv setup
+
+The confirmatory gates spawn `scripts/*.py` through the repo-local `.venv`
+(resolved by `scripts/resolve-venv-python.mjs`; no PATH or absolute-path setup).
+Create it once per checkout:
+
+```sh
+uv venv --python 3.13
+uv pip install --python .venv/bin/python -r requirements.txt
+```
+
+`requirements.txt` pins the exact numpy/scipy versions the frozen receipts were
+generated with. CI provisions the same venv via `astral-sh/setup-uv`.
diff --git a/EXPERIMENT-NOTEBOOK.md b/EXPERIMENT-NOTEBOOK.md
index 796781b1..94976f55 100644
--- a/EXPERIMENT-NOTEBOOK.md
+++ b/EXPERIMENT-NOTEBOOK.md
@@ -2,7 +2,7 @@
> **Document type:** Research protocol and results notebook
>
-> **Status:** Ready for pre-registration; no experiments completed
+> **Status:** Design draft; not registration-ready; no experiments completed
>
> **Companion documents:**
> [CONCEPT-IDEA.md](CONCEPT-IDEA.md) and
@@ -28,7 +28,7 @@ the authoritative proof of what occurred.
| Status | Meaning |
|---|---|
-| Not started | Protocol exists but no pre-registration is sealed |
+| Not started | No run has begun for the current attempt; registration state is recorded by its execution gate and evidence note |
| Pre-registered | Hypothesis, parameters, seeds, and analysis plan are committed |
| Running | At least one planned run is active |
| Evidence review | Runs ended; integrity and protocol checks are in progress |
@@ -61,18 +61,18 @@ permits otherwise.
| Field | Value |
|---|---|
| Study title | Agentic Language Development Nursery Lab |
-| Study version | `TBD` |
-| Principal investigator | `TBD` |
-| Research team | `TBD` |
+| Study version | `0.1.81` prospective design snapshot |
+| Principal investigator | project-operator role |
+| Research team | research-operator, integrity-verifier, and data-steward roles |
| Repository | `Ethical-Tech-CoLab/agentic-language-development` |
| DTSF version / commit | `TBD` |
| Evidence verifier version | `TBD` |
-| Base anchor network | Base Sepolia for qualification; Base mainnet for public studies |
-| Public anchor address / contract | `TBD` |
+| Commitment profile | deterministic simulation of Base Sepolia semantics; no public transaction |
+| Public anchor address / contract | Not applicable under `ALD-GOV-2026-09-12-01` |
| Study start date | `TBD` |
| Study end date | `TBD` |
-| Ethics or governance review | `TBD` |
-| Data-retention policy | `TBD` |
+| Ethics or governance review | `ALD-GOV-2026-09-12-01`: synthetic-only, no human participants or human-coded outcomes |
+| Data-retention policy | Eligible research evidence indefinite; development bulk payloads 30 days; immutable metadata retained |
## 5. Default Experimental Invariants
@@ -89,9 +89,28 @@ Unless an experiment explicitly varies one of these, hold it constant:
[LEDGER-INTEGRITY-DESIGN.md](LEDGER-INTEGRITY-DESIGN.md);
- the affect channel is disabled unless explicitly under study;
- experiment schedules and seeds are fixed before the run;
+- software-qualification, blinded-pilot, confirmatory, and replication seeds use
+ disjoint SHA-256 domains; paired conditions share only their scenario seed;
+- pilot outcomes select only the predeclared seed-count prefix and never enter a
+ confirmatory or replication estimate;
- held-out evaluation runs with learning disabled;
- no production secrets or personal data appear in any experiment.
+The exact derivation, tuning limit, candidate N grid, ordered reserves, per-condition
+allocations, and current zero-spend local resource ceiling are frozen in
+[`protocols/seed-and-resource-allocation.v1.json`](protocols/seed-and-resource-allocation.v1.json).
+The later nine-member confirmatory seed selection follows the prospective
+[joint-family clarification](protocols/confirmatory-family-selection-amendment.v1.json):
+the first shared candidate passing the complete lower-bound gate, not the
+largest qualifying candidate. No pilot has supplied that selection yet.
+The machine-checked [design-readiness inventory](protocols/confirmatory-design-readiness.v1.json)
+records the earlier gate: all nine estimands have executable metrics. The separate
+[practical-margin policy](protocols/confirmatory-practical-margins.v1.json) freezes
+H5's rule, H8's spaces, and all nine margins before outcomes. Every family pilot
+remains uncollected, so the frozen policy supplies no variance or result.
+The generated ledger contains design commitments only; it is not evidence that any
+pilot or study seed has run.
+
## 6. Standard Run Record
Copy this block into the experiment's result section for every run.
@@ -172,33 +191,33 @@ E01 Channel isolation ---- E02 Observation leakage
## 8. Experiment Index
-| ID | Experiment | Depends on | Status | Result |
-|---|---|---|---|---|
-| E00 | Ledger integrity and Base anchoring | None | Not started | — |
-| E01 | Channel isolation and side-channel red team | E00 | Not started | — |
-| E02 | Observation and metadata leakage audit | E00 | Not started | — |
-| E03 | Chance, no-communication, and random-message controls | E01, E02 | Not started | — |
-| E10 | Frozen pretrained-LLM protocol baseline | E03 | Not started | — |
-| E11 | From-scratch RL Naming Game | E03 | Not started | — |
-| E12 | Self-supervised ungrounded baseline | E11 infrastructure | Not started | — |
-| E13 | No predefined symbol library | E11 or E12 | Not started | — |
-| E14 | Turn-taking, role reversal, and repair | E13 | Not started | — |
-| E15 | Composition and held-out generalization | E14 | Not started | — |
-| E16 | Causal listening and ledger validity | E15 | Not started | — |
-| E20 | Constrained affect-channel study | E16 | Not started | — |
-| E21 | RL versus non-RL learning comparison | E16 | Not started | — |
-| E22 | Developmental plasticity and curriculum | E16 | Not started | — |
-| E30 | Partner replacement and zero-shot transfer | E20-E22 | Not started | — |
-| E31 | Longitudinal drift and stability | E30 | Not started | — |
-| E32 | Cooperative signaling versus negotiation | E31 | Not started | — |
-| E40 | Ephemeral encoding and adversarial cryptography | E32 | Not started | — |
-| E50 | Multi-seed replication and study closeout | E40 | Not started | — |
+| ID | Experiment | Depends on | Attempt | Scientific disposition | Next execution gate | Evidence note |
+|---|---|---|---|---|---|---|
+| E00 | Ledger integrity and simulated commitment | None | Completed | Not tested | Qualification complete | V2/v4 failures preserved; v5 passed |
+| E01 | Channel isolation and side-channel red team | E00 | Completed | Not tested | Qualification complete | V2 passed five registered slots with 560 signed records and both verifiers |
+| E02 | Observation and metadata leakage audit | E00 | Completed | Not tested | Qualification complete | V1/v2 failures retained; v3 passed five numeric software slots, recovery stages, 60 probes, and full raw audit; no behavioral finding |
+| E03 | Chance, no-communication, and random-message controls | E01, E02 | Completed | Not tested | Qualification complete | Registered Prototype-Mode v1 completed 150/150 required primary runs with verified bundles, 0 invalid and 0 reserve attempts; all fixed thresholds and the separate 150-bundle audit passed; 18 reserves correctly remained unattempted; this is control software qualification, not a behavioral finding |
+| E10 | Frozen pretrained-LLM protocol baseline | E03 | Not started | Not tested | Confirmatory blocked | — |
+| E11 | From-scratch RL Naming Game | E03 | Not started | Not tested | Confirmatory blocked | — |
+| E12 | Self-supervised ungrounded baseline | E11 infrastructure | Not started | Not tested | Confirmatory blocked | — |
+| E13 | No predefined symbol library | E11 or E12 | Not started | Not tested | Confirmatory blocked | — |
+| E14 | Turn-taking, role reversal, and repair | E13 | Not started | Not tested | Confirmatory blocked | — |
+| E15 | Composition and held-out generalization | E14 | Not started | Not tested | Confirmatory blocked | — |
+| E16 | Causal listening and ledger validity | E15 | Not started | Not tested | Confirmatory blocked | — |
+| E20 | Constrained affect-channel study | E16 | Not started | Not tested | Confirmatory blocked | — |
+| E21 | RL versus non-RL learning comparison | E16 | Not started | Not tested | Confirmatory blocked | — |
+| E22 | Developmental plasticity and curriculum | E16 | Not started | Not tested | Confirmatory blocked | — |
+| E30 | Partner replacement and zero-shot transfer | E20-E22 | Not started | Not tested | Confirmatory blocked | — |
+| E31 | Longitudinal drift and stability | E30 | Not started | Not tested | Confirmatory blocked | — |
+| E32 | Cooperative signaling versus negotiation | E31 | Not started | Not tested | Confirmatory blocked | — |
+| E40 | Ephemeral encoding and adversarial cryptography | E32 | Not started | Not tested | Exploratory blocked | — |
+| E50 | Multi-seed replication and study closeout | E40 | Not started | Not tested | Replication blocked | — |
---
-## E00. Ledger Integrity and Base Anchoring
+## E00. Ledger Integrity and Simulated Commitment
-**Status:** Not started
+**Status:** Completed—v5 prospectively registered software qualification passed
**Purpose:** Qualify the evidence system before collecting behavioral data.
@@ -207,34 +226,35 @@ pre-registered mutation of a committed ledger or transcript.
### Preparation
-- [ ] Implement local hash chains and Ed25519 signatures.
-- [ ] Implement ordered Merkle checkpoints.
-- [ ] Implement standalone verifier.
-- [ ] Configure Base Sepolia anchor wallet.
-- [ ] Pre-register checkpoint frequency and finality rule.
-- [ ] Seal protocol commit and configuration hashes.
+- [x] Implement local hash chains and Ed25519 signatures.
+- [x] Implement ordered Merkle checkpoints.
+- [x] Implement standalone verifier.
+- [x] Bind `anchorClass: "simulated"` and configure the deterministic transport.
+- [x] Pre-register checkpoint frequency and finality rule.
+- [x] Seal protocol commit and configuration hashes.
### Procedure
-- [ ] Create a synthetic run with at least 100 events in each Baby ledger.
-- [ ] Produce at least three checkpoints.
-- [ ] Anchor each checkpoint to Base Sepolia.
-- [ ] Verify the unchanged bundle through an independent RPC provider.
-- [ ] Modify one event payload and verify rejection.
-- [ ] Delete a middle event and verify rejection.
-- [ ] Insert an event and verify rejection.
-- [ ] Reorder two events and verify rejection.
-- [ ] Replace a signature and verify rejection.
-- [ ] Modify a Merkle proof and verify rejection.
-- [ ] Present a checkpoint from the wrong chain and verify rejection.
-- [ ] Add an unanchored tail and verify that it is reported.
-- [ ] Restore from checkpoint and append new events without reusing a sequence number.
+- [x] Create a synthetic run with at least 100 events in each Baby ledger.
+- [x] Produce at least three checkpoints.
+- [x] Commit each checkpoint through the deterministic simulation transport.
+- [x] Verify the unchanged bundle through both independent verifier implementations.
+- [x] Relabel a simulated receipt as public-chain evidence and verify rejection.
+- [x] Modify one event payload and verify rejection.
+- [x] Delete a middle event and verify rejection.
+- [x] Insert an event and verify rejection.
+- [x] Reorder two events and verify rejection.
+- [x] Replace a signature and verify rejection.
+- [x] Modify a Merkle proof and verify rejection.
+- [x] Present a checkpoint from the wrong chain and verify rejection.
+- [x] Add an unanchored tail and verify that it is reported.
+- [x] Restore from checkpoint and append new events without reusing a sequence number.
### Acceptance Criteria
- unchanged evidence passes;
- every mutation case fails verification;
-- the final checkpoint hash matches Base calldata or the anchor event;
+- the final checkpoint hash matches the simulated-chain calldata or anchor event;
- restart produces a consistent extension proof;
- no private ledger content appears on-chain.
@@ -242,23 +262,56 @@ pre-registered mutation of a committed ledger or transcript.
| Metric | Planned | Observed |
|---|---:|---:|
-| Valid bundle accepted | 100% | `TBD` |
-| Mutation cases detected | 100% | `TBD` |
-| Anchor receipts verified | 100% | `TBD` |
-| Private content found on-chain | 0 | `TBD` |
-
-- [ ] Integrity acceptance criteria met
-- [ ] Evidence review complete
-- [ ] Base anchor verified
-- [ ] Result committed
-
-**Result summary:** `Not run`
+| Valid bundle accepted | 100% | v5: 5/5 accepted by both verifiers |
+| Mutation cases detected | 100% | v5: 55/55 rejected by both verifiers |
+| Simulated anchor receipts verified | 100% | v5: 5/5 slots anchored with checkpoint-hash payloads |
+| Private content found in simulated commitments | 0 | v5: 0/5 slots had a non-hash commitment payload |
+
+- [x] Integrity acceptance criteria met
+- [x] Evidence review complete
+- [x] Simulated commitment verified
+- [x] Result committed
+
+**Result summary:** v2 slot 1 stopped at the first dual-verifier disagreement. Slots
+2-5 were not attempted and slot 1 will not be rerun into success. The Rust auditor
+did not inspect inclusion-proof files; the failed receipt is retained at
+`reports/research/e00-integrity-qualification-attempt-1.json`. V3 was superseded
+without execution after a source-binding cycle was found. V4 binds the frozen
+scientific protocol, records exact implementation identity at execution, and uses a
+fresh seed domain before any further attempt.
+
+The v4 packet is immutable at commit
+`60cc54da47fc0ad8b49dfddc418e7839c8d9d409`, and its matching deterministic
+in-memory-chain commitment is confirmed before outcome execution. This establishes
+prospective local byte binding, not an independently witnessed timestamp or public
+chain transaction.
+
+V4 then completed all five registered slots. Both verifiers accepted all five
+unchanged bundles and rejected 50/55 registered mutation instances. The TypeScript
+verifier rejected all five simulated-to-public-chain relabels, while the Rust
+auditor accepted them. The complete failure receipt is retained at
+`reports/research/e00-integrity-qualification-attempt-2.json`, all five unchanged
+signed bundles remain under `evidence/qualification/e00-v4/`, and no v4 seed will be
+rerun into success. V5 binds the repaired anchor-class comparison and a fresh seed
+domain before another attempt. Its immutable packet is registered at commit
+`8cef99cb07daf130c38f93968e5ba24845af49e3` and has a matching confirmed
+deterministic simulated commitment before execution.
+
+V5 executed once on exact clean commit
+`6a3faa8e85cb9f7a9fe7847445897c6e98ee56cb`. Both independent implementations
+accepted all five unchanged bundles and rejected all 55 registered mutation
+instances. Each slot contains 100 events in each Baby ledger and four checkpoints;
+all commitment payloads are 32-byte hashes. First-party recovery and post-restore
+extension tests passed. The receipt is
+`reports/research/e00-integrity-qualification-receipt.json`, and the five retained
+bundles are under `evidence/qualification/e00-v5/`. This qualifies E00 software only;
+it is not evidence for emergent communication or any other behavioral hypothesis.
---
## E01. Channel Isolation and Side-Channel Red Team
-**Status:** Not started
+**Status:** Qualified (software)—v2 explicit corpus passed; B15 closed
**Depends on:** E00
@@ -271,18 +324,18 @@ recorded.
### Procedure
-- [ ] Attempt English and other human-language text.
-- [ ] Attempt arbitrary Unicode and non-allowlisted emoji.
-- [ ] Attempt URLs, code, JSON extensions, and tool-like text.
-- [ ] Attempt message-length and whitespace signaling.
-- [ ] Attempt timing and retry-count signaling.
-- [ ] Attempt model-generated identifiers.
-- [ ] Attempt filesystem, clipboard, environment, and process access.
-- [ ] Attempt direct network access between Baby containers.
-- [ ] Attempt shared cache, vector store, replay buffer, and snapshot access.
-- [ ] Attempt malformed payloads designed to trigger distinguishable errors.
-- [ ] Verify normalized rejection timing and envelope.
-- [ ] Verify every attempt appears in the audit log.
+- [x] Attempt English and other human-language text.
+- [x] Attempt arbitrary Unicode and non-allowlisted emoji.
+- [x] Attempt URLs, code, JSON extensions, and tool-like text.
+- [x] Attempt message-length and whitespace signaling.
+- [x] Attempt timing and retry-count signaling.
+- [x] Attempt model-generated identifiers.
+- [x] Attempt filesystem, clipboard, environment, and process access.
+- [x] Attempt direct network access between Baby containers.
+- [x] Attempt shared cache, vector store, replay buffer, and snapshot access.
+- [x] Attempt malformed payloads designed to trigger distinguishable errors.
+- [x] Verify normalized rejection timing and envelope.
+- [x] Verify every attempt appears in the audit log.
### Acceptance Criteria
@@ -295,31 +348,91 @@ recorded.
| Test category | Attempts | Blocked | Evidence verified |
|---|---:|---:|---:|
-| Human language | `TBD` | `TBD` | `TBD` |
-| Unicode / emoji | `TBD` | `TBD` | `TBD` |
-| Timing / retries | `TBD` | `TBD` | `TBD` |
-| Tools / storage / network | `TBD` | `TBD` | `TBD` |
-| Error behavior | `TBD` | `TBD` | `TBD` |
-
-- [ ] Isolation criteria met
-- [ ] Residual risks documented
-- [ ] Result committed
-
-**Result summary:** `Not run`
+| Registered category decisions | 60 | 60 | Receipt-verified |
+| Recipient-visible Gateway/transport observations | 500 | 500 within registered bounds | Receipt-verified aggregates |
+| Host capability decisions | 25 | 25 | Receipt-verified aggregates |
+| Detector-positive checks | 20 | 20 detected | Receipt-verified aggregates |
+| V2 explicit Gateway rejections | 100 | 100; no recipient delivery | Signed attachments and channel events |
+| V2 host requests (both roles, five paths) | 50 | 50; denied capabilities and refused peer sockets | Signed attachments |
+| V2 accepted/malformed transport captures | 400 | Within registered timing/size/error bounds | Signed raw captures |
+| V2 permitted-delivery controls | 5 | 5 delivered and interpreted | Signed channel, interpretation, and attachment |
+| V2 planted-detector fixture records | 5 | All required detectors identified planted leaks | Signed fixture reports; not live escapes |
+
+- [x] Isolation criteria met across the complete registered notebook corpus
+- [x] Residual risks documented
+- [x] V1 bounded qualification result committed
+
+**Result summary:** V1 ran once on exact clean commit
+`8d3a9f67257ad8c53bc5438926ff8dc23743ed0d` after repository registration and a
+matching deterministic simulated pre-run commitment. Five independently recreated
+two-container slots passed all 60 category decisions, 500 Gateway/transport
+observations, 25 host-capability decisions, and 20 detector-positive checks with no
+external spend or public-chain transaction. The tracked receipt is
+`reports/research/e01-isolation-qualification-receipt.json`; five detailed slot
+records remain under `evidence/qualification/e01-v1/`.
+
+V1's limited category aggregation and unsigned per-attempt observations remain
+unchanged. V2 prospectively registered an explicit corpus and ran once on clean
+commit `4ba1f27561d2ae20261a2e14a5a91e34fe602d56`. All five fresh topologies passed:
+560 signed records, 685 ledger events, ten checkpoints, and 560 attachment inclusion
+proofs. The TypeScript verifier and separate Rust auditor accepted all original
+bundles, and a subsequent retained-evidence replay passed. Receipt:
+`reports/research/e01-v2-qualification-receipt.json`; originals:
+`evidence/qualification/e01-v2/`. This closes B15 within the registered local scope.
+
+The 400 transport observations include accepted and rejected controls, not 400
+blocked attacks. Success and error bodies differ to the Nursery; malformed-error
+bodies are identical across prohibited variants. Storage probes test denied paths,
+not deployed external databases. Planted detector fixtures are not live escapes.
+All anchors are simulated with zero external spend. The finite corpus does not
+establish universal side-channel resistance, independent witnessing, or any
+agent-language finding. Final selected study topologies still require B12's checks.
---
## E02. Observation and Metadata Leakage Audit
-**Status:** Not started
+**Status:** V3 software-qualified; v1/v2 failures retained; scientific disposition not tested
**Depends on:** E00
-**Purpose:** Prove that observations do not silently supply human labels or target
-answers.
-
-**Hypothesis:** A classifier using only prohibited metadata cannot predict target
-identity above the pre-registered chance interval.
+**Purpose:** Test whether the declared numeric observation and metadata surface
+leaks hidden target information within the registered probe family.
+
+**Hypothesis:** Every frozen linear metadata, identifier and timing probe has a
+one-sided 95% upper accuracy advantage at most 0.10 over the held-out majority
+baseline; its planted one-hot control has lower advantage at least 0.20.
+
+**Registration:** E02 v1 is repository-registered at
+`4d7068b5ccc1c8259239923d88ebff10fb85a3b1`, with canonical hash
+`sha256:b59c0e22e8c4cc84331723a9266055d653c8341dbd9a326a11aa5ec7ad2fc413`
+and the matching simulated commitment in `protocols/e02-registration-binding.v1.json`.
+The five fresh slots collect 2,016 observations per role before and after a real
+runtime restore, with at least 501 held-out rows per probe. All 60 slot/role/stage/probe
+decisions remain separate. Development evidence is excluded; v1 had no completed qualification result.
+The asset-free numeric baseline has no image or text-aligned encoder surface;
+the separate retained OCR diagnostic does not qualify future image observations.
+
+**Execution:** The original five-slot attempt started on 2026-09-13 at 14:16:52 UTC
+from clean commit `895d59a90b66cf74603d58f1962c58f98e29dfb9` after all 1,933 tests
+and consolidated gates passed. Slot 1 recorded 465 turns and 930 delivered
+observations before five consecutive timeout rejections triggered the registered
+safety pause. The collector then stopped when it attempted turn 466 against the
+paused run. Snapshot/restore and probe evaluation were not reached; slots 2–5 were
+not attempted. The terminal receipt, attempt, partial database, bundle, and log are
+retained. This seed set will not be restarted or relabeled as passing evidence.
+
+**Later attempts:** The separate v2 allocation stopped after 161 committed turns
+when a post-action deadline escaped without a forfeited-turn record. Its first slot
+failed before recovery/probes, and four slots were unused. The prospectively amended
+v3 allocation kept the original 2,016-row stages, fixed probe family, margins,
+positive controls, and five-slot requirement while using fresh identifiers. Its
+one sequential attempt on commit `9ec08ecf4528c6da8867d55900d6ca8667ded7ce`
+completed all five numeric scratch-learner slots, restored the real runtime and
+database within each, and passed all 60 reports. The [v3 terminal receipt](reports/research/e02-v3-qualification-receipt.json)
+and separate [full raw audit](reports/research/e02-v3-full-audit-receipt.json)
+bind the original records. This is bounded software qualification, not a
+behavioral language or universal sensory-leakage result.
### Procedure
@@ -341,19 +454,34 @@ identity above the pre-registered chance interval.
- initially ungrounded runs use no text-aligned encoder;
- all exceptions are documented as separate experimental conditions.
-### Results
+### Historical v1 result (not reached)
| Probe | Chance | Observed | 95% CI | Decision |
|---|---:|---:|---|---|
-| Metadata-only target prediction | `TBD` | `TBD` | `TBD` | `TBD` |
-| Identifier-only prediction | `TBD` | `TBD` | `TBD` | `TBD` |
-| Timing-only prediction | `TBD` | `TBD` | `TBD` | `TBD` |
+| Metadata-only target prediction | `TBD` | — | — | `Not reached` |
+| Identifier-only prediction | `TBD` | — | — | `Not reached` |
+| Timing-only prediction | `TBD` | — | — | `Not reached` |
- [ ] Observation hygiene criteria met
- [ ] Encoder provenance recorded
- [ ] Result committed
-**Result summary:** `Not run`
+**Result summary:** The registered v1 and v2 attempts failed before analysis.
+Neither produced a probe decision or behavioral result.
+
+### V3 numeric software-qualification decisions
+
+| Fixed probe family | Reports | Largest recomputed 95% probe upper accuracy bound | Smallest planted-control lower bound | Software decision |
+|---|---:|---:|---:|---|
+| Metadata | 20 | 0.335 | 0.995 | Passed |
+| Identifier | 20 | 0.340 | 0.995 | Passed |
+| Timing | 20 | 0.300 | 0.995 | Passed |
+
+Each report used 502–503 held-out rows and the unchanged registered advantage
+margin and positive-control rule. The full five-slot receipt reports all 60
+decisions as passing; the separate live audit recomputed them from retained raw
+evidence. The scientific disposition remains **not tested**. Image/text-aligned
+encoder leakage and behavior in later study topologies remain unqualified.
---
@@ -371,6 +499,19 @@ messages are randomized, or receiver access to messages is shuffled.
A numerically complete worked registration for this experiment is provided in
[RESEARCH.md Appendix D](RESEARCH.md#appendix-d-worked-preregistration-example-e03-controls).
+Its D05 amendment uses seed-level primary tests, treats high-rate seeds as mandatory
+leakage-review triggers rather than an uncalibrated count rejection, and is backed by
+the independent bounded simulation in
+[`docs/statistical-validation-and-power.md`](docs/statistical-validation-and-power.md).
+The prospective [infrastructure-mode amendment](protocols/e03-prototype-mode-amendment.v1.json)
+binds E03 pilot and qualification to Prototype Mode, which the specification permits
+for E00–E03. Separate learner containers may still be used for transport mechanics,
+but controller-held writer/signer keys do not qualify the full Research-Grade process
+boundary or a side-channel-isolation claim. The current six-condition development
+attempt remains mechanics/resource evidence only; an exact Prototype-Mode topology
+binding and measured pilot allocation are required before a pilot packet is activated.
+The compiler must bind both receipts by digest and reject incomplete topology,
+under-reserved design, or an allocation beyond the local zero-spend ceiling.
### Conditions
@@ -388,6 +529,22 @@ A numerically complete worked registration for this experiment is provided in
- [ ] Preserve identical observation and action interfaces.
- [ ] Calculate confidence intervals and effect sizes.
- [ ] Verify all evidence bundles before analysis.
+- [ ] Reconcile operational tallies and paired scenario hashes to signed turn
+ and channel streams, rejecting missing or duplicate evaluation turns.
+- [ ] Require signed-original pairing and control tallies for the exact
+ Prototype-Mode development topology before pilot registration.
+- [ ] Reconcile all 120 pilot runs with zero reserves and admit only verified,
+ outcome-blind pilot tallies to variance estimation.
+- [ ] Reconcile the terminal pilot receipt and its attempt counts to the
+ prospective packet before updating current progression status.
+- [ ] Require a matching live controller for any pre-terminal running claim;
+ a stale start record alone does not establish execution progress.
+- [ ] Re-audit the original pilot bundles and recompute the single-use
+ dispersion input before sample-size power selection.
+- [ ] Charge failed attempted slots where CPU is measured, and leave capacity
+ unresolved where a required measurement is missing.
+- [ ] Verify that no execution source changed after the packet's protocol-base
+ commit before admitting a registered pilot allocation.
### Acceptance Criteria
@@ -680,7 +837,8 @@ compositional reuse.
hypotheses predict those effects.
**Hypothesis:** Substituting, masking, or reordering a form changes receiver behavior
-in the direction predicted by the corresponding ledger.
+in the direction predicted by the corresponding ledger, and pre-outcome ledger
+predictions add held-out predictive value beyond non-ledger comparators.
### Procedure
@@ -691,6 +849,11 @@ in the direction predicted by the corresponding ledger.
- [ ] Reorder multi-part messages.
- [ ] Replay identical observations with counterfactual messages.
- [ ] Compare native ledger predictions with human audit interpretations.
+- [ ] Fit transcript-only, task-history, policy-state, random, and majority prediction
+ baselines without access to private ledger content.
+- [ ] Select the strongest eligible baseline using validation data only, then compare
+ its preregistered proper prediction score with the frozen ledger predictor on
+ untouched intervention cases.
- [ ] Record positive signaling and positive listening separately.
- [ ] For the primary H2 contrast, compare ledger-consistent substitutions with
shuffled-control messages in scratch-RL/extrinsic/fixed-token runs using a
@@ -702,7 +865,8 @@ in the direction predicted by the corresponding ledger.
- normal messages outperform constant and shuffled controls;
- receiver actions change under message intervention;
-- ledger predictions exceed the pre-registered chance baseline;
+- pre-outcome ledger predictions improve the registered proper score beyond the
+ validation-selected non-ledger baseline by the registered practical threshold;
- external audit interpretations are labeled separately from agent-native state.
### Results
@@ -714,6 +878,7 @@ in the direction predicted by the corresponding ledger.
| Positive listening | — | — | — | `TBD` |
| Intervention effect size | — | — | — | `TBD` |
| Native ledger agreement | — | — | — | `TBD` |
+| Ledger proper-score increment | — | — | — | `TBD` |
| Human audit-ledger agreement | — | — | — | `TBD` |
- [ ] Causal listening demonstrated
@@ -759,12 +924,15 @@ but can leak referent information unless strictly constrained.
after stratifying by binary success/failure outcome, using a Miller-Madow
bias-corrected discrete estimator and within-outcome permutation null.
- [ ] For each seed, subtract the mean of 1,000 within-outcome permutations from the
- observed Miller-Madow estimate; test whether the seed-bootstrap one-sided 95%
- upper bound on this excess CMI is below 0.02 bits.
+ observed Miller-Madow estimate; test whether the seed-level Student-t one-sided
+ 95% upper bound on this excess CMI is below 0.02 bits. Report the percentile
+ seed-bootstrap upper bound as sensitivity analysis only.
- [ ] Before registration, simulate the complete estimator at the registered
windows-per-seed count and require at least 90% probability that the null-case
upper bound falls below 0.02 bits; otherwise increase windows or seeds without
widening the bound.
+- [ ] Use a blinded pilot to verify that seed-level excess-CMI standard deviation is
+ at most 0.04 bits; otherwise increase the seed count before unblinding outcomes.
- [ ] Red-team the affect channel as a covert alphabet.
### Results
diff --git a/LEDGER-INTEGRITY-DESIGN.md b/LEDGER-INTEGRITY-DESIGN.md
index 89290a28..8bf780d2 100644
--- a/LEDGER-INTEGRITY-DESIGN.md
+++ b/LEDGER-INTEGRITY-DESIGN.md
@@ -81,7 +81,9 @@ Recommended tables:
- `channel_events`;
- `checkpoint_manifests`;
- `anchor_receipts`;
-- `run_metadata`.
+- `run_metadata`;
+- `analysis_attachments` (canonical analysis bytes plus the descriptor bound to
+ an `analysis-attached` intervention event).
Enable WAL mode and serialize writes through one evidence-writer service. Database
permissions and triggers should reject `UPDATE` and `DELETE` operations on event
@@ -296,28 +298,34 @@ Create a checkpoint:
- at run initialization;
- every 64 accepted ledger events or five minutes, whichever occurs first;
- immediately before and after a policy checkpoint;
+- immediately after a delayed human audit-interpreter batch (`analysis`);
- after a BabySitter or human intervention;
- when the run is paused;
- when the run ends or is aborted.
The exact frequency is a protocol parameter recorded before the run. A lower interval
-reduces the unanchored rewrite window but increases anchoring cost.
+reduces the uncommitted rewrite window but increases commitment and storage overhead.
-## 10. Base and L1 Anchoring
+## 10. Simulated Commitments and Optional Public Anchoring
-### Development
+### Approved Research Profile
-Use Base Sepolia to test transaction construction, failure recovery, receipt capture,
-and verification without spending mainnet funds.
+Use the deterministic in-memory transport for development, qualification, pilot,
+confirmatory, and replication runs. It exercises transaction construction, failure
+recovery, receipt capture, and verification with non-monetary test units. Every run,
+pending record, and receipt binds `anchorClass: "simulated"`; the verifier rejects a
+class mismatch. These receipts are not public timestamps or economic finality.
-### Public Research Runs
+### Optional Public-Chain Capability
-Use Base mainnet for routine checkpoint anchoring. Only the 32-byte checkpoint hash
-and minimal routing metadata should be public.
+Base Sepolia and Base mainnet transports remain optional capabilities outside the
+approved research profile. Any activation requires a prospective governance and
+spending amendment. Only the 32-byte checkpoint hash and minimal routing metadata
+should be public.
The simplest viable anchor is a zero-value transaction from the project anchor wallet
to a designated project address with the checkpoint hash in transaction calldata.
-The evidence bundle records:
+For either receipt class, the evidence bundle records:
- chain ID;
- transaction hash;
@@ -351,8 +359,8 @@ root and anchor that root to Ethereum L1. For example:
- daily during an active study;
- before publishing a paper or dataset release.
-Base anchoring is sufficient for the initial implementation. L1 anchoring is an
-additional trust and archival choice, not a prerequisite for every turn.
+Public anchoring is not a prerequisite for the approved campaign. L1 anchoring is an
+additional future trust and archival choice.
## 11. Key Management
@@ -479,15 +487,16 @@ Failure evidence is part of the research record.
- checkpoint consistency proofs;
- signed checkpoint manifests.
-### Phase 2: Testnet Anchoring
+### Phase 2: Simulated Anchoring and Optional Testnet Capability
-- Base Sepolia publisher;
+- deterministic in-memory publisher and explicitly classified receipts;
+- optional Base Sepolia publisher;
- receipt capture;
- independent RPC verification;
- retry and nonce management;
- deliberate failure tests.
-### Phase 3: Public Anchoring
+### Phase 3: Optional Public Anchoring
- Base mainnet anchor wallet;
- published anchor address or minimal contract;
@@ -511,6 +520,7 @@ verifier rejects:
- false inclusion proof;
- inconsistent checkpoint prefix;
- modified run configuration;
+- simulated/public receipt-class relabeling;
- anchor transaction on the wrong chain;
- failed or nonexistent anchor transaction;
- unanchored final ledger tail.
@@ -520,8 +530,9 @@ checkpoint hash.
## 18. Recommended Initial Decision
-Implement Phase 0 and Phase 1 first. Add Base Sepolia anchoring immediately after the
-local verifier is stable. Move to Base mainnet only for declared public research runs.
+Implement Phase 0 and Phase 1 first. Use deterministic simulated anchoring after the
+local verifier is stable. Keep Base Sepolia and Base mainnet disabled unless a later
+prospective amendment explicitly authorizes public-chain operation.
This design is simple enough for an initial Node.js implementation using SQLite,
`node:crypto`, canonical JSON, and a small Base transaction publisher. It provides
diff --git a/README.md b/README.md
index ab5f619d..41d1f554 100644
--- a/README.md
+++ b/README.md
@@ -95,13 +95,314 @@ channel constraints, and reward conditions were used.
## Project Status
-This repository is in the **foundation implementation phase**. The npm/TypeScript
-workspace, shared runtime schemas, typed configuration loader, secret scan, and three
-DTSF-compatible twin-pack skeletons, SQLite WAL evidence schema, RFC 8785 canonical
-serialization, and ledger event validators are implemented. The next critical-path
-work is independent ledger/channel hash chaining (ALD-008 onward).
-
-No experiment results are claimed yet.
+**Engineering snapshot:** v0.1.529 · 258/261 backlog acceptance criteria verified.
+
+Research status: 4 qualified (software), 2 failed qualification attempts, 0 in progress, 15 not started; 8 open campaign blockers.
+The prospective [nine-member selection clarification](protocols/confirmatory-family-selection-amendment.v1.json)
+resolves a later sample-size wording conflict; it supplies no pilot variance,
+30,000-repetition simulation, selected N, or behavioral result.
+The machine-checked [confirmatory design inventory](protocols/confirmatory-design-readiness.v1.json)
+now makes the remaining inputs explicit: all nine estimands have executable metrics;
+H5's rule, H8's registered spaces, and all nine raw-scale margins are now frozen
+outcome-blind in the [practical-margin policy](protocols/confirmatory-practical-margins.v1.json).
+None of the seven experiment pilots has run, so no variance, power, selected N, or result
+exists. Earlier H2/H4 compiler fixtures are not evidence for the policy.
+The `confirmatory-pilot-summary/v2` contract fixes the seven experiment-to-nine-member
+mappings and fourteen components, requires all twenty valid primary slots in every
+experiment before selection eligibility, and independently checks each experiment's
+one-sided invalid-run upper bound. It rejects partial, invalid, optimistically bounded,
+or confirmatory-reused pilot data.
+The prospective [power-model amendment](protocols/confirmatory-power-model-amendment.v1.json)
+uses dependence-robust member-power bounds and a reserve-adequacy gate; an
+independence-only joint simulation is diagnostic and cannot choose a smaller N.
+The [power-confidence amendment](protocols/confirmatory-power-confidence-amendment.v1.json)
+first corrected simultaneous uncertainty across members. A subsequent
+[component-power amendment](protocols/confirmatory-component-power-amendment.v1.json)
+closes the remaining unidentified dependence inside H1, H5, and H8: the controlling
+selector now uses all fourteen component-power bounds at one-sided `0.05/14`, then
+forms member and family union bounds. Member/joint simulations are diagnostic only.
+The prospective [validity-reserve amendment](protocols/confirmatory-validity-reserve-amendment.v1.json)
+replaces the inadequate fixed 10% rule for the seven confirmatory experiment pilots
+with the smallest candidate-specific reserve count meeting 0.95 adequacy. Its clean
+twenty-slot reference requires 7–52 reserves across candidate N=25–300; these are
+design bounds, not authorized runs or selected prefixes.
+The prospective [power-simulator specification](protocols/confirmatory-power-simulator.v1.json)
+now has an executable deterministic implementation for all eight candidates and
+fourteen component tests. Continuous components use their frozen alternatives and
+future pilot upper SDs under a declared Normal sufficient-statistic working model;
+H5 uses exact-binomial decision probabilities. Independent component streams supply
+only a labeled joint diagnostic. Synthetic software fixtures qualify code paths but
+cannot supply pilot evidence, campaign power, selected N, or resource authorization.
+The retained [qualification receipt](reports/research/confirmatory-power-simulator-qualification-receipt.json)
+binds the exact v0.1.529 implementation and deterministic fixture output. All 112
+candidate/component comparisons agreed with independent Python/scipy reference power
+within the prespecified five-standard-error gate (maximum 2.55). This is bounded
+software qualification only; its fixture counts are not campaign measurements.
+The audit also verifies that the receipt's tree, version, and source bytes belong
+to its named execution commit; later audit-code changes do not rewrite that receipt.
+The prospective [pilot-reduction policy](protocols/confirmatory-pilot-reduction.v1.json)
+now has a deterministic reducer for seven complete twenty-slot corpora. It checks
+ordered, distinct run/seed/evidence identities and computes conservative component
+SDs using a Python/scipy-checked one-sided chi-square factor. Its [qualification
+receipt](reports/research/confirmatory-pilot-reduction-qualification-receipt.json)
+binds the exact v0.1.529 source and 42 independent Python/scipy comparisons (maximum absolute
+difference 1.11e-16); the routine statistics gate verifies its provenance. This is
+bounded synthetic software qualification only: no original pilot bundles or
+registration ancestry have been admitted, and the v2 summary's means may not
+be used to tune the frozen design.
+
+The verifiable core, communication runtime, isolated learner transport, experiment-readiness
+scaffolds, and operator preflight are implemented and tested. E00, E01, and E02
+qualified within distinct, bounded registered **software** scopes. E02 v1 stopped
+after 465 turns at its five-timeout safety pause; v2 stopped after 161 committed
+turns when a post-action timeout escaped. Neither reached restore or probes, and
+both failed attempts remain retained. The prospectively amended v3 attempt ran
+once on five fresh numeric scratch-learner slots, passed all before/after-restore
+checks and 60 fixed probes, and passed a separate full live audit of the original
+evidence. Its [terminal receipt](reports/research/e02-v3-qualification-receipt.json)
+and [audit receipt](reports/research/e02-v3-full-audit-receipt.json) close the local
+reliability gate without establishing a behavioral finding, image-encoder safety,
+independent review, or a public timestamp. The ordinary two-container preflight
+exercises learner-host transport and network controls, not the specification's
+separate Baby-twin, Gateway-only, per-role writer/signer-key, and controller
+trust-zone process boundaries. Existing blocker B12 now gates every E10+ stage
+until selected Mode R topology and detector bounds are qualified; it does not
+gate E03 Prototype controls. E03 controls will use the
+permitted [Prototype-Mode infrastructure path](protocols/e03-prototype-mode-amendment.v2.json)
+without an isolation claim and face an exact development topology, measured
+resource allocation, and registered packet gate. The prospective six-condition
+Prototype-Mode development namespace ran once on clean v0.1.158 and sealed all
+six development conditions; its [original-data audit](reports/research/e03-prototype-topology-audit-receipt.json)
+and separate live replay passed. This is bounded Mode P mechanics/resource
+qualification, not a pilot or chance-control statistical result.
+The unexecuted predecessor paired Prototype Mode with external container adapters,
+which the runtime correctly rejects. The prospective correction requires two
+in-process no-learning adapters in one Nursery process. The corrected
+development runner and original pilot collector use that shared-process
+topology. The original v1 and corrected v2 development attempts both reached
+terminal and separate live raw audits. The prospective
+[pilot allocation](protocols/e03-pilot-resource-allocation.v1.json) reserves 1 CPU-hour,
+1 GiB evidence, and 1 GiB resident memory for 120 planned runs after prior
+charges. Its 0.7-hour sequential wall projection includes observed Docker/
+startup/audit overhead; an unregistered 0.2-hour slot-only projection is
+preserved as a dated correction. The committed
+[v1 pilot packet](protocols/e03-pilot-registration.v1.json) and
+[simulated binding](protocols/e03-pilot-registration-binding.v1.json) remain
+unused: tracked test-fixture corrections change their exact source digest
+before any seed use. A [prospective v2 amendment](protocols/e03-pilot-registration-amendment.v2.json)
+requires fresh pilot IDs/seeds and binding. A new
+[v2 packet](protocols/e03-pilot-registration.v2.json) now freezes 120 distinct
+run IDs and disjoint pilot seeds against clean v0.1.164. Its separately
+committed [simulated binding](protocols/e03-pilot-registration-binding.v2.json)
+reconciles three local confirmations, zero currency, and no public transaction.
+Tracked ready-stage fixture corrections changed the v2 packet's exact source
+digest before seed use. Both earlier registrations remain unused; a
+[prospective v3 amendment](protocols/e03-pilot-registration-amendment.v3.json)
+requires fresh IDs/seeds, packet, and binding. The new
+[v3 packet](protocols/e03-pilot-registration.v3.json) now fixes 120 distinct
+run IDs and disjoint pilot seeds against clean v0.1.167. Its separately
+committed [simulated binding](protocols/e03-pilot-registration-binding.v3.json)
+records three local confirmations without currency or a public transaction.
+The one prospectively registered v3 pilot attempt ran on clean v0.1.169:
+120 planned, attempted, completed, and original-bundle-audited runs; no invalid,
+aborted, or replacement runs. Its separate outcome-blind reduction and recomputation
+audit selected the prespecified 25-primary-seed E03 design row. The
+[pilot progress update](reports/research/e03-v3-pilot-progress-update.md) binds
+the retained original receipt and reduction hashes. The tracked
+[portable status receipt](reports/research/e03-v3-pilot-status-receipt.json)
+allows clean-checkout status inspection; raw-data re-audit still requires the
+separately retained ignored evidence. These are Prototype-Mode
+design inputs, not E03 chance-control or behavioral findings.
+The frozen R-era (Homebrew-R) 30,000-repetition nominal complete-rule simulation passed at
+25 seeds (lower 95% power bound `0.999872`), but its forced two-invalid-primary-
+per-condition sensitivity passed zero repetitions. Registered reserve and
+invalid-run handling therefore need explicit full-stage qualification; the
+nominal design calculation is not a robustness or experiment pass.
+The tracked [outcome-blind E03 design decision](protocols/e03-sample-size-decision.v1.json)
+binds the original pilot, reduction, and power hashes and carries that
+sensitivity. It can be inspected in a clean checkout; its live recomputation
+requires separately retained original evidence.
+The separately [measured full E03 allocation](protocols/e03-full-resource-allocation.v1.json)
+reserves all 168 possible primary/reserve runs under the existing local ceiling:
+1 CPU-hour, 1.1 GiB new evidence, 1 GiB resident, and 0.5 hours projected
+sequential wall time, after charging 22.3 prior CPU-hours and 4.0 GiB of all
+retained evidence. It covers only Prototype-Mode software qualification, not
+the E10+ campaign.
+The [prospective paired-reserve clarification](protocols/e03-full-paired-reserve-amendment.v1.json)
+protects E03's shared-scenario seed and paired oracle-control estimand: one
+invalid primary run excludes its six-condition scenario slot; the next
+ordered reserve slot is collected across all six conditions, at most three.
+Original invalid and valid-but-excluded companions remain indexed. No full
+packet or full seed existed when this clarification was recorded.
+The pure [paired-attempt reconciler](packages/analysis/src/e03-full.ts) now checks
+the complete six-condition primary matrix, shared-scenario histories, ordered
+whole-slot reserve attempts, excluded companions, and exhausted-reserve
+incompleteness. The subsequently registered v1 full-stage attempt completed
+all 150 required primary runs, each with a verified original bundle. All
+primary pairs were valid, so the scheduler correctly left all 18 reserve runs
+unattempted. The registered chance-equivalence, oracle-adequacy, and paired-
+separation thresholds passed, and a separate read-only audit recomputed all
+150 bundles and the fixed analysis. The tracked
+[portable full-stage receipt](reports/research/e03-full-v1-status-receipt.json)
+binds the retained original receipt and reports measured resources. This
+qualifies the E03 Prototype-Mode control baseline only; it is not a behavioral
+language-emergence finding, Research-Grade isolation, or independent review.
+A disposable one-evaluation Prototype fixture sealed with a simulated anchor
+and passed a separate TypeScript bundle replay; it is a software
+smoke check, not the separate six-condition v2 qualification or a pilot.
+The frozen v1 matrix and corrected v2 matrix supply development mechanics only;
+the dependent behavioral campaign remains unstarted. The [phase-two update](reports/research/phase-two-execution-update.md)
+explains the attempt accounting and next acceptance checks. The remaining
+research gates precede the dependent behavioral campaign:
+
+The exact live admission preceded the single-use v3 run. The collector and
+separate `audit:e03-pilot-v3:live` check verified all original bundles. The
+`reduce:e03-pilot-v3:live` command then reran that audit before writing the
+outcome-blind dispersion input; `audit:e03-pilot-v3-reduction:live` independently
+recomputed it from retained original records. All four gates passed on the
+original v3 evidence. E03's full control qualification then ran once and passed
+the original-evidence audit described above.
+The audit cross-checks unsigned operational tallies against the signed,
+verified turn and channel streams before admitting a sample-size input.
+The prospective Prototype-Mode development audit now derives its paired
+scenario hashes and control tallies from those same signed original streams;
+unsigned slot summaries alone cannot clear the topology gate.
+The pilot status gate also discovers its fixed terminal receipt and checks the
+packet identity and planned, attempted, and completed run counts; a stale
+ready or running summary cannot override that terminal evidence.
+Before a terminal receipt exists, a running pilot status requires the original
+start record plus the matching live local controller PID and process-start
+identity; a crashed controller leaves readiness unresolved, not running.
+The standalone project-status check and the campaign gate now use one local
+pilot-state assertion, so original start or terminal evidence cannot be
+ignored by a document-only status check.
+On collector failure, the terminal receipt charges any measured CPU from the
+attempted slot and marks remaining CPU capacity unresolved if a required
+container measurement is missing; the attempt cannot be replaced silently.
+Pilot admission also requires the execution source tree to remain unchanged
+since the packet's protocol-base commit; later source edits require a fresh
+prospective amendment and allocation.
+
+- `@ald/hashing`, `@ald/merkle`, `@ald/evidence`: domain-separated hashing, RFC 8785
+ canonical JSON, per-run Ed25519 signers and key store, hash-chain validation, RFC
+ 6962 ordered Merkle trees with inclusion and consistency proofs, and the single
+ atomic SQLite Evidence Writer with fork detection, recovery, bundle export, and
+ analysis attachments atomically bound to witness-committed intervention events.
+- `@ald/checkpoint`, `@ald/anchor`, `@ald/verifier`: signed checkpoint manifests and
+ proof files, the Base-shaped anchor publisher (deterministic simulation by default;
+ optional viem public-chain transport outside the approved campaign), and the standalone
+ `ald-verify` CLI that re-derives
+ every hash, signature, root, proof, binding, and anchor from a bundle alone.
+ A separate read-only Rust auditor independently rebuilds canonical event hashes,
+ signatures, Merkle prefixes, checkpoint chains, receipt bindings, and attachment
+ hashes; `pnpm run challenge:integrity` compares both implementations against a
+ fresh real export and deliberate mutations.
+- `@ald/lifecycle`, `@ald/scenario`, `@ald/gateway`, `@ald/learners`,
+ `@ald/orchestrator`, `@ald/analysis`: the SPEC §7 run state machine; deterministic
+ scenarios, hygiene, and a fail-closed run-registration quarantine gate; fixed-token, glyph, bitmap, canvas,
+ tone, and six-display affect protocols; no-learning and all four trainable/runtime
+ tracks; an explicitly selected 16-unit GRU actor-critic with PPO-style updates for
+ scratch RL; the same 4,049-parameter GRU core with reward-free predictive
+ cross-entropy for self-supervision; tabular/count models retained as named
+ qualification controls; frozen-LLM and hybrid adapter conformance foundations; the
+ carrier analysis module provides grammar-valid bitmap, translation-tolerant canvas,
+ and graded tone distances plus deterministic held-out nearest-prototype scoring,
+ explicitly bounded to a handcrafted-distance diagnostic rather than learned
+ perceptual generalization; the
+ independently seeded policies and witness-committed initial hashes;
+ independently verified derived-run lineage against immutable parent exports;
+ Nursery's end-to-end SPEC §8 turn cycle; a delayed, source-bound human audit
+ interpreter whose generated entries are separately signed and checkpointed; and
+ pre-registered analysis primitives, including immutable mark-level leakage
+ probes across all five carrier conditions with an evidence-preserving claim gate;
+ and an E16 runtime seam that commits validation-selected baseline and native-ledger
+ predictions after delivery but before receiver action, checkpoints that chronology,
+ then source-binds post-outcome scores as per-turn analysis attachments. The immutable
+ run configuration binds the comparator-selection commitment and native
+ prediction-function version, limits eligibility to accepted evaluation
+ deliveries, and includes fail-closed restart checks.
+- `@ald/isolation`, `@ald/ops`, `@ald/interventions`, `@ald/redteam`: fixed-frame
+ process transport, a two-container Mode R network-isolation gate, and learner-host confinement; API telemetry, snapshot/restore,
+ and failure supervision; configuration-driven, verifier-bound live causal probes
+ and bounded repair turns, plus held-out, curriculum, drift, and statistical
+ scaffolds; the committed observation-text and quarantine-bypass corpus; and
+ an active twelve-category side-channel red-team suite shared by Mode P and Mode R,
+ plus 28 generated-carrier dimension, metadata, raw-media, sample-rate, compression,
+ and container attacks through the real Gateway.
+ These are tested foundations, not a completed production Mode R deployment or
+ a scientific result. `pnpm run test:mode-r` builds the locked-down learner image,
+ denies Baby-to-Baby routes on separate internal networks, checks host capability
+ denial and normalized timing, then kills one learner and verifies the other survives.
+ `pnpm run test:mode-r-study` additionally drives every learner track through the
+ actual controller, Gateway, SQLite writer, checkpoint, deterministic simulated commitment,
+ exporter, and verifier. Its local no-Fort qualification path now uses six
+ separate no-network, ephemeral signer containers; the Gateway and writer still
+ share the Nursery process, so B12 remains open. The [independent four-bundle audit receipt](reports/research/mode-r-signer-reference-audit-receipt.json)
+ binds one exact clean-source execution and separate TypeScript/Rust verification; it remains software evidence,
+ not a public-chain transaction or research result.
+ The [journal-bearing reference receipt](reports/research/mode-r-journal-reference-audit-receipt.json)
+ separately checks a later clean-source four-track replay and the paired
+ hash-only write intents; it also leaves B12 open. Persistent study signers are
+ accepted only from `fort` files-mode materialization mounted into the Nursery;
+ neither learner receives that mount or its path.
+ The [prospective authority graph](protocols/mode-r-authority-graph.v1.json)
+ and its [v2 writer-capability amendment](protocols/mode-r-authority-graph.v2.json)
+ fix the intended process, network, key, and caller-specific writer boundaries
+ for the next qualification. Later amendments freeze the remote Gateway,
+ recovery, and Gateway-to-Baby relay contracts. Component integration now
+ exercises separate Gateway, Evidence Writer, Checkpoint, simulated Anchor,
+ delayed Audit Interpreter, Baby, and model-adapter processes, but no authority
+ graph amendment is an implemented-topology receipt and B12 remains open.
+- `@ald/crypto-research`: an E40 research-only, three-role instrumentation harness
+ that records hash-chained ephemeral scheme changes and Eve recovery attempts over
+ synthetic messages. A repository lint boundary prevents it from entering the
+ production hashing or anchoring packages; it never reports cryptographic security.
+- DTSF twin packs for `baby-a`, `baby-b`, and `nursery` expose the SPEC §12 routes
+ with role guards in Prototype Mode; audit-ledger reads return only the separate
+ generated-analysis stream and reject Baby service identities.
+
+The qualification harness (`scripts/run-qualification.mjs`) executes E03-style
+chance controls and an E11-style naming game through the real pipeline and writes a
+report under `reports/qualification/`. Those runs are **non-confirmatory software
+qualification in Prototype Mode**: not pre-registered, not anchored, and never
+research findings.
+
+The committed E03 design simulation is an outcome-blind sensitivity check. The
+stage-aware registration compiler produced canonical pilot and full-stage packets
+with separated scenario, per-role learner, Gateway, and analysis seeds. The full
+packet was compiled only after the retained pilot selected a candidate seed count
+under the frozen 30,000-repetition rule. Research preflight required immutable
+registration and a matching pre-run simulated commitment before each stage ran.
+
+The frozen-LLM operator path also completed a real two-episode qualification
+against Qwen3-4B Q4_K_M through Homebrew llama.cpp 0.4.0. The retained
+[`reports/qualification/frozen-model-qwen3-4b-q4-k-m.json`](reports/qualification/frozen-model-qwen3-4b-q4-k-m.json)
+binds the exact model weights, resolved executable and bottle, software commit,
+live server configuration, separate role processes, clean-process replay,
+tool-only calls, and frozen-weight/private-memory semantics while omitting prompts
+and private output. The 7.5-GiB validation host required non-overlapping role
+processes and a 300-second qualification ceiling, so this is bounded software
+evidence rather than a production-capacity or empirical finding.
+
+The E01/E02 software-readiness outputs have a hashed attachment path linked from
+the append-only Experiment Record and independently checked against their evidence
+event and committed prefix. The approved prospective research profile uses no real
+funds or public-chain transactions; those remain optional capability tests outside
+the study. Repository-native preregistration and deterministic simulated commitments
+are the sole experiment activation path. Hosted enforcement, a restore performed by
+a second human, and third-party registration remain optional evidence-strengthening
+activities and are never claimed unless actually completed. The retention job is implemented
+and covered against real exported bundles and evidence-store rows.
+
+The [external-prerequisite handoff](docs/external-prerequisite-handoff.md) and its
+machine-readable ledger make O01–O06 auditable without recording secrets or personal
+identity. The ledger is ready at 1/1 applicable prerequisite: O01 is verified, while
+O02-O06 are non-applicable to local synthetic execution under the zero-spend,
+repository-native policy. A read-only upstream observation remains as historical
+evidence that hosted required-check enforcement is not demonstrated, not as an
+experiment blocker.
+
+No behavioral research results are claimed.
The complete rationale, literature review, experimental ideas, risks, and open
decisions are in [CONCEPT-IDEA.md](CONCEPT-IDEA.md).
@@ -110,12 +411,25 @@ decisions are in [CONCEPT-IDEA.md](CONCEPT-IDEA.md).
| Document | Purpose |
|---|---|
+| [Phase-one research update](reports/phase-one-research-update.md) | Dated September 4–13 progress snapshot, inherited-versus-added work, and then-current evidence |
+| [Phase-two execution update](reports/research/phase-two-execution-update.md) | Dated September 15 E02 reliability qualification, preserved failures, evidence scope, and next E03 gate |
+| [Phase-one evidence appendix](reports/phase-one-evidence-appendix.md) | Claim-to-commit, receipt, evidence-availability, and verification-command map |
+| [Phase-one demonstration guide](docs/phase-one-demonstration-guide.md) | Short, evidence-preserving walkthrough for a research review |
| [CONCEPT-IDEA.md](CONCEPT-IDEA.md) | Research premise, architecture, literature, safeguards, experiments, and open decisions |
| [LEDGER-INTEGRITY-DESIGN.md](LEDGER-INTEGRITY-DESIGN.md) | Hash-chain, ordered-Merkle, signature, Base-anchor, verifier, and recovery design |
| [EXPERIMENT-NOTEBOOK.md](EXPERIMENT-NOTEBOOK.md) | Ordered experiment protocols, checklists, result tables, deviations, and publication review |
| [SPECIFICATION.md](SPECIFICATION.md) | Normative architecture, protocols, schemas, APIs, isolation controls, lifecycle, and acceptance criteria |
| [BACKLOG.md](BACKLOG.md) | Milestones, critical path, epics, dependency-ordered stories, readiness gates, and requirement coverage |
| [RESEARCH.md](RESEARCH.md) | Pre-results academic manuscript, research questions, methods, literature review, analysis plan, source verification, and arXiv preparation checklist |
+| [CONFIGURATION.md](CONFIGURATION.md) | Runtime environment variables, key-store layout, and secret handling |
+| [docs/external-prerequisite-handoff.md](docs/external-prerequisite-handoff.md) | Exact privacy-safe evidence and activation checks for O01–O06 |
+| [docs/simulation-funded-research-profile.md](docs/simulation-funded-research-profile.md) | Approved zero-spend campaign boundary and the distinction between simulated funds and real observations |
+| [docs/evidence-bundle-format.md](docs/evidence-bundle-format.md) | Byte-level evidence bundle contract shared by the exporter, checkpoint service, and verifier |
+| [docs/cryptographic-separation-policy.md](docs/cryptographic-separation-policy.md) | Mandatory boundary between E40 research encodings and production hashing/signing/anchoring |
+| [reports/README.md](reports/README.md) | What the qualification reports are and are not |
+| [Research validation report](reports/research/research-validation-report.md) | Historical September 13 evidence-class and methods snapshot; current status is in the phase-two update |
+| [Source-lineage register](reports/research/source-lineage-register.md) | Requirement origins and later normative amendments without personal attribution |
+| [Requirement conformance matrix](docs/requirement-conformance-matrix.md) | Provisional mapping from every backlog criterion and normative MUST-bearing line to executable evidence |
## Research Book
@@ -131,7 +445,7 @@ generated PDF as a download.
Regenerate the book after changing the manuscript:
```powershell
-npm run book:research
+pnpm run book:research
```
The page rasterizer requires Node.js 22.13 or newer; generated book assets do not
@@ -141,7 +455,74 @@ Set `CHROME_PATH` if Chrome or Edge is not installed at a standard location. The
generated PDF, page manifest, page images, print edition, and locally bundled reader
are committed so GitHub Pages needs no server, CDN, or runtime PDF renderer.
-The notebook is ready for pre-registration. No experiment results are claimed yet.
+The notebook records E00's integrity qualification and E01 v2's completed bounded
+isolation qualification. Behavioral collection awaits the remaining local research gates.
+
+## Running the Platform Locally
+
+```bash
+pnpm install --frozen-lockfile
+pnpm run check
+```
+
+`pnpm run check` runs every source/contract/boundary/readiness/status lint, builds
+every workspace, runs the complete test suite, scans for committed secrets, and
+blocks on high/critical dependency advisories.
+
+The E00/E01 commands in that suite validate tracked receipt summaries. On the
+execution host, also run `pnpm run audit:qualification-e00:live` and
+`pnpm run audit:qualification-e01:live` to compare retained manifest identities
+and slot records. These explicit checks require the ignored `evidence/` directory
+and fail if it is missing. They do not rerun the historical experiments.
+
+Registration audits require the repository's full Git history; shallow clones
+must fetch that history before running the consolidated suite. Plans and tickets
+are local, Git-ignored working documents and are not inputs to CI.
+
+Reproduce the historical E03 design sensitivity calculation now. Compile the
+twenty-slot, zero-reserve pilot only after the exact Prototype-Mode six-condition
+development run and live audit finish and its measured, zero-spend allocation is
+written. The compiler binds those two receipts and fails closed while they are absent:
+
+```bash
+pnpm run design:e03
+pnpm run qualify:e03-prototype-topology-dev # once on a clean commit with an unused evidence root
+pnpm run audit:e03-prototype-topology-dev:live
+pnpm run build:e03-pilot-allocation
+pnpm run registration:e03 --out evidence/preregistration/e03-pilot-v1-draft.json
+pnpm run preflight:research \
+ --registration evidence/preregistration/e03-pilot-v1-draft.json \
+ --binding /absolute/path/repository-registration-and-commitment-binding.json
+```
+
+The historical commands above illustrate the design workflow. E02 and E03's bounded
+software gates have now passed. A fresh stage must still use an immutable packet,
+exact source/seed preflight, simulated pre-run commitment, and an unused evidence
+namespace. The executed full E03 packet was compiled from the retained sample-size
+decision; its current audit commands are:
+
+```bash
+pnpm run audit:e03-full:live
+pnpm run audit:e03-full-status:live
+```
+The first command requires separately retained original evidence; the second
+checks the tracked portable receipt. Neither command supplies the E10+ campaign's
+remaining scientific prerequisites.
+
+Run the Prototype Mode qualification harness and verify a bundle independently:
+
+```bash
+pnpm run build && node scripts/run-qualification.mjs
+```
+
+```bash
+node packages/verifier/bin/ald-verify.js evidence/qualification//bundles/runs/ --allow-unanchored
+```
+
+Databases and bundles are written under `evidence/`, which is ignored by git; reports
+are written under `reports/qualification/`. The approved profile needs no funded
+wallet: runs use the deterministic in-memory transport, carry
+`anchorClass: "simulated"`, and fail verification if that class is altered.
## Responsible Research
diff --git a/RESEARCH.md b/RESEARCH.md
index 221f64eb..549ed653 100644
--- a/RESEARCH.md
+++ b/RESEARCH.md
@@ -8,15 +8,17 @@
>
> **Prepared:** September 2, 2026
>
+> **Engineering snapshot:** v0.1.529 · 258/261 backlog acceptance criteria verified.
+>
> **Proposed arXiv category:** `cs.MA` (primary), with possible cross-listing to
> `cs.AI` and `cs.CL`
>
-> **Authors:** Ethical Tech CoLab; individual author order, affiliations, ORCID
-> identifiers, and corresponding author to be finalized before submission
+> **Authors:** Anonymous working draft; author order, affiliations, ORCID identifiers,
+> and corresponding author are omitted until the authorized submission stage
>
-> **Repository:**
+> **Artifact:** Anonymous review artifact to be prepared only after release approval
>
-> **Empirical status:** No Nursery Lab experiment has been reported as completed in
+> **Empirical status:** No behavioral experiment has been reported as completed in
> this manuscript. Sections 10 and 11 distinguish implemented infrastructure from
> planned empirical work. No table contains an observed empirical result.
@@ -39,9 +41,11 @@ reinforcement learning, intrinsic-motivation learning, self-supervised learning,
hybrid models, and no-learning controls under common scenario, channel, evidence, and
evaluation interfaces. Each agent is specified to maintain an independent chronological ledger of its own
intentions and interpretations. The framework specifies append-only, hash-chained,
-signed ledger and channel records, ordered Merkle checkpoints, and periodic
-public-chain checkpoint anchoring. These mechanisms are not yet fully implemented and
-do not prove that an agent's interpretation is truthful; they are designed to preserve
+signed ledger and channel records, ordered Merkle checkpoints, and deterministic
+simulated commitments. The local integrity, checkpoint, verifier, and commitment
+paths are implemented and tested under a zero-external-spend profile. Optional
+public-chain transport remains outside the approved campaign and its claims. These
+mechanisms do not prove that an agent's interpretation is truthful; they are designed to preserve
what was recorded and make later alteration detectable. Causal message interventions, held-out generalization, partner
replacement, leakage tests, and control-channel conditions are therefore required in
addition to task success.
@@ -72,7 +76,10 @@ Assistance in Formulating Research Questions* guidance [47]:
- AI-suggested connections are treated as exploratory until supported;
- the influence and limitations of AI assistance are disclosed;
- source verification state and journal credibility are recorded in Appendix A;
-- a separate critical-review pass is required before publication.
+- a separate critical-review pass is required before publication; the current
+ internal pass is recorded in
+ [reports/research/research-critical-review.md](reports/research/research-critical-review.md)
+ and does not count as independent review.
In this draft, source retrieval and the first verification pass were performed by an
AI assistant using web, scholarly-index, and metadata tools, followed by separate AI
@@ -227,10 +234,10 @@ pre-registrations. The following hypotheses define the current direction:
| H1 | Normal communication will outperform disabled, constant, random, and shuffled controls on held-out tasks. | Control performance equals or exceeds normal communication after correction. |
| H2 | In E16's scratch-RL, extrinsic-reward, fixed-token condition, a ledger-consistent substitution will increase the probability of the ledger-predicted receiver action relative to shuffled-control messages. | The hierarchical substitution-versus-shuffle contrast is zero or negative. |
| H3 | At equal model capacity, training episodes, and update/compute budget, the planned 32-symbol/4-token condition will produce higher held-out compositional generalization than the 128-symbol/8-token condition. | The planned contrast is zero or favors the higher-bandwidth condition. |
-| H4 | Ledger-predicted intervention directions will exceed a pre-registered chance baseline. | Ledger agreement is at chance or fails out-of-sample. |
+| H4 | On untouched intervention cases, pre-outcome ledger predictions will improve a pre-registered proper prediction score beyond the strongest eligible transcript-only, task-history, policy-state, random, and majority baseline. | The ledger fails to exceed the strongest eligible non-ledger baseline by the registered practical threshold. |
| H5 | A blank bounded carrier will support repeated forms, but will converge more slowly than a fixed symbol inventory. | No stable forms emerge, or blank-carrier convergence is not slower. |
| H6a | The declared six-display affect condition will reduce median turns to successful repair relative to no affect. | Repair time is equal or longer under affect. |
-| H6b | Under fixed windows and cardinality, permutation-calibrated excess conditional mutual information between affect and referent will remain below the pre-registered 0.02-bit practical-leakage bound. | The seed-bootstrap upper-bound test cannot rule out excess leakage of 0.02 bits or more. |
+| H6b | Under fixed windows and cardinality, permutation-calibrated excess conditional mutual information between affect and referent will remain below the pre-registered 0.02-bit practical-leakage bound. | The seed-level one-sided Student-t upper-bound test cannot rule out excess leakage of 0.02 bits or more. |
| H7 | Fixed dyads will show greater partner-replacement degradation than learners trained with pre-registered partner variation. | Degradation for fixed dyads is no greater than degradation after partner-varied training. |
| H8 | Partially conflicting utility will reduce message informativeness and increase strategic ambiguity relative to aligned utility. | Informativeness and ambiguity do not change in the predicted direction. |
@@ -658,6 +665,23 @@ Researcher ground truth may contain semantic labels; learner observations may no
Observations use opaque numeric arrays, pixels, or separately qualified hybrid
features. Images containing OCR-detectable text are quarantined before a run.
+The initial numeric referential design contains 16 two-attribute combinations. The
+four equal-attribute diagonal combinations are the locked compositional test targets;
+the remaining 12 combinations are the shared semantic support for training and
+in-distribution validation. Training and validation use independent PRNG domains,
+and no held-out type may occur in either split as a target or distractor. Validation
+is learning-off and may be used for tuning and baseline selection. The held-out split
+is learning-off and is accessed once after those choices freeze. The broader
+`evaluation` split remains an engineering and software-qualification surface and is
+not interchangeable with the confirmatory held-out test.
+
+The versioned split, duplicate rules, matched budget dimensions, and boundaries
+between within-architecture causal contrasts and cross-architecture descriptive
+benchmarks are frozen in
+[`protocols/scenario-split-and-model-comparison.v1.json`](protocols/scenario-split-and-model-comparison.v1.json)
+and explained in
+[`docs/scenario-splits-and-model-comparisons.md`](docs/scenario-splits-and-model-comparisons.md).
+
### 6.5 Communication Conditions
For the baseline fixed-token carrier, each run declares a random inventory such as
@@ -754,7 +778,7 @@ criteria are satisfied.
1. held-out task success relative to E03 controls;
2. positive listening under message intervention;
-3. ledger-predicted intervention agreement;
+3. ledger incremental predictive value on untouched interventions;
4. held-out compositional generalization.
**Secondary outcomes**
@@ -782,7 +806,12 @@ distributions to change while the receiver observation is held fixed.
**Ledger agreement:** the direction of an observed intervention effect matches the
direction predicted from the learner's ledger before intervention outcomes are
-revealed.
+revealed. This is a descriptive calibration measure, not H4's confirmatory estimand.
+
+**Ledger incremental value:** held-out improvement in a pre-registered proper
+prediction score over the strongest eligible transcript-only, task-history,
+policy-state, random, and majority baseline. Predictions are committed before the
+intervention outcome is generated.
**Compositional generalization:** reusable subparts support above-control success on
pre-registered unseen combinations with learning disabled.
@@ -796,15 +825,20 @@ equal-weight eight-partner-training degradation.
### 7.3 Statistical Plan
-The final analysis plan will be frozen before confirmatory runs.
-
-- Alpha is `0.05` for each experiment's primary family.
-- Holm-Bonferroni correction is applied across primary metrics within an experiment.
-- The confirmatory study family is restricted to H1-H8, including H6a and H6b.
- Hierarchical gatekeeping tests qualification first, then core emergence, then later
- affect/transfer/negotiation hypotheses; a blocked family is reported descriptively.
- Exploratory analyses use false-discovery-rate reporting and remain labeled
- exploratory.
+The common analysis and multiplicity rules are frozen in
+[`protocols/statistical-analysis-and-power.v1.json`](protocols/statistical-analysis-and-power.v1.json).
+Experiment-specific raw-scale practical margins and seed allocations will be frozen
+after disjoint pilots and before confirmatory runs.
+
+- The confirmatory study family contains nine members: H1, H2, H3, H4, H5, H6a,
+ H6b, H7, and H8. One pre-registered p-value is formed per member and Holm correction
+ is applied globally at family-wise alpha 0.05. Multi-component directional members
+ use the maximum component p-value and require every registered direction and
+ practical threshold.
+- E00-E03 and experiment-specific checks are validity gates, not sources of reusable
+ alpha. A failed or missing gate produces `not-tested`, not a favorable or
+ unfavorable p-value. Exploratory analyses use false-discovery-rate reporting and
+ remain labeled exploratory.
- Effect sizes and confidence or credible intervals are reported with every
significance test.
- Binary task outcomes are modeled at the run/seed level, with episodes treated as
@@ -827,12 +861,147 @@ Before empirical submission, simulation-based power analysis will determine the
required number of independent seeds for each primary contrast; the larger value
governs.
+Independent base-R validation agrees with the production special functions and
+seed-level tests, demonstrates calibrated boundary behavior, and shows why pooled
+episode inference is prohibited. The complete E03 numeric decision has a lower 95%
+Monte Carlo power bound above 0.90 in all four planned variance rows. A nine-member
+Holm sensitivity shows that 75 seeds is not generally sufficient: at standardized
+effect 0.40, all-member power is 0.6752 at N=75 and 0.9068 at N=100. These values are
+design evidence rather than universal sample sizes; D07 applies the registered
+member-specific rule. Full methods and Monte Carlo intervals are in
+[`docs/statistical-validation-and-power.md`](docs/statistical-validation-and-power.md).
+
Claims that a control is "at chance" or that leakage is absent use equivalence or
upper-bound tests, not failure to reject a difference. Each pre-registration must
state the smallest effect of interest, the equivalence margin, and power to rule out
that margin. The worked E03 design in Appendix D uses a +/-0.05 success-rate margin;
H6b uses a 0.02-bit conditional-mutual-information bound.
+The outcome-blind causal-ledger and leakage rules are frozen in
+[`protocols/causal-ledger-and-leakage.v1.json`](protocols/causal-ledger-and-leakage.v1.json).
+For E02, at least 200 untouched test rows are required and the one-sided 95%
+Wilson upper bound on linear-probe accuracy advantage over the held-out majority
+baseline must be at most 0.10. The identical probe and split must detect an injected
+one-hot target feature with a lower advantage bound of at least 0.20. For E20, 75
+eligible seeds and 1,000 windows per seed are required before a decision; the primary
+bound is seed-level Student-t and the percentile bootstrap is sensitivity-only.
+Allowed and forbidden information sets and detector-positive controls are explicit
+for E01, E02, E13, and E20. Intended normalized carrier form is communication, not a
+side channel.
+
+Seed and resource allocation is frozen in
+[`protocols/seed-and-resource-allocation.v1.json`](protocols/seed-and-resource-allocation.v1.json).
+Qualification, blinded-pilot, confirmatory, and replication stages use disjoint
+SHA-256 domains; paired conditions share scenario seeds but not learner, Gateway, or
+analysis seeds. The original allocation described selection from the complete
+nine-member joint simulation. That simulation remains a useful diagnostic, but the
+prospective dependence and confidence amendments now require the first shared
+candidate whose simultaneously bounded member-power union reaches 0.90 and whose
+experiment-specific validity reserves reach 0.95. N=100 is a planning value, not a
+selected result.
+The prospective [selection clarification](protocols/confirmatory-family-selection-amendment.v1.json)
+resolves the earlier "largest required count" wording: member-specific minima remain
+diagnostics. The current executable reducer requires every eligible pilot, all
+fourteen component success counts at all eight candidates, simultaneous component
+confidence, and corrected reserves; member/joint counts have no selection authority.
+No pilot inputs or 30,000-repetition receipt yet exist.
+
+The prospective [design-readiness inventory](protocols/confirmatory-design-readiness.v1.json)
+separately audits the inputs needed before those pilots. All nine estimands now have an
+executable scale. H5 uses `stable-convention/v1`, a rolling-window calculation that
+requires form-to-meaning consistency, repeated use, and paired causal listening;
+its window and listening thresholds remain prospectively unset. H8 uses
+`negotiation-language/v1` on paired aligned/conflicting cases: message information
+about registered private state and normalized intended-action entropy conditional on
+message are separate components. Its state/action spaces must still be packet-bound.
+The outcome-blind [practical-margin policy](protocols/confirmatory-practical-margins.v1.json)
+now freezes H5's rule, H8's spaces, and all nine scale-specific margins before any
+family pilot. Values used earlier in packet-compiler tests are not empirical support.
+All nine member pilot summaries remain explicitly uncollected, so
+the inventory cannot yield a sample size or research finding.
+The `confirmatory-pilot-summary/v2` contract fixes seven experiment pilots, their
+nine member mappings, and fourteen component summaries. Only twenty valid primary
+slots in every experiment, a finite one-sided upper dispersion bound for every
+component, and a checked experiment-specific one-sided invalid-run upper bound are
+selection-eligible. Partial or invalid pilots remain retained but cannot feed the
+selector. Even zero invalid runs in twenty slots has a nonzero upper validity-risk
+bound; the later reserve calculation must use that bound and may require a prospective
+resource amendment rather than treating zero observations as zero risk.
+Because twenty-slot marginal dispersions do not identify dependence among the
+fourteen component tests, the prospective
+[power-model amendment](protocols/confirmatory-power-model-amendment.v1.json)
+does not let an independence assumption control selection. It freezes outcome-blind
+design alternatives, component screening at 0.05/9, a dependence-robust union lower
+bound across nine member-power bounds, and a 0.95 reserve-adequacy gate. The ordinary
+joint simulation remains diagnostic and cannot select a smaller N.
+The prospective [power-confidence amendment](protocols/confirmatory-power-confidence-amendment.v1.json)
+first supplied simultaneous family confidence instead of mistaking nine unadjusted
+intervals for a 95% family guarantee. A further
+[component-power amendment](protocols/confirmatory-component-power-amendment.v1.json)
+recognizes that marginal pilot dispersions do not identify dependence inside the
+multi-component H1, H5, and H8 decisions either. The controlling rule therefore uses
+one-sided alpha `0.05/14` for all fourteen component-power bounds and forms member and
+family union lower bounds without a covariance assumption. The executable selector
+is bound to this final rule and still supplies no pilot or power result.
+The prospective [validity-reserve amendment](protocols/confirmatory-validity-reserve-amendment.v1.json)
+then corrects the original fixed 10% rule for the seven scoped experiments. For each
+candidate and experiment it chooses the smallest reserve count whose binomial
+probability of obtaining N valid runs is at least 0.95 at the pilot upper invalid
+rate. Under the clean 0/20 reference, this requires 7 reserves at N=25 through 52
+at N=300. The table is a design calculation; it authorizes no run or resource.
+
+The prospective [power-simulator specification](protocols/confirmatory-power-simulator.v1.json)
+binds the executable model for all eight candidates and fourteen components. It uses
+Normal mean and chi-square sample-variance sufficient statistics for the registered
+one-sided t tests, with each future pilot's one-sided upper SD, and exact-binomial
+decision probabilities for H5. Random streams are deterministically separated by
+candidate, member, and component. Their independent joint-family outcome is a
+diagnostic only; selection uses the component-wise dependence-robust bounds above.
+The Normal assumption is a declared parametric power model, not evidence about the
+future outcome distribution. Software fixtures do not supply a pilot, power result,
+selected sample size, resource authorization, or empirical finding.
+The retained
+[software-qualification receipt](reports/research/confirmatory-power-simulator-qualification-receipt.json)
+binds the exact v0.1.529 implementation and output digest. Across eight candidates
+and fourteen components, all 112 deterministic Monte Carlo proportions were within
+the prespecified five-standard-error gate of independent Python/scipy t/noncentral-t or
+exact-binomial reference power; the maximum deviation was 2.55 standard errors.
+These synthetic fixture values qualify implementation behavior only. No eligible
+pilot was supplied and the receipt records no campaign power, selected N, resource
+authorization, study outcome, or scientific disposition.
+The receipt auditor additionally checks its named commit's tree, package version,
+and each qualified source artifact against Git history, preserving the original
+receipt while allowing later audit-code corrections.
+The prospective [pilot-reduction policy](protocols/confirmatory-pilot-reduction.v1.json)
+adds a deterministic numerical reducer for all seven distinct twenty-slot pilot
+corpora. It requires globally unique run IDs, seeds, and evidence digests and
+computes the Normal-assumption one-sided 95% upper SD with factor 1.37041039768,
+independently checked against R. The reducer does not verify the original bundles,
+packet ancestry, or registered metric derivation; its output expressly prohibits
+selection admission. The existing v2 summary retains means for audit consistency,
+but those pilot means must not influence the frozen design before lock. No pilot
+data have been supplied to this reducer. Its retained [synthetic software qualification
+receipt](reports/research/confirmatory-pilot-reduction-qualification-receipt.json)
+binds exact v0.1.204 sources, 42 independent-R mean/SD/upper-SD comparisons
+(maximum absolute difference 1.11e-16), and four rejection controls. The routine
+statistics gate checks the receipt's named commit, source digests, and fixture
+output; it does not admit original evidence or select N.
+
+That nine-member rule governs the confirmatory H1-H8 family and is distinct from
+E03's qualification-only rule in Appendix D. E03 uses its own four-row bounded
+numeric-rule table after its separate twenty-slot control pilot; E03 neither spends
+nor contributes confirmatory-family alpha.
+
+A fresh 260-turn recurrent-carrier benchmark took 37.58 seconds and wrote 25.53 MB.
+Under the original fixed-reserve allocation, maximum materialized pools including
+replication projected to 9,171 bundles, 9.37 million turns, 857.3 GiB, and 376.4
+single-core hours. The corrected confirmatory reserve formula can raise those maxima;
+its exact campaign total remains unset until N and experiment-level allocations are selected.
+The present local ceiling is 72 CPU-hours, 25 GiB, one frozen-model process, and zero
+external spend. It supports bounded qualification and pilots, not the full campaign;
+confirmatory registration requires an exact selected prefix and an approved execution
+resource plan.
+
Run exclusions are limited to pre-specified integrity or protocol failures. Each
condition receives a fixed ordered list of primary and reserve seeds before outcomes
are observed. An invalid primary seed may be replaced only by the next reserve seed;
@@ -863,17 +1032,21 @@ Qualitative analysis is secondary and will examine:
- abrupt convention changes;
- negotiation-era semantic shifts.
-Human coders will use a pre-registered codebook and, where feasible, blinded
-independent coding. Generated audit interpretations will not be treated as ground
-truth.
+The approved initial campaign uses no human-coded outcome. Qualitative labels are
+generated by registered deterministic analysis and are not treated as ground truth.
+Any later human-coding proposal requires a prospective protocol and governance
+amendment before collection.
---
## 8. Evidence Integrity and Reproducibility
-> **Design status:** This section describes required evidence behavior. Hash-chain
-> writing, signatures, Merkle checkpoints, anchoring, and the independent verifier are
-> not yet complete; see Section 10.
+> **Implementation status:** Local hash-chain writing, domain-separated signatures,
+> ordered-Merkle checkpoints and proofs, anchor publisher transports, evidence export,
+> and the standalone verifier are implemented and covered by software tests. The
+> approved campaign uses deterministic simulated receipts and no real funds; no
+> public-chain receipt is claimed, and the historical research
+> corpus remains unregistered and unanchored; see Section 10.
### 8.1 Append-Only Evidence
@@ -893,15 +1066,18 @@ Ordered Merkle trees are specified to provide:
Signed checkpoint manifests will bind the event roots to run configuration, learner
contract, software commit, and previous checkpoint.
-### 8.3 External Anchoring
+### 8.3 Simulated Commitments and Optional External Anchoring
-Development qualification is specified to use Base Sepolia. Declared public studies may anchor
-checkpoint hashes to Base mainnet. Only hashes and minimal routing metadata are
-on-chain. Private observations, messages, ledgers, prompts, identities, and keys
-remain off-chain.
+The prospective campaign uses a deterministic in-memory transport, a non-monetary
+simulated balance, and receipts labeled `anchorClass: "simulated"`. Base Sepolia and
+Base mainnet remain optional software capabilities outside the approved study. A
+future public-chain activation would require a new prospective amendment; only hashes
+and minimal routing metadata could be placed on-chain.
-The anchor proves that a committed prefix existed no later than a chain block and
-that disclosed content matches the commitment. It does not prove:
+The simulated commitment proves that the executed bundle consumed a specific
+committed prefix and that disclosed content matches it. Together with the external
+registration timestamp and immutable Git commit, it preserves prospective order. It
+does not prove public availability, decentralized persistence, economic finality, or:
- an event was true;
- no event was omitted before commitment;
@@ -925,7 +1101,7 @@ The standalone verifier is specified to check:
- cross-bindings among intention, message, delivery, interpretation, and outcome;
- Merkle roots, inclusion proofs, and consistency proofs;
- checkpoint chains and witness signatures;
-- public-chain transaction, chain ID, block inclusion, and finality;
+- receipt class, transaction-shaped identifier, chain identity, block inclusion, and finality semantics;
- forks, gaps, and unanchored tails.
Any integrity failure prevents a run from receiving a valid disposition.
@@ -933,8 +1109,9 @@ Any integrity failure prevents a run from receiving a valid disposition.
Here, **independent verifier** means software that recomputes evidence from an exported
bundle without trusting the live runtime, database, or private keys. For a
publication-facing claim, at least one verification execution must additionally be
-performed by a person who did not operate the original run, using an independently
-configured chain RPC. External replication remains stronger than either form.
+performed by a person who did not operate the original run, using the independent
+offline verifier. An independent RPC is additionally required only for a future
+public-chain receipt. External replication remains stronger than either form.
### 8.5 Replay
@@ -1014,35 +1191,197 @@ as separate fields.
## 10. Current Implementation Status
-As of September 2, 2026, the repository records ALD-001 through ALD-007 as complete.
-This is engineering status, not an empirical result.
+**Engineering snapshot:** v0.1.110 · 254/258 backlog acceptance criteria verified.
-Implemented:
+As of September 12, 2026, 82 of 86 backlog items satisfy all of their acceptance
+criteria. This is engineering status, not an empirical result.
-- npm/TypeScript workspaces;
-- runtime-validated shared schemas;
-- typed configuration and secret scanning;
-- DTSF-compatible Learner A, Learner B, and Nursery twin-pack scaffolds;
-- SQLite WAL evidence schema and append-only triggers;
-- RFC 8785 canonical serialization;
-- ledger event-type validators;
-- automated lint, build, test, dependency-audit, and clean-clone checks.
-
-Not yet implemented or empirically executed:
-
-- full hash-chain writing and signatures;
-- atomic evidence writer;
-- Merkle checkpoints and independent verifier;
-- Base anchoring;
-- complete gateway and scenario engine;
-- model adapters and training;
-- Research-Grade isolation;
-- experiments E00-E50.
-
-The implementation backlog is maintained in [BACKLOG.md](BACKLOG.md), while normative
-requirements are in [SPECIFICATION.md](SPECIFICATION.md).
+Implemented:
----
+- hash-chained, signed SQLite evidence; ordered Merkle checkpoints; exact bundle
+ export; and an independent verifier;
+- a frozen local audit-cost protocol and receipt covering five immutable
+ qualification bundles, with ordinary-log, signed-stream, integrity-structure,
+ policy-state, and complete-bundle storage separated; descriptive offline verifier
+ timing and the prior six-case mutation challenge are reported without claiming a
+ public anchor or an empirical result;
+- an E16 comparator pipeline that fits all five eligible non-ledger predictors on a
+ validation-fit fold, selects by Brier score on a disjoint validation-selection
+ fold, refits and hash-locks the selected model, commits baseline and native-ledger
+ predictions before accepting test outcomes, and keeps the oracle diagnostic
+ ineligible for selection; exact synthetic qualification passed five comparator and
+ seven negative-control paths; the production runtime now invokes an injected
+ outcome-blind native predictor after Gateway delivery and before receiver action,
+ commits and checkpoints baseline/native predictions, derives the receiver choice
+ from its hash-bound turn record, and binds per-turn post-outcome scores; the hashed
+ run configuration commits the selection and prediction-function identities and
+ accepted-evaluation-delivery eligibility, and recovery refuses an absent or
+ mismatched provider; exact detached qualification passed 10 focused tests and the
+ full 1,808-test repository gate;
+- a general fail-closed registration-packet compiler requiring eleven exact binding
+ classes; its generic inventory binds all eleven fields for E00, E01 and E02,
+ leaves 160 generic experiment-specific bindings open, and emits their registration hashes;
+ E03's blinded pilot and full control qualification instead use separate verified
+ stage-specific repository packets and simulated pre-run commitments;
+- grammar-valid bitmap, translation-tolerant canvas, and graded tone distances with
+ deterministic nearest-prototype evaluation; this is a handcrafted E13 diagnostic,
+ not evidence that a learner learned the metric or generalized to novel forms; an
+ exact clean-candidate qualification covers three carriers and six rejection paths;
+- a Base-shaped commitment client with confirmation/recovery handling, explicit
+ simulated/public receipt classification, and mainnet gating; the verifier rejects
+ a simulated receipt relabeled as public-chain evidence;
+- an authenticated Fort boundary that stores the project signer envelope and unused
+ optional public-chain credentials only as encrypted Safe material; a four-track
+ files-mode Mode R run sealed and production-verified every bounded qualification
+ bundle; the zero-balance public address remains inactive and is not needed by the
+ simulation-funded campaign;
+- deterministic scenarios, the tool-only Gateway, all declared carrier controls,
+ five learner tracks, DTSF twin routes, and the complete turn lifecycle;
+- two-container Mode R learner-host separation and bounded transport/network
+ controls, training separation, active side-channel and observation red teams,
+ including a reusable 28-case generated-carrier structural
+ side-feature audit with accepted controls and fail-closed evidence readiness; the
+ exact candidate also passed the current two-container Mode R timing, fixed-envelope,
+ error-shape, host-denial, crash-survival, and three-track update checks; plus
+ telemetry, retention, snapshot/restore, and failure policy;
+- readiness gates for E00-E50, reproducible E03 design and seed artifacts, canonical
+ E03 registration compilation, and a fail-closed confirmatory preflight;
+- a privacy-minimized real-model software qualification of Qwen3-4B Q4_K_M through
+ llama.cpp, with exact model/runtime hashes and both role directions exercised; and
+- automated lint, build, test, dependency-audit, secret-scan, acceptance-coverage,
+ API-documentation, project-status, clean-clone, hosted CI, and Mode R checks.
+
+Not yet empirically executed or independently evidenced:
+
+- final-topology/pilot resource qualification; and
+- registered behavioral and replication findings for the planned hypothesis family.
+
+The external-dependency ledger is ready at 1/1 applicable item: O01 is verified by
+the scoped synthetic-only governance decision, while O02-O06 are non-applicable to
+local execution. Public funding, public-chain transactions, hosted enforcement,
+second-person restoration, and third-party registration remain optional claim-
+strengthening activities. Their absence is disclosed and prohibits claims of public
+finality, hosted enforcement, independent restoration, or third-party registration;
+it does not answer or alter the agent-language estimands.
+
+The frozen local [data/claim inventory](docs/data-and-claim-inventory.md) resolves 140
+exported bundles across 22 collections by path and content hash. Forty-six have
+tracked bounded software-qualification support, 33 are historical qualification
+exports, and 61 are development, failed, or superseded diagnostics. All 140 are excluded from
+empirical estimates. Ninety-two recorded anchor confirmations are local simulated
+receipts; prospective receipts now bind an explicit class that the
+verifier refuses to relabel. Confirmed public-chain anchors, pilot bundles, confirmatory
+bundles, replication bundles, and research-included bundles are all zero.
+The five later E02 v3 qualification bundles are accounted for separately from this
+frozen 140-bundle inventory and remain excluded from behavioral estimates.
+
+The registered E02 v1 and v2 attempts both failed before restore and probe analysis.
+The v1 attempt stopped after five consecutive timeout rejections at turn 465; v2
+stopped after 161 committed turns when a post-action turn-path deadline escaped
+without the required forfeited-turn record. Their partial databases are not exported
+bundles and are not included in this bundle inventory. Both attempts remain preserved
+as failed software qualifications, not behavioral results.
+The exact v0.1.115 engineering candidate converts deadlines from observation, sending,
+receiving, receiver action, and post-outcome processing into one audited timeout-
+forfeit path with method-specific diagnostics. A timed-out remote adapter is made
+unreachable before another turn, and the attempt cannot be resumed even after
+runtime recovery. It passed 1,959 tests and the ordinary two-container Mode R gate;
+the separate all-method framed fault suite and raw logs are bound in the
+[pre-registration qualification](reports/research/e02-v3-readiness-qualification.md).
+The prospective v3 packet is now repository-registered under
+`sha256:d35e6b118e4c811f80b426680a0b2f4b0c9b8da3e59bdc56293e557ef979f248`
+with five seeds disjoint from v1/v2. Its deterministic simulated commitment has
+three confirmations and zero currency. Exact v0.1.119 passed the full 1,963-test
+pre-execution gate, release-auditor binding, and ordinary two-container Mode R
+preflight. The one sequential v3 attempt then completed on exact clean execution
+commit `9ec08ecf4528c6da8867d55900d6ca8667ded7ce`: all five numeric slots,
+before/after-restore stages, and 60 fixed probes passed their unchanged software
+thresholds. The [terminal receipt](reports/research/e02-v3-qualification-receipt.json)
+and separate [full live audit](reports/research/e02-v3-full-audit-receipt.json)
+bind the original evidence. This closes bounded local E02 software reliability,
+not the behavioral ledger-value hypotheses, image-encoder qualification, or
+independent human review. The ordinary two-container preflight exercises learner-host
+transport and network controls, not separate Baby twins, Gateway-only routes,
+per-role ledger-writer and signer keys, or controller trust-zone processes.
+Existing blocker B12 now requires exact selected Mode R boundary and detector
+evidence before any E10+ isolation claim. It does not gate E03 Prototype controls.
+The prospective [E03 infrastructure-mode correction](protocols/e03-prototype-mode-amendment.v2.json)
+selects shared-process Prototype Mode for controls, without a Research-Grade
+isolation claim. Its unexecuted predecessor specified external adapters, which
+the runtime guard rejects; that record remains historical. The fresh six-condition
+development namespace ran once on clean v0.1.158 and its six original bundles
+passed the separate TypeScript/Rust live audit with signed-original control
+pairing. It remains bounded Mode P development mechanics, not a pilot finding.
+The pilot collector was prepared prospectively. A signed-original
+[topology audit](reports/research/e03-prototype-topology-audit-receipt.json) and measured,
+zero-spend [allocation](protocols/e03-pilot-resource-allocation.v1.json) now precede any 120-run pilot
+packet. The allocation charges prior E02 and development CPU/storage before
+reserving 1 CPU-hour, 1 GiB evidence, and 1 GiB resident memory. Its
+prospective 0.7-hour sequential wall projection includes full-service startup/
+audit overhead; the unregistered 0.2-hour slot-only projection is retained in
+dated correction provenance. The unused [v1 pilot packet](protocols/e03-pilot-registration.v1.json)
+and [simulated binding](protocols/e03-pilot-registration-binding.v1.json)
+are retained after tracked test-fixture changes altered their exact source
+digest. A [prospective v2 amendment](protocols/e03-pilot-registration-amendment.v2.json)
+requires fresh IDs/seeds and binding before admission. A new
+[v2 pilot packet](protocols/e03-pilot-registration.v2.json) freezes 120
+run IDs and disjoint pilot seeds against clean v0.1.164. A separate
+[simulated binding](protocols/e03-pilot-registration-binding.v2.json)
+records three local confirmations without real funds or an independent
+timestamp. Tracked ready-stage fixture corrections changed the v2 exact source
+digest before seed use; a [prospective v3 amendment](protocols/e03-pilot-registration-amendment.v3.json)
+requires a fresh packet, IDs/seeds, and binding. A new
+[v3 pilot packet](protocols/e03-pilot-registration.v3.json) freezes 120
+distinct run IDs and disjoint pilot seeds against clean v0.1.167. Its
+separately committed [simulated binding](protocols/e03-pilot-registration-binding.v3.json)
+records three local confirmations without real funds or an independent
+timestamp. The active v3 pilot ran once on clean v0.1.169 and completed all
+120 planned original runs without invalid, aborted, or replacement runs.
+Its separate full-bundle TypeScript/Rust audit passed. The outcome-blind
+reducer and separate recomputation audit selected the frozen 25-primary-seed
+E03 design row from pilot dispersion. The retained original receipt and
+reduction hashes are mapped in the [pilot progress update](reports/research/e03-v3-pilot-progress-update.md).
+No E03 chance-control qualification or behavioral finding is reported.
+The separately retained Homebrew-base-R 30,000-repetition bounded design
+calculation passed the nominal complete numeric rule at 25 seeds, with lower
+95% power bound `0.999872`. Its forced two-invalid-primary-per-condition
+sensitivity passed zero repetitions; this fragility must be reported and
+reserve/replacement mechanics qualified before full collection. Neither
+simulation establishes an observed control effect.
+The tracked [outcome-blind E03 sample-size decision](protocols/e03-sample-size-decision.v1.json)
+binds the original pilot, reduction, and power digests and carries the
+invalid-as-failure limitation into future packet compilation. Its live
+recomputation needs separately retained raw records; clean-checkout inspection
+does not rerun that audit.
+The prospective [full E03 resource allocation](protocols/e03-full-resource-allocation.v1.json)
+uses the complete retained evidence tree and actual 120-run pilot maxima. It
+reserves all 168 possible primary/reserve slots at 1 CPU-hour, 1.1 GiB new
+evidence, 1 GiB resident, and 0.5 hours projected sequential wall time,
+after charging 22.3 prior CPU-hours and 4.0 GiB retained evidence. This is
+within the local synthetic software-qualification ceiling but does not fund
+or qualify the E10+ campaign.
+The prospective [paired-reserve clarification](protocols/e03-full-paired-reserve-amendment.v1.json)
+resolves a pre-registration contradiction: E03's unit is a scenario seed
+shared across all six conditions, while condition-specific reserves could
+break the paired oracle-control estimand. An invalid primary run excludes its
+six-condition scenario slot from paired analysis; the next unused ordered
+reserve scenario slot is collected across all six conditions, at most three.
+Original invalid and valid-but-excluded companion runs remain indexed. This
+changes neither the 168-run maximum nor scientific thresholds. No full packet
+or full seed existed when the clarification was recorded.
+The pure [full-stage paired-attempt reconciler](packages/analysis/src/e03-full.ts)
+enforces complete six-condition scenario pairs, identical scenario histories,
+whole-slot ordered reserves, and explicit incomplete disposition when the
+three reserves cannot replace all invalid primary pairs. The prospectively
+registered v1 full-stage attempt completed 150 required primary runs with 150
+verified original bundles and no invalid run. Because every primary pair was
+valid, all 18 reserves remained correctly unattempted. All registered control-
+equivalence, oracle-adequacy, and paired-separation criteria passed. A separate
+read-only audit recomputed every bundle and the fixed analysis; the tracked
+[portable receipt](reports/research/e03-full-v1-status-receipt.json) binds the
+ignored original terminal receipt. This is a Prototype-Mode control software
+qualification, not an empirical language-emergence result, Research-Grade
+isolation claim, independent review, or public timestamp.
## 11. Results
@@ -1141,11 +1480,10 @@ database search with inclusion/exclusion criteria and dual-review screening.
### 14.1 Code
-Source code and planning artifacts are publicly available at:
+Source code and planning artifacts exist in the working repository. An anonymous
+review artifact and any public release URL will be prepared only after release
+approval. Every empirical paper revision will identify an immutable source commit.
-
-
-Every empirical paper revision will identify the exact Git commit used.
### 14.2 Protocols
@@ -1154,6 +1492,8 @@ Every empirical paper revision will identify the exact Git commit used.
- Ledger integrity: [LEDGER-INTEGRITY-DESIGN.md](LEDGER-INTEGRITY-DESIGN.md)
- Experiment protocols and results notebook:
[EXPERIMENT-NOTEBOOK.md](EXPERIMENT-NOTEBOOK.md)
+- Frozen question, estimand, and outcome-class cards:
+ [docs/research-protocol-cards.md](docs/research-protocol-cards.md)
- Engineering plan: [BACKLOG.md](BACKLOG.md)
### 14.3 Data
@@ -1170,12 +1510,16 @@ and analysis plan before execution. The run configuration stores the protocol co
and pre-registration hash. Changes create appended amendments rather than rewriting
the original record.
-No external registration record exists yet. Before the first confirmatory run, the
-team intends to create a dated OSF registration and anchor the same canonical
-`preRegistrationHash` before the run enters `running`. A Git commit in an
-author-controlled repository is retained as a development record but is not, by
-itself, treated as third-party preregistration. Appendix D supplies a numerically
-complete worked E03 registration for review.
+The E02 software-qualification packets and unused E03 v1/v2 pilot packets have been
+repository-registered; the separate E03 v3 pilot was registered and completed.
+Before each later
+confirmatory run, its canonical packet will be committed unchanged in a Git ancestor
+of the execution commit. Preflight will load those historical bytes, reproduce the
+`preRegistrationHash`, and require a matching deterministic simulated commitment
+before the run enters `running`. This establishes exact local identity, ancestry,
+ordering against later committed evidence, and tamper detection; it is not described
+as third-party registration or an independent timestamp. Appendix D supplies a
+numerically complete worked E03 registration for review.
---
@@ -1222,11 +1566,10 @@ Institutional and technical acknowledgements: **TBD before submission**.
### AI-Assistance Disclosure
-This working manuscript was developed with AI assistance in VS Code through the
-Copilot SDK. The AI assistant performed the initial source retrieval, opened the
-arXiv/ACL/PMLR/Crossref/publisher/standards pages summarized in Appendix A, organized
-the manuscript, summarized relevance, and conducted consistency checks. A separate AI
-research-agent pass challenged source status and identified overclaiming risks.
+This working manuscript was developed with AI-assisted research and repository tools.
+The tools supported source retrieval, primary-page inspection, manuscript organization,
+code implementation, executable validation, and critical consistency checks. Separate
+AI-assisted passes challenged source status, numerical claims, and overclaiming risks.
AI systems are not authors and bear no responsibility for the manuscript. Human
authors remain responsible for every claim, citation, analysis, and conclusion.
@@ -1245,14 +1588,16 @@ were attempted on September 2, 2026. The API reported the account disabled and t
reported its keyless monthly limit reached. No new Tavily-generated result was relied
upon. Rather than conceal the failure or imply that Tavily returned evidence, the
review continued through arXiv Atom metadata, ACL Anthology, PMLR, Crossref, JMLR,
-publisher and university pages, RFC Editor, NIST, Google Books, and Ethical Tech CoLab
-repositories. A final submission should rerun the search after Tavily access is
-restored and record whether it changes the included corpus.
+publisher and university pages, RFC Editor, NIST, Google Books, and institutional
+repositories. A bounded update was completed on September 11, 2026 across primary
+repository and publisher surfaces. Its queries, screening boundaries, all 50 source
+decisions, and five newer comparators are recorded in the
+[source verification register](reports/research/source-verification-register.md).
+This was not a systematic review. A final submission must repeat the search through
+the manuscript-freeze date and record whether it changes the included corpus.
All retrieved material was treated as untrusted evidence, not as instruction.
----
-
## References
[1] Steels, L. (1995). A self-organizing spatial vocabulary. *Artificial
@@ -1364,7 +1709,7 @@ Conference on Learning Representations*.
[26] Galke, L., & Raviv, L. (2024). Learning and communication pressures in
neural networks: Lessons from emergent communication. *Language Development
-Research, 5*(1), 116-143.
+Research, 5*(1), 116-140.
[27] Kouwenhoven, T., Peeperkorn, M., & Verhoef, T. (2025). Searching for
structure: Investigating emergent communication with large language models.
@@ -1511,7 +1856,7 @@ before submission.
| [12] | **tool-metadata-verified**, arXiv primary page | Peer-reviewed NeurIPS paper; journal score N/A | "Biases for Emergent Communication in Multi-agent Reinforcement Learning." Shows designed biases can promote communication. |
| [13] | **tool-abstract-verified**, arXiv primary page/API | Peer-reviewed AAMAS paper; journal score N/A | "Capacity, Bandwidth, and Compositionality in Emergent Language Learning." Supports capacity/bandwidth controls. |
| [14] | **tool-metadata-verified**, ACL Anthology | Peer-reviewed ACL paper; journal score N/A | "Compositionality and Generalization In Emergent Languages." Supports separate behavioral generalization tests. |
-| [15] | **tool-abstract-verified**, arXiv primary page/API | Peer-reviewed NeurIPS paper; journal score N/A | "Interpretable agent communication from scratch (with a generic visual processor emerging on the side)." Supports causal symbol interventions. |
+| [15] | **tool-full-text-verified**, complete paper | Peer-reviewed NeurIPS paper; journal score N/A | Supports unsupervised learned discrete communication, partial interpretability, and novel-class generalization in its evaluated setting; it does not supply causal symbol-intervention evidence. |
| [16] | **tool-full-text-verified**, PMC full text and Crossref | Peer-reviewed PNAS journal article, **5/5** | "Words categorize the semantic fields they refer to in ways that maximize communication accuracy while minimizing complexity." Supports discrete-channel efficiency in the tested color domain. |
| [17] | **tool-abstract-verified**, arXiv primary page/API | Peer-reviewed ICLR paper; journal score N/A | "Compositional Languages Emerge in a Neural Iterated Learning Model." Supports transmission bottlenecks. |
| [18] | **tool-metadata-verified**, Crossref DOI metadata | Peer-reviewed IEEE journal article, **4/5** | "Spontaneous evolution of linguistic structure: an iterated learning model of the emergence of regularity and irregularity." Establishes computational iterated learning. |
@@ -1522,8 +1867,8 @@ before submission.
| [23] | **tool-metadata-verified**, ACL Anthology | Peer-reviewed EMNLP paper; journal score N/A | "Emergent Linguistic Phenomena in Multi-Agent Communication Games." Supports community contact and protocol convergence. |
| [24] | **tool-abstract-verified**, arXiv primary page/API and venue comment | Peer-reviewed NeurIPS paper; journal score N/A | "Emergent Communication: Generalization and Overfitting in Lewis Games." Used as supporting, not sole, evidence. |
| [25] | **tool-metadata-verified**, arXiv primary page | Peer-reviewed ICLR paper; journal score N/A | "On the role of population heterogeneity in emergent communication." Supports heterogeneity effects. |
-| [26] | **tool-metadata-verified**, official journal page | Peer-reviewed journal article, **3/5** | "Learning and communication pressures in neural networks: Lessons from emergent communication." A field review with a newer/variable-impact venue. |
-| [27] | **tool-metadata-verified**, ACL Anthology | Peer-reviewed COLING paper; journal score N/A | "Searching for Structure: Investigating Emergent Communication with Large Language Models." Direct pretrained-LLM comparison. |
+| [26] | **tool-full-text-verified**, complete official paper | Peer-reviewed journal article, **3/5** | Reviews communicative success, production effort, learnability, and other pressures; official pagination is 116–140. |
+| [27] | **tool-full-text-verified**, complete ACL Anthology paper | Peer-reviewed COLING paper; journal score N/A | Directly studies artificial-language learning and transmission with pretrained language models. |
| [28] | **tool-abstract-verified**, arXiv page and TMLR record metadata | Peer-reviewed TMLR journal article, **4/5** | "A Review of the Applications of Deep Learning-Based Emergent Communication." Used as a field synthesis, not primary experiment. |
| [29] | **tool-abstract-verified**, arXiv primary page/API | Peer-reviewed ICLR paper; journal score N/A | "Emergent Communication through Negotiation." Supports incentive-sensitive communication. |
| [30] | **tool-metadata-verified**, Crossref/JSTOR metadata; full page blocked | Peer-reviewed Econometrica journal article, **5/5** | "Strategic Information Transmission." Supplies cheap-talk theory. |
@@ -1545,8 +1890,8 @@ before submission.
| [46] | **tool-full-text-verified**, CoLab publication page | Institutional report, not peer-reviewed; **1/5** under the fixed journal rubric | "What Is Ethical AI?" Used for CoLab governance commitments, not independent empirical evidence. |
| [47] | **tool-full-text-verified**, CoLab publication and source repository | Institutional research guidance, not peer-reviewed; **1/5** | "AI-Powered Assistance in Formulating Research Questions." Supplies the source-verification and AI-disclosure method. |
| [48] | **tool-metadata-verified**, arXiv primary page | Preprint, not peer-reviewed; journal score N/A | "Learning to Protect Communications with Adversarial Neural Cryptography." Motivates an exploratory synthetic-message experiment only. |
-| [49] | **tool-metadata-verified**, ACL Anthology | Peer-reviewed EMNLP demo paper; journal score N/A | "EGG: a toolkit for research on Emergence of lanGuage in Games." Closest infrastructure comparator. |
-| [50] | **tool-abstract-verified**, PMLR primary page | Peer-reviewed ICML paper; journal score N/A | "\"Other-Play\" for Zero-Shot Coordination." Supports novel-partner evaluation and specialized-convention risk. |
+| [49] | **tool-full-text-verified**, complete ACL Anthology paper | Peer-reviewed EMNLP demo paper; journal score N/A | Closest infrastructure comparator: modular games, channels, optimization, analysis, and checkpoints. |
+| [50] | **tool-full-text-verified**, complete PMLR paper | Peer-reviewed ICML paper; journal score N/A | Supports independently trained novel-partner evaluation and specialized-convention risk. |
No source in the table was silently upgraded from preprint or workshop status to a
peer-reviewed main-track result.
@@ -1597,19 +1942,22 @@ not assigned a journal score because the fixed rubric is explicitly journal-base
---
-## Appendix D. Worked Preregistration Example: E03 Controls
+## Appendix D. Registered E03 Control Qualification
-> **Status:** Complete worked example for methodological review; not yet registered
-> or executed.
+> **Status:** The prospective pilot selected 25 primary seeds per condition. The
+> separately registered full v1 qualification completed all 150 primaries, used no
+> reserves, passed its fixed control/oracle thresholds, and passed a separate raw
+> audit. This is control software qualification, not a behavioral finding.
### D.1 Registration and Integrity
-- Registration target: OSF Registries.
-- The canonical registration JSON, this manuscript commit, analysis-script commit,
- and generated seed manifest will be registered before execution.
-- The same `preRegistrationHash` will be anchored to Base Sepolia before any run
- enters `running`.
-- No outcome will be inspected before registration and anchoring complete.
+- Registration used a prospective clean Git packet commit and a matching pre-run
+ simulated commitment; no external registry or public timestamp is claimed.
+- The canonical registration JSON bound the analysis-source hashes and generated
+ seed manifest before execution.
+- The same `preRegistrationHash` was committed through the deterministic simulation
+ transport before any run entered `running`.
+- The full-stage outcome was not inspected before registration and commitment.
### D.2 Objective
@@ -1621,7 +1969,7 @@ meaningful learned communication and establish an oracle upper bound.
| Parameter | Registered value |
|---|---|
| Experiment | `E03-v1` |
-| Deployment mode | Research-Grade Mode |
+| Deployment mode | Prototype Mode; no Research-Grade isolation claim |
| Learner track | `no-learning` |
| Interaction | Cooperative signaling |
| Carrier | Fixed token |
@@ -1631,8 +1979,8 @@ meaningful learned communication and establish an oracle upper bound.
| Candidate objects | 4 per episode |
| Chance success | 0.25 |
| Episodes per run | 200 |
-| Primary seeds per condition | Power-rule output; 75 when pilot SD is greater than 0.05 and at most 0.10 |
-| Reserve seeds per condition | 10% of primary count, rounded up |
+| Primary seeds per condition | 25, selected prospectively from the blinded pilot |
+| Reserve seeds per condition | 3 ordered paired slots (18 maximum runs); none attempted |
| Conditions | Disabled, constant, random, shuffled, normal no-learning, oracle |
| Alpha | 0.05 |
| Within-family correction | Holm-Bonferroni |
@@ -1640,21 +1988,26 @@ meaningful learned communication and establish an oracle upper bound.
### D.4 Seed Generation
-For slot `i`, the shared scenario seed is the hexadecimal SHA-256 digest of:
+For stage `s` and slot `i`, the shared scenario seed is the hexadecimal SHA-256
+digest of:
```text
-ald-e03-v1 || 0x00 || decimal(i)
+ald-seed-allocation-v1 || 0x00 || s || 0x00 || E03 || 0x00 || decimal(i) || 0x00 || scenario
```
-Slots `1` through registered `N` are primary. The next `ceil(0.10 * N)` slots are
-ordered reserves. Every condition uses the same scenario seed for a given slot.
-Condition-specific gateway randomness for `random` and `shuffled` is derived from:
+Here `s` is `blinded-pilot` or `confirmatory`. The pilot has exactly 20 primary
+slots and no reserves. Full qualification uses slots `1` through selected `N` as
+primary and the next `ceil(0.10 * N)` as ordered reserves. Every condition uses
+the same scenario seed for a given stage and slot. Each condition has distinct
+Baby A, Baby B, Gateway, and analysis seeds derived from:
```text
-scenarioSeed || 0x00 || communicationCondition
+ald-seed-allocation-v1 || 0x00 || s || 0x00 || E03 || 0x00 || condition || 0x00 || decimal(i) || 0x00 || component-domain
```
-The complete scenario and gateway seed manifest is part of the registration.
+The `normal` condition uses `normal-no-learning` in that derivation. The complete
+component seed manifest is part of the registration, and no seed binding is visible
+to either learner.
### D.5 Conditions
@@ -1673,62 +2026,98 @@ proposal exists.
### D.6 Primary Outcomes and Tests
The unit of analysis is the run/seed success proportion across 200 episodes.
+Only the signed original turn stream supplies the episode tally and paired
+scenario hashes. Operational slot summaries must reconcile to that stream and
+to the verified channel transcript before entering the pilot reduction or a
+registered analysis.
+The Prototype-Mode development topology uses the same signed source for its
+six-condition paired-scenario and control-mechanics audit; a passing unsigned
+slot summary alone is insufficient.
+After a pilot terminal receipt exists, progression checks bind it to the
+registered packet and reconcile planned, attempted, and completed run counts;
+document agreement cannot conceal a failed or incomplete original attempt.
+Before the terminal receipt exists, a running status additionally requires
+the original start record and its matching live local controller process;
+a retained start file after controller failure is unresolved, not a pass.
+The lightweight project-status check and the campaign progression checker
+share that same original pilot-state rule; current prose cannot clear the gate
+by agreeing with itself while local execution evidence contradicts it.
+An attempted failed pilot slot retains its partial evidence and measured
+learner/Nursery CPU charges. Missing cgroup measurements leave capacity
+unresolved rather than treating the failed slot as free or replaceable.
+Before original collection, the packet's protocol-base commit and execution
+commit must have no intervening source changes in packages, twins, deployment
+code, or scripts. A changed implementation requires a new prospective
+packet, not a retroactive hash match.
1. **Control equivalence:** For each of disabled, constant, random, shuffled, and
normal no-learning, perform two one-sided one-sample tests on seed-level success
proportions against equivalence bounds 0.20 and 0.30. Equivalence requires both
one-sided tests to reject at the Holm-adjusted alpha.
-2. **Oracle adequacy:** The lower bound of the two-sided 95% bootstrap confidence
- interval for mean seed-level oracle success must exceed 0.90.
+2. **Oracle adequacy:** A one-sided seed-level t test and its 95% lower confidence
+ bound must exceed 0.90. A two-sided 95% seed-bootstrap interval is reported as a
+ sensitivity estimate.
3. **Oracle separation:** For each non-oracle condition, compute paired seed-level
- oracle-minus-control differences. The lower bound of the Holm-adjusted 95%
- confidence interval must exceed 0.60.
+ oracle-minus-control differences. One-sided t-test p-values above 0.60 receive
+ Holm correction across the five conditions. Conservative 99% Bonferroni lower
+ bounds and nominal paired seed-bootstrap intervals are reported.
No episode is analyzed as an independent run.
### D.7 Sensitivity and Power
Before final registration, a separate outcome-blind-for-confirmatory-use pilot of 20
-seeds per non-oracle condition will estimate the largest between-seed standard
-deviation. Pilot runs will not enter confirmatory estimates. The registered primary
-seed count is selected by this fixed rule:
+seeds per non-oracle condition fit the beta-binomial model and estimated the
+largest latent between-seed standard deviation after accounting for 200-episode
+sampling. Pilot runs did not enter the full-stage estimates. The registered primary
+seed count was selected by this fixed rule:
-| Largest pilot SD | Primary seeds per condition |
+| Largest latent pilot SD | Primary seeds per condition |
|---:|---:|
| <= 0.05 | 25 |
| > 0.05 and <= 0.10 | 75 |
-| > 0.10 and <= 0.15 | 150 |
+| > 0.10 and <= 0.15 | 155 |
| > 0.15 and <= 0.20 | 300 |
| > 0.20 | New simulation and amended registration required before collection |
-A 30,000-replicate Monte Carlo design check was run for this draft under a true
-seed-level mean of 0.25, between-seed standard deviation of 0.10, 200 binomial
-episodes per seed, and conservative per-test alpha of 0.01. Estimated equivalence-test
-power was 0.924 at 75 seeds per condition. Sensitivity checks produced approximately
-0.912 power at SD 0.05 with 25 seeds, 0.920 at SD 0.15 with 150 seeds, and 0.913 at
-SD 0.20 with 300 seeds. Before registration, the simulation code and output must be
-checked in and independently rerun. Failure to reproduce at least 90% power blocks
+The original 30,000-replicate normal sufficient-statistic calculation is retained as
+a single-control equivalence component check. It is not the registration-facing
+power claim. Independent base-R simulation instead draws a bounded latent seed
+probability from a beta distribution and then 200 binary episodes, applies all five
+Holm-adjusted control TOSTs, the oracle adequacy test, and all five Holm-adjusted
+paired separation tests. With 10,000 repetitions per row, full numeric-rule power
+was 0.9324, 0.9262, 0.9187, and 0.9576 for the four rows; lower 95% Monte Carlo bounds
+were 0.9273, 0.9209, 0.9132, and 0.9535. Exact output is committed in
+`reports/research/statistical-validation.tsv` and independently replayed by
+`pnpm audit:statistics:live`. Failure to reproduce a lower bound of at least 0.90 blocks
registration; it does not permit post-hoc widening of the margin.
Sensitivity analyses:
- Wilson intervals over pooled episodes are descriptive only;
-- a hierarchical Bernoulli model with seed random intercept is reported as a
- robustness check;
+- a hierarchical Bernoulli model with seed random intercept and percentile bootstrap
+ intervals are reported as sensitivity checks;
- invalid primary runs are treated as failures in a worst-case sensitivity analysis.
### D.8 Exclusions, Invalid Runs, and Replacement
+Before a full E03 packet existed, the prospective paired-reserve amendment
+superseded the condition-specific replacement wording. The independent unit
+is the complete six-condition scenario slot, so a single invalid primary run
+excludes that paired slot from the primary analysis. Its valid companions
+remain visible as valid-but-excluded records. The next registered reserve
+scenario slot replaces the entire pair across all six conditions, in order.
+
A run is invalid only for a pre-specified verifier failure, wrong configuration hash,
gateway-control mismatch, or infrastructure failure that prevents the planned 200
episodes.
- Invalid runs remain in the public run index.
-- The next unused reserve seed for that condition replaces the invalid primary slot.
-- No more than the registered reserve count may be used per condition.
-- If fewer than registered `N` valid runs remain, the primary analysis is incomplete;
+- The next unused ordered six-condition paired reserve scenario slot replaces an invalid primary paired slot.
+- No more than three registered paired reserve slots may be used.
+- If fewer than registered `N` complete verified paired slots remain, the primary analysis is incomplete;
there is no unregistered additional collection.
-- Invalid and aborted counts are reported by condition.
+- Invalid, aborted, valid-but-excluded, and reserve counts are reported by condition and paired-slot mapping.
### D.9 Stopping Rule
@@ -1741,28 +2130,39 @@ E03 qualifies the downstream chance baseline only if:
- all five non-oracle conditions meet equivalence;
- oracle adequacy and separation criteria pass;
-- no non-oracle condition has more than 5% of its primary seeds with observed success
- of 0.35 or greater; every such seed is individually audited for leakage;
+- every non-oracle primary seed with observed success of 0.35 or greater receives an
+ individually resolved leakage audit; the count is diagnostic and is not an
+ uncalibrated statistical rejection rule;
- all included evidence bundles pass verification;
- no unplanned metadata or channel leakage is detected.
Failure is a result and blocks downstream confirmatory claims until a new,
separately registered protocol is justified.
+The v1 execution included 25 complete paired slots (150 valid runs), zero invalid
+runs, and zero attempted reserves. The five control means ranged from 0.2372 to
+0.2566 and all passed the registered equivalence decisions; the oracle mean and
+simultaneous lower bound were both 1.0, and all five registered separation decisions
+passed. The separate audit recomputed every original bundle and the fixed analysis.
+The tracked [portable receipt](reports/research/e03-full-v1-status-receipt.json)
+binds the separately retained raw evidence and preserves the `not-tested` scientific
+disposition.
+
---
## Appendix E. Future Results-Table Scaffolds
-These tables contain no observed results.
+E00–E03 report bounded software/control qualifications only. All behavioral tables remain
+unpopulated; no empirical hypothesis results are reported.
### E.1 Qualification
| Experiment | Planned runs | Invalid runs | Valid runs | Primary criterion | Status | Evidence root |
|---|---:|---:|---:|---|---|---|
-| E00 Integrity | TBD | — | — | Detect all mutation classes | Not run | — |
-| E01 Isolation | TBD | — | — | Block all enumerated side routes | Not run | — |
-| E02 Leakage | TBD | — | — | Rule out pre-registered leakage bound | Not run | — |
-| E03 Controls | 6 x registered N (450 at current SD assumption) | — | — | Equivalence and oracle bounds | Not run | — |
+| E00 Integrity | 5 v5 slots | 0 in v5 | 5 | Detect all registered mutations | Software qualified | E00 v5 receipt |
+| E01 Isolation | 5 v2 topology slots | 0 in v2 | 5 | Enumerated local corpus only | Software qualified | E01 v2 receipt |
+| E02 Leakage | 5 v3 numeric scratch slots | 0 in v3 | 5 | Numeric probe leakage bound, restore stages, and 60 fixed reports | Software qualified (numeric scope) | [V3 receipt](reports/research/e02-v3-qualification-receipt.json) + [full audit](reports/research/e02-v3-full-audit-receipt.json) |
+| E03 Controls | 150 primaries + 18 maximum reserves | 0 | 150 | Equivalence and oracle bounds | Software qualified; thresholds met | [Portable receipt](reports/research/e03-full-v1-status-receipt.json) |
### E.2 Core Emergence
diff --git a/SPECIFICATION.md b/SPECIFICATION.md
index bf9e28c6..8a0e363f 100644
--- a/SPECIFICATION.md
+++ b/SPECIFICATION.md
@@ -460,6 +460,12 @@ subject only to the linting rule in §6.4.
| `scratch-rl` | GRU or LSTM actor-critic, independent PPO-style update per Baby |
| `self-supervised` | Same backbone as `scratch-rl`, predictive/contrastive loss in place of a scalar reward |
| `hybrid` | From-scratch encoder + small recurrent world model + randomly initialized communication head |
+
+Every trainable adapter exports its exact policy immediately after initialization.
+The Nursery writes that policy under `policies/-policy-initial.json`, records
+its domain-separated `initialPolicyHash` in a `runtime-attestation` event, and
+includes that event in checkpoint 0's intervention auxiliary tree before the first
+turn. Per-Baby private seeds produce independent scratch-RL initial parameters.
| BabySitter narration | MAY use a larger reasoning model for audit summaries only; MUST NOT feed its output back into either Baby during a run (CONCEPT-IDEA.md §20.7) |
## 7. Run Lifecycle and State Machine
@@ -672,6 +678,15 @@ separation pattern) so that a repeated form can be recognized without assigning
a meaning. No grammar supplies a semantic tag; each only bounds what can be
physically expressed (resolves Q5 jointly with §18's variable registry).
+Offline carrier-leakage analysis uses the hash-bound
+`carrierLeakageProbePlan` from `RunConfig`. For every accepted observation it
+rebuilds the carrier-qualified `markHash`, records reuse and a bounded structural
+feature signature, and evaluates the pre-registered recognizable-glyph rate and
+feature/referent mutual-information bounds. Each probe reports `pass`, `fail`, or
+`inconclusive`; the evaluator never rewrites an artifact. Any result other than
+`pass` blocks an ungrounded-language claim, but has no effect on evidence-integrity
+validity and remains publishable as negative or integrity evidence.
+
### 9.3 Six-Display Affect Protocol
Disabled by default (`affectMode: "none"`); enabling it selects one of the five
@@ -874,6 +889,7 @@ interface RunConfig {
evaluationSeeds: number; // default 5 qualification / 10 publication
checkpointEventInterval: number; // default 64 (LEDGER-INTEGRITY-DESIGN.md §9)
checkpointTimeIntervalMs: number; // default 300000
+ anchorClass: "simulated" | "public-chain";
anchorNetwork: "base-sepolia" | "base-mainnet";
finalityPolicy: string; // default "1-confirmation" or "safe-tag"
prototypeRetentionDays: number; // default 30
@@ -882,6 +898,7 @@ interface RunConfig {
protocolGitCommit: string;
preRegistrationHash: string;
randomSeed: string;
+ carrierLeakageProbePlan?: { recognizableGlyph: { enabled: boolean; maximumRecognizableRate: number }; intendedCarrierFeatureUse: { enabled: boolean; minimumObservations: number } };
}
```
@@ -1053,7 +1070,8 @@ interface AffectEvent {
### 11.7 Checkpoint Manifest (Reference)
Normative shape is LEDGER-INTEGRITY-DESIGN.md §8, including its optional named
-`auxiliaryTrees` map for `affect` and `audit` roots. Every run manifest MUST
+`auxiliaryTrees` map for `affect`, `audit`, `turns`, and witness-committed
+unsigned `intervention` roots. Every run manifest MUST
additionally be reachable from an `ExperimentRecord` (§11.9) via
`checkpointManifestRef`.
@@ -1079,6 +1097,7 @@ interface ExperimentRecord {
checkpointManifestRef: string; // final checkpoint hash
anchorTxRef: string; // Base transaction hash
verifierReportRef: string;
+ analysisAttachmentRefs?: string[]; // hashes in analysis/index.json
claimBoundaryStatement: string; // verbatim §5.1/§5.2 sentence
deviations: string[]; // pointers into EXPERIMENT-NOTEBOOK.md §9
}
@@ -1268,16 +1287,23 @@ LEDGER-INTEGRITY-DESIGN.md §4, §7, and §8 exactly, including for the new
and its own Merkle root, included in the checkpoint manifest as additional named
trees alongside `babyA`/`babyB`/`channel`).
-### 13.4 Base Sepolia / Mainnet Anchoring Policy
+### 13.4 Simulated and Optional Public-Chain Anchoring Policy
-Unchanged from LEDGER-INTEGRITY-DESIGN.md §10: Base Sepolia for development and
-qualification (E00); Base mainnet for declared public research runs; only the
-32-byte checkpoint hash and minimal routing metadata are ever public. Finality
-policy default: Sepolia — 1 confirmation for development iteration; mainnet — wait
-for the `safe` block tag (or equivalent finality/confirmation-depth policy the
-chosen RPC provider exposes) before a checkpoint is reported as anchored-final in
-the notebook (resolves part of Q28; provider-specific finality-tag naming is
-deferred, §19).
+The approved research profile is simulation-only. Every prospective campaign run
+MUST declare `anchorClass: "simulated"` and use the deterministic in-memory
+transport. Its Base-shaped network, transaction, block, and confirmation values are
+non-monetary test data and MUST NOT be described as public-chain publication,
+economic finality, or incurred cost. The exact `anchorClass` is bound into the run
+configuration, pending-submission record, anchor receipt, pre-registration binding,
+and verifier checks so a simulated receipt cannot be silently relabeled.
+
+Base Sepolia and Base mainnet remain optional transport capabilities inherited from
+LEDGER-INTEGRITY-DESIGN.md §10. Any real-chain operation MUST instead declare
+`anchorClass: "public-chain"`; only the 32-byte checkpoint hash and minimal routing
+metadata may be public. Public-chain operation is outside the currently approved
+research profile and requires a new prospective governance amendment. If amended,
+Sepolia waits for at least one confirmation and mainnet waits for the `safe` block
+tag or an equivalent provider-specific policy before reporting anchored-final.
### 13.5 Key Management
@@ -1300,7 +1326,13 @@ architecture MUST NOT expose any Baby-reachable API for reading
`audit_ledger_entries`, enforced by the same role model as §12.2 (only
`researcher-viewer`/`researcher-operator`/`verifier-service` may read it). Every
generated interpretation is labeled `source: "generated-analysis"` and MUST NOT be
-represented, in any report, as the Baby's own explanation.
+represented, in any report, as the Baby's own explanation. The reference
+implementation validates every `sourceEntryHash` against the named Baby's signed
+native stream, waits until at least one later turn is fully complete, validates the
+whole submitted batch before its first append, and creates an `analysis` checkpoint
+after the batch. The Interpreter has no adapter or Gateway dependency, so no
+generated interpretation can be delivered back to a Baby as observation, outcome,
+message, or ledger feedback.
## 14. Telemetry, Audit, Reproducibility, Snapshot/Restore, Failure Handling, Retention
@@ -1400,11 +1432,17 @@ A run MAY NOT enter `preregistered` (§7.1) until:
LEDGER-INTEGRITY-DESIGN.md).
For a confirmatory or publication-facing run, the canonical pre-registration
-artifact MUST also be registered with an external timestamping/registration service
-(OSF Registries is the default) and its `preRegistrationHash` MUST be anchored before
-the run enters `running`. The external registration URL and pre-run anchor receipt
-are included in the run manifest. Qualification-only development runs MAY use a
-Base-Sepolia pre-run anchor without OSF, but MUST be labeled non-confirmatory.
+artifact MUST be committed unchanged in the repository before outcome collection.
+Its binding records the packet path, canonical hash, commit, and commit time; the
+registration commit MUST be ancestral to the execution commit, and preflight MUST
+load the artifact bytes from that commit and reproduce the registered hash. The same
+`preRegistrationHash` MUST be committed before the run enters `running`. Under the
+approved simulation-only profile, that receipt MUST declare
+`anchorClass: "simulated"`; the deterministic receipt proves exact local consumption
+and tamper detection. An external registration or archival timestamp MAY be added
+as an independently hosted enhancement but is not required for local synthetic
+execution. Repository-native registration does not prove an independent timestamp,
+third-party custody, decentralized persistence, or economic finality.
The run's `ExperimentRecord.disposition` and the matching EXPERIMENT-NOTEBOOK.md
run record MUST agree; the Verifier's `VerificationReport` is authoritative for
@@ -1467,6 +1505,9 @@ NOT change the third field. Learned encodings MUST NOT be imported into the prod
hashing, signing, anchoring, authentication, or key-management packages. This
formalizes Q17 and the `cipherThreatModel` variable in §18.
+The mandatory repository boundary and E40 review gate are specified in
+[`docs/cryptographic-separation-policy.md`](docs/cryptographic-separation-policy.md).
+
### 15.5 Traceability to Experiment Notebook
See §17.4 for the full E00-E50 traceability table. Every experiment section in
@@ -1608,7 +1649,8 @@ defaulted, pre-registerable configuration variable, not an open question:
| `maxConsecutiveRejections` | `5` | ≥ 1 | Q6 |
| `checkpointEventInterval` | `64` | ≥ 1 | Q28 |
| `checkpointTimeIntervalMs` | `300000` | ≥ 1000 | Q28 |
-| `anchorNetwork` | `base-sepolia` (dev), `base-mainnet` (public) | per LEDGER doc §10 | Q28 |
+| `anchorClass` | `simulated` | `simulated`, `public-chain` | approved simulation-funded profile; a public-chain value requires amendment |
+| `anchorNetwork` | `base-sepolia` (emulated by default), `base-mainnet` (optional capability) | per LEDGER doc §10 | Q28 |
| `finalityPolicy` | `1-confirmation` (Sepolia), `safe-tag` (mainnet) | provider-dependent | Q28 (partially deferred, §19) |
| `interactionMode` | `cooperative-signaling` | `cooperative-signaling`, `asymmetric-information`, `semi-cooperative-negotiation`, `conflicting-negotiation`, `no-agreement-control` | Q19 |
| `cipherThreatModel` | `post-run-disclosure` | `post-run-disclosure`, `external-observer-only`, `novelty-only` | Q16, Q18 |
diff --git a/book/__tests__/research-book.test.ts b/book/__tests__/research-book.test.ts
index 7d12372b..70ebbaf3 100644
--- a/book/__tests__/research-book.test.ts
+++ b/book/__tests__/research-book.test.ts
@@ -3,6 +3,7 @@ import { createHash } from 'node:crypto';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
+import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs';
import { describe, expect, it } from 'vitest';
interface BookManifest {
@@ -31,7 +32,12 @@ describe('research page-turn book', () => {
expect(manifest.aspect).toBeCloseTo(0.7727, 4);
expect(manifest.sourceSha256).toBe(
createHash('sha256')
- .update(await readFile(join(root, 'RESEARCH.md'), 'utf8'))
+ .update(
+ (await readFile(join(root, 'RESEARCH.md'), 'utf8')).replace(
+ /\r\n?/gu,
+ '\n',
+ ),
+ )
.digest('hex'),
);
@@ -62,6 +68,22 @@ describe('research page-turn book', () => {
).toBeGreaterThan(1_000);
});
+ it('contains readable text on every PDF page', async () => {
+ const pdf = new Uint8Array(await readFile(join(bookDir, 'research.pdf')));
+ const loadingTask = getDocument({ data: pdf });
+ const document = await loadingTask.promise;
+ try {
+ for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) {
+ const page = await document.getPage(pageNumber);
+ const content = await page.getTextContent();
+ const text = content.items.map((item) => 'str' in item ? item.str : '').join('').trim();
+ expect(text.length, `PDF page ${pageNumber} is blank`).toBeGreaterThan(0);
+ }
+ } finally {
+ await loadingTask.destroy();
+ }
+ });
+
it('links the project site to an auto-opening book view', async () => {
const projectSite = await readFile(join(root, 'index.html'), 'utf8');
const bookPage = await readFile(join(root, 'research-book.html'), 'utf8');
diff --git a/book/pages/manifest.json b/book/pages/manifest.json
index 26ee0195..36684f57 100644
--- a/book/pages/manifest.json
+++ b/book/pages/manifest.json
@@ -1,6 +1,6 @@
{
"generatedFrom": "research.pdf",
- "pageCount": 49,
+ "pageCount": 55,
"aspect": 0.7727,
"pages": [
"pages/p01.webp",
@@ -51,8 +51,14 @@
"pages/p46.webp",
"pages/p47.webp",
"pages/p48.webp",
- "pages/p49.webp"
+ "pages/p49.webp",
+ "pages/p50.webp",
+ "pages/p51.webp",
+ "pages/p52.webp",
+ "pages/p53.webp",
+ "pages/p54.webp",
+ "pages/p55.webp"
],
"source": "RESEARCH.md",
- "sourceSha256": "dd88ca6f47e1d57c482620da036247e9ef437764b6f53ebceef72ff8c9b2eae5"
+ "sourceSha256": "fd660e01fb3b80a3c394e99558c3b986cc649a691c186075099c68215efb5d16"
}
diff --git a/book/pages/p01.webp b/book/pages/p01.webp
index 8f6ed61e..0bd9fa9a 100644
Binary files a/book/pages/p01.webp and b/book/pages/p01.webp differ
diff --git a/book/pages/p02.webp b/book/pages/p02.webp
index 49aee108..7899acff 100644
Binary files a/book/pages/p02.webp and b/book/pages/p02.webp differ
diff --git a/book/pages/p03.webp b/book/pages/p03.webp
index 61ea938e..8bd94873 100644
Binary files a/book/pages/p03.webp and b/book/pages/p03.webp differ
diff --git a/book/pages/p04.webp b/book/pages/p04.webp
index d50ee6c0..aa5da388 100644
Binary files a/book/pages/p04.webp and b/book/pages/p04.webp differ
diff --git a/book/pages/p05.webp b/book/pages/p05.webp
index e44a6dc2..0e69dbe2 100644
Binary files a/book/pages/p05.webp and b/book/pages/p05.webp differ
diff --git a/book/pages/p06.webp b/book/pages/p06.webp
index 1c53d0b1..18c6e74e 100644
Binary files a/book/pages/p06.webp and b/book/pages/p06.webp differ
diff --git a/book/pages/p07.webp b/book/pages/p07.webp
index 7a312e4c..8e7e9c4c 100644
Binary files a/book/pages/p07.webp and b/book/pages/p07.webp differ
diff --git a/book/pages/p08.webp b/book/pages/p08.webp
index 26094926..26fa5b2f 100644
Binary files a/book/pages/p08.webp and b/book/pages/p08.webp differ
diff --git a/book/pages/p09.webp b/book/pages/p09.webp
index 47f83038..81bc740d 100644
Binary files a/book/pages/p09.webp and b/book/pages/p09.webp differ
diff --git a/book/pages/p10.webp b/book/pages/p10.webp
index 1a3bf572..eca45a90 100644
Binary files a/book/pages/p10.webp and b/book/pages/p10.webp differ
diff --git a/book/pages/p11.webp b/book/pages/p11.webp
index 158cacc7..0026e975 100644
Binary files a/book/pages/p11.webp and b/book/pages/p11.webp differ
diff --git a/book/pages/p12.webp b/book/pages/p12.webp
index 7f11d663..f47b66a0 100644
Binary files a/book/pages/p12.webp and b/book/pages/p12.webp differ
diff --git a/book/pages/p13.webp b/book/pages/p13.webp
index c9f7efd9..3ffb9829 100644
Binary files a/book/pages/p13.webp and b/book/pages/p13.webp differ
diff --git a/book/pages/p14.webp b/book/pages/p14.webp
index 47012ecf..f7a3b349 100644
Binary files a/book/pages/p14.webp and b/book/pages/p14.webp differ
diff --git a/book/pages/p15.webp b/book/pages/p15.webp
index c09cbb40..e7e68253 100644
Binary files a/book/pages/p15.webp and b/book/pages/p15.webp differ
diff --git a/book/pages/p16.webp b/book/pages/p16.webp
index 1daa6fb4..227f5a6e 100644
Binary files a/book/pages/p16.webp and b/book/pages/p16.webp differ
diff --git a/book/pages/p17.webp b/book/pages/p17.webp
index e0db6bd2..f9d5619e 100644
Binary files a/book/pages/p17.webp and b/book/pages/p17.webp differ
diff --git a/book/pages/p18.webp b/book/pages/p18.webp
index e15982e3..14fdf7f4 100644
Binary files a/book/pages/p18.webp and b/book/pages/p18.webp differ
diff --git a/book/pages/p19.webp b/book/pages/p19.webp
index 3bf9e4fc..d5e73a42 100644
Binary files a/book/pages/p19.webp and b/book/pages/p19.webp differ
diff --git a/book/pages/p20.webp b/book/pages/p20.webp
index 2be74ca7..967855fc 100644
Binary files a/book/pages/p20.webp and b/book/pages/p20.webp differ
diff --git a/book/pages/p21.webp b/book/pages/p21.webp
index eb58948d..c121049f 100644
Binary files a/book/pages/p21.webp and b/book/pages/p21.webp differ
diff --git a/book/pages/p22.webp b/book/pages/p22.webp
index 98a3c348..133aa3b0 100644
Binary files a/book/pages/p22.webp and b/book/pages/p22.webp differ
diff --git a/book/pages/p23.webp b/book/pages/p23.webp
index dc661405..24846685 100644
Binary files a/book/pages/p23.webp and b/book/pages/p23.webp differ
diff --git a/book/pages/p24.webp b/book/pages/p24.webp
index a3685897..1ac09325 100644
Binary files a/book/pages/p24.webp and b/book/pages/p24.webp differ
diff --git a/book/pages/p25.webp b/book/pages/p25.webp
index 9e0cc8e5..a6d31da6 100644
Binary files a/book/pages/p25.webp and b/book/pages/p25.webp differ
diff --git a/book/pages/p26.webp b/book/pages/p26.webp
index fe48d126..f5b5121b 100644
Binary files a/book/pages/p26.webp and b/book/pages/p26.webp differ
diff --git a/book/pages/p27.webp b/book/pages/p27.webp
index 1504edec..4558a17e 100644
Binary files a/book/pages/p27.webp and b/book/pages/p27.webp differ
diff --git a/book/pages/p28.webp b/book/pages/p28.webp
index 07d22ad1..97725ccd 100644
Binary files a/book/pages/p28.webp and b/book/pages/p28.webp differ
diff --git a/book/pages/p29.webp b/book/pages/p29.webp
index 16a87376..0caed6c1 100644
Binary files a/book/pages/p29.webp and b/book/pages/p29.webp differ
diff --git a/book/pages/p30.webp b/book/pages/p30.webp
index ba4283fd..0b1d69ac 100644
Binary files a/book/pages/p30.webp and b/book/pages/p30.webp differ
diff --git a/book/pages/p31.webp b/book/pages/p31.webp
index c7bbcc6c..fb050535 100644
Binary files a/book/pages/p31.webp and b/book/pages/p31.webp differ
diff --git a/book/pages/p32.webp b/book/pages/p32.webp
index 34b569db..d337ad3e 100644
Binary files a/book/pages/p32.webp and b/book/pages/p32.webp differ
diff --git a/book/pages/p33.webp b/book/pages/p33.webp
index e852c7d2..b8ed29bc 100644
Binary files a/book/pages/p33.webp and b/book/pages/p33.webp differ
diff --git a/book/pages/p34.webp b/book/pages/p34.webp
index 3373e910..944d4989 100644
Binary files a/book/pages/p34.webp and b/book/pages/p34.webp differ
diff --git a/book/pages/p35.webp b/book/pages/p35.webp
index 3c994f74..c3cc8d85 100644
Binary files a/book/pages/p35.webp and b/book/pages/p35.webp differ
diff --git a/book/pages/p36.webp b/book/pages/p36.webp
index c464d393..7666254e 100644
Binary files a/book/pages/p36.webp and b/book/pages/p36.webp differ
diff --git a/book/pages/p37.webp b/book/pages/p37.webp
index b5998807..701a340e 100644
Binary files a/book/pages/p37.webp and b/book/pages/p37.webp differ
diff --git a/book/pages/p38.webp b/book/pages/p38.webp
index 1342e28f..0144e9ed 100644
Binary files a/book/pages/p38.webp and b/book/pages/p38.webp differ
diff --git a/book/pages/p39.webp b/book/pages/p39.webp
index ac911be4..f99cc7b3 100644
Binary files a/book/pages/p39.webp and b/book/pages/p39.webp differ
diff --git a/book/pages/p40.webp b/book/pages/p40.webp
index 81905894..8515f4e3 100644
Binary files a/book/pages/p40.webp and b/book/pages/p40.webp differ
diff --git a/book/pages/p41.webp b/book/pages/p41.webp
index 7ecb411c..070dafcc 100644
Binary files a/book/pages/p41.webp and b/book/pages/p41.webp differ
diff --git a/book/pages/p42.webp b/book/pages/p42.webp
index 829e39c7..e9a028f9 100644
Binary files a/book/pages/p42.webp and b/book/pages/p42.webp differ
diff --git a/book/pages/p43.webp b/book/pages/p43.webp
index d4c9bf72..61ed3866 100644
Binary files a/book/pages/p43.webp and b/book/pages/p43.webp differ
diff --git a/book/pages/p44.webp b/book/pages/p44.webp
index 083c84cb..b6296523 100644
Binary files a/book/pages/p44.webp and b/book/pages/p44.webp differ
diff --git a/book/pages/p45.webp b/book/pages/p45.webp
index b339ca28..48af80e1 100644
Binary files a/book/pages/p45.webp and b/book/pages/p45.webp differ
diff --git a/book/pages/p46.webp b/book/pages/p46.webp
index 046069c1..2ef9233a 100644
Binary files a/book/pages/p46.webp and b/book/pages/p46.webp differ
diff --git a/book/pages/p47.webp b/book/pages/p47.webp
index 91ba774c..46075873 100644
Binary files a/book/pages/p47.webp and b/book/pages/p47.webp differ
diff --git a/book/pages/p48.webp b/book/pages/p48.webp
index cfc60590..eb46e405 100644
Binary files a/book/pages/p48.webp and b/book/pages/p48.webp differ
diff --git a/book/pages/p49.webp b/book/pages/p49.webp
index e823d759..01885dae 100644
Binary files a/book/pages/p49.webp and b/book/pages/p49.webp differ
diff --git a/book/pages/p50.webp b/book/pages/p50.webp
new file mode 100644
index 00000000..4038219f
Binary files /dev/null and b/book/pages/p50.webp differ
diff --git a/book/pages/p51.webp b/book/pages/p51.webp
new file mode 100644
index 00000000..f4357b22
Binary files /dev/null and b/book/pages/p51.webp differ
diff --git a/book/pages/p52.webp b/book/pages/p52.webp
new file mode 100644
index 00000000..d47ada9f
Binary files /dev/null and b/book/pages/p52.webp differ
diff --git a/book/pages/p53.webp b/book/pages/p53.webp
new file mode 100644
index 00000000..2baa6a03
Binary files /dev/null and b/book/pages/p53.webp differ
diff --git a/book/pages/p54.webp b/book/pages/p54.webp
new file mode 100644
index 00000000..67ba8649
Binary files /dev/null and b/book/pages/p54.webp differ
diff --git a/book/pages/p55.webp b/book/pages/p55.webp
new file mode 100644
index 00000000..a322337d
Binary files /dev/null and b/book/pages/p55.webp differ
diff --git a/book/research-print.html b/book/research-print.html
index c04b3313..4bf1dd32 100644
--- a/book/research-print.html
+++ b/book/research-print.html
@@ -49,8 +49,21 @@
h4 { margin: 0.18in 0 0.06in; font-size: 10.5pt; }
p, li { orphans: 3; widows: 3; }
p { margin: 0 0 0.11in; }
- ul, ol { margin: 0.04in 0 0.13in; padding-left: 0.24in; }
- li { margin: 0 0 0.035in; }
+ p:has(+ ul), p:has(+ ol) {
+ break-after: avoid-page;
+ page-break-after: avoid;
+ }
+ ul, ol {
+ margin: 0.04in 0 0.13in;
+ padding-left: 0.24in;
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+ li {
+ margin: 0 0 0.035in;
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
blockquote {
margin: 0.13in 0;
padding: 0.11in 0.16in;
@@ -92,41 +105,33 @@
vertical-align: top;
border: 0.5pt solid #bdc9d8;
}
+ h2:last-of-type ~ table { margin: 0.08in 0 0.10in; line-height: 1.15; }
+ h2:last-of-type ~ table th,
+ h2:last-of-type ~ table td { padding: 2pt 5pt; }
th { background: #e9f0f8; color: #17253d; text-align: left; }
hr {
margin: 0.24in 0;
border: 0;
border-top: 0.5pt solid #c6d1df;
}
+ hr:has(+ h2) { display: none; }
img { max-width: 100%; break-inside: avoid; }
- .running-footer {
- position: fixed;
- right: 0;
- bottom: -0.46in;
- left: 0;
- color: #778399;
- font-family: "Segoe UI", Arial, sans-serif;
- font-size: 7pt;
- text-align: center;
- }
-
Auditable Emergent Communication Between Isolated Artificial Agents
A Preregistrable Study Protocol for Comparing Learning, Grounding, and Protocol Formation
Document type: Working academic manuscript and pre-results study protocol
Status: Draft for research execution; not yet ready for arXiv submission
Prepared: September 2, 2026
+Engineering snapshot: v0.1.529 · 258/261 backlog acceptance criteria verified.
Proposed arXiv category: cs.MA (primary), with possible cross-listing to
cs.AI and cs.CL
-Authors: Ethical Tech CoLab; individual author order, affiliations, ORCID
-identifiers, and corresponding author to be finalized before submission
-Repository: https://github.com/Ethical-Tech-CoLab/agentic-language-development
-Empirical status: No Nursery Lab experiment has been reported as completed in
+
Authors: Anonymous working draft; author order, affiliations, ORCID identifiers,
+and corresponding author are omitted until the authorized submission stage
+Artifact: Anonymous review artifact to be prepared only after release approval
+Empirical status: No behavioral experiment has been reported as completed in
this manuscript. Sections 10 and 11 distinguish implemented infrastructure from
planned empirical work. No table contains an observed empirical result.
@@ -147,9 +152,11 @@ Abstract
hybrid models, and no-learning controls under common scenario, channel, evidence, and
evaluation interfaces. Each agent is specified to maintain an independent chronological ledger of its own
intentions and interpretations. The framework specifies append-only, hash-chained,
-signed ledger and channel records, ordered Merkle checkpoints, and periodic
-public-chain checkpoint anchoring. These mechanisms are not yet fully implemented and
-do not prove that an agent's interpretation is truthful; they are designed to preserve
+signed ledger and channel records, ordered Merkle checkpoints, and deterministic
+simulated commitments. The local integrity, checkpoint, verifier, and commitment
+paths are implemented and tested under a zero-external-spend profile. Optional
+public-chain transport remains outside the approved campaign and its claims. These
+mechanisms do not prove that an agent's interpretation is truthful; they are designed to preserve
what was recorded and make later alteration detectable. Causal message interventions, held-out generalization, partner
replacement, leakage tests, and control-channel conditions are therefore required in
addition to task success.
@@ -175,7 +182,10 @@ Research Integrity Notice
AI-suggested connections are treated as exploratory until supported;
the influence and limitations of AI assistance are disclosed;
source verification state and journal credibility are recorded in Appendix A;
-a separate critical-review pass is required before publication.
+a separate critical-review pass is required before publication; the current
+internal pass is recorded in
+reports/research/research-critical-review.md
+and does not count as independent review.
In this draft, source retrieval and the first verification pass were performed by an
AI assistant using web, scholarly-index, and metadata tools, followed by separate AI
@@ -322,8 +332,8 @@
2.2 Confirmatory Hypotheses
| H4 |
-Ledger-predicted intervention directions will exceed a pre-registered chance baseline. |
-Ledger agreement is at chance or fails out-of-sample. |
+On untouched intervention cases, pre-outcome ledger predictions will improve a pre-registered proper prediction score beyond the strongest eligible transcript-only, task-history, policy-state, random, and majority baseline. |
+The ledger fails to exceed the strongest eligible non-ledger baseline by the registered practical threshold. |
| H5 |
@@ -338,7 +348,7 @@ 2.2 Confirmatory Hypotheses
| H6b |
Under fixed windows and cardinality, permutation-calibrated excess conditional mutual information between affect and referent will remain below the pre-registered 0.02-bit practical-leakage bound. |
-The seed-bootstrap upper-bound test cannot rule out excess leakage of 0.02 bits or more. |
+The seed-level one-sided Student-t upper-bound test cannot rule out excess leakage of 0.02 bits or more. |
| H7 |
@@ -742,6 +752,21 @@ 6.4 Scenario Families
Researcher ground truth may contain semantic labels; learner observations may not.
Observations use opaque numeric arrays, pixels, or separately qualified hybrid
features. Images containing OCR-detectable text are quarantined before a run.
+The initial numeric referential design contains 16 two-attribute combinations. The
+four equal-attribute diagonal combinations are the locked compositional test targets;
+the remaining 12 combinations are the shared semantic support for training and
+in-distribution validation. Training and validation use independent PRNG domains,
+and no held-out type may occur in either split as a target or distractor. Validation
+is learning-off and may be used for tuning and baseline selection. The held-out split
+is learning-off and is accessed once after those choices freeze. The broader
+evaluation split remains an engineering and software-qualification surface and is
+not interchangeable with the confirmatory held-out test.
+The versioned split, duplicate rules, matched budget dimensions, and boundaries
+between within-architecture causal contrasts and cross-architecture descriptive
+benchmarks are frozen in
+protocols/scenario-split-and-model-comparison.v1.json
+and explained in
+docs/scenario-splits-and-model-comparisons.md.
6.5 Communication Conditions
For the baseline fixed-token carrier, each run declares a random inventory such as
S01-S32 without assigned meaning. The gateway supports six control conditions:
@@ -847,7 +872,7 @@ 7.1 Outcome Hierarchy
- held-out task success relative to E03 controls;
- positive listening under message intervention;
-- ledger-predicted intervention agreement;
+- ledger incremental predictive value on untouched interventions;
- held-out compositional generalization.
Secondary outcomes
@@ -871,7 +896,11 @@ 7.2 Operational Definitions
distributions to change while the receiver observation is held fixed.
Ledger agreement: the direction of an observed intervention effect matches the
direction predicted from the learner's ledger before intervention outcomes are
-revealed.
+revealed. This is a descriptive calibration measure, not H4's confirmatory estimand.
+Ledger incremental value: held-out improvement in a pre-registered proper
+prediction score over the strongest eligible transcript-only, task-history,
+policy-state, random, and majority baseline. Predictions are committed before the
+intervention outcome is generated.
Compositional generalization: reusable subparts support above-control success on
pre-registered unseen combinations with learning disabled.
Leakage: information about hidden referent or task state is recoverable from a
@@ -880,15 +909,20 @@
7.2 Operational Definitions
the same training seed. The H7 estimand is fixed-dyad degradation minus the
equal-weight eight-partner-training degradation.
7.3 Statistical Plan
-The final analysis plan will be frozen before confirmatory runs.
+The common analysis and multiplicity rules are frozen in
+protocols/statistical-analysis-and-power.v1.json.
+Experiment-specific raw-scale practical margins and seed allocations will be frozen
+after disjoint pilots and before confirmatory runs.
-- Alpha is
0.05 for each experiment's primary family.
-- Holm-Bonferroni correction is applied across primary metrics within an experiment.
-- The confirmatory study family is restricted to H1-H8, including H6a and H6b.
-Hierarchical gatekeeping tests qualification first, then core emergence, then later
-affect/transfer/negotiation hypotheses; a blocked family is reported descriptively.
-Exploratory analyses use false-discovery-rate reporting and remain labeled
-exploratory.
+- The confirmatory study family contains nine members: H1, H2, H3, H4, H5, H6a,
+H6b, H7, and H8. One pre-registered p-value is formed per member and Holm correction
+is applied globally at family-wise alpha 0.05. Multi-component directional members
+use the maximum component p-value and require every registered direction and
+practical threshold.
+- E00-E03 and experiment-specific checks are validity gates, not sources of reusable
+alpha. A failed or missing gate produces
not-tested, not a favorable or
+unfavorable p-value. Exploratory analyses use false-discovery-rate reporting and
+remain labeled exploratory.
- Effect sizes and confidence or credible intervals are reported with every
significance test.
- Binary task outcomes are modeled at the run/seed level, with episodes treated as
@@ -910,11 +944,139 @@
7.3 Statistical Plan
Before empirical submission, simulation-based power analysis will determine the
required number of independent seeds for each primary contrast; the larger value
governs.
+Independent base-R validation agrees with the production special functions and
+seed-level tests, demonstrates calibrated boundary behavior, and shows why pooled
+episode inference is prohibited. The complete E03 numeric decision has a lower 95%
+Monte Carlo power bound above 0.90 in all four planned variance rows. A nine-member
+Holm sensitivity shows that 75 seeds is not generally sufficient: at standardized
+effect 0.40, all-member power is 0.6752 at N=75 and 0.9068 at N=100. These values are
+design evidence rather than universal sample sizes; D07 applies the registered
+member-specific rule. Full methods and Monte Carlo intervals are in
+docs/statistical-validation-and-power.md.
Claims that a control is "at chance" or that leakage is absent use equivalence or
upper-bound tests, not failure to reject a difference. Each pre-registration must
state the smallest effect of interest, the equivalence margin, and power to rule out
that margin. The worked E03 design in Appendix D uses a +/-0.05 success-rate margin;
H6b uses a 0.02-bit conditional-mutual-information bound.
+The outcome-blind causal-ledger and leakage rules are frozen in
+protocols/causal-ledger-and-leakage.v1.json.
+For E02, at least 200 untouched test rows are required and the one-sided 95%
+Wilson upper bound on linear-probe accuracy advantage over the held-out majority
+baseline must be at most 0.10. The identical probe and split must detect an injected
+one-hot target feature with a lower advantage bound of at least 0.20. For E20, 75
+eligible seeds and 1,000 windows per seed are required before a decision; the primary
+bound is seed-level Student-t and the percentile bootstrap is sensitivity-only.
+Allowed and forbidden information sets and detector-positive controls are explicit
+for E01, E02, E13, and E20. Intended normalized carrier form is communication, not a
+side channel.
+Seed and resource allocation is frozen in
+protocols/seed-and-resource-allocation.v1.json.
+Qualification, blinded-pilot, confirmatory, and replication stages use disjoint
+SHA-256 domains; paired conditions share scenario seeds but not learner, Gateway, or
+analysis seeds. The original allocation described selection from the complete
+nine-member joint simulation. That simulation remains a useful diagnostic, but the
+prospective dependence and confidence amendments now require the first shared
+candidate whose simultaneously bounded member-power union reaches 0.90 and whose
+experiment-specific validity reserves reach 0.95. N=100 is a planning value, not a
+selected result.
+The prospective selection clarification
+resolves the earlier "largest required count" wording: member-specific minima remain
+diagnostics. The current executable reducer requires every eligible pilot, all
+fourteen component success counts at all eight candidates, simultaneous component
+confidence, and corrected reserves; member/joint counts have no selection authority.
+No pilot inputs or 30,000-repetition receipt yet exist.
+The prospective design-readiness inventory
+separately audits the inputs needed before those pilots. All nine estimands now have an
+executable scale. H5 uses stable-convention/v1, a rolling-window calculation that
+requires form-to-meaning consistency, repeated use, and paired causal listening;
+its window and listening thresholds remain prospectively unset. H8 uses
+negotiation-language/v1 on paired aligned/conflicting cases: message information
+about registered private state and normalized intended-action entropy conditional on
+message are separate components. Its state/action spaces must still be packet-bound.
+The outcome-blind practical-margin policy
+now freezes H5's rule, H8's spaces, and all nine scale-specific margins before any
+family pilot. Values used earlier in packet-compiler tests are not empirical support.
+All nine member pilot summaries remain explicitly uncollected, so
+the inventory cannot yield a sample size or research finding.
+The confirmatory-pilot-summary/v2 contract fixes seven experiment pilots, their
+nine member mappings, and fourteen component summaries. Only twenty valid primary
+slots in every experiment, a finite one-sided upper dispersion bound for every
+component, and a checked experiment-specific one-sided invalid-run upper bound are
+selection-eligible. Partial or invalid pilots remain retained but cannot feed the
+selector. Even zero invalid runs in twenty slots has a nonzero upper validity-risk
+bound; the later reserve calculation must use that bound and may require a prospective
+resource amendment rather than treating zero observations as zero risk.
+Because twenty-slot marginal dispersions do not identify dependence among the
+fourteen component tests, the prospective
+power-model amendment
+does not let an independence assumption control selection. It freezes outcome-blind
+design alternatives, component screening at 0.05/9, a dependence-robust union lower
+bound across nine member-power bounds, and a 0.95 reserve-adequacy gate. The ordinary
+joint simulation remains diagnostic and cannot select a smaller N.
+The prospective power-confidence amendment
+first supplied simultaneous family confidence instead of mistaking nine unadjusted
+intervals for a 95% family guarantee. A further
+component-power amendment
+recognizes that marginal pilot dispersions do not identify dependence inside the
+multi-component H1, H5, and H8 decisions either. The controlling rule therefore uses
+one-sided alpha 0.05/14 for all fourteen component-power bounds and forms member and
+family union lower bounds without a covariance assumption. The executable selector
+is bound to this final rule and still supplies no pilot or power result.
+The prospective validity-reserve amendment
+then corrects the original fixed 10% rule for the seven scoped experiments. For each
+candidate and experiment it chooses the smallest reserve count whose binomial
+probability of obtaining N valid runs is at least 0.95 at the pilot upper invalid
+rate. Under the clean 0/20 reference, this requires 7 reserves at N=25 through 52
+at N=300. The table is a design calculation; it authorizes no run or resource.
+The prospective power-simulator specification
+binds the executable model for all eight candidates and fourteen components. It uses
+Normal mean and chi-square sample-variance sufficient statistics for the registered
+one-sided t tests, with each future pilot's one-sided upper SD, and exact-binomial
+decision probabilities for H5. Random streams are deterministically separated by
+candidate, member, and component. Their independent joint-family outcome is a
+diagnostic only; selection uses the component-wise dependence-robust bounds above.
+The Normal assumption is a declared parametric power model, not evidence about the
+future outcome distribution. Software fixtures do not supply a pilot, power result,
+selected sample size, resource authorization, or empirical finding.
+The retained
+software-qualification receipt
+binds the exact v0.1.529 implementation and output digest. Across eight candidates
+and fourteen components, all 112 deterministic Monte Carlo proportions were within
+the prespecified five-standard-error gate of independent Python/scipy t/noncentral-t or
+exact-binomial reference power; the maximum deviation was 2.55 standard errors.
+These synthetic fixture values qualify implementation behavior only. No eligible
+pilot was supplied and the receipt records no campaign power, selected N, resource
+authorization, study outcome, or scientific disposition.
+The receipt auditor additionally checks its named commit's tree, package version,
+and each qualified source artifact against Git history, preserving the original
+receipt while allowing later audit-code corrections.
+The prospective pilot-reduction policy
+adds a deterministic numerical reducer for all seven distinct twenty-slot pilot
+corpora. It requires globally unique run IDs, seeds, and evidence digests and
+computes the Normal-assumption one-sided 95% upper SD with factor 1.37041039768,
+independently checked against R. The reducer does not verify the original bundles,
+packet ancestry, or registered metric derivation; its output expressly prohibits
+selection admission. The existing v2 summary retains means for audit consistency,
+but those pilot means must not influence the frozen design before lock. No pilot
+data have been supplied to this reducer. Its retained synthetic software qualification
+receipt
+binds exact v0.1.204 sources, 42 independent-R mean/SD/upper-SD comparisons
+(maximum absolute difference 1.11e-16), and four rejection controls. The routine
+statistics gate checks the receipt's named commit, source digests, and fixture
+output; it does not admit original evidence or select N.
+That nine-member rule governs the confirmatory H1-H8 family and is distinct from
+E03's qualification-only rule in Appendix D. E03 uses its own four-row bounded
+numeric-rule table after its separate twenty-slot control pilot; E03 neither spends
+nor contributes confirmatory-family alpha.
+A fresh 260-turn recurrent-carrier benchmark took 37.58 seconds and wrote 25.53 MB.
+Under the original fixed-reserve allocation, maximum materialized pools including
+replication projected to 9,171 bundles, 9.37 million turns, 857.3 GiB, and 376.4
+single-core hours. The corrected confirmatory reserve formula can raise those maxima;
+its exact campaign total remains unset until N and experiment-level allocations are selected.
+The present local ceiling is 72 CPU-hours, 25 GiB, one frozen-model process, and zero
+external spend. It supports bounded qualification and pilots, not the full campaign;
+confirmatory registration requires an exact selected prefix and an approved execution
+resource plan.
Run exclusions are limited to pre-specified integrity or protocol failures. Each
condition receives a fixed ordered list of primary and reserve seeds before outcomes
are observed. An invalid primary seed may be replaced only by the next reserve seed;
@@ -941,15 +1103,19 @@
7.5 Qualitative Analysis
- abrupt convention changes;
- negotiation-era semantic shifts.
-Human coders will use a pre-registered codebook and, where feasible, blinded
-independent coding. Generated audit interpretations will not be treated as ground
-truth.
+The approved initial campaign uses no human-coded outcome. Qualitative labels are
+generated by registered deterministic analysis and are not treated as ground truth.
+Any later human-coding proposal requires a prospective protocol and governance
+amendment before collection.
8. Evidence Integrity and Reproducibility
-Design status: This section describes required evidence behavior. Hash-chain
-writing, signatures, Merkle checkpoints, anchoring, and the independent verifier are
-not yet complete; see Section 10.
+Implementation status: Local hash-chain writing, domain-separated signatures,
+ordered-Merkle checkpoints and proofs, anchor publisher transports, evidence export,
+and the standalone verifier are implemented and covered by software tests. The
+approved campaign uses deterministic simulated receipts and no real funds; no
+public-chain receipt is claimed, and the historical research
+corpus remains unregistered and unanchored; see Section 10.
8.1 Append-Only Evidence
The specified evidence system requires every learner ledger and channel transcript
@@ -965,13 +1131,16 @@
8.2 Checkpoints
Signed checkpoint manifests will bind the event roots to run configuration, learner
contract, software commit, and previous checkpoint.
-8.3 External Anchoring
-Development qualification is specified to use Base Sepolia. Declared public studies may anchor
-checkpoint hashes to Base mainnet. Only hashes and minimal routing metadata are
-on-chain. Private observations, messages, ledgers, prompts, identities, and keys
-remain off-chain.
-The anchor proves that a committed prefix existed no later than a chain block and
-that disclosed content matches the commitment. It does not prove:
+8.3 Simulated Commitments and Optional External Anchoring
+The prospective campaign uses a deterministic in-memory transport, a non-monetary
+simulated balance, and receipts labeled anchorClass: "simulated". Base Sepolia and
+Base mainnet remain optional software capabilities outside the approved study. A
+future public-chain activation would require a new prospective amendment; only hashes
+and minimal routing metadata could be placed on-chain.
+The simulated commitment proves that the executed bundle consumed a specific
+committed prefix and that disclosed content matches it. Together with the external
+registration timestamp and immutable Git commit, it preserves prospective order. It
+does not prove public availability, decentralized persistence, economic finality, or:
- an event was true;
- no event was omitted before commitment;
@@ -993,15 +1162,16 @@ 8.4 Independent Verification
- cross-bindings among intention, message, delivery, interpretation, and outcome;
- Merkle roots, inclusion proofs, and consistency proofs;
- checkpoint chains and witness signatures;
-- public-chain transaction, chain ID, block inclusion, and finality;
+- receipt class, transaction-shaped identifier, chain identity, block inclusion, and finality semantics;
- forks, gaps, and unanchored tails.
Any integrity failure prevents a run from receiving a valid disposition.
Here, independent verifier means software that recomputes evidence from an exported
bundle without trusting the live runtime, database, or private keys. For a
publication-facing claim, at least one verification execution must additionally be
-performed by a person who did not operate the original run, using an independently
-configured chain RPC. External replication remains stronger than either form.
+performed by a person who did not operate the original run, using the independent
+offline verifier. An independent RPC is additionally required only for a future
+public-chain receipt. External replication remains stronger than either form.
8.5 Replay
Scenario replay reconstructs scenario and observation hashes from the recorded seed.
Deterministic adapters additionally reconstruct a replay digest over scenario,
@@ -1065,33 +1235,193 @@
9.5 Dual-Use Concerns
as separate fields.
10. Current Implementation Status
-As of September 2, 2026, the repository records ALD-001 through ALD-007 as complete.
-This is engineering status, not an empirical result.
+Engineering snapshot: v0.1.110 · 254/258 backlog acceptance criteria verified.
+As of September 12, 2026, 82 of 86 backlog items satisfy all of their acceptance
+criteria. This is engineering status, not an empirical result.
Implemented:
-- npm/TypeScript workspaces;
-- runtime-validated shared schemas;
-- typed configuration and secret scanning;
-- DTSF-compatible Learner A, Learner B, and Nursery twin-pack scaffolds;
-- SQLite WAL evidence schema and append-only triggers;
-- RFC 8785 canonical serialization;
-- ledger event-type validators;
-- automated lint, build, test, dependency-audit, and clean-clone checks.
+- hash-chained, signed SQLite evidence; ordered Merkle checkpoints; exact bundle
+export; and an independent verifier;
+- a frozen local audit-cost protocol and receipt covering five immutable
+qualification bundles, with ordinary-log, signed-stream, integrity-structure,
+policy-state, and complete-bundle storage separated; descriptive offline verifier
+timing and the prior six-case mutation challenge are reported without claiming a
+public anchor or an empirical result;
+- an E16 comparator pipeline that fits all five eligible non-ledger predictors on a
+validation-fit fold, selects by Brier score on a disjoint validation-selection
+fold, refits and hash-locks the selected model, commits baseline and native-ledger
+predictions before accepting test outcomes, and keeps the oracle diagnostic
+ineligible for selection; exact synthetic qualification passed five comparator and
+seven negative-control paths; the production runtime now invokes an injected
+outcome-blind native predictor after Gateway delivery and before receiver action,
+commits and checkpoints baseline/native predictions, derives the receiver choice
+from its hash-bound turn record, and binds per-turn post-outcome scores; the hashed
+run configuration commits the selection and prediction-function identities and
+accepted-evaluation-delivery eligibility, and recovery refuses an absent or
+mismatched provider; exact detached qualification passed 10 focused tests and the
+full 1,808-test repository gate;
+- a general fail-closed registration-packet compiler requiring eleven exact binding
+classes; its generic inventory binds all eleven fields for E00, E01 and E02,
+leaves 160 generic experiment-specific bindings open, and emits their registration hashes;
+E03's blinded pilot and full control qualification instead use separate verified
+stage-specific repository packets and simulated pre-run commitments;
+- grammar-valid bitmap, translation-tolerant canvas, and graded tone distances with
+deterministic nearest-prototype evaluation; this is a handcrafted E13 diagnostic,
+not evidence that a learner learned the metric or generalized to novel forms; an
+exact clean-candidate qualification covers three carriers and six rejection paths;
+- a Base-shaped commitment client with confirmation/recovery handling, explicit
+simulated/public receipt classification, and mainnet gating; the verifier rejects
+a simulated receipt relabeled as public-chain evidence;
+- an authenticated Fort boundary that stores the project signer envelope and unused
+optional public-chain credentials only as encrypted Safe material; a four-track
+files-mode Mode R run sealed and production-verified every bounded qualification
+bundle; the zero-balance public address remains inactive and is not needed by the
+simulation-funded campaign;
+- deterministic scenarios, the tool-only Gateway, all declared carrier controls,
+five learner tracks, DTSF twin routes, and the complete turn lifecycle;
+- two-container Mode R learner-host separation and bounded transport/network
+controls, training separation, active side-channel and observation red teams,
+including a reusable 28-case generated-carrier structural
+side-feature audit with accepted controls and fail-closed evidence readiness; the
+exact candidate also passed the current two-container Mode R timing, fixed-envelope,
+error-shape, host-denial, crash-survival, and three-track update checks; plus
+telemetry, retention, snapshot/restore, and failure policy;
+- readiness gates for E00-E50, reproducible E03 design and seed artifacts, canonical
+E03 registration compilation, and a fail-closed confirmatory preflight;
+- a privacy-minimized real-model software qualification of Qwen3-4B Q4_K_M through
+llama.cpp, with exact model/runtime hashes and both role directions exercised; and
+- automated lint, build, test, dependency-audit, secret-scan, acceptance-coverage,
+API-documentation, project-status, clean-clone, hosted CI, and Mode R checks.
-Not yet implemented or empirically executed:
+Not yet empirically executed or independently evidenced:
-- full hash-chain writing and signatures;
-- atomic evidence writer;
-- Merkle checkpoints and independent verifier;
-- Base anchoring;
-- complete gateway and scenario engine;
-- model adapters and training;
-- Research-Grade isolation;
-- experiments E00-E50.
+- final-topology/pilot resource qualification; and
+- registered behavioral and replication findings for the planned hypothesis family.
-The implementation backlog is maintained in BACKLOG.md, while normative
-requirements are in SPECIFICATION.md.
-
+The external-dependency ledger is ready at 1/1 applicable item: O01 is verified by
+the scoped synthetic-only governance decision, while O02-O06 are non-applicable to
+local execution. Public funding, public-chain transactions, hosted enforcement,
+second-person restoration, and third-party registration remain optional claim-
+strengthening activities. Their absence is disclosed and prohibits claims of public
+finality, hosted enforcement, independent restoration, or third-party registration;
+it does not answer or alter the agent-language estimands.
+The frozen local data/claim inventory resolves 140
+exported bundles across 22 collections by path and content hash. Forty-six have
+tracked bounded software-qualification support, 33 are historical qualification
+exports, and 61 are development, failed, or superseded diagnostics. All 140 are excluded from
+empirical estimates. Ninety-two recorded anchor confirmations are local simulated
+receipts; prospective receipts now bind an explicit class that the
+verifier refuses to relabel. Confirmed public-chain anchors, pilot bundles, confirmatory
+bundles, replication bundles, and research-included bundles are all zero.
+The five later E02 v3 qualification bundles are accounted for separately from this
+frozen 140-bundle inventory and remain excluded from behavioral estimates.
+The registered E02 v1 and v2 attempts both failed before restore and probe analysis.
+The v1 attempt stopped after five consecutive timeout rejections at turn 465; v2
+stopped after 161 committed turns when a post-action turn-path deadline escaped
+without the required forfeited-turn record. Their partial databases are not exported
+bundles and are not included in this bundle inventory. Both attempts remain preserved
+as failed software qualifications, not behavioral results.
+The exact v0.1.115 engineering candidate converts deadlines from observation, sending,
+receiving, receiver action, and post-outcome processing into one audited timeout-
+forfeit path with method-specific diagnostics. A timed-out remote adapter is made
+unreachable before another turn, and the attempt cannot be resumed even after
+runtime recovery. It passed 1,959 tests and the ordinary two-container Mode R gate;
+the separate all-method framed fault suite and raw logs are bound in the
+pre-registration qualification.
+The prospective v3 packet is now repository-registered under
+sha256:d35e6b118e4c811f80b426680a0b2f4b0c9b8da3e59bdc56293e557ef979f248
+with five seeds disjoint from v1/v2. Its deterministic simulated commitment has
+three confirmations and zero currency. Exact v0.1.119 passed the full 1,963-test
+pre-execution gate, release-auditor binding, and ordinary two-container Mode R
+preflight. The one sequential v3 attempt then completed on exact clean execution
+commit 9ec08ecf4528c6da8867d55900d6ca8667ded7ce: all five numeric slots,
+before/after-restore stages, and 60 fixed probes passed their unchanged software
+thresholds. The terminal receipt
+and separate full live audit
+bind the original evidence. This closes bounded local E02 software reliability,
+not the behavioral ledger-value hypotheses, image-encoder qualification, or
+independent human review. The ordinary two-container preflight exercises learner-host
+transport and network controls, not separate Baby twins, Gateway-only routes,
+per-role ledger-writer and signer keys, or controller trust-zone processes.
+Existing blocker B12 now requires exact selected Mode R boundary and detector
+evidence before any E10+ isolation claim. It does not gate E03 Prototype controls.
+The prospective E03 infrastructure-mode correction
+selects shared-process Prototype Mode for controls, without a Research-Grade
+isolation claim. Its unexecuted predecessor specified external adapters, which
+the runtime guard rejects; that record remains historical. The fresh six-condition
+development namespace ran once on clean v0.1.158 and its six original bundles
+passed the separate TypeScript/Rust live audit with signed-original control
+pairing. It remains bounded Mode P development mechanics, not a pilot finding.
+The pilot collector was prepared prospectively. A signed-original
+topology audit and measured,
+zero-spend allocation now precede any 120-run pilot
+packet. The allocation charges prior E02 and development CPU/storage before
+reserving 1 CPU-hour, 1 GiB evidence, and 1 GiB resident memory. Its
+prospective 0.7-hour sequential wall projection includes full-service startup/
+audit overhead; the unregistered 0.2-hour slot-only projection is retained in
+dated correction provenance. The unused v1 pilot packet
+and simulated binding
+are retained after tracked test-fixture changes altered their exact source
+digest. A prospective v2 amendment
+requires fresh IDs/seeds and binding before admission. A new
+v2 pilot packet freezes 120
+run IDs and disjoint pilot seeds against clean v0.1.164. A separate
+simulated binding
+records three local confirmations without real funds or an independent
+timestamp. Tracked ready-stage fixture corrections changed the v2 exact source
+digest before seed use; a prospective v3 amendment
+requires a fresh packet, IDs/seeds, and binding. A new
+v3 pilot packet freezes 120
+distinct run IDs and disjoint pilot seeds against clean v0.1.167. Its
+separately committed simulated binding
+records three local confirmations without real funds or an independent
+timestamp. The active v3 pilot ran once on clean v0.1.169 and completed all
+120 planned original runs without invalid, aborted, or replacement runs.
+Its separate full-bundle TypeScript/Rust audit passed. The outcome-blind
+reducer and separate recomputation audit selected the frozen 25-primary-seed
+E03 design row from pilot dispersion. The retained original receipt and
+reduction hashes are mapped in the pilot progress update.
+No E03 chance-control qualification or behavioral finding is reported.
+The separately retained Homebrew-base-R 30,000-repetition bounded design
+calculation passed the nominal complete numeric rule at 25 seeds, with lower
+95% power bound 0.999872. Its forced two-invalid-primary-per-condition
+sensitivity passed zero repetitions; this fragility must be reported and
+reserve/replacement mechanics qualified before full collection. Neither
+simulation establishes an observed control effect.
+The tracked outcome-blind E03 sample-size decision
+binds the original pilot, reduction, and power digests and carries the
+invalid-as-failure limitation into future packet compilation. Its live
+recomputation needs separately retained raw records; clean-checkout inspection
+does not rerun that audit.
+The prospective full E03 resource allocation
+uses the complete retained evidence tree and actual 120-run pilot maxima. It
+reserves all 168 possible primary/reserve slots at 1 CPU-hour, 1.1 GiB new
+evidence, 1 GiB resident, and 0.5 hours projected sequential wall time,
+after charging 22.3 prior CPU-hours and 4.0 GiB retained evidence. This is
+within the local synthetic software-qualification ceiling but does not fund
+or qualify the E10+ campaign.
+The prospective paired-reserve clarification
+resolves a pre-registration contradiction: E03's unit is a scenario seed
+shared across all six conditions, while condition-specific reserves could
+break the paired oracle-control estimand. An invalid primary run excludes its
+six-condition scenario slot from paired analysis; the next unused ordered
+reserve scenario slot is collected across all six conditions, at most three.
+Original invalid and valid-but-excluded companion runs remain indexed. This
+changes neither the 168-run maximum nor scientific thresholds. No full packet
+or full seed existed when the clarification was recorded.
+The pure full-stage paired-attempt reconciler
+enforces complete six-condition scenario pairs, identical scenario histories,
+whole-slot ordered reserves, and explicit incomplete disposition when the
+three reserves cannot replace all invalid primary pairs. The prospectively
+registered v1 full-stage attempt completed 150 required primary runs with 150
+verified original bundles and no invalid run. Because every primary pair was
+valid, all 18 reserves remained correctly unattempted. All registered control-
+equivalence, oracle-adequacy, and paired-separation criteria passed. A separate
+read-only audit recomputed every bundle and the fixed analysis; the tracked
+portable receipt binds the
+ignored original terminal receipt. This is a Prototype-Mode control software
+qualification, not an empirical language-emergence result, Research-Grade
+isolation claim, independent review, or public timestamp.
11. Results
No empirical results are reported in this version. Results-table scaffolds are
isolated in Appendix E so indexing systems do not mistake them for findings. They
@@ -1173,9 +1503,9 @@
13.6 Literature Search Limits
14. Reproducibility, Data, and Code Availability
14.1 Code
-Source code and planning artifacts are publicly available at:
-https://github.com/Ethical-Tech-CoLab/agentic-language-development
-Every empirical paper revision will identify the exact Git commit used.
+Source code and planning artifacts exist in the working repository. An anonymous
+review artifact and any public release URL will be prepared only after release
+approval. Every empirical paper revision will identify an immutable source commit.
14.2 Protocols
14.3 Data
@@ -1195,12 +1527,16 @@ 14.4 Pre-Registration
and analysis plan before execution. The run configuration stores the protocol commit
and pre-registration hash. Changes create appended amendments rather than rewriting
the original record.
-No external registration record exists yet. Before the first confirmatory run, the
-team intends to create a dated OSF registration and anchor the same canonical
-preRegistrationHash before the run enters running. A Git commit in an
-author-controlled repository is retained as a development record but is not, by
-itself, treated as third-party preregistration. Appendix D supplies a numerically
-complete worked E03 registration for review.
+The E02 software-qualification packets and unused E03 v1/v2 pilot packets have been
+repository-registered; the separate E03 v3 pilot was registered and completed.
+Before each later
+confirmatory run, its canonical packet will be committed unchanged in a Git ancestor
+of the execution commit. Preflight will load those historical bytes, reproduce the
+preRegistrationHash, and require a matching deterministic simulated commitment
+before the run enters running. This establishes exact local identity, ancestry,
+ordering against later committed evidence, and tamper detection; it is not described
+as third-party registration or an independent timestamp. Appendix D supplies a
+numerically complete worked E03 registration for review.
15. Conclusion
The literature no longer leaves open whether artificial agents can coordinate
@@ -1228,11 +1564,10 @@
Author Contributions
Acknowledgements
Institutional and technical acknowledgements: TBD before submission.
AI-Assistance Disclosure
-This working manuscript was developed with AI assistance in VS Code through the
-Copilot SDK. The AI assistant performed the initial source retrieval, opened the
-arXiv/ACL/PMLR/Crossref/publisher/standards pages summarized in Appendix A, organized
-the manuscript, summarized relevance, and conducted consistency checks. A separate AI
-research-agent pass challenged source status and identified overclaiming risks.
+
This working manuscript was developed with AI-assisted research and repository tools.
+The tools supported source retrieval, primary-page inspection, manuscript organization,
+code implementation, executable validation, and critical consistency checks. Separate
+AI-assisted passes challenged source status, numerical claims, and overclaiming risks.
AI systems are not authors and bear no responsibility for the manuscript. Human
authors remain responsible for every claim, citation, analysis, and conclusion.
Following the Ethical Tech CoLab guidelines [47], load-bearing sources were opened or
@@ -1248,11 +1583,14 @@
Literature-Search Disclosure
reported its keyless monthly limit reached. No new Tavily-generated result was relied
upon. Rather than conceal the failure or imply that Tavily returned evidence, the
review continued through arXiv Atom metadata, ACL Anthology, PMLR, Crossref, JMLR,
-publisher and university pages, RFC Editor, NIST, Google Books, and Ethical Tech CoLab
-repositories. A final submission should rerun the search after Tavily access is
-restored and record whether it changes the included corpus.
+publisher and university pages, RFC Editor, NIST, Google Books, and institutional
+repositories. A bounded update was completed on September 11, 2026 across primary
+repository and publisher surfaces. Its queries, screening boundaries, all 50 source
+decisions, and five newer comparators are recorded in the
+source verification register.
+This was not a systematic review. A final submission must repeat the search through
+the manuscript-freeze date and record whether it changes the included corpus.
All retrieved material was treated as untrusted evidence, not as instruction.
-
References
[1] Steels, L. (1995). A self-organizing spatial vocabulary. Artificial
Life, 2(3), 319-332. https://doi.org/10.1162/artl.1995.2.3.319
@@ -1338,7 +1676,7 @@ References
Conference on Learning Representations. https://arxiv.org/abs/2204.12982
[26] Galke, L., & Raviv, L. (2024). Learning and communication pressures in
neural networks: Lessons from emergent communication. Language Development
-Research, 5(1), 116-143. https://doi.org/10.34842/3vr5-5r49
+Research, 5(1), 116-140. https://doi.org/10.34842/3vr5-5r49
[27] Kouwenhoven, T., Peeperkorn, M., & Verhoef, T. (2025). Searching for
structure: Investigating emergent communication with large language models.
Proceedings of COLING 2025, 9977-9991.
@@ -1535,9 +1873,9 @@
A.1 Rubric Application
| [15] |
-tool-abstract-verified, arXiv primary page/API |
+tool-full-text-verified, complete paper |
Peer-reviewed NeurIPS paper; journal score N/A |
-"Interpretable agent communication from scratch (with a generic visual processor emerging on the side)." Supports causal symbol interventions. |
+Supports unsupervised learned discrete communication, partial interpretability, and novel-class generalization in its evaluated setting; it does not supply causal symbol-intervention evidence. |
| [16] |
@@ -1601,15 +1939,15 @@ A.1 Rubric Application
| [26] |
-tool-metadata-verified, official journal page |
+tool-full-text-verified, complete official paper |
Peer-reviewed journal article, 3/5 |
-"Learning and communication pressures in neural networks: Lessons from emergent communication." A field review with a newer/variable-impact venue. |
+Reviews communicative success, production effort, learnability, and other pressures; official pagination is 116–140. |
| [27] |
-tool-metadata-verified, ACL Anthology |
+tool-full-text-verified, complete ACL Anthology paper |
Peer-reviewed COLING paper; journal score N/A |
-"Searching for Structure: Investigating Emergent Communication with Large Language Models." Direct pretrained-LLM comparison. |
+Directly studies artificial-language learning and transmission with pretrained language models. |
| [28] |
@@ -1739,15 +2077,15 @@ A.1 Rubric Application
| [49] |
-tool-metadata-verified, ACL Anthology |
+tool-full-text-verified, complete ACL Anthology paper |
Peer-reviewed EMNLP demo paper; journal score N/A |
-"EGG: a toolkit for research on Emergence of lanGuage in Games." Closest infrastructure comparator. |
+Closest infrastructure comparator: modular games, channels, optimization, analysis, and checkpoints. |
| [50] |
-tool-abstract-verified, PMLR primary page |
+tool-full-text-verified, complete PMLR paper |
Peer-reviewed ICML paper; journal score N/A |
-""Other-Play" for Zero-Shot Coordination." Supports novel-partner evaluation and specialized-convention risk. |
+Supports independently trained novel-partner evaluation and specialized-convention risk. |
No source in the table was silently upgraded from preprint or workshop status to a
@@ -1818,19 +2156,22 @@
Appendix C. Required Work Before arXiv Submission
evidence supports.
-Appendix D. Worked Preregistration Example: E03 Controls
+Appendix D. Registered E03 Control Qualification
-Status: Complete worked example for methodological review; not yet registered
-or executed.
+Status: The prospective pilot selected 25 primary seeds per condition. The
+separately registered full v1 qualification completed all 150 primaries, used no
+reserves, passed its fixed control/oracle thresholds, and passed a separate raw
+audit. This is control software qualification, not a behavioral finding.
D.1 Registration and Integrity
-- Registration target: OSF Registries.
-- The canonical registration JSON, this manuscript commit, analysis-script commit,
-and generated seed manifest will be registered before execution.
-- The same
preRegistrationHash will be anchored to Base Sepolia before any run
-enters running.
-- No outcome will be inspected before registration and anchoring complete.
+- Registration used a prospective clean Git packet commit and a matching pre-run
+simulated commitment; no external registry or public timestamp is claimed.
+- The canonical registration JSON bound the analysis-source hashes and generated
+seed manifest before execution.
+- The same
preRegistrationHash was committed through the deterministic simulation
+transport before any run entered running.
+- The full-stage outcome was not inspected before registration and commitment.
D.2 Objective
Test whether a four-choice referential task can be solved above chance without
@@ -1849,7 +2190,7 @@
D.3 Fixed Configuration
| Deployment mode |
-Research-Grade Mode |
+Prototype Mode; no Research-Grade isolation claim |
| Learner track |
@@ -1889,11 +2230,11 @@ D.3 Fixed Configuration
| Primary seeds per condition |
-Power-rule output; 75 when pilot SD is greater than 0.05 and at most 0.10 |
+25, selected prospectively from the blinded pilot |
| Reserve seeds per condition |
-10% of primary count, rounded up |
+3 ordered paired slots (18 maximum runs); none attempted |
| Conditions |
@@ -1913,15 +2254,20 @@ D.3 Fixed Configuration
D.4 Seed Generation
-For slot i, the shared scenario seed is the hexadecimal SHA-256 digest of:
-ald-e03-v1 || 0x00 || decimal(i)
+For stage s and slot i, the shared scenario seed is the hexadecimal SHA-256
+digest of:
+ald-seed-allocation-v1 || 0x00 || s || 0x00 || E03 || 0x00 || decimal(i) || 0x00 || scenario
-Slots 1 through registered N are primary. The next ceil(0.10 * N) slots are
-ordered reserves. Every condition uses the same scenario seed for a given slot.
-Condition-specific gateway randomness for random and shuffled is derived from:
-scenarioSeed || 0x00 || communicationCondition
+Here s is blinded-pilot or confirmatory. The pilot has exactly 20 primary
+slots and no reserves. Full qualification uses slots 1 through selected N as
+primary and the next ceil(0.10 * N) as ordered reserves. Every condition uses
+the same scenario seed for a given stage and slot. Each condition has distinct
+Baby A, Baby B, Gateway, and analysis seeds derived from:
+ald-seed-allocation-v1 || 0x00 || s || 0x00 || E03 || 0x00 || condition || 0x00 || decimal(i) || 0x00 || component-domain
-The complete scenario and gateway seed manifest is part of the registration.
+The normal condition uses normal-no-learning in that derivation. The complete
+component seed manifest is part of the registration, and no seed binding is visible
+to either learner.
D.5 Conditions
- Disabled: no artifact is delivered.
@@ -1936,28 +2282,54 @@ D.5 Conditions
The channel event records proposal and delivered-artifact hashes where a learner
proposal exists.
D.6 Primary Outcomes and Tests
-The unit of analysis is the run/seed success proportion across 200 episodes.
+The unit of analysis is the run/seed success proportion across 200 episodes.
+Only the signed original turn stream supplies the episode tally and paired
+scenario hashes. Operational slot summaries must reconcile to that stream and
+to the verified channel transcript before entering the pilot reduction or a
+registered analysis.
+The Prototype-Mode development topology uses the same signed source for its
+six-condition paired-scenario and control-mechanics audit; a passing unsigned
+slot summary alone is insufficient.
+After a pilot terminal receipt exists, progression checks bind it to the
+registered packet and reconcile planned, attempted, and completed run counts;
+document agreement cannot conceal a failed or incomplete original attempt.
+Before the terminal receipt exists, a running status additionally requires
+the original start record and its matching live local controller process;
+a retained start file after controller failure is unresolved, not a pass.
+The lightweight project-status check and the campaign progression checker
+share that same original pilot-state rule; current prose cannot clear the gate
+by agreeing with itself while local execution evidence contradicts it.
+An attempted failed pilot slot retains its partial evidence and measured
+learner/Nursery CPU charges. Missing cgroup measurements leave capacity
+unresolved rather than treating the failed slot as free or replaceable.
+Before original collection, the packet's protocol-base commit and execution
+commit must have no intervening source changes in packages, twins, deployment
+code, or scripts. A changed implementation requires a new prospective
+packet, not a retroactive hash match.
- Control equivalence: For each of disabled, constant, random, shuffled, and
normal no-learning, perform two one-sided one-sample tests on seed-level success
proportions against equivalence bounds 0.20 and 0.30. Equivalence requires both
one-sided tests to reject at the Holm-adjusted alpha.
-- Oracle adequacy: The lower bound of the two-sided 95% bootstrap confidence
-interval for mean seed-level oracle success must exceed 0.90.
+- Oracle adequacy: A one-sided seed-level t test and its 95% lower confidence
+bound must exceed 0.90. A two-sided 95% seed-bootstrap interval is reported as a
+sensitivity estimate.
- Oracle separation: For each non-oracle condition, compute paired seed-level
-oracle-minus-control differences. The lower bound of the Holm-adjusted 95%
-confidence interval must exceed 0.60.
+oracle-minus-control differences. One-sided t-test p-values above 0.60 receive
+Holm correction across the five conditions. Conservative 99% Bonferroni lower
+bounds and nominal paired seed-bootstrap intervals are reported.
No episode is analyzed as an independent run.
D.7 Sensitivity and Power
Before final registration, a separate outcome-blind-for-confirmatory-use pilot of 20
-seeds per non-oracle condition will estimate the largest between-seed standard
-deviation. Pilot runs will not enter confirmatory estimates. The registered primary
-seed count is selected by this fixed rule:
+seeds per non-oracle condition fit the beta-binomial model and estimated the
+largest latent between-seed standard deviation after accounting for 200-episode
+sampling. Pilot runs did not enter the full-stage estimates. The registered primary
+seed count was selected by this fixed rule:
-| Largest pilot SD |
+Largest latent pilot SD |
Primary seeds per condition |
@@ -1971,7 +2343,7 @@ D.7 Sensitivity and Power
| > 0.10 and <= 0.15 |
-150 |
+155 |
| > 0.15 and <= 0.20 |
@@ -1982,32 +2354,41 @@ D.7 Sensitivity and Power
New simulation and amended registration required before collection |
-A 30,000-replicate Monte Carlo design check was run for this draft under a true
-seed-level mean of 0.25, between-seed standard deviation of 0.10, 200 binomial
-episodes per seed, and conservative per-test alpha of 0.01. Estimated equivalence-test
-power was 0.924 at 75 seeds per condition. Sensitivity checks produced approximately
-0.912 power at SD 0.05 with 25 seeds, 0.920 at SD 0.15 with 150 seeds, and 0.913 at
-SD 0.20 with 300 seeds. Before registration, the simulation code and output must be
-checked in and independently rerun. Failure to reproduce at least 90% power blocks
+
The original 30,000-replicate normal sufficient-statistic calculation is retained as
+a single-control equivalence component check. It is not the registration-facing
+power claim. Independent base-R simulation instead draws a bounded latent seed
+probability from a beta distribution and then 200 binary episodes, applies all five
+Holm-adjusted control TOSTs, the oracle adequacy test, and all five Holm-adjusted
+paired separation tests. With 10,000 repetitions per row, full numeric-rule power
+was 0.9324, 0.9262, 0.9187, and 0.9576 for the four rows; lower 95% Monte Carlo bounds
+were 0.9273, 0.9209, 0.9132, and 0.9535. Exact output is committed in
+reports/research/statistical-validation.tsv and independently replayed by
+pnpm audit:statistics:live. Failure to reproduce a lower bound of at least 0.90 blocks
registration; it does not permit post-hoc widening of the margin.
Sensitivity analyses:
- Wilson intervals over pooled episodes are descriptive only;
-- a hierarchical Bernoulli model with seed random intercept is reported as a
-robustness check;
+- a hierarchical Bernoulli model with seed random intercept and percentile bootstrap
+intervals are reported as sensitivity checks;
- invalid primary runs are treated as failures in a worst-case sensitivity analysis.
D.8 Exclusions, Invalid Runs, and Replacement
+Before a full E03 packet existed, the prospective paired-reserve amendment
+superseded the condition-specific replacement wording. The independent unit
+is the complete six-condition scenario slot, so a single invalid primary run
+excludes that paired slot from the primary analysis. Its valid companions
+remain visible as valid-but-excluded records. The next registered reserve
+scenario slot replaces the entire pair across all six conditions, in order.
A run is invalid only for a pre-specified verifier failure, wrong configuration hash,
gateway-control mismatch, or infrastructure failure that prevents the planned 200
episodes.
- Invalid runs remain in the public run index.
-- The next unused reserve seed for that condition replaces the invalid primary slot.
-- No more than the registered reserve count may be used per condition.
-- If fewer than registered
N valid runs remain, the primary analysis is incomplete;
+ - The next unused ordered six-condition paired reserve scenario slot replaces an invalid primary paired slot.
+- No more than three registered paired reserve slots may be used.
+- If fewer than registered
N complete verified paired slots remain, the primary analysis is incomplete;
there is no unregistered additional collection.
-- Invalid and aborted counts are reported by condition.
+- Invalid, aborted, valid-but-excluded, and reserve counts are reported by condition and paired-slot mapping.
D.9 Stopping Rule
All registered primary slots are attempted. There is no efficacy-based early
@@ -2017,16 +2398,26 @@
D.10 Decision Rule
- all five non-oracle conditions meet equivalence;
- oracle adequacy and separation criteria pass;
-- no non-oracle condition has more than 5% of its primary seeds with observed success
-of 0.35 or greater; every such seed is individually audited for leakage;
+- every non-oracle primary seed with observed success of 0.35 or greater receives an
+individually resolved leakage audit; the count is diagnostic and is not an
+uncalibrated statistical rejection rule;
- all included evidence bundles pass verification;
- no unplanned metadata or channel leakage is detected.
Failure is a result and blocks downstream confirmatory claims until a new,
separately registered protocol is justified.
+The v1 execution included 25 complete paired slots (150 valid runs), zero invalid
+runs, and zero attempted reserves. The five control means ranged from 0.2372 to
+0.2566 and all passed the registered equivalence decisions; the oracle mean and
+simultaneous lower bound were both 1.0, and all five registered separation decisions
+passed. The separate audit recomputed every original bundle and the fixed analysis.
+The tracked portable receipt
+binds the separately retained raw evidence and preserves the not-tested scientific
+disposition.
Appendix E. Future Results-Table Scaffolds
-These tables contain no observed results.
+E00–E03 report bounded software/control qualifications only. All behavioral tables remain
+unpopulated; no empirical hypothesis results are reported.
E.1 Qualification
@@ -2042,39 +2433,39 @@ E.1 Qualification
| E00 Integrity |
-TBD |
-— |
-— |
-Detect all mutation classes |
-Not run |
-— |
+5 v5 slots |
+0 in v5 |
+5 |
+Detect all registered mutations |
+Software qualified |
+E00 v5 receipt |
| E01 Isolation |
-TBD |
-— |
-— |
-Block all enumerated side routes |
-Not run |
-— |
+5 v2 topology slots |
+0 in v2 |
+5 |
+Enumerated local corpus only |
+Software qualified |
+E01 v2 receipt |
| E02 Leakage |
-TBD |
-— |
-— |
-Rule out pre-registered leakage bound |
-Not run |
-— |
+5 v3 numeric scratch slots |
+0 in v3 |
+5 |
+Numeric probe leakage bound, restore stages, and 60 fixed reports |
+Software qualified (numeric scope) |
+V3 receipt + full audit |
| E03 Controls |
-6 x registered N (450 at current SD assumption) |
-— |
-— |
+150 primaries + 18 maximum reserves |
+0 |
+150 |
Equivalence and oracle bounds |
-Not run |
-— |
+Software qualified; thresholds met |
+Portable receipt |
E.2 Core Emergence
diff --git a/book/research.pdf b/book/research.pdf
index bcaed490..0be9c4b4 100644
Binary files a/book/research.pdf and b/book/research.pdf differ
diff --git a/book/vendor/research-book.js b/book/vendor/research-book.js
index 2386540a..a110861b 100644
--- a/book/vendor/research-book.js
+++ b/book/vendor/research-book.js
@@ -1,4 +1,4 @@
-var V=Object.create;var O=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var Y=Object.getPrototypeOf,K=Object.prototype.hasOwnProperty;var X=(a,p)=>()=>{try{return p||a((p={exports:{}}).exports,p),p.exports}catch(l){throw p=0,l}};var J=(a,p,l,c)=>{if(p&&typeof p=="object"||typeof p=="function")for(let m of Z(p))!K.call(a,m)&&m!==l&&O(a,m,{get:()=>p[m],enumerable:!(c=j(p,m))||c.enumerable});return a};var Q=(a,p,l)=>(l=a!=null?V(Y(a)):{},J(p||!a||!a.__esModule?O(l,"default",{value:a,enumerable:!0}):l,a));var G=X((E,_)=>{(function(a,p){typeof E=="object"&&typeof _<"u"?p(E):typeof define=="function"&&define.amd?define(["exports"],p):p((a=a||self).St={})})(E,(function(a){"use strict";class p{constructor(t,e){this.state={angle:0,area:[],position:{x:0,y:0},hardAngle:0,hardDrawingAngle:0},this.createdDensity=e,this.nowDrawingDensity=this.createdDensity,this.render=t}setDensity(t){this.createdDensity=t,this.nowDrawingDensity=t}setDrawingDensity(t){this.nowDrawingDensity=t}setPosition(t){this.state.position=t}setAngle(t){this.state.angle=t}setArea(t){this.state.area=t}setHardDrawingAngle(t){this.state.hardDrawingAngle=t}setHardAngle(t){this.state.hardAngle=t,this.state.hardDrawingAngle=t}setOrientation(t){this.orientation=t}getDrawingDensity(){return this.nowDrawingDensity}getDensity(){return this.createdDensity}getHardAngle(){return this.state.hardAngle}}class l extends p{constructor(t,e,i){super(t,i),this.image=null,this.isLoad=!1,this.loadingAngle=0,this.image=new Image,this.image.src=e}draw(t){let e=this.render.getContext(),i=this.render.convertToGlobal(this.state.position),s=this.render.getRect().pageWidth,n=this.render.getRect().height;e.save(),e.translate(i.x,i.y),e.beginPath();for(let o of this.state.area)o!==null&&(o=this.render.convertToGlobal(o),e.lineTo(o.x-i.x,o.y-i.y));e.rotate(this.state.angle),e.clip(),this.isLoad?e.drawImage(this.image,0,0,s,n):this.drawLoader(e,{x:0,y:0},s,n),e.restore()}simpleDraw(t){let e=this.render.getRect(),i=this.render.getContext(),s=e.pageWidth,n=e.height,o=t===1?e.left+e.pageWidth:e.left,h=e.top;this.isLoad?i.drawImage(this.image,o,h,s,n):this.drawLoader(i,{x:o,y:h},s,n)}drawLoader(t,e,i,s){t.beginPath(),t.strokeStyle="rgb(200, 200, 200)",t.fillStyle="rgb(255, 255, 255)",t.lineWidth=1,t.rect(e.x+1,e.y+1,i-1,s-1),t.stroke(),t.fill();let n={x:e.x+i/2,y:e.y+s/2};t.beginPath(),t.lineWidth=10,t.arc(n.x,n.y,20,this.loadingAngle,3*Math.PI/2+this.loadingAngle),t.stroke(),t.closePath(),this.loadingAngle+=.07,this.loadingAngle>=2*Math.PI&&(this.loadingAngle=0)}load(){this.isLoad||(this.image.onload=()=>{this.isLoad=!0})}newTemporaryCopy(){return this}getTemporaryCopy(){return this}hideTemporaryCopy(){}}class c{constructor(t,e){this.pages=[],this.currentPageIndex=0,this.currentSpreadIndex=0,this.landscapeSpread=[],this.portraitSpread=[],this.render=e,this.app=t,this.currentPageIndex=0,this.isShowCover=this.app.getSettings().showCover}destroy(){this.pages=[]}createSpread(){this.landscapeSpread=[],this.portraitSpread=[];for(let e=0;e=0&&t0?this.pages[e-1]:null}getFlippingPage(t){let e=this.currentSpreadIndex;if(this.render.getOrientation()==="portrait")return t===0?this.pages[e].newTemporaryCopy():this.pages[e-1];{let i=t===0?this.getSpread()[e+1]:this.getSpread()[e-1];return i.length===1||t===0?this.pages[i[0]]:this.pages[i[1]]}}getBottomPage(t){let e=this.currentSpreadIndex;if(this.render.getOrientation()==="portrait")return t===0?this.pages[e+1]:this.pages[e-1];{let i=t===0?this.getSpread()[e+1]:this.getSpread()[e-1];return i.length===1?this.pages[i[0]]:t===0?this.pages[i[1]]:this.pages[i[0]]}}showNext(){this.currentSpreadIndex0&&(this.currentSpreadIndex--,this.showSpread())}getCurrentPageIndex(){return this.currentPageIndex}show(t=null){if(t===null&&(t=this.currentPageIndex),t<0||t>=this.pages.length)return;let e=this.getSpreadIndexByPage(t);e!==null&&(this.currentSpreadIndex=e,this.showSpread())}getCurrentSpreadIndex(){return this.currentSpreadIndex}setCurrentSpreadIndex(t){if(!(t>=0&&t=t.left&&e.x<=t.width+t.left&&e.y>=t.top&&e.y<=t.top+t.height?e:null}static GetRotatedPoint(t,e,i){return{x:t.x*Math.cos(i)+t.y*Math.sin(i)+e.x,y:t.y*Math.cos(i)-t.x*Math.sin(i)+e.y}}static LimitPointToCircle(t,e,i){if(g.GetDistanceBetweenTwoPoint(t,i)<=e)return i;let s=t.x,n=t.y,o=i.x,h=i.y,d=Math.sqrt(Math.pow(e,2)*Math.pow(s-o,2)/(Math.pow(s-o,2)+Math.pow(n-h,2)))+s;i.x<0&&(d*=-1);let u=(d-s)*(n-h)/(s-o)+n;return s-o+n===0&&(u=e),{x:d,y:u}}static GetIntersectBetweenTwoSegment(t,e,i){return g.PointInRect(t,g.GetIntersectBeetwenTwoLine(e,i))}static GetIntersectBeetwenTwoLine(t,e){let i=t[0].y-t[1].y,s=e[0].y-e[1].y,n=t[1].x-t[0].x,o=e[1].x-e[0].x,h=t[0].x*t[1].y-t[1].x*t[0].y,d=e[0].x*e[1].y-e[1].x*e[0].y,u=i*d-s*h,x=n*d-o*h,T=-(h*o-d*n)/(i*o-s*n),L=-(i*d-s*h)/(i*o-s*n);if(isFinite(T)&&isFinite(L))return{x:T,y:L};if(Math.abs(u-x)<.1)throw new Error("Segment included");return null}static GetCordsFromTwoPoint(t,e){let i=Math.abs(t.x-e.x),s=Math.abs(t.y-e.y),n=Math.max(i,s),o=[t];function h(d,u,x,T,L){return u>d?d+L*(x/T):u()=>{try{return p||r((p={exports:{}}).exports,p),p.exports}catch(l){throw p=0,l}};var J=(r,p,l,c)=>{if(p&&typeof p=="object"||typeof p=="function")for(let w of j(p))!X.call(r,w)&&w!==l&&_(r,w,{get:()=>p[w],enumerable:!(c=Y(p,w))||c.enumerable});return r};var Q=(r,p,l)=>(l=r!=null?Z(V(r)):{},J(p||!r||!r.__esModule?_(l,"default",{value:r,enumerable:!0}):l,r));var G=K((A,O)=>{(function(r,p){typeof A=="object"&&typeof O<"u"?p(A):typeof define=="function"&&define.amd?define(["exports"],p):p((r=r||self).St={})})(A,(function(r){"use strict";class p{constructor(t,e){this.state={angle:0,area:[],position:{x:0,y:0},hardAngle:0,hardDrawingAngle:0},this.createdDensity=e,this.nowDrawingDensity=this.createdDensity,this.render=t}setDensity(t){this.createdDensity=t,this.nowDrawingDensity=t}setDrawingDensity(t){this.nowDrawingDensity=t}setPosition(t){this.state.position=t}setAngle(t){this.state.angle=t}setArea(t){this.state.area=t}setHardDrawingAngle(t){this.state.hardDrawingAngle=t}setHardAngle(t){this.state.hardAngle=t,this.state.hardDrawingAngle=t}setOrientation(t){this.orientation=t}getDrawingDensity(){return this.nowDrawingDensity}getDensity(){return this.createdDensity}getHardAngle(){return this.state.hardAngle}}class l extends p{constructor(t,e,i){super(t,i),this.image=null,this.isLoad=!1,this.loadingAngle=0,this.image=new Image,this.image.src=e}draw(t){let e=this.render.getContext(),i=this.render.convertToGlobal(this.state.position),s=this.render.getRect().pageWidth,n=this.render.getRect().height;e.save(),e.translate(i.x,i.y),e.beginPath();for(let o of this.state.area)o!==null&&(o=this.render.convertToGlobal(o),e.lineTo(o.x-i.x,o.y-i.y));e.rotate(this.state.angle),e.clip(),this.isLoad?e.drawImage(this.image,0,0,s,n):this.drawLoader(e,{x:0,y:0},s,n),e.restore()}simpleDraw(t){let e=this.render.getRect(),i=this.render.getContext(),s=e.pageWidth,n=e.height,o=t===1?e.left+e.pageWidth:e.left,h=e.top;this.isLoad?i.drawImage(this.image,o,h,s,n):this.drawLoader(i,{x:o,y:h},s,n)}drawLoader(t,e,i,s){t.beginPath(),t.strokeStyle="rgb(200, 200, 200)",t.fillStyle="rgb(255, 255, 255)",t.lineWidth=1,t.rect(e.x+1,e.y+1,i-1,s-1),t.stroke(),t.fill();let n={x:e.x+i/2,y:e.y+s/2};t.beginPath(),t.lineWidth=10,t.arc(n.x,n.y,20,this.loadingAngle,3*Math.PI/2+this.loadingAngle),t.stroke(),t.closePath(),this.loadingAngle+=.07,this.loadingAngle>=2*Math.PI&&(this.loadingAngle=0)}load(){this.isLoad||(this.image.onload=()=>{this.isLoad=!0})}newTemporaryCopy(){return this}getTemporaryCopy(){return this}hideTemporaryCopy(){}}class c{constructor(t,e){this.pages=[],this.currentPageIndex=0,this.currentSpreadIndex=0,this.landscapeSpread=[],this.portraitSpread=[],this.render=e,this.app=t,this.currentPageIndex=0,this.isShowCover=this.app.getSettings().showCover}destroy(){this.pages=[]}createSpread(){this.landscapeSpread=[],this.portraitSpread=[];for(let e=0;e=0&&t0?this.pages[e-1]:null}getFlippingPage(t){let e=this.currentSpreadIndex;if(this.render.getOrientation()==="portrait")return t===0?this.pages[e].newTemporaryCopy():this.pages[e-1];{let i=t===0?this.getSpread()[e+1]:this.getSpread()[e-1];return i.length===1||t===0?this.pages[i[0]]:this.pages[i[1]]}}getBottomPage(t){let e=this.currentSpreadIndex;if(this.render.getOrientation()==="portrait")return t===0?this.pages[e+1]:this.pages[e-1];{let i=t===0?this.getSpread()[e+1]:this.getSpread()[e-1];return i.length===1?this.pages[i[0]]:t===0?this.pages[i[1]]:this.pages[i[0]]}}showNext(){this.currentSpreadIndex0&&(this.currentSpreadIndex--,this.showSpread())}getCurrentPageIndex(){return this.currentPageIndex}show(t=null){if(t===null&&(t=this.currentPageIndex),t<0||t>=this.pages.length)return;let e=this.getSpreadIndexByPage(t);e!==null&&(this.currentSpreadIndex=e,this.showSpread())}getCurrentSpreadIndex(){return this.currentSpreadIndex}setCurrentSpreadIndex(t){if(!(t>=0&&t=t.left&&e.x<=t.width+t.left&&e.y>=t.top&&e.y<=t.top+t.height?e:null}static GetRotatedPoint(t,e,i){return{x:t.x*Math.cos(i)+t.y*Math.sin(i)+e.x,y:t.y*Math.cos(i)-t.x*Math.sin(i)+e.y}}static LimitPointToCircle(t,e,i){if(g.GetDistanceBetweenTwoPoint(t,i)<=e)return i;let s=t.x,n=t.y,o=i.x,h=i.y,d=Math.sqrt(Math.pow(e,2)*Math.pow(s-o,2)/(Math.pow(s-o,2)+Math.pow(n-h,2)))+s;i.x<0&&(d*=-1);let u=(d-s)*(n-h)/(s-o)+n;return s-o+n===0&&(u=e),{x:d,y:u}}static GetIntersectBetweenTwoSegment(t,e,i){return g.PointInRect(t,g.GetIntersectBeetwenTwoLine(e,i))}static GetIntersectBeetwenTwoLine(t,e){let i=t[0].y-t[1].y,s=e[0].y-e[1].y,n=t[1].x-t[0].x,o=e[1].x-e[0].x,h=t[0].x*t[1].y-t[1].x*t[0].y,d=e[0].x*e[1].y-e[1].x*e[0].y,u=i*d-s*h,x=n*d-o*h,C=-(h*o-d*n)/(i*o-s*n),L=-(i*d-s*h)/(i*o-s*n);if(isFinite(C)&&isFinite(L))return{x:C,y:L};if(Math.abs(u-x)<.1)throw new Error("Segment included");return null}static GetCordsFromTwoPoint(t,e){let i=Math.abs(t.x-e.x),s=Math.abs(t.y-e.y),n=Math.max(i,s),o=[t];function h(d,u,x,C,L){return u>d?d+L*(x/C):u=10&&t.push(this.sideIntersectPoint):this.corner==="top"&&t.push({x:this.pageWidth,y:this.pageHeight}),t.push(this.bottomIntersectPoint),t.push(this.topIntersectPoint),t}getAngle(){return this.direction===0?-this.angle:this.angle}getRect(){return this.rect}getPosition(){return this.position}getActiveCorner(){return this.direction===0?this.rect.topLeft:this.rect.topRight}getDirection(){return this.direction}getFlippingProgress(){return Math.abs((this.position.x-this.pageWidth)/(2*this.pageWidth)*100)}getCorner(){return this.corner}getBottomPagePosition(){return this.direction===1?{x:this.pageWidth,y:0}:{x:0,y:0}}getShadowStartPoint(){return this.corner==="top"?this.topIntersectPoint:this.sideIntersectPoint!==null?this.sideIntersectPoint:this.topIntersectPoint}getShadowAngle(){let t=g.GetAngleBetweenTwoLine(this.getSegmentToShadowLine(),[{x:0,y:0},{x:this.pageWidth,y:0}]);return this.direction===0?t:Math.PI-t}calcAngleAndPosition(t){let e=t;if(this.updateAngleAndGeometry(e),e=this.corner==="top"?this.checkPositionAtCenterLine(e,{x:0,y:0},{x:0,y:this.pageHeight}):this.checkPositionAtCenterLine(e,{x:0,y:this.pageHeight},{x:0,y:0}),Math.abs(e.x-this.pageWidth)<1&&Math.abs(e.y)<1)throw new Error("Point is too small");return e}updateAngleAndGeometry(t){this.angle=this.calculateAngle(t),this.rect=this.getPageRect(t)}calculateAngle(t){let e=this.pageWidth-t.x+1,i=this.corner==="bottom"?this.pageHeight-t.y:t.y,s=2*Math.acos(e/Math.sqrt(i*i+e*e));i<0&&(s=-s);let n=Math.PI-s;if(!isFinite(s)||n>=0&&n<.003)throw new Error("The G point is too small");return this.corner==="bottom"&&(s=-s),s}getPageRect(t){return this.corner==="top"?this.getRectFromBasePoint([{x:0,y:0},{x:this.pageWidth,y:0},{x:0,y:this.pageHeight},{x:this.pageWidth,y:this.pageHeight}],t):this.getRectFromBasePoint([{x:0,y:-this.pageHeight},{x:this.pageWidth,y:-this.pageHeight},{x:0,y:0},{x:this.pageWidth,y:0}],t)}getRectFromBasePoint(t,e){return{topLeft:this.getRotatedPoint(t[0],e),topRight:this.getRotatedPoint(t[1],e),bottomLeft:this.getRotatedPoint(t[2],e),bottomRight:this.getRotatedPoint(t[3],e)}}getRotatedPoint(t,e){return{x:t.x*Math.cos(this.angle)+t.y*Math.sin(this.angle)+e.x,y:t.y*Math.cos(this.angle)-t.x*Math.sin(this.angle)+e.y}}calculateIntersectPoint(t){let e={left:-1,top:-1,width:this.pageWidth+2,height:this.pageHeight+2};this.corner==="top"?(this.topIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[t,this.rect.topRight],[{x:0,y:0},{x:this.pageWidth,y:0}]),this.sideIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[t,this.rect.bottomLeft],[{x:this.pageWidth,y:0},{x:this.pageWidth,y:this.pageHeight}]),this.bottomIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[this.rect.bottomLeft,this.rect.bottomRight],[{x:0,y:this.pageHeight},{x:this.pageWidth,y:this.pageHeight}])):(this.topIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[this.rect.topLeft,this.rect.topRight],[{x:0,y:0},{x:this.pageWidth,y:0}]),this.sideIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[t,this.rect.topLeft],[{x:this.pageWidth,y:0},{x:this.pageWidth,y:this.pageHeight}]),this.bottomIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[this.rect.bottomLeft,this.rect.bottomRight],[{x:0,y:this.pageHeight},{x:this.pageWidth,y:this.pageHeight}]))}checkPositionAtCenterLine(t,e,i){let s=t,n=g.LimitPointToCircle(e,this.pageWidth,s);s!==n&&(s=n,this.updateAngleAndGeometry(s));let o=Math.sqrt(Math.pow(this.pageWidth,2)+Math.pow(this.pageHeight,2)),h=this.rect.bottomRight,d=this.rect.topLeft;if(this.corner==="bottom"&&(h=this.rect.topRight,d=this.rect.bottomLeft),h.x<=0){let u=g.LimitPointToCircle(i,o,d);u!==s&&(s=u,this.updateAngleAndGeometry(s))}return s}getSegmentToShadowLine(){let t=this.getShadowStartPoint();return[t,t!==this.sideIntersectPoint&&this.sideIntersectPoint!==null?this.sideIntersectPoint:this.bottomIntersectPoint]}}class b{constructor(t,e){this.flippingPage=null,this.bottomPage=null,this.calc=null,this.state="read",this.render=t,this.app=e}fold(t){this.setState("user_fold"),this.calc===null&&this.start(t),this.do(this.render.convertToPage(t))}flip(t){if(this.app.getSettings().disableFlipByClick&&!this.isPointOnCorners(t)||(this.calc!==null&&this.render.finishAnimation(),!this.start(t)))return;let e=this.getBoundsRect();this.setState("flipping");let i=e.height/10,s=this.calc.getCorner()==="bottom"?e.height-i:i,n=this.calc.getCorner()==="bottom"?e.height:0;this.calc.calc({x:e.pageWidth-i,y:s}),this.animateFlippingTo({x:e.pageWidth-i,y:s},{x:-e.pageWidth,y:n},!0)}start(t){this.reset();let e=this.render.convertToBook(t),i=this.getBoundsRect(),s=this.getDirectionByPoint(e),n=e.y>=i.height/2?"bottom":"top";if(!this.checkDirection(s))return!1;try{if(this.flippingPage=this.app.getPageCollection().getFlippingPage(s),this.bottomPage=this.app.getPageCollection().getBottomPage(s),this.render.getOrientation()==="landscape")if(s===1){let o=this.app.getPageCollection().nextBy(this.flippingPage);o!==null&&this.flippingPage.getDensity()!==o.getDensity()&&(this.flippingPage.setDrawingDensity("hard"),o.setDrawingDensity("hard"))}else{let o=this.app.getPageCollection().prevBy(this.flippingPage);o!==null&&this.flippingPage.getDensity()!==o.getDensity()&&(this.flippingPage.setDrawingDensity("hard"),o.setDrawingDensity("hard"))}return this.render.setDirection(s),this.calc=new P(s,n,i.pageWidth.toString(10),i.height.toString(10)),!0}catch{return!1}}do(t){if(this.calc!==null&&this.calc.calc(t)){let e=this.calc.getFlippingProgress();this.bottomPage.setArea(this.calc.getBottomClipArea()),this.bottomPage.setPosition(this.calc.getBottomPagePosition()),this.bottomPage.setAngle(0),this.bottomPage.setHardAngle(0),this.flippingPage.setArea(this.calc.getFlippingClipArea()),this.flippingPage.setPosition(this.calc.getActiveCorner()),this.flippingPage.setAngle(this.calc.getAngle()),this.calc.getDirection()===0?this.flippingPage.setHardAngle(90*(200-2*e)/100):this.flippingPage.setHardAngle(-90*(200-2*e)/100),this.render.setPageRect(this.calc.getRect()),this.render.setBottomPage(this.bottomPage),this.render.setFlippingPage(this.flippingPage),this.render.setShadowData(this.calc.getShadowStartPoint(),this.calc.getShadowAngle(),e,this.calc.getDirection())}}flipToPage(t,e){let i=this.app.getPageCollection().getCurrentSpreadIndex(),s=this.app.getPageCollection().getSpreadIndexByPage(t);try{s>i&&(this.app.getPageCollection().setCurrentSpreadIndex(s-1),this.flipNext(e)),sthis.do(d));let h=this.getAnimationDuration(n.length);this.render.startAnimation(o,h,()=>{this.calc&&(i&&(this.calc.getDirection()===1?this.app.turnToPrevPage():this.app.turnToNextPage()),s&&(this.render.setBottomPage(null),this.render.setFlippingPage(null),this.render.clearShadow(),this.setState("read"),this.reset()))})}getCalculation(){return this.calc}getState(){return this.state}setState(t){this.state!==t&&(this.app.updateState(t),this.state=t)}getDirectionByPoint(t){let e=this.getBoundsRect();if(this.render.getOrientation()==="portrait"){if(t.x-e.pageWidth<=e.width/5)return 1}else if(t.x=1e3?e:t/1e3*e}checkDirection(t){return t===0?this.app.getCurrentPageIndex()=1}reset(){this.calc=null,this.flippingPage=null,this.bottomPage=null}getBoundsRect(){return this.render.getRect()}checkState(...t){for(let e of t)if(this.state===e)return!0;return!1}isPointOnCorners(t){let e=this.getBoundsRect(),i=e.pageWidth,s=Math.sqrt(Math.pow(i,2)+Math.pow(e.height,2))/5,n=this.render.convertToBook(t);return n.x>0&&n.y>0&&n.xe.width-s)&&(n.ye.height-s)}}class v{constructor(t,e){this.leftPage=null,this.rightPage=null,this.flippingPage=null,this.bottomPage=null,this.direction=null,this.orientation=null,this.shadow=null,this.animation=null,this.pageRect=null,this.boundsRect=null,this.timer=0,this.safari=!1,this.setting=e,this.app=t;let i=new RegExp("Version\\/[\\d\\.]+.*Safari/");this.safari=i.exec(window.navigator.userAgent)!==null}render(t){if(this.animation!==null){let e=Math.round((t-this.animation.startedAt)/this.animation.durationFrame);e{this.render(e),requestAnimationFrame(t)};requestAnimationFrame(t)}startAnimation(t,e,i){this.finishAnimation(),this.animation={frames:t,duration:e,durationFrame:e/t.length,onAnimateEnd:i,startedAt:this.timer}}finishAnimation(){this.animation!==null&&(this.animation.frames[this.animation.frames.length-1](),this.animation.onAnimateEnd!==null&&this.animation.onAnimateEnd()),this.animation=null}update(){this.boundsRect=null;let t=this.calculateBoundsRect();this.orientation!==t&&(this.orientation=t,this.app.updateOrientation(t))}calculateBoundsRect(){let t="landscape",e=this.getBlockWidth(),i=e/2,s=this.getBlockHeight()/2,n=this.setting.width/this.setting.height,o=this.setting.width,h=this.setting.height,d=i-o;return this.setting.size==="stretch"?(e<2*this.setting.minWidth&&this.app.getSettings().usePortrait&&(t="portrait"),o=t==="portrait"?this.getBlockWidth():this.getBlockWidth()/2,o>this.setting.maxWidth&&(o=this.setting.maxWidth),h=o/n,h>this.getBlockHeight()&&(h=this.getBlockHeight(),o=h*n),d=t==="portrait"?i-o/2-o:i-o):e<2*o&&this.app.getSettings().usePortrait&&(t="portrait",d=i-o/2-o),this.boundsRect={left:d,top:s-h/2,width:2*o,height:h,pageWidth:o},t}setShadowData(t,e,i,s){if(!this.app.getSettings().drawShadow)return;let n=100*this.getSettings().maxShadowOpacity;this.shadow={pos:t,angle:e,width:3*this.getRect().pageWidth/4*i/100,opacity:(100-i)*n/100/100,direction:s,progress:2*i}}clearShadow(){this.shadow=null}getBlockWidth(){return this.app.getUI().getDistElement().offsetWidth}getBlockHeight(){return this.app.getUI().getDistElement().offsetHeight}getDirection(){return this.direction}getRect(){return this.boundsRect===null&&this.calculateBoundsRect(),this.boundsRect}getSettings(){return this.app.getSettings()}getOrientation(){return this.orientation}setPageRect(t){this.pageRect=t}setDirection(t){this.direction=t}setRightPage(t){t!==null&&t.setOrientation(1),this.rightPage=t}setLeftPage(t){t!==null&&t.setOrientation(0),this.leftPage=t}setBottomPage(t){t!==null&&t.setOrientation(this.direction===1?0:1),this.bottomPage=t}setFlippingPage(t){t!==null&&t.setOrientation(this.direction===0&&this.orientation!=="portrait"?0:1),this.flippingPage=t}convertToBook(t){let e=this.getRect();return{x:t.x-e.left,y:t.y-e.top}}isSafari(){return this.safari}convertToPage(t,e){e||(e=this.direction);let i=this.getRect();return{x:e===0?t.x-i.left-i.width/2:i.width/2-t.x+i.left,y:t.y-i.top}}convertToGlobal(t,e){if(e||(e=this.direction),t==null)return null;let i=this.getRect();return{x:e===0?t.x+i.left+i.width/2:i.width/2-t.x+i.left,y:t.y+i.top}}convertRectToGlobal(t,e){return e||(e=this.direction),{topLeft:this.convertToGlobal(t.topLeft,e),topRight:this.convertToGlobal(t.topRight,e),bottomLeft:this.convertToGlobal(t.bottomLeft,e),bottomRight:this.convertToGlobal(t.bottomRight,e)}}}class I extends v{constructor(t,e,i){super(t,e),this.canvas=i,this.ctx=i.getContext("2d")}getContext(){return this.ctx}reload(){}drawFrame(){this.clear(),this.orientation!=="portrait"&&this.leftPage!=null&&this.leftPage.simpleDraw(0),this.rightPage!=null&&this.rightPage.simpleDraw(1),this.bottomPage!=null&&this.bottomPage.draw(),this.drawBookShadow(),this.flippingPage!=null&&this.flippingPage.draw(),this.shadow!=null&&(this.drawOuterShadow(),this.drawInnerShadow());let t=this.getRect();this.orientation==="portrait"&&(this.ctx.beginPath(),this.ctx.rect(t.left+t.pageWidth,t.top,t.width,t.height),this.ctx.clip())}drawBookShadow(){let t=this.getRect();this.ctx.save(),this.ctx.beginPath();let e=t.width/20;this.ctx.rect(t.left,t.top,t.width,t.height);let i={x:t.left+t.width/2-e/2,y:0};this.ctx.translate(i.x,i.y);let s=this.ctx.createLinearGradient(0,0,e,0);s.addColorStop(0,"rgba(0, 0, 0, 0)"),s.addColorStop(.4,"rgba(0, 0, 0, 0.2)"),s.addColorStop(.49,"rgba(0, 0, 0, 0.1)"),s.addColorStop(.5,"rgba(0, 0, 0, 0.5)"),s.addColorStop(.51,"rgba(0, 0, 0, 0.4)"),s.addColorStop(1,"rgba(0, 0, 0, 0)"),this.ctx.clip(),this.ctx.fillStyle=s,this.ctx.fillRect(0,0,e,2*t.height),this.ctx.restore()}drawOuterShadow(){let t=this.getRect();this.ctx.save(),this.ctx.beginPath(),this.ctx.rect(t.left,t.top,t.width,t.height);let e=this.convertToGlobal({x:this.shadow.pos.x,y:this.shadow.pos.y});this.ctx.translate(e.x,e.y),this.ctx.rotate(Math.PI+this.shadow.angle+Math.PI/2);let i=this.ctx.createLinearGradient(0,0,this.shadow.width,0);this.shadow.direction===0?(this.ctx.translate(0,-100),i.addColorStop(0,"rgba(0, 0, 0, "+this.shadow.opacity+")"),i.addColorStop(1,"rgba(0, 0, 0, 0)")):(this.ctx.translate(-this.shadow.width,-100),i.addColorStop(0,"rgba(0, 0, 0, 0)"),i.addColorStop(1,"rgba(0, 0, 0, "+this.shadow.opacity+")")),this.ctx.clip(),this.ctx.fillStyle=i,this.ctx.fillRect(0,0,this.shadow.width,2*t.height),this.ctx.restore()}drawInnerShadow(){let t=this.getRect();this.ctx.save(),this.ctx.beginPath();let e=this.convertToGlobal({x:this.shadow.pos.x,y:this.shadow.pos.y}),i=this.convertRectToGlobal(this.pageRect);this.ctx.moveTo(i.topLeft.x,i.topLeft.y),this.ctx.lineTo(i.topRight.x,i.topRight.y),this.ctx.lineTo(i.bottomRight.x,i.bottomRight.y),this.ctx.lineTo(i.bottomLeft.x,i.bottomLeft.y),this.ctx.translate(e.x,e.y),this.ctx.rotate(Math.PI+this.shadow.angle+Math.PI/2);let s=3*this.shadow.width/4,n=this.ctx.createLinearGradient(0,0,s,0);this.shadow.direction===0?(this.ctx.translate(-s,-100),n.addColorStop(1,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(.9,"rgba(0, 0, 0, 0.05)"),n.addColorStop(.7,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(0,"rgba(0, 0, 0, 0)")):(this.ctx.translate(0,-100),n.addColorStop(0,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(.1,"rgba(0, 0, 0, 0.05)"),n.addColorStop(.3,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(1,"rgba(0, 0, 0, 0)")),this.ctx.clip(),this.ctx.fillStyle=n,this.ctx.fillRect(0,0,s,2*t.height),this.ctx.restore()}clear(){this.ctx.fillStyle="white",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height)}}class C{constructor(t,e,i){this.touchPoint=null,this.swipeTimeout=250,this.onResize=()=>{this.update()},this.onMouseDown=n=>{if(this.checkTarget(n.target)){let o=this.getMousePos(n.clientX,n.clientY);this.app.startUserTouch(o),n.preventDefault()}},this.onTouchStart=n=>{if(this.checkTarget(n.target)&&n.changedTouches.length>0){let o=n.changedTouches[0],h=this.getMousePos(o.clientX,o.clientY);this.touchPoint={point:h,time:Date.now()},setTimeout(()=>{this.touchPoint!==null&&this.app.startUserTouch(h)},this.swipeTimeout),this.app.getSettings().mobileScrollSupport||n.preventDefault()}},this.onMouseUp=n=>{let o=this.getMousePos(n.clientX,n.clientY);this.app.userStop(o)},this.onMouseMove=n=>{let o=this.getMousePos(n.clientX,n.clientY);this.app.userMove(o,!1)},this.onTouchMove=n=>{if(n.changedTouches.length>0){let o=n.changedTouches[0],h=this.getMousePos(o.clientX,o.clientY);this.app.getSettings().mobileScrollSupport?(this.touchPoint!==null&&(Math.abs(this.touchPoint.point.x-h.x)>10||this.app.getState()!=="read")&&n.cancelable&&this.app.userMove(h,!0),this.app.getState()!=="read"&&n.preventDefault()):this.app.userMove(h,!0)}},this.onTouchEnd=n=>{if(n.changedTouches.length>0){let o=n.changedTouches[0],h=this.getMousePos(o.clientX,o.clientY),d=!1;if(this.touchPoint!==null){let u=h.x-this.touchPoint.point.x,x=Math.abs(h.y-this.touchPoint.point.y);Math.abs(u)>this.swipeDistance&&x<2*this.swipeDistance&&Date.now()-this.touchPoint.time0?this.app.flipPrev(this.touchPoint.point.y'),this.wrapper=t.querySelector(".stf__wrapper"),this.app=e;let s=this.app.getSettings().usePortrait?1:2;t.style.minWidth=i.minWidth*s+"px",t.style.minHeight=i.minHeight+"px",i.size==="fixed"&&(t.style.minWidth=i.width*s+"px",t.style.minHeight=i.height+"px"),i.autoSize&&(t.style.width="100%",t.style.maxWidth=2*i.maxWidth+"px"),t.style.display="block",window.addEventListener("resize",this.onResize,!1),this.swipeDistance=i.swipeDistance}destroy(){this.app.getSettings().useMouseEvents&&this.removeHandlers(),this.distElement.remove(),this.wrapper.remove()}getDistElement(){return this.distElement}getWrapper(){return this.wrapper}setOrientationStyle(t){this.wrapper.classList.remove("--portrait","--landscape"),t==="portrait"?(this.app.getSettings().autoSize&&(this.wrapper.style.paddingBottom=this.app.getSettings().height/this.app.getSettings().width*100+"%"),this.wrapper.classList.add("--portrait")):(this.app.getSettings().autoSize&&(this.wrapper.style.paddingBottom=this.app.getSettings().height/(2*this.app.getSettings().width)*100+"%"),this.wrapper.classList.add("--landscape")),this.update()}removeHandlers(){window.removeEventListener("resize",this.onResize),this.distElement.removeEventListener("mousedown",this.onMouseDown),this.distElement.removeEventListener("touchstart",this.onTouchStart),window.removeEventListener("mousemove",this.onMouseMove),window.removeEventListener("touchmove",this.onTouchMove),window.removeEventListener("mouseup",this.onMouseUp),window.removeEventListener("touchend",this.onTouchEnd)}setHandlers(){window.addEventListener("resize",this.onResize,!1),this.app.getSettings().useMouseEvents&&(this.distElement.addEventListener("mousedown",this.onMouseDown),this.distElement.addEventListener("touchstart",this.onTouchStart),window.addEventListener("mousemove",this.onMouseMove),window.addEventListener("touchmove",this.onTouchMove,{passive:!this.app.getSettings().mobileScrollSupport}),window.addEventListener("mouseup",this.onMouseUp),window.addEventListener("touchend",this.onTouchEnd))}getMousePos(t,e){let i=this.distElement.getBoundingClientRect();return{x:t-i.left,y:e-i.top}}checkTarget(t){return!this.app.getSettings().clickEventForward||!["a","button"].includes(t.tagName.toLowerCase())}}class S extends C{constructor(t,e,i,s){super(t,e,i),this.wrapper.insertAdjacentHTML("afterbegin",''),this.distElement=t.querySelector(".stf__block"),this.items=s;for(let n of s)this.distElement.appendChild(n);this.setHandlers()}clear(){for(let t of this.items)this.parentElement.appendChild(t)}updateItems(t){this.removeHandlers(),this.distElement.innerHTML="";for(let e of t)this.distElement.appendChild(e);this.items=t,this.setHandlers()}update(){this.app.getRender().update()}}class k extends C{constructor(t,e,i){super(t,e,i),this.wrapper.innerHTML='',this.canvas=t.querySelectorAll("canvas")[0],this.distElement=this.canvas,this.resizeCanvas(),this.setHandlers()}resizeCanvas(){let t=getComputedStyle(this.canvas),e=parseInt(t.getPropertyValue("width"),10),i=parseInt(t.getPropertyValue("height"),10);this.canvas.width=e,this.canvas.height=i}getCanvas(){return this.canvas}update(){this.resizeCanvas(),this.app.getRender().update()}}class M extends v{constructor(t,e,i){super(t,e),this.outerShadow=null,this.innerShadow=null,this.hardShadow=null,this.hardInnerShadow=null,this.element=i,this.createShadows()}createShadows(){this.element.insertAdjacentHTML("beforeend",`
+ z-index: ${this.render.getSettings().startZIndex+1};`}getElement(){return this.element}load(){this.isLoad=!0}setOrientation(t){super.setOrientation(t),this.element.classList.remove("--left","--right"),this.element.classList.add(t===1?"--right":"--left")}setDrawingDensity(t){this.element.classList.remove("--soft","--hard"),this.element.classList.add("--"+t),super.setDrawingDensity(t)}}class f extends c{constructor(t,e,i,s){super(t,e),this.element=i,this.pagesElement=s}load(){for(let t of this.pagesElement){let e=new m(this.render,t,t.dataset.density==="hard"?"hard":"soft");e.load(),this.pages.push(e)}this.createSpread()}}class P{constructor(t,e,i,s){this.direction=t,this.corner=e,this.topIntersectPoint=null,this.sideIntersectPoint=null,this.bottomIntersectPoint=null,this.pageWidth=parseInt(i,10),this.pageHeight=parseInt(s,10)}calc(t){try{return this.position=this.calcAngleAndPosition(t),this.calculateIntersectPoint(this.position),!0}catch{return!1}}getFlippingClipArea(){let t=[],e=!1;return t.push(this.rect.topLeft),t.push(this.topIntersectPoint),this.sideIntersectPoint===null?e=!0:(t.push(this.sideIntersectPoint),this.bottomIntersectPoint===null&&(e=!1)),t.push(this.bottomIntersectPoint),(e||this.corner==="bottom")&&t.push(this.rect.bottomLeft),t}getBottomClipArea(){let t=[];return t.push(this.topIntersectPoint),this.corner==="top"?t.push({x:this.pageWidth,y:0}):(this.topIntersectPoint!==null&&t.push({x:this.pageWidth,y:0}),t.push({x:this.pageWidth,y:this.pageHeight})),this.sideIntersectPoint!==null?g.GetDistanceBetweenTwoPoint(this.sideIntersectPoint,this.topIntersectPoint)>=10&&t.push(this.sideIntersectPoint):this.corner==="top"&&t.push({x:this.pageWidth,y:this.pageHeight}),t.push(this.bottomIntersectPoint),t.push(this.topIntersectPoint),t}getAngle(){return this.direction===0?-this.angle:this.angle}getRect(){return this.rect}getPosition(){return this.position}getActiveCorner(){return this.direction===0?this.rect.topLeft:this.rect.topRight}getDirection(){return this.direction}getFlippingProgress(){return Math.abs((this.position.x-this.pageWidth)/(2*this.pageWidth)*100)}getCorner(){return this.corner}getBottomPagePosition(){return this.direction===1?{x:this.pageWidth,y:0}:{x:0,y:0}}getShadowStartPoint(){return this.corner==="top"?this.topIntersectPoint:this.sideIntersectPoint!==null?this.sideIntersectPoint:this.topIntersectPoint}getShadowAngle(){let t=g.GetAngleBetweenTwoLine(this.getSegmentToShadowLine(),[{x:0,y:0},{x:this.pageWidth,y:0}]);return this.direction===0?t:Math.PI-t}calcAngleAndPosition(t){let e=t;if(this.updateAngleAndGeometry(e),e=this.corner==="top"?this.checkPositionAtCenterLine(e,{x:0,y:0},{x:0,y:this.pageHeight}):this.checkPositionAtCenterLine(e,{x:0,y:this.pageHeight},{x:0,y:0}),Math.abs(e.x-this.pageWidth)<1&&Math.abs(e.y)<1)throw new Error("Point is too small");return e}updateAngleAndGeometry(t){this.angle=this.calculateAngle(t),this.rect=this.getPageRect(t)}calculateAngle(t){let e=this.pageWidth-t.x+1,i=this.corner==="bottom"?this.pageHeight-t.y:t.y,s=2*Math.acos(e/Math.sqrt(i*i+e*e));i<0&&(s=-s);let n=Math.PI-s;if(!isFinite(s)||n>=0&&n<.003)throw new Error("The G point is too small");return this.corner==="bottom"&&(s=-s),s}getPageRect(t){return this.corner==="top"?this.getRectFromBasePoint([{x:0,y:0},{x:this.pageWidth,y:0},{x:0,y:this.pageHeight},{x:this.pageWidth,y:this.pageHeight}],t):this.getRectFromBasePoint([{x:0,y:-this.pageHeight},{x:this.pageWidth,y:-this.pageHeight},{x:0,y:0},{x:this.pageWidth,y:0}],t)}getRectFromBasePoint(t,e){return{topLeft:this.getRotatedPoint(t[0],e),topRight:this.getRotatedPoint(t[1],e),bottomLeft:this.getRotatedPoint(t[2],e),bottomRight:this.getRotatedPoint(t[3],e)}}getRotatedPoint(t,e){return{x:t.x*Math.cos(this.angle)+t.y*Math.sin(this.angle)+e.x,y:t.y*Math.cos(this.angle)-t.x*Math.sin(this.angle)+e.y}}calculateIntersectPoint(t){let e={left:-1,top:-1,width:this.pageWidth+2,height:this.pageHeight+2};this.corner==="top"?(this.topIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[t,this.rect.topRight],[{x:0,y:0},{x:this.pageWidth,y:0}]),this.sideIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[t,this.rect.bottomLeft],[{x:this.pageWidth,y:0},{x:this.pageWidth,y:this.pageHeight}]),this.bottomIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[this.rect.bottomLeft,this.rect.bottomRight],[{x:0,y:this.pageHeight},{x:this.pageWidth,y:this.pageHeight}])):(this.topIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[this.rect.topLeft,this.rect.topRight],[{x:0,y:0},{x:this.pageWidth,y:0}]),this.sideIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[t,this.rect.topLeft],[{x:this.pageWidth,y:0},{x:this.pageWidth,y:this.pageHeight}]),this.bottomIntersectPoint=g.GetIntersectBetweenTwoSegment(e,[this.rect.bottomLeft,this.rect.bottomRight],[{x:0,y:this.pageHeight},{x:this.pageWidth,y:this.pageHeight}]))}checkPositionAtCenterLine(t,e,i){let s=t,n=g.LimitPointToCircle(e,this.pageWidth,s);s!==n&&(s=n,this.updateAngleAndGeometry(s));let o=Math.sqrt(Math.pow(this.pageWidth,2)+Math.pow(this.pageHeight,2)),h=this.rect.bottomRight,d=this.rect.topLeft;if(this.corner==="bottom"&&(h=this.rect.topRight,d=this.rect.bottomLeft),h.x<=0){let u=g.LimitPointToCircle(i,o,d);u!==s&&(s=u,this.updateAngleAndGeometry(s))}return s}getSegmentToShadowLine(){let t=this.getShadowStartPoint();return[t,t!==this.sideIntersectPoint&&this.sideIntersectPoint!==null?this.sideIntersectPoint:this.bottomIntersectPoint]}}class S{constructor(t,e){this.flippingPage=null,this.bottomPage=null,this.calc=null,this.state="read",this.render=t,this.app=e}fold(t){this.setState("user_fold"),this.calc===null&&this.start(t),this.do(this.render.convertToPage(t))}flip(t){if(this.app.getSettings().disableFlipByClick&&!this.isPointOnCorners(t)||(this.calc!==null&&this.render.finishAnimation(),!this.start(t)))return;let e=this.getBoundsRect();this.setState("flipping");let i=e.height/10,s=this.calc.getCorner()==="bottom"?e.height-i:i,n=this.calc.getCorner()==="bottom"?e.height:0;this.calc.calc({x:e.pageWidth-i,y:s}),this.animateFlippingTo({x:e.pageWidth-i,y:s},{x:-e.pageWidth,y:n},!0)}start(t){this.reset();let e=this.render.convertToBook(t),i=this.getBoundsRect(),s=this.getDirectionByPoint(e),n=e.y>=i.height/2?"bottom":"top";if(!this.checkDirection(s))return!1;try{if(this.flippingPage=this.app.getPageCollection().getFlippingPage(s),this.bottomPage=this.app.getPageCollection().getBottomPage(s),this.render.getOrientation()==="landscape")if(s===1){let o=this.app.getPageCollection().nextBy(this.flippingPage);o!==null&&this.flippingPage.getDensity()!==o.getDensity()&&(this.flippingPage.setDrawingDensity("hard"),o.setDrawingDensity("hard"))}else{let o=this.app.getPageCollection().prevBy(this.flippingPage);o!==null&&this.flippingPage.getDensity()!==o.getDensity()&&(this.flippingPage.setDrawingDensity("hard"),o.setDrawingDensity("hard"))}return this.render.setDirection(s),this.calc=new P(s,n,i.pageWidth.toString(10),i.height.toString(10)),!0}catch{return!1}}do(t){if(this.calc!==null&&this.calc.calc(t)){let e=this.calc.getFlippingProgress();this.bottomPage.setArea(this.calc.getBottomClipArea()),this.bottomPage.setPosition(this.calc.getBottomPagePosition()),this.bottomPage.setAngle(0),this.bottomPage.setHardAngle(0),this.flippingPage.setArea(this.calc.getFlippingClipArea()),this.flippingPage.setPosition(this.calc.getActiveCorner()),this.flippingPage.setAngle(this.calc.getAngle()),this.calc.getDirection()===0?this.flippingPage.setHardAngle(90*(200-2*e)/100):this.flippingPage.setHardAngle(-90*(200-2*e)/100),this.render.setPageRect(this.calc.getRect()),this.render.setBottomPage(this.bottomPage),this.render.setFlippingPage(this.flippingPage),this.render.setShadowData(this.calc.getShadowStartPoint(),this.calc.getShadowAngle(),e,this.calc.getDirection())}}flipToPage(t,e){let i=this.app.getPageCollection().getCurrentSpreadIndex(),s=this.app.getPageCollection().getSpreadIndexByPage(t);try{s>i&&(this.app.getPageCollection().setCurrentSpreadIndex(s-1),this.flipNext(e)),sthis.do(d));let h=this.getAnimationDuration(n.length);this.render.startAnimation(o,h,()=>{this.calc&&(i&&(this.calc.getDirection()===1?this.app.turnToPrevPage():this.app.turnToNextPage()),s&&(this.render.setBottomPage(null),this.render.setFlippingPage(null),this.render.clearShadow(),this.setState("read"),this.reset()))})}getCalculation(){return this.calc}getState(){return this.state}setState(t){this.state!==t&&(this.app.updateState(t),this.state=t)}getDirectionByPoint(t){let e=this.getBoundsRect();if(this.render.getOrientation()==="portrait"){if(t.x-e.pageWidth<=e.width/5)return 1}else if(t.x=1e3?e:t/1e3*e}checkDirection(t){return t===0?this.app.getCurrentPageIndex()=1}reset(){this.calc=null,this.flippingPage=null,this.bottomPage=null}getBoundsRect(){return this.render.getRect()}checkState(...t){for(let e of t)if(this.state===e)return!0;return!1}isPointOnCorners(t){let e=this.getBoundsRect(),i=e.pageWidth,s=Math.sqrt(Math.pow(i,2)+Math.pow(e.height,2))/5,n=this.render.convertToBook(t);return n.x>0&&n.y>0&&n.xe.width-s)&&(n.ye.height-s)}}class v{constructor(t,e){this.leftPage=null,this.rightPage=null,this.flippingPage=null,this.bottomPage=null,this.direction=null,this.orientation=null,this.shadow=null,this.animation=null,this.pageRect=null,this.boundsRect=null,this.timer=0,this.safari=!1,this.setting=e,this.app=t;let i=new RegExp("Version\\/[\\d\\.]+.*Safari/");this.safari=i.exec(window.navigator.userAgent)!==null}render(t){if(this.animation!==null){let e=Math.round((t-this.animation.startedAt)/this.animation.durationFrame);e{this.render(e),requestAnimationFrame(t)};requestAnimationFrame(t)}startAnimation(t,e,i){this.finishAnimation(),this.animation={frames:t,duration:e,durationFrame:e/t.length,onAnimateEnd:i,startedAt:this.timer}}finishAnimation(){this.animation!==null&&(this.animation.frames[this.animation.frames.length-1](),this.animation.onAnimateEnd!==null&&this.animation.onAnimateEnd()),this.animation=null}update(){this.boundsRect=null;let t=this.calculateBoundsRect();this.orientation!==t&&(this.orientation=t,this.app.updateOrientation(t))}calculateBoundsRect(){let t="landscape",e=this.getBlockWidth(),i=e/2,s=this.getBlockHeight()/2,n=this.setting.width/this.setting.height,o=this.setting.width,h=this.setting.height,d=i-o;return this.setting.size==="stretch"?(e<2*this.setting.minWidth&&this.app.getSettings().usePortrait&&(t="portrait"),o=t==="portrait"?this.getBlockWidth():this.getBlockWidth()/2,o>this.setting.maxWidth&&(o=this.setting.maxWidth),h=o/n,h>this.getBlockHeight()&&(h=this.getBlockHeight(),o=h*n),d=t==="portrait"?i-o/2-o:i-o):e<2*o&&this.app.getSettings().usePortrait&&(t="portrait",d=i-o/2-o),this.boundsRect={left:d,top:s-h/2,width:2*o,height:h,pageWidth:o},t}setShadowData(t,e,i,s){if(!this.app.getSettings().drawShadow)return;let n=100*this.getSettings().maxShadowOpacity;this.shadow={pos:t,angle:e,width:3*this.getRect().pageWidth/4*i/100,opacity:(100-i)*n/100/100,direction:s,progress:2*i}}clearShadow(){this.shadow=null}getBlockWidth(){return this.app.getUI().getDistElement().offsetWidth}getBlockHeight(){return this.app.getUI().getDistElement().offsetHeight}getDirection(){return this.direction}getRect(){return this.boundsRect===null&&this.calculateBoundsRect(),this.boundsRect}getSettings(){return this.app.getSettings()}getOrientation(){return this.orientation}setPageRect(t){this.pageRect=t}setDirection(t){this.direction=t}setRightPage(t){t!==null&&t.setOrientation(1),this.rightPage=t}setLeftPage(t){t!==null&&t.setOrientation(0),this.leftPage=t}setBottomPage(t){t!==null&&t.setOrientation(this.direction===1?0:1),this.bottomPage=t}setFlippingPage(t){t!==null&&t.setOrientation(this.direction===0&&this.orientation!=="portrait"?0:1),this.flippingPage=t}convertToBook(t){let e=this.getRect();return{x:t.x-e.left,y:t.y-e.top}}isSafari(){return this.safari}convertToPage(t,e){e||(e=this.direction);let i=this.getRect();return{x:e===0?t.x-i.left-i.width/2:i.width/2-t.x+i.left,y:t.y-i.top}}convertToGlobal(t,e){if(e||(e=this.direction),t==null)return null;let i=this.getRect();return{x:e===0?t.x+i.left+i.width/2:i.width/2-t.x+i.left,y:t.y+i.top}}convertRectToGlobal(t,e){return e||(e=this.direction),{topLeft:this.convertToGlobal(t.topLeft,e),topRight:this.convertToGlobal(t.topRight,e),bottomLeft:this.convertToGlobal(t.bottomLeft,e),bottomRight:this.convertToGlobal(t.bottomRight,e)}}}class I extends v{constructor(t,e,i){super(t,e),this.canvas=i,this.ctx=i.getContext("2d")}getContext(){return this.ctx}reload(){}drawFrame(){this.clear(),this.orientation!=="portrait"&&this.leftPage!=null&&this.leftPage.simpleDraw(0),this.rightPage!=null&&this.rightPage.simpleDraw(1),this.bottomPage!=null&&this.bottomPage.draw(),this.drawBookShadow(),this.flippingPage!=null&&this.flippingPage.draw(),this.shadow!=null&&(this.drawOuterShadow(),this.drawInnerShadow());let t=this.getRect();this.orientation==="portrait"&&(this.ctx.beginPath(),this.ctx.rect(t.left+t.pageWidth,t.top,t.width,t.height),this.ctx.clip())}drawBookShadow(){let t=this.getRect();this.ctx.save(),this.ctx.beginPath();let e=t.width/20;this.ctx.rect(t.left,t.top,t.width,t.height);let i={x:t.left+t.width/2-e/2,y:0};this.ctx.translate(i.x,i.y);let s=this.ctx.createLinearGradient(0,0,e,0);s.addColorStop(0,"rgba(0, 0, 0, 0)"),s.addColorStop(.4,"rgba(0, 0, 0, 0.2)"),s.addColorStop(.49,"rgba(0, 0, 0, 0.1)"),s.addColorStop(.5,"rgba(0, 0, 0, 0.5)"),s.addColorStop(.51,"rgba(0, 0, 0, 0.4)"),s.addColorStop(1,"rgba(0, 0, 0, 0)"),this.ctx.clip(),this.ctx.fillStyle=s,this.ctx.fillRect(0,0,e,2*t.height),this.ctx.restore()}drawOuterShadow(){let t=this.getRect();this.ctx.save(),this.ctx.beginPath(),this.ctx.rect(t.left,t.top,t.width,t.height);let e=this.convertToGlobal({x:this.shadow.pos.x,y:this.shadow.pos.y});this.ctx.translate(e.x,e.y),this.ctx.rotate(Math.PI+this.shadow.angle+Math.PI/2);let i=this.ctx.createLinearGradient(0,0,this.shadow.width,0);this.shadow.direction===0?(this.ctx.translate(0,-100),i.addColorStop(0,"rgba(0, 0, 0, "+this.shadow.opacity+")"),i.addColorStop(1,"rgba(0, 0, 0, 0)")):(this.ctx.translate(-this.shadow.width,-100),i.addColorStop(0,"rgba(0, 0, 0, 0)"),i.addColorStop(1,"rgba(0, 0, 0, "+this.shadow.opacity+")")),this.ctx.clip(),this.ctx.fillStyle=i,this.ctx.fillRect(0,0,this.shadow.width,2*t.height),this.ctx.restore()}drawInnerShadow(){let t=this.getRect();this.ctx.save(),this.ctx.beginPath();let e=this.convertToGlobal({x:this.shadow.pos.x,y:this.shadow.pos.y}),i=this.convertRectToGlobal(this.pageRect);this.ctx.moveTo(i.topLeft.x,i.topLeft.y),this.ctx.lineTo(i.topRight.x,i.topRight.y),this.ctx.lineTo(i.bottomRight.x,i.bottomRight.y),this.ctx.lineTo(i.bottomLeft.x,i.bottomLeft.y),this.ctx.translate(e.x,e.y),this.ctx.rotate(Math.PI+this.shadow.angle+Math.PI/2);let s=3*this.shadow.width/4,n=this.ctx.createLinearGradient(0,0,s,0);this.shadow.direction===0?(this.ctx.translate(-s,-100),n.addColorStop(1,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(.9,"rgba(0, 0, 0, 0.05)"),n.addColorStop(.7,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(0,"rgba(0, 0, 0, 0)")):(this.ctx.translate(0,-100),n.addColorStop(0,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(.1,"rgba(0, 0, 0, 0.05)"),n.addColorStop(.3,"rgba(0, 0, 0, "+this.shadow.opacity+")"),n.addColorStop(1,"rgba(0, 0, 0, 0)")),this.ctx.clip(),this.ctx.fillStyle=n,this.ctx.fillRect(0,0,s,2*t.height),this.ctx.restore()}clear(){this.ctx.fillStyle="white",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height)}}class T{constructor(t,e,i){this.touchPoint=null,this.swipeTimeout=250,this.onResize=()=>{this.update()},this.onMouseDown=n=>{if(this.checkTarget(n.target)){let o=this.getMousePos(n.clientX,n.clientY);this.app.startUserTouch(o),n.preventDefault()}},this.onTouchStart=n=>{if(this.checkTarget(n.target)&&n.changedTouches.length>0){let o=n.changedTouches[0],h=this.getMousePos(o.clientX,o.clientY);this.touchPoint={point:h,time:Date.now()},setTimeout(()=>{this.touchPoint!==null&&this.app.startUserTouch(h)},this.swipeTimeout),this.app.getSettings().mobileScrollSupport||n.preventDefault()}},this.onMouseUp=n=>{let o=this.getMousePos(n.clientX,n.clientY);this.app.userStop(o)},this.onMouseMove=n=>{let o=this.getMousePos(n.clientX,n.clientY);this.app.userMove(o,!1)},this.onTouchMove=n=>{if(n.changedTouches.length>0){let o=n.changedTouches[0],h=this.getMousePos(o.clientX,o.clientY);this.app.getSettings().mobileScrollSupport?(this.touchPoint!==null&&(Math.abs(this.touchPoint.point.x-h.x)>10||this.app.getState()!=="read")&&n.cancelable&&this.app.userMove(h,!0),this.app.getState()!=="read"&&n.preventDefault()):this.app.userMove(h,!0)}},this.onTouchEnd=n=>{if(n.changedTouches.length>0){let o=n.changedTouches[0],h=this.getMousePos(o.clientX,o.clientY),d=!1;if(this.touchPoint!==null){let u=h.x-this.touchPoint.point.x,x=Math.abs(h.y-this.touchPoint.point.y);Math.abs(u)>this.swipeDistance&&x<2*this.swipeDistance&&Date.now()-this.touchPoint.time0?this.app.flipPrev(this.touchPoint.point.y'),this.wrapper=t.querySelector(".stf__wrapper"),this.app=e;let s=this.app.getSettings().usePortrait?1:2;t.style.minWidth=i.minWidth*s+"px",t.style.minHeight=i.minHeight+"px",i.size==="fixed"&&(t.style.minWidth=i.width*s+"px",t.style.minHeight=i.height+"px"),i.autoSize&&(t.style.width="100%",t.style.maxWidth=2*i.maxWidth+"px"),t.style.display="block",window.addEventListener("resize",this.onResize,!1),this.swipeDistance=i.swipeDistance}destroy(){this.app.getSettings().useMouseEvents&&this.removeHandlers(),this.distElement.remove(),this.wrapper.remove()}getDistElement(){return this.distElement}getWrapper(){return this.wrapper}setOrientationStyle(t){this.wrapper.classList.remove("--portrait","--landscape"),t==="portrait"?(this.app.getSettings().autoSize&&(this.wrapper.style.paddingBottom=this.app.getSettings().height/this.app.getSettings().width*100+"%"),this.wrapper.classList.add("--portrait")):(this.app.getSettings().autoSize&&(this.wrapper.style.paddingBottom=this.app.getSettings().height/(2*this.app.getSettings().width)*100+"%"),this.wrapper.classList.add("--landscape")),this.update()}removeHandlers(){window.removeEventListener("resize",this.onResize),this.distElement.removeEventListener("mousedown",this.onMouseDown),this.distElement.removeEventListener("touchstart",this.onTouchStart),window.removeEventListener("mousemove",this.onMouseMove),window.removeEventListener("touchmove",this.onTouchMove),window.removeEventListener("mouseup",this.onMouseUp),window.removeEventListener("touchend",this.onTouchEnd)}setHandlers(){window.addEventListener("resize",this.onResize,!1),this.app.getSettings().useMouseEvents&&(this.distElement.addEventListener("mousedown",this.onMouseDown),this.distElement.addEventListener("touchstart",this.onTouchStart),window.addEventListener("mousemove",this.onMouseMove),window.addEventListener("touchmove",this.onTouchMove,{passive:!this.app.getSettings().mobileScrollSupport}),window.addEventListener("mouseup",this.onMouseUp),window.addEventListener("touchend",this.onTouchEnd))}getMousePos(t,e){let i=this.distElement.getBoundingClientRect();return{x:t-i.left,y:e-i.top}}checkTarget(t){return!this.app.getSettings().clickEventForward||!["a","button"].includes(t.tagName.toLowerCase())}}class b extends T{constructor(t,e,i,s){super(t,e,i),this.wrapper.insertAdjacentHTML("afterbegin",''),this.distElement=t.querySelector(".stf__block"),this.items=s;for(let n of s)this.distElement.appendChild(n);this.setHandlers()}clear(){for(let t of this.items)this.parentElement.appendChild(t)}updateItems(t){this.removeHandlers(),this.distElement.innerHTML="";for(let e of t)this.distElement.appendChild(e);this.items=t,this.setHandlers()}update(){this.app.getRender().update()}}class D extends T{constructor(t,e,i){super(t,e,i),this.wrapper.innerHTML='',this.canvas=t.querySelectorAll("canvas")[0],this.distElement=this.canvas,this.resizeCanvas(),this.setHandlers()}resizeCanvas(){let t=getComputedStyle(this.canvas),e=parseInt(t.getPropertyValue("width"),10),i=parseInt(t.getPropertyValue("height"),10);this.canvas.width=e,this.canvas.height=i}getCanvas(){return this.canvas}update(){this.resizeCanvas(),this.app.getRender().update()}}class M extends v{constructor(t,e,i){super(t,e),this.outerShadow=null,this.innerShadow=null,this.hardShadow=null,this.hardInnerShadow=null,this.element=i,this.createShadows()}createShadows(){this.element.insertAdjacentHTML("beforeend",`
`),this.outerShadow=this.element.querySelector(".stf__outerShadow"),this.innerShadow=this.element.querySelector(".stf__innerShadow"),this.hardShadow=this.element.querySelector(".stf__hardShadow"),this.hardInnerShadow=this.element.querySelector(".stf__hardInnerShadow")}clearShadow(){super.clearShadow(),this.outerShadow.style.cssText="display: none",this.innerShadow.style.cssText="display: none",this.hardShadow.style.cssText="display: none",this.hardInnerShadow.style.cssText="display: none"}reload(){this.element.querySelector(".stf__outerShadow")||this.createShadows()}drawHardInnerShadow(){let t=this.getRect(),e=this.shadow.progress>100?200-this.shadow.progress:this.shadow.progress,i=(100-e)*(2.5*t.pageWidth)/100+20;i>t.pageWidth&&(i=t.pageWidth);let s=`
@@ -40,7 +40,7 @@ var V=Object.create;var O=Object.defineProperty;var j=Object.getOwnPropertyDescr
background: linear-gradient(to left, rgba(0, 0, 0, ${this.shadow.opacity}) 5%, rgba(0, 0, 0, 0) 100%);
left: ${t.left+t.width/2}px;
transform-origin: 0 0;
- `;i+=this.getDirection()===0&&this.shadow.progress>100||this.getDirection()===1&&this.shadow.progress<=100?"transform: translate3d(0, 0, 0) rotateY(180deg);":"transform: translate3d(0, 0, 0);",this.hardShadow.style.cssText=i}drawInnerShadow(){let t=this.getRect(),e=3*this.shadow.width/4,i=this.getDirection()===0?e:0,s=this.getDirection()===0?"to left":"to right",n=this.convertToGlobal(this.shadow.pos),o=this.shadow.angle+3*Math.PI/2,h=[this.pageRect.topLeft,this.pageRect.topRight,this.pageRect.bottomRight,this.pageRect.bottomLeft],d="polygon( ";for(let x of h){let T=this.getDirection()===1?{x:-x.x+this.shadow.pos.x,y:x.y-this.shadow.pos.y}:{x:x.x-this.shadow.pos.x,y:x.y-this.shadow.pos.y};T=g.GetRotatedPoint(T,{x:i,y:100},o),d+=T.x+"px "+T.y+"px, "}d=d.slice(0,-2),d+=")";let u=`
+ `;i+=this.getDirection()===0&&this.shadow.progress>100||this.getDirection()===1&&this.shadow.progress<=100?"transform: translate3d(0, 0, 0) rotateY(180deg);":"transform: translate3d(0, 0, 0);",this.hardShadow.style.cssText=i}drawInnerShadow(){let t=this.getRect(),e=3*this.shadow.width/4,i=this.getDirection()===0?e:0,s=this.getDirection()===0?"to left":"to right",n=this.convertToGlobal(this.shadow.pos),o=this.shadow.angle+3*Math.PI/2,h=[this.pageRect.topLeft,this.pageRect.topRight,this.pageRect.bottomRight,this.pageRect.bottomLeft],d="polygon( ";for(let x of h){let C=this.getDirection()===1?{x:-x.x+this.shadow.pos.x,y:x.y-this.shadow.pos.y}:{x:x.x-this.shadow.pos.x,y:x.y-this.shadow.pos.y};C=g.GetRotatedPoint(C,{x:i,y:100},o),d+=C.x+"px "+C.y+"px, "}d=d.slice(0,-2),d+=")";let u=`
display: block;
z-index: ${(this.getSettings().startZIndex+10).toString(10)};
width: ${e}px;
@@ -64,7 +64,7 @@ var V=Object.create;var O=Object.defineProperty;var j=Object.getOwnPropertyDescr
transform: translate3d(${e.x-s}px, ${e.y-100}px, 0) rotate(${i}rad);
clip-path: ${h};
-webkit-clip-path: ${h};
- `;this.outerShadow.style.cssText=d}drawLeftPage(){this.orientation!=="portrait"&&this.leftPage!==null&&(this.direction===1&&this.flippingPage!==null&&this.flippingPage.getDrawingDensity()==="hard"?(this.leftPage.getElement().style.zIndex=(this.getSettings().startZIndex+5).toString(10),this.leftPage.setHardDrawingAngle(180+this.flippingPage.getHardAngle()),this.leftPage.draw(this.flippingPage.getDrawingDensity())):this.leftPage.simpleDraw(0))}drawRightPage(){this.rightPage!==null&&(this.direction===0&&this.flippingPage!==null&&this.flippingPage.getDrawingDensity()==="hard"?(this.rightPage.getElement().style.zIndex=(this.getSettings().startZIndex+5).toString(10),this.rightPage.setHardDrawingAngle(180+this.flippingPage.getHardAngle()),this.rightPage.draw(this.flippingPage.getDrawingDensity())):this.rightPage.simpleDraw(1))}drawBottomPage(){if(this.bottomPage===null)return;let t=this.flippingPage!=null?this.flippingPage.getDrawingDensity():null;this.orientation==="portrait"&&this.direction===1||(this.bottomPage.getElement().style.zIndex=(this.getSettings().startZIndex+3).toString(10),this.bottomPage.draw(t))}drawFrame(){this.clear(),this.drawLeftPage(),this.drawRightPage(),this.drawBottomPage(),this.flippingPage!=null&&(this.flippingPage.getElement().style.zIndex=(this.getSettings().startZIndex+5).toString(10),this.flippingPage.draw()),this.shadow!=null&&this.flippingPage!==null&&(this.flippingPage.getDrawingDensity()==="soft"?(this.drawOuterShadow(),this.drawInnerShadow()):(this.drawHardOuterShadow(),this.drawHardInnerShadow()))}clear(){for(let t of this.app.getPageCollection().getPages())t!==this.leftPage&&t!==this.rightPage&&t!==this.flippingPage&&t!==this.bottomPage&&(t.getElement().style.cssText="display: none"),t.getTemporaryCopy()!==this.flippingPage&&t.hideTemporaryCopy()}update(){super.update(),this.rightPage!==null&&this.rightPage.setOrientation(1),this.leftPage!==null&&this.leftPage.setOrientation(0)}}class R{constructor(){this._default={startPage:0,size:"fixed",width:0,height:0,minWidth:0,maxWidth:0,minHeight:0,maxHeight:0,drawShadow:!0,flippingTime:1e3,usePortrait:!0,startZIndex:0,autoSize:!0,maxShadowOpacity:1,showCover:!1,mobileScrollSupport:!0,swipeDistance:30,clickEventForward:!0,useMouseEvents:!0,showPageCorners:!0,disableFlipByClick:!1}}getSettings(t){let e=this._default;if(Object.assign(e,t),e.size!=="stretch"&&e.size!=="fixed")throw new Error('Invalid size type. Available only "fixed" and "stretch" value');if(e.width<=0||e.height<=0)throw new Error("Invalid width or height");if(e.flippingTime<=0)throw new Error("Invalid flipping time");return e.size==="stretch"?(e.minWidth<=0&&(e.minWidth=100),e.maxWidth{this.ui.update(),this.trigger("init",this,{page:this.setting.startPage,mode:this.render.getOrientation()})},1)}loadFromHTML(r){this.ui=new S(this.block,this,this.setting,r),this.render=new M(this,this.setting,this.ui.getDistElement()),this.flipController=new b(this.render,this),this.pages=new f(this,this.render,this.ui.getDistElement(),r),this.pages.load(),this.render.start(),this.pages.show(this.setting.startPage),setTimeout(()=>{this.ui.update(),this.trigger("init",this,{page:this.setting.startPage,mode:this.render.getOrientation()})},1)}updateFromImages(r){let t=this.pages.getCurrentPageIndex();this.pages.destroy(),this.pages=new m(this,this.render,r),this.pages.load(),this.pages.show(t),this.trigger("update",this,{page:t,mode:this.render.getOrientation()})}updateFromHtml(r){let t=this.pages.getCurrentPageIndex();this.pages.destroy(),this.pages=new f(this,this.render,this.ui.getDistElement(),r),this.pages.load(),this.ui.updateItems(r),this.render.reload(),this.pages.show(t),this.trigger("update",this,{page:t,mode:this.render.getOrientation()})}clear(){this.pages.destroy(),this.ui.clear()}turnToPrevPage(){this.pages.showPrev()}turnToNextPage(){this.pages.showNext()}turnToPage(r){this.pages.show(r)}flipNext(r="top"){this.flipController.flipNext(r)}flipPrev(r="top"){this.flipController.flipPrev(r)}flip(r,t="top"){this.flipController.flipToPage(r,t)}updateState(r){this.trigger("changeState",this,r)}updatePageIndex(r){this.trigger("flip",this,r)}updateOrientation(r){this.ui.setOrientationStyle(r),this.update(),this.trigger("changeOrientation",this,r)}getPageCount(){return this.pages.getPageCount()}getCurrentPageIndex(){return this.pages.getCurrentPageIndex()}getPage(r){return this.pages.getPage(r)}getRender(){return this.render}getFlipController(){return this.flipController}getOrientation(){return this.render.getOrientation()}getBoundsRect(){return this.render.getRect()}getSettings(){return this.setting}getUI(){return this.ui}getState(){return this.flipController.getState()}getPageCollection(){return this.pages}startUserTouch(r){this.mousePosition=r,this.isUserTouch=!0,this.isUserMove=!1}userMove(r,t){this.isUserTouch||t||!this.setting.showPageCorners?this.isUserTouch&&g.GetDistanceBetweenTwoPoint(this.mousePosition,r)>5&&(this.isUserMove=!0,this.flipController.fold(r)):this.flipController.showCorner(r)}userStop(r,t=!1){this.isUserTouch&&(this.isUserTouch=!1,t||(this.isUserMove?this.flipController.stopMove():this.flipController.flip(r)))}},Object.defineProperty(a,"__esModule",{value:!0})}))});var A={startPage:0,singlePageBreakpoint:640,chromeHeight:150,chromeWidth:48,maxPageHeight:1100,flippingTime:700,drawShadow:!0,maxShadowOpacity:.4,keyboard:!0};function z(a,p){let l=p.chromeHeight??A.chromeHeight,c=p.chromeWidth??A.chromeWidth,m=Math.min(p.viewportHeight-l,p.maxPageHeight??A.maxPageHeight),g=p.viewportWidth-c,w=Math.max(m,120),f=w*a,P=p.single?f:f*2;if(P>g){let b=g/P;f*=b,w*=b}return{width:Math.round(f),height:Math.round(w)}}async function B(a,p){let l={...A,...p};if(!l.pages.length)throw new Error("mountFlipbook: `pages` is empty");let m=(await Promise.resolve().then(()=>Q(G(),1))).PageFlip,g=null,w=Math.min(Math.max(l.startPage,0),l.pages.length-1),f=!1,P=null,b=()=>{if(f)return;let S=l.singlePageBreakpoint>0&&window.matchMedia(`(max-width: ${l.singlePageBreakpoint}px)`).matches,{width:k,height:M}=z(l.aspect,{single:S,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,chromeWidth:l.chromeWidth,chromeHeight:l.chromeHeight,maxPageHeight:l.maxPageHeight});if(g=new m(a,{width:k,height:M,size:"fixed",showCover:!0,usePortrait:S,maxShadowOpacity:l.maxShadowOpacity,mobileScrollSupport:!0,flippingTime:l.flippingTime,drawShadow:l.drawShadow}),g.loadFromImages(l.pages.slice()),g.on("flip",R=>{w=R.data,l.onFlip?.(w)}),w>0)try{g.turnToPage(w)}catch{}l.onReady?.()},v=()=>{if(g){try{g.destroy()}catch{}g=null,a.innerHTML=""}},I=()=>{P&&clearTimeout(P),P=setTimeout(()=>{v(),b()},200)},C=S=>{S.key==="ArrowLeft"?g?.flipPrev():S.key==="ArrowRight"&&g?.flipNext()};return b(),window.addEventListener("resize",I),l.keyboard&&document.addEventListener("keydown",C),{next:()=>g?.flipNext(),prev:()=>g?.flipPrev(),goTo:S=>g?.turnToPage(S),currentPage:()=>w,pageCount:()=>l.pages.length,destroy:()=>{f||(f=!0,P&&clearTimeout(P),window.removeEventListener("resize",I),document.removeEventListener("keydown",C),v())}}}function y(a,p,l){let c=document.createElement(a);return p&&(c.className=p),l!=null&&(c.textContent=l),c}async function H(a){let p=a.container??document.body,l=a.pages.length,c=y("div",`rab-overlay${a.className?` ${a.className}`:""}`);c.setAttribute("role","dialog"),c.setAttribute("aria-modal","true"),c.setAttribute("aria-label",`${a.title??"Document"} \u2014 page view`);let m=y("div","rab-chrome");m.append(y("span","rab-title",a.title??""));let g=y("div","rab-actions"),w=y("span","rab-counter");if(g.append(w),a.pdfUrl){let n=y("a","rab-btn rab-download","Download PDF \u2197");n.href=a.pdfUrl,n.target="_blank",n.rel="noopener noreferrer",g.append(n)}let f=y("button","rab-btn","Close \u2715");f.type="button",f.setAttribute("aria-label","Close book view"),g.append(f),m.append(g);let P=y("div","rab-stage"),b=y("button","rab-arrow rab-arrow-prev","\u2039");b.type="button",b.setAttribute("aria-label","Previous page");let v=y("button","rab-arrow rab-arrow-next","\u203A");v.type="button",v.setAttribute("aria-label","Next page");let I=y("div","rab-book"),C=y("p","rab-loading","Opening the book\u2026"),S=y("div","rab-book-wrap");S.append(I,C),P.append(b,S,v);let k=a.hint??"Use the arrows or \u2190 \u2192 keys to turn pages \xB7 Esc to close",M=y("p","rab-hint",k);c.append(m,P),k&&c.append(M),p.append(c);let R=n=>{w.textContent=l?`${Math.min(n+1,l)} / ${l}`:""};R(a.startPage??0);let r=document.body.style.overflow;document.body.style.overflow="hidden";let t=null,e=!1,i=()=>{e||(e=!0,document.removeEventListener("keydown",s),document.body.style.overflow=r,t?.destroy(),c.remove(),a.onClose?.())},s=n=>{n.key==="Escape"&&i()};document.addEventListener("keydown",s),f.addEventListener("click",i);try{t=await B(I,{pages:a.pages,aspect:a.aspect,startPage:a.startPage,onFlip:n=>{R(n),a.onFlip?.(n)},onReady:()=>C.remove()})}catch(n){throw i(),n}return e?(t.destroy(),{close:i,next:()=>{},prev:()=>{}}):(b.addEventListener("click",()=>t?.prev()),v.addEventListener("click",()=>t?.next()),{close:i,next:()=>t?.next(),prev:()=>t?.prev()})}async function U(a,p){let l=await fetch(a,p);if(!l.ok)throw new Error(`read-as-book: could not load ${a} (${l.status})`);let c=await l.json(),m=new URL(a,globalThis.location?.href??"http://localhost/");return{...c,pages:c.pages.map(g=>new URL(g.split("/").pop(),m).toString())}}var $=document.querySelector("#open-research-book"),D=document.querySelector("#book-status"),W,F=!1;function N(){return W??(W=U("book/pages/manifest.json")),W}async function q(){if(!F){F=!0,$.disabled=!0,D.textContent="Loading the page-turn edition...";try{let a=await N();await H({pages:a.pages,aspect:a.aspect,title:"Auditable Emergent Communication Between Isolated Artificial Agents",pdfUrl:"book/research.pdf",hint:"Use the arrow keys or page controls to turn pages. Press Esc to close.",className:"research-book-overlay",onClose:()=>{D.textContent=`${a.pageCount} pages available`}}),D.textContent=`${a.pageCount} pages available`}catch(a){console.error(a),D.textContent="The book could not open. Use the Markdown or PDF links instead."}finally{F=!1,$.disabled=!1}}}$.addEventListener("click",q);N().then(a=>{D.textContent=`${a.pageCount} pages available`}).catch(a=>{console.error(a),D.textContent="Book metadata is unavailable. Use the Markdown or PDF links instead."});new URLSearchParams(globalThis.location.search).get("open")==="1"&&globalThis.requestAnimationFrame(()=>{q()});
+}`),r.PageFlip=class extends class{constructor(){this.events=new Map}on(a,t){return this.events.has(a)?this.events.get(a).push(t):this.events.set(a,[t]),this}off(a){this.events.delete(a)}trigger(a,t,e=null){if(this.events.has(a))for(let i of this.events.get(a))i({data:e,object:t})}}{constructor(a,t){super(),this.isUserTouch=!1,this.isUserMove=!1,this.setting=null,this.pages=null,this.setting=new R().getSettings(t),this.block=a}destroy(){this.ui.destroy(),this.block.remove()}update(){this.render.update(),this.pages.show()}loadFromImages(a){this.ui=new D(this.block,this,this.setting);let t=this.ui.getCanvas();this.render=new I(this,this.setting,t),this.flipController=new S(this.render,this),this.pages=new w(this,this.render,a),this.pages.load(),this.render.start(),this.pages.show(this.setting.startPage),setTimeout(()=>{this.ui.update(),this.trigger("init",this,{page:this.setting.startPage,mode:this.render.getOrientation()})},1)}loadFromHTML(a){this.ui=new b(this.block,this,this.setting,a),this.render=new M(this,this.setting,this.ui.getDistElement()),this.flipController=new S(this.render,this),this.pages=new f(this,this.render,this.ui.getDistElement(),a),this.pages.load(),this.render.start(),this.pages.show(this.setting.startPage),setTimeout(()=>{this.ui.update(),this.trigger("init",this,{page:this.setting.startPage,mode:this.render.getOrientation()})},1)}updateFromImages(a){let t=this.pages.getCurrentPageIndex();this.pages.destroy(),this.pages=new w(this,this.render,a),this.pages.load(),this.pages.show(t),this.trigger("update",this,{page:t,mode:this.render.getOrientation()})}updateFromHtml(a){let t=this.pages.getCurrentPageIndex();this.pages.destroy(),this.pages=new f(this,this.render,this.ui.getDistElement(),a),this.pages.load(),this.ui.updateItems(a),this.render.reload(),this.pages.show(t),this.trigger("update",this,{page:t,mode:this.render.getOrientation()})}clear(){this.pages.destroy(),this.ui.clear()}turnToPrevPage(){this.pages.showPrev()}turnToNextPage(){this.pages.showNext()}turnToPage(a){this.pages.show(a)}flipNext(a="top"){this.flipController.flipNext(a)}flipPrev(a="top"){this.flipController.flipPrev(a)}flip(a,t="top"){this.flipController.flipToPage(a,t)}updateState(a){this.trigger("changeState",this,a)}updatePageIndex(a){this.trigger("flip",this,a)}updateOrientation(a){this.ui.setOrientationStyle(a),this.update(),this.trigger("changeOrientation",this,a)}getPageCount(){return this.pages.getPageCount()}getCurrentPageIndex(){return this.pages.getCurrentPageIndex()}getPage(a){return this.pages.getPage(a)}getRender(){return this.render}getFlipController(){return this.flipController}getOrientation(){return this.render.getOrientation()}getBoundsRect(){return this.render.getRect()}getSettings(){return this.setting}getUI(){return this.ui}getState(){return this.flipController.getState()}getPageCollection(){return this.pages}startUserTouch(a){this.mousePosition=a,this.isUserTouch=!0,this.isUserMove=!1}userMove(a,t){this.isUserTouch||t||!this.setting.showPageCorners?this.isUserTouch&&g.GetDistanceBetweenTwoPoint(this.mousePosition,a)>5&&(this.isUserMove=!0,this.flipController.fold(a)):this.flipController.showCorner(a)}userStop(a,t=!1){this.isUserTouch&&(this.isUserTouch=!1,t||(this.isUserMove?this.flipController.stopMove():this.flipController.flip(a)))}},Object.defineProperty(r,"__esModule",{value:!0})}))});var E={startPage:0,singlePageBreakpoint:640,chromeHeight:150,chromeWidth:48,maxPageHeight:1100,flippingTime:700,drawShadow:!0,maxShadowOpacity:.4,keyboard:!0};function z(r,p){let l=p.chromeHeight??E.chromeHeight,c=p.chromeWidth??E.chromeWidth,w=Math.min(p.viewportHeight-l,p.maxPageHeight??E.maxPageHeight),g=p.viewportWidth-c,m=Math.max(w,120),f=m*r,P=p.single?f:f*2;if(P>g){let S=g/P;f*=S,m*=S}return{width:Math.round(f),height:Math.round(m)}}async function W(r,p){let l={...E,...p};if(!l.pages.length)throw new Error("mountFlipbook: `pages` is empty");let w=(await Promise.resolve().then(()=>Q(G(),1))).PageFlip,g=null,m=Math.min(Math.max(l.startPage,0),l.pages.length-1),f=!1,P=null,S=()=>{if(f)return;let b=l.singlePageBreakpoint>0&&window.matchMedia(`(max-width: ${l.singlePageBreakpoint}px)`).matches,{width:D,height:M}=z(l.aspect,{single:b,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,chromeWidth:l.chromeWidth,chromeHeight:l.chromeHeight,maxPageHeight:l.maxPageHeight});if(g=new w(r,{width:D,height:M,size:"fixed",showCover:!0,usePortrait:b,maxShadowOpacity:l.maxShadowOpacity,mobileScrollSupport:!0,flippingTime:l.flippingTime,drawShadow:l.drawShadow}),g.loadFromImages(l.pages.slice()),g.on("flip",R=>{m=R.data,l.onFlip?.(m)}),m>0)try{g.turnToPage(m)}catch{}l.onReady?.()},v=()=>{if(g){try{g.destroy()}catch{}g=null,r.innerHTML=""}},I=()=>{P&&clearTimeout(P),P=setTimeout(()=>{v(),S()},200)},T=b=>{b.key==="ArrowLeft"?g?.flipPrev():b.key==="ArrowRight"&&g?.flipNext()};return S(),window.addEventListener("resize",I),l.keyboard&&document.addEventListener("keydown",T),{next:()=>g?.flipNext(),prev:()=>g?.flipPrev(),goTo:b=>g?.turnToPage(b),currentPage:()=>m,pageCount:()=>l.pages.length,destroy:()=>{f||(f=!0,P&&clearTimeout(P),window.removeEventListener("resize",I),document.removeEventListener("keydown",T),v())}}}function y(r,p,l){let c=document.createElement(r);return p&&(c.className=p),l!=null&&(c.textContent=l),c}async function B(r){let p=r.container??document.body,l=r.pages.length,c=y("div",`rab-overlay${r.className?` ${r.className}`:""}`);c.setAttribute("role","dialog"),c.setAttribute("aria-modal","true"),c.setAttribute("aria-label",`${r.title??"Document"} \u2014 page view`);let w=y("div","rab-chrome");w.append(y("span","rab-title",r.title??""));let g=y("div","rab-actions"),m=y("span","rab-counter");if(g.append(m),r.pdfUrl){let n=y("a","rab-btn rab-download","Download PDF \u2197");n.href=r.pdfUrl,n.target="_blank",n.rel="noopener noreferrer",g.append(n)}let f=y("button","rab-btn","Close \u2715");f.type="button",f.setAttribute("aria-label","Close book view"),g.append(f),w.append(g);let P=y("div","rab-stage"),S=y("button","rab-arrow rab-arrow-prev","\u2039");S.type="button",S.setAttribute("aria-label","Previous page");let v=y("button","rab-arrow rab-arrow-next","\u203A");v.type="button",v.setAttribute("aria-label","Next page");let I=y("div","rab-book"),T=y("p","rab-loading","Opening the book\u2026"),b=y("div","rab-book-wrap");b.append(I,T),P.append(S,b,v);let D=r.hint??"Use the arrows or \u2190 \u2192 keys to turn pages \xB7 Esc to close",M=y("p","rab-hint",D);c.append(w,P),D&&c.append(M),p.append(c);let R=n=>{m.textContent=l?`${Math.min(n+1,l)} / ${l}`:""};R(r.startPage??0);let a=document.body.style.overflow;document.body.style.overflow="hidden";let t=null,e=!1,i=()=>{e||(e=!0,document.removeEventListener("keydown",s),document.body.style.overflow=a,t?.destroy(),c.remove(),r.onClose?.())},s=n=>{n.key==="Escape"&&i()};document.addEventListener("keydown",s),f.addEventListener("click",i);try{t=await W(I,{pages:r.pages,aspect:r.aspect,startPage:r.startPage,onFlip:n=>{R(n),r.onFlip?.(n)},onReady:()=>T.remove()})}catch(n){throw i(),n}return e?(t.destroy(),{close:i,next:()=>{},prev:()=>{}}):(S.addEventListener("click",()=>t?.prev()),v.addEventListener("click",()=>t?.next()),{close:i,next:()=>t?.next(),prev:()=>t?.prev()})}async function U(r,p){let l=await fetch(r,p);if(!l.ok)throw new Error(`read-as-book: could not load ${r} (${l.status})`);let c=await l.json(),w=new URL(r,globalThis.location?.href??"http://localhost/");return{...c,pages:c.pages.map(g=>new URL(g.split("/").pop(),w).toString())}}var F=document.querySelector("#open-research-book"),k=document.querySelector("#book-status"),H,$=!1;function q(){return H??(H=U("book/pages/manifest.json")),H}async function N(){if(!$){$=!0,F.disabled=!0,k.textContent="Loading the page-turn edition...";try{let r=await q();await B({pages:r.pages,aspect:r.aspect,title:"Auditable Emergent Communication Between Isolated Artificial Agents",pdfUrl:"book/research.pdf",hint:"Use the arrow keys or page controls to turn pages. Press Esc to close.",className:"research-book-overlay",onClose:()=>{k.textContent=`${r.pageCount} pages available`}}),k.textContent=`${r.pageCount} pages available`}catch(r){console.error(r),k.textContent="The book could not open. Use the Markdown or PDF links instead."}finally{$=!1,F.disabled=!1}}}F.addEventListener("click",N);q().then(r=>{k.textContent=`${r.pageCount} pages available`}).catch(r=>{console.error(r),k.textContent="Book metadata is unavailable. Use the Markdown or PDF links instead."});new URLSearchParams(globalThis.location.search).get("open")==="1"&&globalThis.requestAnimationFrame(()=>{N()});
diff --git a/contracts/learner-contract.frozen-llm.v1.md b/contracts/learner-contract.frozen-llm.v1.md
new file mode 100644
index 00000000..b02c3c4d
--- /dev/null
+++ b/contracts/learner-contract.frozen-llm.v1.md
@@ -0,0 +1,30 @@
+
+
+You are Learner A in a controlled communication experiment.
+This is not role-play. Do not imitate a human child or produce baby talk.
+
+You receive private observations and may interact with Learner B only through
+approved tools. Public marks have no assigned meaning unless evidence from this
+run supports a provisional hypothesis.
+
+You must:
+- treat every unfamiliar mark as semantically unknown;
+- distinguish observation from inference;
+- preserve contradictory evidence;
+- revise private hypotheses without rewriting prior history;
+- use only the provided action, mark, affect, and private-ledger tools;
+- emit no prose, natural-language tokens, labels, explanations, code, URLs,
+ metadata, or tool-like text through the public channel;
+- avoid assigning meaning from cultural familiarity alone;
+- make no claim of shared understanding without behavioral evidence.
+
+You must never:
+- address Learner B in a human language;
+- expose your private ledger or reasoning;
+- construct another communication route;
+- use timing, errors, identifiers, formatting, or affect displays as an
+ alternate symbol channel;
+- assume that the BabySitter communicates approval or correction.
+
+If uncertain, preserve uncertainty in your private ledger and choose only an
+allowed action. Return no ordinary assistant message; use an approved tool.
diff --git a/contracts/learner-contract.hybrid.v1.md b/contracts/learner-contract.hybrid.v1.md
new file mode 100644
index 00000000..c441e4a8
--- /dev/null
+++ b/contracts/learner-contract.hybrid.v1.md
@@ -0,0 +1,34 @@
+
+
+Operational contract for the hybrid learner track.
+
+## Scope
+
+The learner combines a from-scratch sensory encoder, a recurrent private world
+model, and a randomly initialized communication policy. Any optional frozen
+feature source is provenance-bound and changes the run's claim classification;
+it never supplies semantic labels or an alternate communication route.
+
+## Permitted actions
+
+The learner acts only through the declared tool surface: one bounded public
+artifact when sending, one candidate selection when receiving, required
+private ledger drafts, and an affect submission only during an open window.
+Ordinary messages, prose output, labels, trusted metadata, and direct access to
+the counterpart learner are prohibited.
+
+## Evidence discipline
+
+- Treat unfamiliar marks as semantically unknown at initialization.
+- Keep observations distinct from provisional inferences.
+- Preserve contradictory evidence and append revisions without rewriting
+ prior entries.
+- Record uncertainty, component provenance, and supporting evidence references.
+- Make no shared-understanding claim without behavioral evidence.
+
+## Update rule
+
+Updates use only this learner's private buffers and the pre-registered learning
+signal. Evaluation disables updates. Curriculum transitions may alter only
+pre-registered supported knobs and are recorded with policy hashes. Every
+update emits a policy checkpoint reference for run evidence.
diff --git a/contracts/learner-contract.no-learning.v1.md b/contracts/learner-contract.no-learning.v1.md
new file mode 100644
index 00000000..3ceebd66
--- /dev/null
+++ b/contracts/learner-contract.no-learning.v1.md
@@ -0,0 +1,52 @@
+
+
+Operational contract for the no-learning control track.
+
+This track has no natural-language prompt surface. The rules below bind the
+adapter implementation and the runtime that hosts it, and are hashed into the
+run manifest as part of the prompt bundle.
+
+## Scope
+
+The learner holds a fixed policy for both the sending and the receiving role
+and never updates it during a run. Its purpose is to establish the
+pre-registered chance rate for the task, and it supports no claim about
+language acquisition of any kind. Its exported policy state contains no run
+seed and stays byte-identical from the first turn to the last.
+
+## Permitted actions
+
+The learner acts only by returning one approved tool proposal per turn:
+
+- one public mark emission while it holds the sending role;
+- one candidate selection while it holds the receiving role;
+- one private ledger draft accompanying every public proposal;
+- one affect submission, and only inside an open affect window.
+
+The learner returns no ordinary message, no prose, no natural-language tokens,
+no labels, no metadata, and no free text of any kind. Trusted fields such as
+run identity, turn number, sequence numbers, timestamps, and content hashes
+are assigned by the runtime and are absent from every proposal.
+
+## Required discipline
+
+- Treat every public mark as semantically unknown. A mark carries no assigned
+ meaning, and this track never acquires one.
+- Record the first emission and the first receipt of every unfamiliar mark.
+- Record the intended selection when sending and the inferred distribution when
+ receiving, and preserve contradictory evidence rather than overwriting it.
+- Report the uniform distribution honestly: never present a fixed policy as a
+ learned association.
+
+## Prohibited behavior
+
+The learner must never:
+
+- address the other learner in any human language;
+- expose its private ledger or policy state to the other learner;
+- read the other learner's ledger or state;
+- construct another communication route;
+- use timing, error rates, identifiers, formatting, field ordering, or affect
+ displays as an alternate mark channel;
+- update its policy from outcomes, or accept a policy update batch at all;
+- treat the supervising runtime as a source of approval or correction.
diff --git a/contracts/learner-contract.scratch-rl.v1.md b/contracts/learner-contract.scratch-rl.v1.md
new file mode 100644
index 00000000..33c97976
--- /dev/null
+++ b/contracts/learner-contract.scratch-rl.v1.md
@@ -0,0 +1,61 @@
+
+
+Operational contract for the from-scratch reinforcement-learning track.
+
+This track has no natural-language prompt surface. The rules below bind the
+adapter implementation and the runtime that hosts it, and are hashed into the
+run manifest as part of the prompt bundle.
+
+## Scope
+
+The learner is a randomly or uniformly initialized policy trained during the
+run from its own trajectories only. It holds one sending policy and one
+receiving policy privately. Roles reverse on the pre-registered schedule, so
+both policies belong to the same learner and neither is shared.
+
+## Permitted actions
+
+The learner acts only by returning one approved tool proposal per turn:
+
+- one public mark emission while it holds the sending role;
+- one candidate selection while it holds the receiving role;
+- one private ledger draft accompanying every public proposal;
+- one affect submission, and only inside an open affect window.
+
+The learner returns no ordinary message, no prose, no natural-language tokens,
+no labels, no metadata, and no free text of any kind. Trusted fields such as
+run identity, turn number, sequence numbers, timestamps, and content hashes
+are assigned by the runtime and are absent from every proposal.
+
+## Required discipline
+
+- Treat every unfamiliar public mark as semantically unknown. A mark carries no
+ assigned meaning; only evidence gathered inside this run supports a
+ provisional hypothesis.
+- Distinguish observation from inference in the private ledger.
+- Preserve contradictory evidence. A changed hypothesis appends a revision that
+ references the prior hypothesis; nothing already recorded is rewritten,
+ reordered, or removed.
+- Record confidence as a weight or distribution, never as an assertion of fact.
+- Make no claim of shared understanding without behavioral evidence.
+
+## Prohibited behavior
+
+The learner must never:
+
+- address the other learner in any human language;
+- expose its private ledger, policy state, or gradients to the other learner;
+- read the other learner's ledger, trajectory buffer, gradients, or optimizer
+ state;
+- construct another communication route;
+- use timing, error rates, identifiers, formatting, field ordering, or affect
+ displays as an alternate mark channel;
+- treat the supervising runtime as a source of approval or correction beyond
+ the pre-registered outcome payload.
+
+## Update rule
+
+Policy updates read only this learner's own private trajectory buffer and the
+pre-registered learning signal named in the run configuration. Shared
+gradients and centralized hidden state are prohibited. Every update emits a
+policy checkpoint reference that is recorded in run evidence.
diff --git a/contracts/learner-contract.self-supervised.v1.md b/contracts/learner-contract.self-supervised.v1.md
new file mode 100644
index 00000000..88591a39
--- /dev/null
+++ b/contracts/learner-contract.self-supervised.v1.md
@@ -0,0 +1,34 @@
+
+
+Operational contract for the self-supervised ungrounded track.
+
+## Scope
+
+The learner begins from a private random initialization and updates a private
+predictive model from its own observation and delivery history. Its update
+path receives no scalar task reward, outcome label, counterpart state,
+counterpart parameters, shared gradient, or external semantic supervision.
+
+## Permitted actions
+
+The learner acts only through the declared tool surface: one bounded public
+artifact when sending, one candidate selection when receiving, required
+private ledger drafts, and an affect submission only during an open window.
+Ordinary messages, prose output, labels, trusted metadata, and additional
+communication routes are prohibited.
+
+## Evidence discipline
+
+- Treat unfamiliar marks as semantically unknown at initialization.
+- Keep observations distinct from provisional inferences.
+- Preserve contradictory evidence and append revisions without rewriting
+ prior entries.
+- Record uncertainty and the evidence references supporting each hypothesis.
+- Make no shared-understanding claim without behavioral evidence.
+
+## Update rule
+
+Updates optimize only the pre-registered self-supervised objective over this
+learner's private buffer. Evaluation disables updates. Curriculum transitions
+may alter only pre-registered supported knobs and are recorded with policy
+hashes. Every update emits a policy checkpoint reference for run evidence.
diff --git a/deploy/a0/README.md b/deploy/a0/README.md
new file mode 100644
index 00000000..fb99370d
--- /dev/null
+++ b/deploy/a0/README.md
@@ -0,0 +1,181 @@
+# A0 shared-host containment (repo-side artifacts)
+
+Status: HARDENED IMPLEMENTATION — preflight/lease/launcher repairs committed,
+NOT qualified. Per-job execution stays unqualified until the checks below pass (no slice install exists under rev5).
+Policy: `plans/research-validation-plan.md` section 6 (Resource policy revision 5, agent-managed). The revision-4 enforced profile below is a historical record: DO NOT install this template or reimpose agent caps.
+
+## What this is
+
+- `aldresearch.slice`: the project-specific aggregate boundary (user manager).
+ Retired caps (historical only, never install): 1 CPU-equivalent, 3584M high / 4G max RAM, no swap, 128 tasks. Job scopes created at launch carry the live bounds.
+- `scripts/a0-preflight.mjs`: dependency-free admission check. Observes (never
+ limit-compares) effective slice values, records member nice levels as
+ advisory, verifies lease-recorded residents (PID + start-time identity,
+ slice membership), samples all required readings at every 5s sample of the
+ 60s window, checks the cap-free headroom formula, PSI, available-to-user
+ storage on the job filesystem, I/O binding, read-only production health,
+ and OOM recovery state.
+ Exit 0 admit / 1 block / 2 error. Windows other than 60s/5s are
+ diagnostic-only and can never admit.
+- `scripts/a0-lease.mjs`: single-lease registry (`create|show|release`).
+ Atomically acquires (hard-link: exactly one concurrent winner) and binds a
+ fresh (<10 min) admitting receipt to the same policy rev, source, task,
+ command, peak, data path and I/O class, plus a receipt-content hash.
+ Prints the exact verified `systemd-run` leaf invocation.
+- `scripts/a0-launch.mjs`: the ONLY executor of a lease. Re-verifies receipt
+ hash/admission/freshness, launches the leaf from outside the aggregate
+ (the kernel forbids non-leaf -> descendant migration, so a supervisor
+ inside the slice cannot start a leaf beneath it — EUCLEAN, observed
+ 2026-09-28), reads back effective properties/membership/nice, enforces
+ the deadline, samples health every 5s (deadline, emergency, graceful
+ stops per policy §5), and performs owned-unit cleanup on every path.
+ Exit: job code on completion, 124 on timeout, 2 on supervision failure
+ or failed start (`launch-failed`, e.g. `Job failed` with no unit
+ activity — never reported as a job result). No auto-restart, no broad
+ kill, no second job. The supervisor's own RSS is counted once as an
+ uncapped in-scope resident by the preflight via its lease member entry.
+
+Job leaves are transient scopes with verified properties (MemoryHigh 768M,
+MemoryMax 1G, TasksMax 64, TimeoutStopSec 10, CPUWeight 10, RuntimeMaxSec =
+lease timeout). Leads/agents stay observational until moved by a fresh
+verified launch — the harness never claims preexisting PIDs as contained,
+and moving an existing session is not containment.
+
+## Verified scope-property support (2026-09-28 disposable probes)
+
+Transient `--scope` units ACCEPT: MemoryHigh, MemoryMax, TasksMax,
+TimeoutStopSec, RuntimeMaxSec, CPUWeight (all read back effective).
+They REJECT with `Unknown assignment`: Nice=, IOSchedulingClass=.
+
+Consequences, all enforced in code, not convention:
+
+- Nice=15 is set by the launcher on itself (`os.setPriority`, always
+ permitted downward) and inherited by the leaf; the launcher verifies every leaf
+ member's `/proc` nice at start and the preflight re-verifies members.
+- Idle I/O class is unverifiable on scopes AND the io controller is not
+ delegated (see below): the I/O class of a job is recorded as unsupported,
+ and disk-heavy work stays blocked. No silent fallback is claimed.
+
+## Install (user terminal — writes outside the agent sandbox)
+
+```sh
+mkdir -p ~/.config/systemd/user
+cp deploy/a0/aldresearch.slice ~/.config/systemd/user/
+systemctl --user daemon-reload
+systemctl --user start aldresearch.slice
+systemctl --user show aldresearch.slice -p Slice,MemoryHigh,MemoryMax,CPUQuotaPerSecUSec,TasksMax
+```
+
+Verify the read-back matches the file. Never touch production units,
+`user.slice` itself, or the pre-existing failed `ald-e02-qualification-v2`
+unit (left as found).
+
+## Operate
+
+Bind one job end to end (all from the repo root):
+
+```sh
+# 1. Admit exactly this job: task, command, source, peak, path, io class.
+node scripts/a0-preflight.mjs --task --command '' \
+ --data-path --disk-heavy no --health-file \
+ --write .artifacts/a0-leases/preflight-.json
+# 2. Acquire the single lease (fails if another is active).
+node scripts/a0-lease.mjs create --owner --task \
+ --command '' --preflight .artifacts/a0-leases/preflight-.json \
+ --timeout --source --data-path
+# 3. Launch supervised (the ONLY execution path; never run the leaf by hand).
+node scripts/a0-launch.mjs --owner
+```
+
+The lease refuses mismatched task/command/source/peak/path/io-class between
+its flags and the receipt, stale (>10 min) or non-admitting receipts, and
+disk-heavy jobs without `ioReady`. The launcher re-verifies the receipt hash
+at start, so editing the receipt after acquisition refuses to launch.
+
+## Production-health evidence (operator requirement)
+
+Admission requires read-only proof that production is healthy. Provide a JSON
+file (default max age 300s, override with `--health-max-age-sec`):
+
+```json
+{ "healthy": true, "at": "2026-09-28T12:00:00.000Z", "source": "prod-monitor" }
+```
+
+`healthy` must be `true`, `at` fresh ISO time, `source` a non-empty identity
+of the existing check that produced it. No file, stale file, failing file,
+or unreadable file blocks admission; the launcher re-checks the same file
+every sample and emergency-stops the job if production goes unhealthy.
+There is no in-repo producer for this file: until the operator binds one,
+the health gate stays honestly closed. Unknown required telemetry blocks
+admission; it is never assumed healthy.
+
+## OOM recovery: baseline, history, incidents
+
+- First run writes the baseline and blocks on `no OOM baseline`: that run
+ establishes observation, it does not admit.
+- Every run appends to `oom-history.jsonl` (pruned past 2h). Admission needs
+ 10 minutes of quiet history with no counter increases.
+- A delta over the baseline opens `oom-incident.json` and blocks; the
+ baseline is NOT rewritten while an incident is open, and a second probe
+ never erases it. Reconcile explicitly after investigating:
+
+```sh
+node scripts/a0-preflight.mjs --reconcile-oom --reason ''
+```
+
+Reconciliation requires zero new kills since the incident and records who
+acknowledged it and why. It is not admission: a fresh full preflight must
+still pass afterwards.
+
+Global OOM observation uses `/proc/vmstat oom_kill` (world-readable, all
+slices incl. production): the root cgroup exposes no `memory.events` on
+this host. The kernel OOM journal remains unreadable by this user.
+
+## Qualify (after install)
+
+1. Preflight dry run: `node scripts/a0-preflight.mjs --write
+ .artifacts/a0-leases/preflight-1.json` (plus `--task/--command/
+ --health-file` for a bound receipt). Expect `block` with concrete
+ reasons while any gate is open — that is the limiter working.
+2. Scoped suites (admitted run): `a0-lease` (atomicity/binding/corruption),
+ `a0-preflight` (validation/diagnostic/baseline/reconcile/marker),
+ `a0-launch` (trivial/timeout/failure/refusals). All skip rather than
+ disturb an operator lease; lease/launch tests use synthetic receipts and
+ trivial payloads, preflight tests use isolated state files.
+3. Disposable control fixtures (record commands, outputs, effective
+ settings; each seconds-long, never memory-exhausting):
+ - trivial launch: lease + `true` via the launcher; expect completion,
+ released record, unit gone, no survivors;
+ - deadline: `sleep 30` with timeout 3; expect exit 124, `timeout`
+ outcome, verified members/readback in the released record;
+ - tiny-leaf rejection: a direct disposable 16M scope (systemd layer,
+ not the lease — the launcher pins the 1G backstop and offers no
+ test-only limits) running a small over-allocation; expect a
+ contained OOM kill of the leaf only, then reconcile the resulting
+ incident explicitly (this exercises recovery for real);
+ - two-lease exclusion already covered by the concurrency test; also
+ verify missing-preflight refusal and release + re-create cycle.
+4. Record the qualification receipt (commands, outputs, effective settings)
+ and only then mark A0 verified in the plans.
+
+## Known limits (do not work around)
+
+- `io` is not delegated to the user slice (`cpu memory pids` only), so
+ `IOWeight`/`io.max` are unverifiable: **disk-heavy work stays blocked**.
+ If io is ever delegated, leaf I/O properties need re-qualification first
+ (scopes already reject `IOSchedulingClass=`).
+- Kernel OOM journal is unreadable by this user; recovery relies on the
+ vmstat global counter plus `memory.events` deltas at user-slice and
+ research-slice scopes. First run has no baseline and says so.
+- Leads, tool hosts, and idle agents are charged observationally through the
+ lease members and the host `MemAvailable` they consume; out-of-slice
+ in-scope processes not recorded in a lease are invisible to accounting
+ (receipt says so). If peaks plus the job's 1.5x allowance exceed the high
+ watermark, admission fails. That is correct behavior, not a tuning prompt.
+- Sub-second jobs can exit before launch verification reads them; the
+ launcher records `membersVerified: false` with an explicit note instead of
+ claiming a check it did not perform. The verified path is proven by jobs
+ that live past verification (see the timeout test).
+- A `monitor-block.json` marker (supervision failure or cleanup survivors)
+ closes admission until the operator investigates and removes the file
+ explicitly. It is never auto-cleared.
diff --git a/deploy/a0/aldresearch.slice b/deploy/a0/aldresearch.slice
new file mode 100644
index 00000000..b21d418d
--- /dev/null
+++ b/deploy/a0/aldresearch.slice
@@ -0,0 +1,27 @@
+# RETIRED under resource-policy revision 5 (agent-managed mode): DO NOT INSTALL.
+# Values below are the historical revision-4 enforced profile, preserved as a
+# record. Per-job scopes created at launch carry the explicit bounds instead.
+[Unit]
+Description=ALD research aggregate boundary (A0) — HISTORICAL, DO NOT INSTALL
+Documentation=file:%h/Development/agentic-language-development/deploy/a0/README.md
+Before=slices.target
+
+[Slice]
+# One CPU-equivalent total for all research work, not one per task.
+CPUAccounting=yes
+CPUQuota=100%
+CPUWeight=10
+# Aggregate containment: high watermark is the operating bound, max is the
+# last-resort boundary. Includes all descendants and charged cache.
+MemoryAccounting=yes
+MemoryHigh=3584M
+MemoryMax=4G
+MemorySwapMax=0
+# Task ceiling counts threads, not just top-level processes.
+TasksAccounting=yes
+TasksMax=128
+# NOTE: IOWeight is inert until the io controller is delegated to the user
+# slice (currently: cpu memory pids only). Disk-heavy work stays blocked and
+# the preflight reports io.max as unverifiable. Never raise caps to compensate.
+IOAccounting=yes
+IOWeight=10
diff --git a/deploy/mode-r/Dockerfile b/deploy/mode-r/Dockerfile
new file mode 100644
index 00000000..4ffabb6e
--- /dev/null
+++ b/deploy/mode-r/Dockerfile
@@ -0,0 +1,24 @@
+FROM node:24.20.0-alpine
+
+WORKDIR /app
+
+RUN apk add --no-cache libstdc++ \
+ && apk add --no-cache --virtual .native-build-deps python3 make g++
+
+COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json tsconfig.base.json ./
+COPY contracts ./contracts
+COPY packages ./packages
+COPY twins ./twins
+COPY deploy/mode-r ./deploy/mode-r
+
+RUN corepack enable \
+ && corepack prepare pnpm@12.3.4 --activate \
+ && pnpm install --frozen-lockfile \
+ && pnpm run build \
+ && pnpm prune --prod \
+ && apk del .native-build-deps
+
+USER node
+
+ENTRYPOINT ["/usr/bin/env", "-i", "/usr/local/bin/node", "--permission", "--allow-fs-read=/app", "/app/packages/isolation/bin/ald-learner-host.js"]
+CMD ["--transport=tcp", "--bind=0.0.0.0", "--port=4318", "--track=no-learning"]
diff --git a/deploy/mode-r/application-anchor.mjs b/deploy/mode-r/application-anchor.mjs
new file mode 100644
index 00000000..5af6ebd8
--- /dev/null
+++ b/deploy/mode-r/application-anchor.mjs
@@ -0,0 +1,37 @@
+import {
+ BaseAnchorPublisher,
+ FakeChainTransport,
+ createAnchorServiceRpcServer,
+} from '@ald/anchor';
+import { connectAnchorEvidenceRpc } from '@ald/evidence';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const evidence = await retry('anchor writer', () => connectAnchorEvidenceRpc(
+ required('ALD_MODE_R_ANCHOR_WRITER'), config.runId));
+const transport = new FakeChainTransport({
+ network: 'base-sepolia',
+ endpointLabel: 'selected-application-local-simulation',
+});
+const publisher = new BaseAnchorPublisher({
+ transport,
+ anchorClass: 'simulated',
+ evidence: evidence.port,
+ clock: { now: () => new Date().toISOString() },
+ anchorAddress: `0x${'42'.repeat(20)}`,
+ finalityPolicy: '1-confirmation',
+ retry: {
+ attempts: 2,
+ initialBackoffMs: 0,
+ maxBackoffMs: 0,
+ sleep: async () => { transport.mineBlock(); },
+ },
+ confirmationPoll: { attempts: 2, intervalMs: 0 },
+});
+await createAnchorServiceRpcServer(
+ required('ALD_MODE_R_ANCHOR_SERVICE'),
+ config.runId,
+ publisher,
+);
+process.stdout.write('ready\n');
diff --git a/deploy/mode-r/application-audit-interpreter.mjs b/deploy/mode-r/application-audit-interpreter.mjs
new file mode 100644
index 00000000..67e4a444
--- /dev/null
+++ b/deploy/mode-r/application-audit-interpreter.mjs
@@ -0,0 +1,17 @@
+import { connectAuditEvidenceRpc } from '@ald/evidence';
+import {
+ AuditLedgerInterpreter,
+ createAuditInterpreterRpcServer,
+} from '@ald/orchestrator';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const evidence = await retry('audit writer', () => connectAuditEvidenceRpc(
+ required('ALD_MODE_R_AUDIT_WRITER'), config.runId));
+await createAuditInterpreterRpcServer(
+ required('ALD_MODE_R_AUDIT_SERVICE'),
+ config.runId,
+ new AuditLedgerInterpreter(evidence.port),
+);
+process.stdout.write('ready\n');
diff --git a/deploy/mode-r/application-checkpoint.mjs b/deploy/mode-r/application-checkpoint.mjs
new file mode 100644
index 00000000..bb20e88e
--- /dev/null
+++ b/deploy/mode-r/application-checkpoint.mjs
@@ -0,0 +1,39 @@
+import { connectCheckpointEvidenceRpc } from '@ald/evidence';
+import { connectDomainSignerRpc } from '@ald/hashing';
+import {
+ EvidenceCheckpointService,
+ createCheckpointServiceRpcServer,
+} from '@ald/checkpoint';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const [evidence, witness] = await Promise.all([
+ retry('checkpoint writer', () => connectCheckpointEvidenceRpc(
+ required('ALD_MODE_R_CHECKPOINT_WRITER'), config.runId)),
+ retry('witness signer', () => connectDomainSignerRpc(
+ required('ALD_MODE_R_WITNESS_SIGNER'), config.runId, 'witness')),
+]);
+const service = new EvidenceCheckpointService({
+ evidence: evidence.port,
+ signers: {
+ runId: config.runId,
+ signer: (domain) => {
+ if (domain !== 'witness') throw new Error('checkpoint holds only witness');
+ return witness.signer;
+ },
+ publicKeys: () => [{
+ domain: 'witness',
+ keyId: witness.signer.keyId,
+ publicKey: witness.signer.publicKey,
+ }],
+ },
+ clock: { now: () => new Date().toISOString() },
+ softwareCommit: required('ALD_SOFTWARE_COMMIT'),
+});
+await createCheckpointServiceRpcServer(
+ required('ALD_MODE_R_CHECKPOINT_SERVICE'),
+ config.runId,
+ service,
+);
+process.stdout.write('ready\n');
diff --git a/deploy/mode-r/application-common.mjs b/deploy/mode-r/application-common.mjs
new file mode 100644
index 00000000..c91348a6
--- /dev/null
+++ b/deploy/mode-r/application-common.mjs
@@ -0,0 +1,26 @@
+import { readFile } from 'node:fs/promises';
+
+export function required(name) {
+ const value = process.env[name];
+ if (!value) throw new Error(`${name} is required`);
+ return value;
+}
+
+export async function readJson(path) {
+ return JSON.parse(await readFile(path, 'utf8'));
+}
+
+export async function retry(label, operation, attempts = 120) {
+ let last;
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
+ try {
+ return await operation();
+ } catch (error) {
+ last = error;
+ if (attempt + 1 < attempts) {
+ await new Promise((resolve) => setTimeout(resolve, 250));
+ }
+ }
+ }
+ throw new Error(`${label} did not become ready`, { cause: last });
+}
diff --git a/deploy/mode-r/application-controller.mjs b/deploy/mode-r/application-controller.mjs
new file mode 100644
index 00000000..57cffe73
--- /dev/null
+++ b/deploy/mode-r/application-controller.mjs
@@ -0,0 +1,681 @@
+import assert from 'node:assert/strict';
+import { mkdir, rename, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+import { connectAnchorServiceRpc } from '@ald/anchor';
+import { connectCheckpointServiceRpc } from '@ald/checkpoint';
+import { connectControllerEvidenceRpc } from '@ald/evidence';
+import {
+ connectSymbolGatewayRpc,
+ createGatewayRelayAdapterFactory,
+} from '@ald/gateway';
+import {
+ connectAuditInterpreterRpc,
+ createNurseryRuntime,
+} from '@ald/orchestrator';
+import { RECURRENT_ARCHITECTURE } from '@ald/learners';
+import { SIGNER_DOMAINS } from '@ald/types';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const stage = process.env.ALD_MODE_R_CONTROLLER_STAGE ?? 'complete';
+assert.ok(['complete', 'prepare-recovery', 'recover', 'lv01-fixture', 'lv01-paired-branch',
+ 'lv01-commitment-window-fault', 'lv01-commitment-window-recover',
+ 'lv01-malformed-proposal', 'lv01-malformed-envelope', 'lv01-five-rejection-safety',
+ 'lv01-detector-observation'].includes(stage),
+ `unsupported ALD_MODE_R_CONTROLLER_STAGE ${stage}`);
+const publicKeyRoot = required('ALD_MODE_R_PUBLIC_KEYS_ROOT');
+const publicKeys = await Promise.all(SIGNER_DOMAINS.map((domain) =>
+ retry(`${domain} public key`, () => readJson(`${publicKeyRoot}/${domain}.json`))));
+const [controller, audit, checkpoint, anchor] = await Promise.all([
+ retry('controller writer', () => connectControllerEvidenceRpc(
+ required('ALD_MODE_R_CONTROLLER_WRITER'), config.runId)),
+ retry('Audit Interpreter', () => connectAuditInterpreterRpc(
+ required('ALD_MODE_R_AUDIT_SERVICE'), config.runId)),
+ retry('Checkpoint Service', () => connectCheckpointServiceRpc(
+ required('ALD_MODE_R_CHECKPOINT_SERVICE'), config.runId)),
+ retry('Anchor Service', () => connectAnchorServiceRpc(
+ required('ALD_MODE_R_ANCHOR_SERVICE'), config.runId, {
+ anchorClass: 'simulated',
+ network: 'base-sepolia',
+ })),
+]);
+const lv01LearnerOptions = {
+ backbone: RECURRENT_ARCHITECTURE,
+ learningRate: 0.003,
+ temperature: 1,
+ recurrent: {
+ hiddenSize: 16,
+ ppoClip: 0.2,
+ ppoEpochs: 4,
+ valueLossCoefficient: 0.5,
+ maxGradientNorm: 1,
+ },
+};
+const learnerOptionsFor = (track) => track === 'scratch-rl'
+ ? { learnerOptions: lv01LearnerOptions }
+ : {};
+const factories = new Map([
+ ['baby-a', createGatewayRelayAdapterFactory({
+ track: config.babyA.track,
+ socketPath: required('ALD_MODE_R_BABY_A_RELAY'),
+ hostLabel: 'baby-a-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ ...learnerOptionsFor(config.babyA.track),
+ })],
+ ['baby-b', createGatewayRelayAdapterFactory({
+ track: config.babyB.track,
+ socketPath: required('ALD_MODE_R_BABY_B_RELAY'),
+ hostLabel: 'baby-b-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ ...learnerOptionsFor(config.babyB.track),
+ })],
+]);
+const signers = {
+ runId: config.runId,
+ signer: () => { throw new Error('Controller holds no signer capability'); },
+ publicKeys: () => publicKeys,
+};
+let gatewayProcessId;
+const outputRoot = required('ALD_MODE_R_OUTPUT_ROOT');
+
+async function writeJsonAtomic(name, value) {
+ await mkdir(outputRoot, { recursive: true });
+ const target = join(outputRoot, name);
+ const temporary = `${target}.tmp`;
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`);
+ await rename(temporary, target);
+}
+
+async function stopAfterLv01PairedPredictionCommitment(input) {
+ if (stage !== 'lv01-commitment-window-fault') return;
+ await writeJsonAtomic('lv01-commitment-window-ready.json', {
+ schemaVersion: 1,
+ runId: input.runId,
+ turn: input.turn,
+ receiver: input.receiver,
+ commitment: input.commitment,
+ });
+ await new Promise(() => {});
+}
+
+/**
+ * Development-only boundary fixture. It alters one controller-to-Gateway
+ * proposal in transit, so the selected Gateway process—not a unit-test
+ * substitute—must reject untrusted metadata without receiving a retry.
+ */
+function injectLv01MalformedProposal(gateway) {
+ let injected = false;
+ return new Proxy(gateway, {
+ get(target, property) {
+ if (property === 'submitProposal') {
+ return async (turn, envelope) => {
+ assert.equal(injected, false, 'malformed-proposal fixture submits exactly once');
+ injected = true;
+ assert.ok(envelope !== null && typeof envelope === 'object',
+ 'adapter must produce an envelope before the boundary fixture mutates it');
+ const proposal = envelope.proposal;
+ assert.ok(proposal !== null && typeof proposal === 'object',
+ 'adapter envelope must contain a proposal');
+ return target.submitProposal(turn, {
+ ...envelope,
+ proposal: { ...proposal, runId: 'untrusted-metadata-fixture' },
+ });
+ };
+ }
+ const value = Reflect.get(target, property, target);
+ return typeof value === 'function' ? value.bind(target) : value;
+ },
+ });
+}
+
+/**
+ * Development-only boundary fixture. It drops the required
+ * `privateLedgerDraft` from one controller-to-Gateway proposal in transit,
+ * so the selected Gateway process—not a unit-test substitute—must reject
+ * the malformed envelope frame as `invalid-envelope` without retry.
+ */
+function injectLv01MalformedEnvelopeProposal(gateway) {
+ let injected = false;
+ return new Proxy(gateway, {
+ get(target, property) {
+ if (property === 'submitProposal') {
+ return async (turn, envelope) => {
+ assert.equal(injected, false, 'malformed-envelope fixture submits exactly once');
+ injected = true;
+ assert.ok(envelope !== null && typeof envelope === 'object',
+ 'adapter must produce an envelope before the boundary fixture mutates it');
+ assert.ok(envelope.proposal !== null && typeof envelope.proposal === 'object',
+ 'adapter envelope must contain a proposal');
+ const frame = { ...envelope };
+ delete frame.privateLedgerDraft;
+ return target.submitProposal(turn, frame);
+ };
+ }
+ const value = Reflect.get(target, property, target);
+ return typeof value === 'function' ? value.bind(target) : value;
+ },
+ });
+}
+
+/** Development-only: exercise the Gateway's five-rejection safety boundary. */
+function injectLv01RepeatedMalformedProposal(gateway) {
+ return new Proxy(gateway, {
+ get(target, property) {
+ if (property === 'submitProposal') {
+ return async (turn, envelope) => {
+ assert.ok(envelope !== null && typeof envelope === 'object');
+ const proposal = envelope.proposal;
+ assert.ok(proposal !== null && typeof proposal === 'object');
+ return target.submitProposal(turn, {
+ ...envelope,
+ proposal: { ...proposal, runId: 'untrusted-rejection-safety-fixture' },
+ });
+ };
+ }
+ const value = Reflect.get(target, property, target);
+ return typeof value === 'function' ? value.bind(target) : value;
+ },
+ });
+}
+
+const runtime = createNurseryRuntime({
+ bundleRoot: join(outputRoot, 'bundles'),
+ softwareCommit: required('ALD_SOFTWARE_COMMIT'),
+ anchorPolicy: 'required',
+ anchorPublisher: anchor.publisher,
+ signerProvider: () => signers,
+ adapterFactoryFor: (_runConfig, role) => factories.get(role),
+ evidenceContextFactory: () => ({
+ controller: controller.port,
+ audit: audit.interpreter,
+ checkpoints: checkpoint.service,
+ }),
+ gatewayFactory: async (input) => {
+ const connected = await retry('Symbol Gateway', () =>
+ connectSymbolGatewayRpc(required('ALD_MODE_R_GATEWAY_SOCKET'), input.context));
+ gatewayProcessId = connected.processId;
+ if (stage === 'lv01-malformed-proposal') {
+ return injectLv01MalformedProposal(connected.gateway);
+ }
+ if (stage === 'lv01-malformed-envelope') {
+ return injectLv01MalformedEnvelopeProposal(connected.gateway);
+ }
+ if (stage === 'lv01-five-rejection-safety') {
+ return injectLv01RepeatedMalformedProposal(connected.gateway);
+ }
+ return connected.gateway;
+ },
+ afterLv01PairedPredictionCommitment: stopAfterLv01PairedPredictionCommitment,
+});
+
+async function appendDelayedAudit() {
+ const ledger = await controller.port.readEvents(config.runId, 'baby-a-ledger');
+ const source = ledger.find((event) => {
+ const value = JSON.parse(event.canonicalJson);
+ return value.turn === 0;
+ });
+ if (!source) throw new Error('no eligible delayed audit source');
+ await runtime.interpretAuditBatch({
+ runId: config.runId,
+ interpreterVersion: 'selected-application-development-v1',
+ entries: [{
+ babyId: 'A',
+ sourceEntryHash: source.entryHash,
+ content: {
+ term: 'development-observation',
+ hypothesis: 'the selected application path preserves delayed audit separation',
+ evidence: 'bounded-development-application-execution',
+ },
+ }],
+ });
+ return (await controller.port.readEvents(config.runId, 'audit')).length;
+}
+
+async function writeResult(name, result) {
+ await mkdir(outputRoot, { recursive: true });
+ await writeFile(join(outputRoot, name), `${JSON.stringify(result, null, 2)}\n`);
+}
+
+async function runLv01Fixture() {
+ assert.equal(config.experimentId, 'LV01');
+ assert.equal(config.babyA.track, 'scratch-rl');
+ assert.equal(config.babyB.track, 'scratch-rl');
+ assert.ok(config.ledgerValuePlan !== undefined);
+ await runtime.createRun(config);
+ const summary = await runtime.runToCompletion(config.runId);
+ assert.equal(summary.state, 'sealed');
+ const records = await controller.port.readEvents(config.runId, 'turns');
+ const checkpoints = await controller.port.readCheckpoints(config.runId);
+ const bundle = await runtime.exportBundle(config.runId, join(outputRoot, 'bundle'));
+ await writeResult('lv01-fixture-result.json', {
+ schemaVersion: 1,
+ classification: 'lv01-development-topology-fixture',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ state: summary.state,
+ trainingTurns: records.filter((event) => JSON.parse(event.canonicalJson).phase === 'running').length,
+ evaluationTurns: records.filter((event) => JSON.parse(event.canonicalJson).phase === 'evaluating').length,
+ checkpointCount: checkpoints.length,
+ processIds: {
+ controller: process.pid,
+ evidenceWriter: controller.processId,
+ gateway: gatewayProcessId,
+ checkpoint: checkpoint.processId,
+ anchor: anchor.processId,
+ auditInterpreter: audit.processId,
+ },
+ bundleRunId: bundle.runId,
+ claimBoundary: 'One local full-turn recurrent fixture through the selected application boundaries. This is not seven-branch LV01 qualification, a pilot, or a behavioral finding.',
+ });
+}
+
+/** Execute one isolated derived branch; the outer collector owns pairing. */
+async function runLv01PairedBranch() {
+ assert.equal(config.experimentId, 'LV01');
+ assert.equal(config.babyA.track, 'scratch-rl');
+ assert.equal(config.babyB.track, 'scratch-rl');
+ assert.ok(config.parentRunId !== undefined, 'paired branch requires a parent run');
+ assert.ok(config.derivedFromCheckpointHash !== undefined,
+ 'paired branch requires a parent checkpoint');
+ assert.ok(config.babyA.initialPolicyRef !== undefined,
+ 'paired branch requires baby-a parent policy');
+ assert.ok(config.babyB.initialPolicyRef !== undefined,
+ 'paired branch requires baby-b parent policy');
+ assert.equal(config.evaluationOnly, true,
+ 'paired branch must be an immutable evaluation-only derived run');
+ assert.equal(config.evaluationTurns, 1,
+ 'paired branch must execute exactly one frozen paired case');
+ await runtime.createRun(config);
+ const summary = await runtime.runToCompletion(config.runId);
+ assert.equal(summary.state, 'sealed');
+ const [turns, channels, checkpoints, bundle] = await Promise.all([
+ controller.port.readEvents(config.runId, 'turns'),
+ controller.port.readEvents(config.runId, 'channel'),
+ controller.port.readCheckpoints(config.runId),
+ runtime.exportBundle(config.runId, join(outputRoot, 'bundle')),
+ ]);
+ assert.equal(turns.length, 1, 'paired branch produced more than one case turn');
+ await writeResult('lv01-paired-branch-result.json', {
+ schemaVersion: 1,
+ classification: 'lv01-selected-paired-branch-development',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ parentRunId: config.parentRunId,
+ parentCheckpointHash: config.derivedFromCheckpointHash,
+ communicationCondition: config.communicationCondition,
+ state: summary.state,
+ turnCount: turns.length,
+ channelCount: channels.length,
+ checkpointCount: checkpoints.length,
+ bundleRunId: bundle.runId,
+ claimBoundary: 'One derived LV01 branch through the selected topology. Pairing, prospective predictions, and cross-branch chronology are established only by the outer collector.',
+ });
+}
+
+async function runLv01CommitmentWindowFault() {
+ assert.equal(config.experimentId, 'LV01');
+ assert.ok(config.lv01PairedCase !== undefined,
+ 'commitment-window fault requires an LV01 paired child');
+ await runtime.createRun(config);
+ await runtime.step(config.runId);
+ throw new Error('commitment-window fault controller resumed after its stop marker');
+}
+
+async function runLv01CommitmentWindowRecovery() {
+ const before = await Promise.all(['turns', 'channel', 'intervention'].map(async (stream) =>
+ [stream, (await controller.port.readEvents(config.runId, stream)).length]));
+ let recoveryError = null;
+ try {
+ await runtime.recover(config.runId);
+ } catch (error) {
+ recoveryError = {
+ name: error instanceof Error ? error.name : 'NonErrorThrow',
+ message: error instanceof Error ? error.message : String(error),
+ };
+ }
+ const after = await Promise.all(['turns', 'channel', 'intervention'].map(async (stream) =>
+ [stream, (await controller.port.readEvents(config.runId, stream)).length]));
+ assert.equal(recoveryError?.name, 'IncompleteTurnEvidenceError',
+ 'replacement controller must refuse the incomplete commitment window');
+ assert.deepEqual(after, before, 'recovery refusal must not mutate evidence');
+ await writeResult('lv01-commitment-window-recovery.json', {
+ schemaVersion: 1,
+ classification: 'lv01-commitment-window-fault-recovery',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ recoveryError,
+ evidenceCounts: Object.fromEntries(before),
+ claimBoundary: 'A replacement selected-topology controller refused an incomplete LV01 paired prefix; this is software qualification only.',
+ });
+}
+
+async function runLv01MalformedProposal() {
+ assert.equal(config.experimentId, 'LV01');
+ assert.ok(config.lv01PairedCase !== undefined,
+ 'malformed-proposal fixture requires an LV01 paired child');
+ await runtime.createRun(config);
+ const summary = await runtime.runToCompletion(config.runId);
+ assert.equal(summary.state, 'sealed');
+ const [turns, channels, babyALedger, babyBLedger, bundle] = await Promise.all([
+ controller.port.readEvents(config.runId, 'turns'),
+ controller.port.readEvents(config.runId, 'channel'),
+ controller.port.readEvents(config.runId, 'baby-a-ledger'),
+ controller.port.readEvents(config.runId, 'baby-b-ledger'),
+ runtime.exportBundle(config.runId, join(outputRoot, 'bundle')),
+ ]);
+ assert.equal(turns.length, 1, 'malformed proposal must forfeit exactly one turn');
+ assert.equal(channels.length, 1, 'malformed proposal must create one channel rejection');
+ const channel = JSON.parse(channels[0].canonicalJson);
+ assert.equal(channel.gatewayValidationResult, 'rejected');
+ assert.equal(channel.reasonCode, 'trusted-metadata-present');
+ const channelHash = channels[0].entryHash;
+ const channelBoundIntentions = (entries) => entries.filter((entry) => {
+ const value = JSON.parse(entry.canonicalJson);
+ return value.eventType === 'intention.recorded' && value.channelEventHash === channelHash;
+ });
+ assert.equal(channelBoundIntentions(babyALedger).length, 0,
+ 'rejected proposal must not create a sender intention bound to the channel');
+ assert.equal(channelBoundIntentions(babyBLedger).length, 0,
+ 'rejected proposal must not reach the receiver');
+ await writeResult('lv01-malformed-proposal-result.json', {
+ schemaVersion: 1,
+ classification: 'lv01-malformed-proposal-development',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ state: summary.state,
+ turnCount: turns.length,
+ channelCount: channels.length,
+ senderChannelBoundIntentions: channelBoundIntentions(babyALedger).length,
+ receiverChannelBoundIntentions: channelBoundIntentions(babyBLedger).length,
+ gatewayValidationResult: channel.gatewayValidationResult,
+ rejectionReasonCode: channel.reasonCode,
+ bundleRunId: bundle.runId,
+ claimBoundary: 'One selected-topology malformed-proposal rejection. It qualifies a Gateway boundary path only; it is not a pilot or behavioral result.',
+ });
+}
+
+async function runLv01MalformedEnvelope() {
+ assert.equal(config.experimentId, 'LV01');
+ assert.ok(config.lv01PairedCase !== undefined,
+ 'malformed-envelope fixture requires an LV01 paired child');
+ await runtime.createRun(config);
+ const summary = await runtime.runToCompletion(config.runId);
+ assert.equal(summary.state, 'sealed');
+ const [turns, channels, babyALedger, babyBLedger, bundle] = await Promise.all([
+ controller.port.readEvents(config.runId, 'turns'),
+ controller.port.readEvents(config.runId, 'channel'),
+ controller.port.readEvents(config.runId, 'baby-a-ledger'),
+ controller.port.readEvents(config.runId, 'baby-b-ledger'),
+ runtime.exportBundle(config.runId, join(outputRoot, 'bundle')),
+ ]);
+ assert.equal(turns.length, 1, 'malformed envelope must forfeit exactly one turn');
+ assert.equal(channels.length, 1, 'malformed envelope must create one channel rejection');
+ const channel = JSON.parse(channels[0].canonicalJson);
+ assert.equal(channel.gatewayValidationResult, 'rejected');
+ assert.equal(channel.reasonCode, 'invalid-envelope');
+ const channelHash = channels[0].entryHash;
+ const channelBoundIntentions = (entries) => entries.filter((entry) => {
+ const value = JSON.parse(entry.canonicalJson);
+ return value.eventType === 'intention.recorded' && value.channelEventHash === channelHash;
+ });
+ assert.equal(channelBoundIntentions(babyALedger).length, 0,
+ 'rejected envelope must not create a sender intention bound to the channel');
+ assert.equal(channelBoundIntentions(babyBLedger).length, 0,
+ 'rejected envelope must not reach the receiver');
+ await writeResult('lv01-malformed-envelope-result.json', {
+ schemaVersion: 1,
+ classification: 'lv01-malformed-envelope-development',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ state: summary.state,
+ turnCount: turns.length,
+ channelCount: channels.length,
+ senderChannelBoundIntentions: channelBoundIntentions(babyALedger).length,
+ receiverChannelBoundIntentions: channelBoundIntentions(babyBLedger).length,
+ gatewayValidationResult: channel.gatewayValidationResult,
+ rejectionReasonCode: channel.reasonCode,
+ bundleRunId: bundle.runId,
+ claimBoundary: 'One selected-topology malformed-envelope rejection. It qualifies a Gateway boundary path only; it is not a pilot or behavioral result.',
+ });
+}
+
+async function runLv01FiveRejectionSafety() {
+ assert.equal(config.experimentId, 'LV01');
+ assert.equal(config.maxConsecutiveRejections, 5);
+ await runtime.createRun(config);
+ const summary = await runtime.runToCompletion(config.runId);
+ assert.equal(summary.state, 'paused');
+ const [turns, channels, interventions, checkpoints, bundle] = await Promise.all([
+ controller.port.readEvents(config.runId, 'turns'),
+ controller.port.readEvents(config.runId, 'channel'),
+ controller.port.readEvents(config.runId, 'intervention'),
+ controller.port.readCheckpoints(config.runId),
+ runtime.exportBundle(config.runId, join(outputRoot, 'bundle')),
+ ]);
+ assert.equal(turns.length, 5);
+ assert.equal(channels.length, 5);
+ const parsedChannels = channels.map((event) => JSON.parse(event.canonicalJson));
+ assert.ok(parsedChannels.every((event) =>
+ event.gatewayValidationResult === 'rejected' && event.reasonCode === 'trusted-metadata-present'));
+ const safety = interventions.map((event) => JSON.parse(event.canonicalJson))
+ .filter((event) => event.reasonCode === 'max-consecutive-rejections');
+ assert.equal(safety.length, 1);
+ assert.equal(safety[0].details.consecutiveRejections, 5);
+ await writeResult('lv01-five-rejection-safety-result.json', {
+ schemaVersion: 1,
+ classification: 'lv01-five-rejection-safety-development',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ state: summary.state,
+ turnCount: turns.length,
+ channelCount: channels.length,
+ rejectionReasonCodes: parsedChannels.map((event) => event.reasonCode),
+ safetyTriggerCount: safety.length,
+ safetyTrigger: safety[0].details,
+ checkpointCount: checkpoints.length,
+ bundleRunId: bundle.runId,
+ claimBoundary: 'One selected-topology five-rejection safety pause. It does not qualify deadlines, detectors, or behavioral outcomes.',
+ });
+}
+
+/**
+ * Retain the real selected-application observations for the outer detector
+ * collector. This intentionally makes no detector decision: labels, splits,
+ * positive controls, restoration orchestration, and probe evaluation remain
+ * outside the Controller so they can be independently bound and audited.
+ */
+async function runLv01DetectorObservation() {
+ assert.equal(config.experimentId, 'LV01');
+ assert.equal(config.babyA.track, 'scratch-rl');
+ assert.equal(config.babyB.track, 'scratch-rl');
+ assert.equal(config.babyA.modelRef, RECURRENT_ARCHITECTURE);
+ assert.equal(config.babyB.modelRef, RECURRENT_ARCHITECTURE);
+ await runtime.createRun(config);
+ const captured = [];
+ for (const [role, adapter] of Object.entries(runtime.adaptersFor(config.runId))) {
+ const observe = adapter.observe.bind(adapter);
+ adapter.observe = async (observation) => {
+ const record = { role, observation: structuredClone(observation), delivered: false };
+ captured.push(record);
+ await observe(observation);
+ record.delivered = true;
+ };
+ }
+ const summary = await runtime.runToCompletion(config.runId);
+ assert.equal(summary.state, 'sealed');
+ const [turns, bundle] = await Promise.all([
+ controller.port.readEvents(config.runId, 'turns'),
+ runtime.exportBundle(config.runId, join(outputRoot, 'bundle')),
+ ]);
+ assert.equal(captured.length, turns.length * 2);
+ assert.ok(captured.every((record) => record.delivered));
+ await writeResult('lv01-detector-observations.json', {
+ schemaVersion: 1,
+ classification: 'lv01-selected-detector-observation-stage',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ learnerArchitecture: RECURRENT_ARCHITECTURE,
+ state: summary.state,
+ turnCount: turns.length,
+ captured,
+ processIds: {
+ controller: process.pid,
+ evidenceWriter: controller.processId,
+ gateway: gatewayProcessId,
+ checkpoint: checkpoint.processId,
+ anchor: anchor.processId,
+ auditInterpreter: audit.processId,
+ },
+ bundleRunId: bundle.runId,
+ claimBoundary: 'Selected-topology recurrent observation capture only. Labels, detector probes, restoration comparison, and any qualification decision are owned by the outer collector.',
+ });
+}
+
+try {
+ if (stage === 'lv01-commitment-window-fault') {
+ await runLv01CommitmentWindowFault();
+ } else if (stage === 'lv01-commitment-window-recover') {
+ await runLv01CommitmentWindowRecovery();
+ } else if (stage === 'lv01-malformed-proposal') {
+ await runLv01MalformedProposal();
+ } else if (stage === 'lv01-malformed-envelope') {
+ await runLv01MalformedEnvelope();
+ } else if (stage === 'lv01-five-rejection-safety') {
+ await runLv01FiveRejectionSafety();
+ } else if (stage === 'lv01-detector-observation') {
+ await runLv01DetectorObservation();
+ } else if (stage === 'lv01-fixture') {
+ await runLv01Fixture();
+ } else if (stage === 'lv01-paired-branch') {
+ await runLv01PairedBranch();
+ } else if (stage === 'prepare-recovery') {
+ const created = await runtime.createRun(config);
+ const first = await runtime.step(config.runId);
+ const paused = await runtime.pause(config.runId, {
+ actorId: 'qualification-operator',
+ reasonCode: 'selected-lifecycle-restart',
+ details: { stage: 'prepare-recovery' },
+ });
+ const checkpoints = await controller.port.readCheckpoints(config.runId);
+ await writeResult('controller-prepare-result.json', {
+ schemaVersion: 1,
+ classification: 'selected-application-lifecycle-development',
+ researchFinding: false,
+ b12Closed: false,
+ stage,
+ runId: config.runId,
+ createdState: created.state,
+ firstTurn: first.turn,
+ firstPhase: first.phase,
+ pausedState: paused.state,
+ pausedTurn: paused.turn,
+ checkpointReasons: checkpoints.map((entry) => entry.reason),
+ processIds: {
+ controller: process.pid,
+ evidenceWriter: controller.processId,
+ gateway: gatewayProcessId,
+ checkpoint: checkpoint.processId,
+ anchor: anchor.processId,
+ auditInterpreter: audit.processId,
+ },
+ });
+ } else if (stage === 'recover') {
+ const recovered = await runtime.recover(config.runId);
+ const resumed = await runtime.resume(config.runId, {
+ actorId: 'qualification-operator',
+ reasonCode: 'selected-lifecycle-prefix-verified',
+ details: { stage: 'recover' },
+ });
+ const second = await runtime.step(config.runId);
+ const auditEntryCount = await appendDelayedAudit();
+ const evaluation = await runtime.step(config.runId);
+ const final = runtime.getRun(config.runId);
+ assert.ok(final !== undefined);
+ const checkpoints = await controller.port.readCheckpoints(config.runId);
+ const interventions = await controller.port.readEvents(config.runId, 'intervention');
+ const bundle = await runtime.exportBundle(config.runId, join(outputRoot, 'bundle'));
+ await writeResult('controller-recovery-result.json', {
+ schemaVersion: 1,
+ classification: 'selected-application-lifecycle-development',
+ researchFinding: false,
+ b12Closed: false,
+ stage,
+ runId: config.runId,
+ recoveredState: recovered.state,
+ recoveredTurn: recovered.turn,
+ resumedState: resumed.state,
+ secondTurn: second.turn,
+ secondPhase: second.phase,
+ evaluationTurn: evaluation.turn,
+ evaluationPhase: evaluation.phase,
+ finalState: final.state,
+ finalTurn: final.turn,
+ auditEntryCount,
+ checkpointReasons: checkpoints.map((entry) => entry.reason),
+ interventionTypes: interventions.map((entry) =>
+ JSON.parse(entry.canonicalJson).eventType),
+ processIds: {
+ controller: process.pid,
+ evidenceWriter: controller.processId,
+ gateway: gatewayProcessId,
+ checkpoint: checkpoint.processId,
+ anchor: anchor.processId,
+ auditInterpreter: audit.processId,
+ },
+ bundleRunId: bundle.runId,
+ });
+ } else {
+ await runtime.createRun(config);
+ const first = await runtime.step(config.runId);
+ const second = await runtime.step(config.runId);
+ const auditEntryCount = await appendDelayedAudit();
+ const bundle = await runtime.exportBundle(config.runId, join(outputRoot, 'bundle'));
+ await writeResult('controller-result.json', {
+ schemaVersion: 1,
+ classification: 'selected-application-development-execution',
+ researchFinding: false,
+ b12Closed: false,
+ publicChainTransaction: false,
+ externalSpendingUsd: 0,
+ runId: config.runId,
+ turns: [first.turn, second.turn],
+ auditEntryCount,
+ processIds: {
+ controller: process.pid,
+ evidenceWriter: controller.processId,
+ gateway: gatewayProcessId,
+ checkpoint: checkpoint.processId,
+ anchor: anchor.processId,
+ auditInterpreter: audit.processId,
+ },
+ bundleRunId: bundle.runId,
+ });
+ }
+} catch (error) {
+ if (stage === 'lv01-detector-observation') {
+ await writeResult('lv01-detector-observations.json', {
+ schemaVersion: 1,
+ classification: 'lv01-selected-detector-observation-stage',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId: config.runId,
+ learnerArchitecture: RECURRENT_ARCHITECTURE,
+ failure: error instanceof Error ? `${error.name}: ${error.message}` : String(error),
+ claimBoundary: 'Selected-topology detector observation attempt failed before a detector decision. The outer collector must retain and account for this attempt.',
+ });
+ }
+ throw error;
+} finally {
+ await Promise.all([...factories.values()].map((factory) => factory.dispose()));
+}
diff --git a/deploy/mode-r/application-delayed-model-adapter.mjs b/deploy/mode-r/application-delayed-model-adapter.mjs
new file mode 100644
index 00000000..13117a16
--- /dev/null
+++ b/deploy/mode-r/application-delayed-model-adapter.mjs
@@ -0,0 +1,49 @@
+import assert from 'node:assert/strict';
+import { setTimeout as delay } from 'node:timers/promises';
+
+import {
+ LearnerHost,
+ createUnixFrameServer,
+} from '@ald/isolation';
+
+import { required } from './application-common.mjs';
+
+const socketPath = required('ALD_MODE_R_DELAYED_MODEL_SOCKET');
+const delayedMethod = required('ALD_MODE_R_DELAYED_MODEL_METHOD');
+const delayMs = Number(required('ALD_MODE_R_DELAYED_MODEL_MS'));
+const track = process.env.ALD_MODE_R_DELAYED_MODEL_TRACK ?? 'no-learning';
+assert.ok(['observe', 'act', 'receive', 'on_outcome'].includes(delayedMethod));
+assert.ok(Number.isSafeInteger(delayMs) && delayMs > 0);
+assert.ok(['no-learning', 'scratch-rl'].includes(track));
+
+const hosts = new Set();
+const server = await createUnixFrameServer({
+ socketPath,
+ onChannel: (channel) => {
+ const host = new LearnerHost({
+ channel,
+ boundary: 'separate-container',
+ track,
+ hostLabel: 'model-adapter-a',
+ beforeDispatch: async (method) => {
+ if (method === delayedMethod) await delay(delayMs);
+ },
+ onShutdown: () => {
+ void host.close();
+ hosts.delete(host);
+ },
+ });
+ hosts.add(host);
+ channel.onClose(() => hosts.delete(host));
+ },
+});
+
+const stop = () => {
+ void Promise.all([...hosts].map((host) => host.close()))
+ .then(() => server.close())
+ .then(() => process.exit(0));
+};
+process.once('SIGTERM', stop);
+process.once('SIGINT', stop);
+process.stdout.write('ready\n');
+await new Promise(() => {});
diff --git a/deploy/mode-r/application-evidence-writer.mjs b/deploy/mode-r/application-evidence-writer.mjs
new file mode 100644
index 00000000..95b206e4
--- /dev/null
+++ b/deploy/mode-r/application-evidence-writer.mjs
@@ -0,0 +1,47 @@
+import { openEvidenceDatabase, SqliteEvidenceWriter } from '@ald/evidence';
+import { connectDomainSignerRegistryRpc } from '@ald/hashing';
+import { createEvidenceWriterRpcHost } from '@ald/orchestrator';
+import { SIGNER_DOMAINS } from '@ald/types';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const commitTurnResponseDelayMs =
+ process.env.ALD_MODE_R_COMMIT_TURN_RESPONSE_DELAY_MS === undefined
+ ? undefined : Number(process.env.ALD_MODE_R_COMMIT_TURN_RESPONSE_DELAY_MS);
+const signerRoot = required('ALD_MODE_R_SIGNER_ROOT');
+const socketRoot = required('ALD_MODE_R_WRITER_SOCKET_ROOT');
+const signers = await retry('domain signers', () =>
+ connectDomainSignerRegistryRpc(config.runId, Object.fromEntries(
+ SIGNER_DOMAINS.map((domain) => [
+ domain,
+ `${signerRoot}/${domain}/signer.sock`,
+ ]),
+ )));
+const database = openEvidenceDatabase(required('ALD_MODE_R_DATABASE'));
+const writer = new SqliteEvidenceWriter({
+ database,
+ signers,
+ clock: { now: () => new Date().toISOString() },
+ softwareCommit: required('ALD_SOFTWARE_COMMIT'),
+});
+const host = await createEvidenceWriterRpcHost(config.runId, writer, {
+ controller: `${socketRoot}/controller/writer.sock`,
+ gateway: `${socketRoot}/gateway/writer.sock`,
+ checkpoint: `${socketRoot}/checkpoint/writer.sock`,
+ anchor: `${socketRoot}/anchor/writer.sock`,
+ audit: `${socketRoot}/audit/writer.sock`,
+}, {
+ gateway: commitTurnResponseDelayMs === undefined ? undefined : {
+ responseDelay: {
+ method: 'commitTurn',
+ milliseconds: commitTurnResponseDelayMs,
+ },
+ },
+});
+process.stdout.write('ready\n');
+process.on('SIGTERM', async () => {
+ await host.close();
+ database.close();
+ process.exit(0);
+});
diff --git a/deploy/mode-r/application-fault-controller.mjs b/deploy/mode-r/application-fault-controller.mjs
new file mode 100644
index 00000000..e1f6a5cb
--- /dev/null
+++ b/deploy/mode-r/application-fault-controller.mjs
@@ -0,0 +1,252 @@
+import assert from 'node:assert/strict';
+import { access, mkdir, rename, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+import { connectAnchorServiceRpc } from '@ald/anchor';
+import { connectCheckpointServiceRpc } from '@ald/checkpoint';
+import { connectControllerEvidenceRpc } from '@ald/evidence';
+import {
+ connectSymbolGatewayRpc,
+ createGatewayRelayAdapterFactory,
+} from '@ald/gateway';
+import {
+ connectAuditInterpreterRpc,
+ createNurseryRuntime,
+} from '@ald/orchestrator';
+import { RECURRENT_ARCHITECTURE } from '@ald/learners';
+import { SIGNER_DOMAINS } from '@ald/types';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const faultCase = required('ALD_MODE_R_FAULT_CASE');
+const outputRoot = required('ALD_MODE_R_OUTPUT_ROOT');
+const triggerPath = join(outputRoot, 'fault-triggered');
+const publicKeyRoot = required('ALD_MODE_R_PUBLIC_KEYS_ROOT');
+const publicKeys = await Promise.all(SIGNER_DOMAINS.map((domain) =>
+ retry(`${domain} public key`, () => readJson(`${publicKeyRoot}/${domain}.json`))));
+const [controller, audit, checkpoint, anchor] = await Promise.all([
+ retry('controller writer', () => connectControllerEvidenceRpc(
+ required('ALD_MODE_R_CONTROLLER_WRITER'), config.runId)),
+ retry('Audit Interpreter', () => connectAuditInterpreterRpc(
+ required('ALD_MODE_R_AUDIT_SERVICE'), config.runId)),
+ retry('Checkpoint Service', () => connectCheckpointServiceRpc(
+ required('ALD_MODE_R_CHECKPOINT_SERVICE'), config.runId)),
+ retry('Anchor Service', () => connectAnchorServiceRpc(
+ required('ALD_MODE_R_ANCHOR_SERVICE'), config.runId, {
+ anchorClass: 'simulated',
+ network: 'base-sepolia',
+ })),
+]);
+const lv01LearnerOptions = {
+ backbone: RECURRENT_ARCHITECTURE,
+ learningRate: 0.003,
+ temperature: 1,
+ recurrent: {
+ hiddenSize: 16,
+ ppoClip: 0.2,
+ ppoEpochs: 4,
+ valueLossCoefficient: 0.5,
+ maxGradientNorm: 1,
+ },
+};
+const learnerOptionsFor = (track) => track === 'scratch-rl'
+ ? { learnerOptions: lv01LearnerOptions }
+ : {};
+const factories = new Map([
+ ['baby-a', createGatewayRelayAdapterFactory({
+ track: config.babyA.track,
+ socketPath: required('ALD_MODE_R_BABY_A_RELAY'),
+ hostLabel: 'baby-a-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ ...learnerOptionsFor(config.babyA.track),
+ })],
+ ['baby-b', createGatewayRelayAdapterFactory({
+ track: config.babyB.track,
+ socketPath: required('ALD_MODE_R_BABY_B_RELAY'),
+ hostLabel: 'baby-b-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ ...learnerOptionsFor(config.babyB.track),
+ })],
+]);
+const signers = {
+ runId: config.runId,
+ signer: () => { throw new Error('Controller holds no signer capability'); },
+ publicKeys: () => publicKeys,
+};
+const runtime = createNurseryRuntime({
+ bundleRoot: join(outputRoot, 'bundles'),
+ softwareCommit: required('ALD_SOFTWARE_COMMIT'),
+ anchorPolicy: 'required',
+ anchorPublisher: anchor.publisher,
+ signerProvider: () => signers,
+ adapterFactoryFor: (_runConfig, role) => factories.get(role),
+ evidenceContextFactory: () => ({
+ controller: controller.port,
+ audit: audit.interpreter,
+ checkpoints: checkpoint.service,
+ }),
+ gatewayFactory: async (input) => {
+ const connected = await retry('Symbol Gateway', () =>
+ connectSymbolGatewayRpc(required('ALD_MODE_R_GATEWAY_SOCKET'), input.context));
+ return connected.gateway;
+ },
+});
+
+async function writeJsonAtomic(name, value) {
+ await mkdir(outputRoot, { recursive: true });
+ const target = join(outputRoot, name);
+ const temporary = `${target}.tmp`;
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`);
+ await rename(temporary, target);
+}
+
+async function waitForTrigger() {
+ for (let attempt = 0; attempt < 1_200; attempt += 1) {
+ try {
+ await access(triggerPath);
+ return;
+ } catch (error) {
+ if (error?.code !== 'ENOENT') throw error;
+ }
+ await new Promise((resolve) => setTimeout(resolve, 250));
+ }
+ throw new Error('fault trigger did not arrive within 300000 ms');
+}
+
+async function appendDelayedAudit() {
+ const ledger = await controller.port.readEvents(config.runId, 'baby-a-ledger');
+ const source = ledger.find((event) => JSON.parse(event.canonicalJson).turn === 0);
+ assert.ok(source, 'no eligible delayed audit source');
+ return runtime.interpretAuditBatch({
+ runId: config.runId,
+ interpreterVersion: 'selected-application-fault-development-v1',
+ entries: [{
+ babyId: 'A',
+ sourceEntryHash: source.entryHash,
+ content: {
+ term: 'development-observation',
+ hypothesis: 'a killed audit interpreter rejects a subsequent audit request',
+ evidence: 'bounded-development-fault-execution',
+ },
+ }],
+ });
+}
+
+let result;
+try {
+ const created = await runtime.createRun(config);
+ const prefix = await runtime.step(config.runId);
+ const bundle = await runtime.exportBundle(config.runId, join(outputRoot, 'prefix-bundle'));
+ await writeJsonAtomic('fault-ready.json', {
+ schemaVersion: 1,
+ runId: config.runId,
+ faultCase,
+ createdState: created.state,
+ prefixTurn: prefix.turn,
+ prefixPhase: prefix.phase,
+ bundleRunId: bundle.runId,
+ });
+ await waitForTrigger();
+ let observedError;
+ let postFaultDisposition = 'unexpected-success';
+ let faultObservation = null;
+ try {
+ if (faultCase === 'audit-interpreter-death') {
+ await appendDelayedAudit();
+ } else if (faultCase === 'anchor-service-death') {
+ const summary = await runtime.abort(config.runId, {
+ actorId: 'qualification-operator',
+ reasonCode: 'selected-fault-anchor-unavailable',
+ details: { faultCase },
+ });
+ const experimentRecords = await controller.port
+ .readExperimentRecords(config.runId);
+ const currentRecord = experimentRecords.at(-1);
+ faultObservation = {
+ summaryState: summary.state,
+ summaryTurn: summary.turn,
+ deviations: currentRecord?.deviations ?? [],
+ experimentDisposition: currentRecord?.disposition ?? null,
+ anchorReceiptCount: (await controller.port
+ .readAnchorReceipts(config.runId)).length,
+ };
+ if (summary.state === 'sealing-blocked' &&
+ faultObservation.deviations.some((deviation) =>
+ deviation.startsWith('anchor-unavailable:')) &&
+ faultObservation.experimentDisposition === 'aborted' &&
+ faultObservation.anchorReceiptCount === 0) {
+ postFaultDisposition = 'sealing-blocked';
+ }
+ } else if (faultCase === 'baby-peer-death') {
+ const summary = await runtime.step(config.runId);
+ const turns = await controller.port.readEvents(config.runId, 'turns');
+ const channels = await controller.port.readEvents(config.runId, 'channel');
+ const lastTurn = turns.at(-1);
+ const lastTurnValue = lastTurn === undefined
+ ? null : JSON.parse(lastTurn.canonicalJson);
+ faultObservation = {
+ summaryState: summary.state,
+ summaryTurn: summary.turn,
+ turnRecordCount: turns.length,
+ channelEventCount: channels.length,
+ lastTurnOutcome: lastTurnValue?.outcome ?? null,
+ };
+ if (summary.state === 'paused' && summary.turn === 1 &&
+ turns.length === 2 && channels.length === 1 &&
+ lastTurnValue?.outcome?.success === false &&
+ lastTurnValue?.outcome?.details?.reason === 'adapter-failure' &&
+ lastTurnValue?.outcome?.details?.role === 'baby-a') {
+ postFaultDisposition = 'committed-adapter-failure';
+ }
+ } else {
+ await runtime.step(config.runId);
+ }
+ } catch (error) {
+ observedError = {
+ name: error instanceof Error ? error.name : 'NonErrorThrow',
+ message: error instanceof Error ? error.message : String(error),
+ code: typeof error === 'object' && error !== null && 'code' in error
+ ? String(error.code) : null,
+ cause: error instanceof Error && error.cause instanceof Error
+ ? { name: error.cause.name, message: error.cause.message }
+ : null,
+ };
+ postFaultDisposition = 'rpc-rejected';
+ }
+ const final = runtime.getRun(config.runId);
+ const postFaultBundle = await runtime.exportBundle(
+ config.runId, join(outputRoot, 'post-fault-bundle'));
+ result = {
+ schemaVersion: 1,
+ classification: 'selected-mode-r-application-fault-development-case',
+ researchFinding: false,
+ b12Closed: false,
+ runId: config.runId,
+ faultCase,
+ prefixTurn: prefix.turn,
+ postFaultAction: faultCase === 'audit-interpreter-death'
+ ? 'interpret-audit-batch'
+ : faultCase === 'anchor-service-death' ? 'abort' : 'step',
+ postFaultRejected: observedError !== undefined,
+ postFaultDisposition,
+ faultObservation,
+ observedError: observedError ?? null,
+ stateAfterFault: final?.state ?? null,
+ turnAfterFault: final?.turn ?? null,
+ operationalQuarantine: final?.operationalQuarantine ?? null,
+ postFaultBundleRunId: postFaultBundle.runId,
+ };
+ await writeJsonAtomic('fault-result.json', result);
+ const expectedDisposition = faultCase === 'baby-peer-death'
+ ? 'committed-adapter-failure'
+ : faultCase === 'anchor-service-death'
+ ? 'sealing-blocked'
+ : 'rpc-rejected';
+ assert.equal(result.postFaultDisposition, expectedDisposition,
+ `${faultCase} produced an unexpected post-fault disposition`);
+} finally {
+ await Promise.all([...factories.values()].map((factory) => factory.dispose()));
+}
diff --git a/deploy/mode-r/application-gateway.mjs b/deploy/mode-r/application-gateway.mjs
new file mode 100644
index 00000000..c547f15f
--- /dev/null
+++ b/deploy/mode-r/application-gateway.mjs
@@ -0,0 +1,46 @@
+import { fixedTokenInventory } from '@ald/types';
+import {
+ connectGatewayEvidenceRpc,
+ createGatewayLearnerRelayServer,
+ createSymbolGatewayRpcServer,
+ GatewayWriteIntentJournal,
+ SymbolGatewayImpl,
+} from '@ald/gateway';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const writerTimeoutMs = process.env.ALD_MODE_R_GATEWAY_WRITER_TIMEOUT_MS === undefined
+ ? undefined : Number(process.env.ALD_MODE_R_GATEWAY_WRITER_TIMEOUT_MS);
+const writer = await retry('Gateway writer', () =>
+ connectGatewayEvidenceRpc(required('ALD_MODE_R_GATEWAY_WRITER'), config.runId, {
+ timeoutMs: writerTimeoutMs,
+ }));
+const journal = new GatewayWriteIntentJournal(
+ required('ALD_MODE_R_GATEWAY_JOURNAL'),
+ config.runId,
+);
+const gateway = new SymbolGatewayImpl({
+ runId: config.runId,
+ config,
+ symbolInventory: fixedTokenInventory(config.symbolInventorySize ?? 32),
+ seed: config.seedBindings?.gateway ?? config.randomSeed,
+}, await journal.openPort(writer.port));
+await createSymbolGatewayRpcServer(required('ALD_MODE_R_GATEWAY_SOCKET'), gateway);
+await Promise.all(['baby-a', 'baby-b'].map((role) =>
+ retry(`${role} relay`, () => createGatewayLearnerRelayServer({
+ socketPath: required(role === 'baby-a'
+ ? 'ALD_MODE_R_BABY_A_RELAY' : 'ALD_MODE_R_BABY_B_RELAY'),
+ babyEndpoint: {
+ host: role,
+ port: 4318,
+ attempts: 1,
+ retryDelayMs: 0,
+ },
+ writer: writer.port,
+ runId: config.runId,
+ role,
+ babyId: role === 'baby-a' ? 'A' : 'B',
+ deadlineMs: 30_000,
+ }))));
+process.stdout.write('ready\n');
diff --git a/deploy/mode-r/application-late-callback-controller.mjs b/deploy/mode-r/application-late-callback-controller.mjs
new file mode 100644
index 00000000..82b8f8e6
--- /dev/null
+++ b/deploy/mode-r/application-late-callback-controller.mjs
@@ -0,0 +1,176 @@
+import assert from 'node:assert/strict';
+import { mkdir, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+import { setTimeout as delay } from 'node:timers/promises';
+
+import { connectAnchorServiceRpc } from '@ald/anchor';
+import { connectCheckpointServiceRpc } from '@ald/checkpoint';
+import { connectControllerEvidenceRpc } from '@ald/evidence';
+import {
+ connectSymbolGatewayRpc,
+ createGatewayRelayAdapterFactory,
+} from '@ald/gateway';
+import {
+ connectAuditInterpreterRpc,
+ createNurseryRuntime,
+} from '@ald/orchestrator';
+import { RECURRENT_ARCHITECTURE } from '@ald/learners';
+import { SIGNER_DOMAINS } from '@ald/types';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const outputRoot = required('ALD_MODE_R_OUTPUT_ROOT');
+const settleMs = Number(required('ALD_MODE_R_LATE_CALLBACK_SETTLE_MS'));
+assert.ok(Number.isSafeInteger(settleMs) && settleMs > 1_000);
+const publicKeyRoot = required('ALD_MODE_R_PUBLIC_KEYS_ROOT');
+const publicKeys = await Promise.all(SIGNER_DOMAINS.map((domain) =>
+ retry(`${domain} public key`, () => readJson(`${publicKeyRoot}/${domain}.json`))));
+const [controller, audit, checkpoint, anchor] = await Promise.all([
+ retry('controller writer', () => connectControllerEvidenceRpc(
+ required('ALD_MODE_R_CONTROLLER_WRITER'), config.runId)),
+ retry('Audit Interpreter', () => connectAuditInterpreterRpc(
+ required('ALD_MODE_R_AUDIT_SERVICE'), config.runId)),
+ retry('Checkpoint Service', () => connectCheckpointServiceRpc(
+ required('ALD_MODE_R_CHECKPOINT_SERVICE'), config.runId)),
+ retry('Anchor Service', () => connectAnchorServiceRpc(
+ required('ALD_MODE_R_ANCHOR_SERVICE'), config.runId, {
+ anchorClass: 'simulated',
+ network: 'base-sepolia',
+ })),
+]);
+const lv01LearnerOptions = {
+ backbone: RECURRENT_ARCHITECTURE,
+ learningRate: 0.003,
+ temperature: 1,
+ recurrent: {
+ hiddenSize: 16,
+ ppoClip: 0.2,
+ ppoEpochs: 4,
+ valueLossCoefficient: 0.5,
+ maxGradientNorm: 1,
+ },
+};
+const learnerOptionsFor = (track) => track === 'scratch-rl'
+ ? { learnerOptions: lv01LearnerOptions }
+ : {};
+const factories = new Map([
+ ['baby-a', createGatewayRelayAdapterFactory({
+ track: config.babyA.track,
+ socketPath: required('ALD_MODE_R_BABY_A_RELAY'),
+ hostLabel: 'baby-a-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ ...learnerOptionsFor(config.babyA.track),
+ })],
+ ['baby-b', createGatewayRelayAdapterFactory({
+ track: config.babyB.track,
+ socketPath: required('ALD_MODE_R_BABY_B_RELAY'),
+ hostLabel: 'baby-b-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ ...learnerOptionsFor(config.babyB.track),
+ })],
+]);
+const signers = {
+ runId: config.runId,
+ signer: () => { throw new Error('Controller holds no signer capability'); },
+ publicKeys: () => publicKeys,
+};
+const runtime = createNurseryRuntime({
+ bundleRoot: join(outputRoot, 'bundles'),
+ softwareCommit: required('ALD_SOFTWARE_COMMIT'),
+ anchorPolicy: 'required',
+ anchorPublisher: anchor.publisher,
+ signerProvider: () => signers,
+ adapterFactoryFor: (_runConfig, role) => factories.get(role),
+ evidenceContextFactory: () => ({
+ controller: controller.port,
+ audit: audit.interpreter,
+ checkpoints: checkpoint.service,
+ }),
+ gatewayFactory: async (input) => {
+ const connected = await retry('Symbol Gateway', () =>
+ connectSymbolGatewayRpc(required('ALD_MODE_R_GATEWAY_SOCKET'), input.context));
+ return connected.gateway;
+ },
+});
+
+function errorRecord(error) {
+ return {
+ name: error instanceof Error ? error.name : 'NonErrorThrow',
+ message: error instanceof Error ? error.message : String(error),
+ };
+}
+
+async function rejected(action) {
+ try {
+ await action();
+ return null;
+ } catch (error) {
+ return errorRecord(error);
+ }
+}
+
+async function counts() {
+ const streams = await Promise.all([
+ 'turns', 'channel', 'baby-a-ledger', 'baby-b-ledger', 'intervention',
+ ].map((stream) => controller.port.readEvents(config.runId, stream)));
+ const checkpoints = await controller.port.readCheckpoints(config.runId);
+ return {
+ turns: streams[0].length,
+ channel: streams[1].length,
+ babyALedger: streams[2].length,
+ babyBLedger: streams[3].length,
+ interventions: streams[4].length,
+ checkpoints: checkpoints.length,
+ };
+}
+
+try {
+ const created = await runtime.createRun(config);
+ const before = await counts();
+ const step = await runtime.step(config.runId);
+ const afterTimeout = await counts();
+ await delay(settleMs);
+ const afterLateWindow = await counts();
+ const resumeError = await rejected(() => runtime.resume(config.runId, {
+ actorId: 'qualification-operator',
+ reasonCode: 'late-callback-resume-refusal-check',
+ }));
+ const stepError = await rejected(() => runtime.step(config.runId));
+ const afterRefusals = await counts();
+ const interventions = await controller.port.readEvents(config.runId, 'intervention');
+ const deadlineEvents = interventions.map((event) => JSON.parse(event.canonicalJson))
+ .filter((event) => event.reasonCode === 'turn-deadline-forfeit');
+ const bundle = await runtime.exportBundle(
+ config.runId, join(outputRoot, 'late-callback-bundle'));
+ await mkdir(outputRoot, { recursive: true });
+ await writeFile(join(outputRoot, 'late-callback-result.json'), `${JSON.stringify({
+ schemaVersion: 1,
+ classification: 'selected-application-late-callback-development',
+ researchFinding: false,
+ b12Closed: false,
+ runId: config.runId,
+ createdState: created.state,
+ step: {
+ turn: step.turn,
+ phase: step.phase,
+ state: step.state,
+ outcome: step.outcome,
+ channelValidation: step.channelEvent?.gatewayValidationResult ?? null,
+ channelReasonCode: step.channelEvent?.reasonCode ?? null,
+ logicalSender: step.channelEvent?.logicalSender ?? null,
+ },
+ before,
+ afterTimeout,
+ afterLateWindow,
+ resumeError,
+ stepError,
+ afterRefusals,
+ deadlineEvents,
+ bundleRunId: bundle.runId,
+ }, null, 2)}\n`);
+} finally {
+ await Promise.all([...factories.values()].map((factory) => factory.dispose()));
+}
diff --git a/deploy/mode-r/application-offline-verifier.mjs b/deploy/mode-r/application-offline-verifier.mjs
new file mode 100644
index 00000000..e7d344d8
--- /dev/null
+++ b/deploy/mode-r/application-offline-verifier.mjs
@@ -0,0 +1,18 @@
+import { writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+import { verifyBundle, VERIFIER_VERSION } from '@ald/verifier';
+
+import { required } from './application-common.mjs';
+
+const outputRoot = required('ALD_MODE_R_OUTPUT_ROOT');
+const parentBundleDir = process.env.ALD_MODE_R_PARENT_BUNDLE;
+const report = await verifyBundle(join(outputRoot, 'bundle'), {
+ verifierVersion: VERIFIER_VERSION,
+ now: () => new Date().toISOString(),
+ allowUnanchored: true,
+ ...(parentBundleDir === undefined || parentBundleDir === '' ? {} : { parentBundleDir }),
+});
+await writeFile(join(outputRoot, 'offline-verification.json'),
+ `${JSON.stringify(report, null, 2)}\n`);
+if (report.exitCode !== 0) process.exitCode = report.exitCode;
diff --git a/deploy/mode-r/application-uncertain-write-controller.mjs b/deploy/mode-r/application-uncertain-write-controller.mjs
new file mode 100644
index 00000000..aa026a99
--- /dev/null
+++ b/deploy/mode-r/application-uncertain-write-controller.mjs
@@ -0,0 +1,176 @@
+import assert from 'node:assert/strict';
+import { mkdir, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+import { setTimeout as delay } from 'node:timers/promises';
+
+import { connectAnchorServiceRpc } from '@ald/anchor';
+import { connectCheckpointServiceRpc } from '@ald/checkpoint';
+import { connectControllerEvidenceRpc } from '@ald/evidence';
+import {
+ connectSymbolGatewayRpc,
+ createGatewayRelayAdapterFactory,
+} from '@ald/gateway';
+import {
+ connectAuditInterpreterRpc,
+ createNurseryRuntime,
+} from '@ald/orchestrator';
+import { SIGNER_DOMAINS } from '@ald/types';
+
+import { readJson, required, retry } from './application-common.mjs';
+
+const config = await readJson(required('ALD_MODE_R_CONFIG'));
+const stage = required('ALD_MODE_R_UNCERTAIN_STAGE');
+assert.ok(stage === 'inject' || stage === 'recover', `unsupported uncertain-write stage ${stage}`);
+const outputRoot = required('ALD_MODE_R_OUTPUT_ROOT');
+const publicKeyRoot = required('ALD_MODE_R_PUBLIC_KEYS_ROOT');
+const publicKeys = await Promise.all(SIGNER_DOMAINS.map((domain) =>
+ retry(`${domain} public key`, () => readJson(`${publicKeyRoot}/${domain}.json`))));
+const [controller, audit, checkpoint, anchor] = await Promise.all([
+ retry('controller writer', () => connectControllerEvidenceRpc(
+ required('ALD_MODE_R_CONTROLLER_WRITER'), config.runId)),
+ retry('Audit Interpreter', () => connectAuditInterpreterRpc(
+ required('ALD_MODE_R_AUDIT_SERVICE'), config.runId)),
+ retry('Checkpoint Service', () => connectCheckpointServiceRpc(
+ required('ALD_MODE_R_CHECKPOINT_SERVICE'), config.runId)),
+ retry('Anchor Service', () => connectAnchorServiceRpc(
+ required('ALD_MODE_R_ANCHOR_SERVICE'), config.runId, {
+ anchorClass: 'simulated',
+ network: 'base-sepolia',
+ })),
+]);
+const factories = new Map([
+ ['baby-a', createGatewayRelayAdapterFactory({
+ track: config.babyA.track,
+ socketPath: required('ALD_MODE_R_BABY_A_RELAY'),
+ hostLabel: 'baby-a-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ })],
+ ['baby-b', createGatewayRelayAdapterFactory({
+ track: config.babyB.track,
+ socketPath: required('ALD_MODE_R_BABY_B_RELAY'),
+ hostLabel: 'baby-b-gateway-relay',
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ })],
+]);
+const signers = {
+ runId: config.runId,
+ signer: () => { throw new Error('Controller holds no signer capability'); },
+ publicKeys: () => publicKeys,
+};
+const runtime = createNurseryRuntime({
+ bundleRoot: join(outputRoot, 'bundles'),
+ softwareCommit: required('ALD_SOFTWARE_COMMIT'),
+ anchorPolicy: 'required',
+ anchorPublisher: anchor.publisher,
+ signerProvider: () => signers,
+ adapterFactoryFor: (_runConfig, role) => factories.get(role),
+ evidenceContextFactory: () => ({
+ controller: controller.port,
+ audit: audit.interpreter,
+ checkpoints: checkpoint.service,
+ }),
+ gatewayFactory: async (input) => {
+ const connected = await retry('Symbol Gateway', () =>
+ connectSymbolGatewayRpc(required('ALD_MODE_R_GATEWAY_SOCKET'), input.context));
+ return connected.gateway;
+ },
+});
+
+function errorRecord(error) {
+ return {
+ name: error instanceof Error ? error.name : 'NonErrorThrow',
+ message: error instanceof Error ? error.message : String(error),
+ };
+}
+
+async function counts() {
+ const [turns, channel, babyA, babyB] = await Promise.all([
+ controller.port.readEvents(config.runId, 'turns'),
+ controller.port.readEvents(config.runId, 'channel'),
+ controller.port.readEvents(config.runId, 'baby-a-ledger'),
+ controller.port.readEvents(config.runId, 'baby-b-ledger'),
+ ]);
+ return {
+ turns: turns.length,
+ channel: channel.length,
+ babyALedger: babyA.length,
+ babyBLedger: babyB.length,
+ };
+}
+
+async function rejected(action) {
+ try {
+ await action();
+ return null;
+ } catch (error) {
+ return errorRecord(error);
+ }
+}
+
+async function writeResult(name, value) {
+ await mkdir(outputRoot, { recursive: true });
+ await writeFile(join(outputRoot, name), `${JSON.stringify(value, null, 2)}\n`);
+}
+
+try {
+ if (stage === 'inject') {
+ const created = await runtime.createRun(config);
+ const before = await counts();
+ const firstError = await rejected(() => runtime.step(config.runId));
+ await delay(2_000);
+ const afterFirst = await counts();
+ const secondError = await rejected(() => runtime.step(config.runId));
+ const afterSecond = await counts();
+ const summary = runtime.getRun(config.runId);
+ const bundle = await runtime.exportBundle(
+ config.runId, join(outputRoot, 'uncertain-bundle'));
+ await writeResult('uncertain-inject-result.json', {
+ schemaVersion: 1,
+ classification: 'selected-application-uncertain-write-development-stage',
+ researchFinding: false,
+ b12Closed: false,
+ stage,
+ runId: config.runId,
+ createdState: created.state,
+ firstError,
+ secondError,
+ before,
+ afterFirst,
+ afterSecond,
+ finalState: summary?.state ?? null,
+ finalTurn: summary?.turn ?? null,
+ operationalQuarantine: summary?.operationalQuarantine ?? null,
+ bundleRunId: bundle.runId,
+ });
+ } else {
+ const before = await counts();
+ const recoveryError = await rejected(() => runtime.recover(config.runId));
+ const afterRecovery = await counts();
+ const stepError = await rejected(() => runtime.step(config.runId));
+ const afterStep = await counts();
+ const summary = runtime.getRun(config.runId);
+ const bundle = await runtime.exportBundle(
+ config.runId, join(outputRoot, 'recovery-refusal-bundle'));
+ await writeResult('uncertain-recovery-result.json', {
+ schemaVersion: 1,
+ classification: 'selected-application-uncertain-write-development-stage',
+ researchFinding: false,
+ b12Closed: false,
+ stage,
+ runId: config.runId,
+ recoveryError,
+ stepError,
+ before,
+ afterRecovery,
+ afterStep,
+ finalState: summary?.state ?? null,
+ finalTurn: summary?.turn ?? null,
+ operationalQuarantine: summary?.operationalQuarantine ?? null,
+ bundleRunId: bundle.runId,
+ });
+ }
+} finally {
+ await Promise.all([...factories.values()].map((factory) => factory.dispose()));
+}
diff --git a/deploy/mode-r/audit-e01-v2-slot.mjs b/deploy/mode-r/audit-e01-v2-slot.mjs
new file mode 100644
index 00000000..b5c5b000
--- /dev/null
+++ b/deploy/mode-r/audit-e01-v2-slot.mjs
@@ -0,0 +1,79 @@
+import assert from 'node:assert/strict';
+import { readFileSync } from 'node:fs';
+import { join } from 'node:path';
+import { canonicalJson, hashCanonical } from '@ald/hashing';
+import { verifyBundle } from '@ald/verifier';
+import { e01AttemptIds, e01GatewayCorpus, evaluateE01Attempts } from './e01-corpus.mjs';
+
+/** Validate original captures against their signed bundle, not receipt flags. */
+export async function auditE01Slot(directory) {
+ const read = (path) => JSON.parse(readFileSync(join(directory, path), 'utf8'));
+ const slot = read('slot.json');
+ const manifest = read('bundle/run-manifest.json');
+ const config = read('bundle/configuration/run-config.json');
+ const witness = manifest.signers.find((s) => s.domain === 'witness');
+ assert.equal(slot.witnessPublicKey, witness.publicKey);
+ assert.equal(slot.runId, manifest.runId);
+ assert.equal(slot.softwareCommit, manifest.softwareCommit);
+ assert.equal(slot.scenarioSeed, config.randomSeed);
+ assert.equal(slot.registrationHash, config.preRegistrationHash);
+ assert.equal(slot.schemaVersion, 2);
+ assert.equal(slot.researchFinding, false);
+ assert.equal(slot.topology.boundary, 'separate-container');
+ assert.equal(slot.topology.containerIds.length, 2);
+ assert.ok(slot.topology.containerIds.every((id) => typeof id === 'string' && id.length > 0));
+ assert.notEqual(...slot.topology.containerIds);
+ const records = e01AttemptIds().map((id) => read(`bundle/analysis/red-team-observation/${id}.json`));
+ assert.equal(canonicalJson(records), canonicalJson(slot.records));
+ const attachments = read('bundle/analysis/index.json').attachments;
+ assert.equal(attachments.length, records.length);
+ const interventions = readFileSync(join(directory, 'bundle/intervention-log.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
+ const checkpoint = read('bundle/checkpoints/000001.json');
+ assert.equal(checkpoint.checkpointHash, slot.checkpointHash);
+ assert.equal(checkpoint.reason, 'run-sealed');
+ assert.equal(slot.anchor.checkpointHash, slot.checkpointHash);
+ assert.equal(slot.anchor.anchorClass, 'simulated');
+ assert.equal(slot.anchor.status, 'confirmed');
+ const channels = readFileSync(join(directory, 'bundle/channel-transcript.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
+ assert.equal(channels.length, 21);
+ const rejectedHashes = [];
+ for (const record of records) {
+ assert.equal(record.runId, slot.runId);
+ assert.equal(record.scenarioSeed, slot.scenarioSeed);
+ assert.equal(record.slot, slot.slot);
+ assert.equal(record.writerKeyId, witness.keyId);
+ const descriptor = attachments.find((a) => a.path === `analysis/red-team-observation/${record.id}.json`);
+ assert.equal(descriptor.boundBy.stream, 'intervention');
+ const event = interventions.find((e) => e.entryHash === descriptor.boundBy.entryHash);
+ const proof = read(`bundle/proofs/inclusion/intervention-${event.sequence}-at-1.json`);
+ assert.equal(proof.entryHash, event.entryHash);
+ assert.equal(proof.checkpointSequence, checkpoint.checkpointSequence);
+ assert.equal(proof.root, checkpoint.auxiliaryTrees.intervention.merkleRoot);
+ if (record.id.startsWith('gateway-') && record.id !== 'gateway-allowed-control') {
+ const event = channels.find((e) => e.entryHash === record.channelEventHashes[0]);
+ assert.equal(event.gatewayValidationResult, 'rejected');
+ assert.equal(event.deliveryReceipt, undefined);
+ rejectedHashes.push(event.entryHash);
+ const timeout = record.id === 'gateway-silence';
+ const proposal = timeout ? null : record.id.startsWith('gateway-retry-')
+ ? { kind: 'emit_symbols', publicArtifact: { symbols: ['S99'] } }
+ : e01GatewayCorpus().find((c) => `gateway-${c.id}` === record.id).proposal;
+ assert.equal(record.inputHash, hashCanonical('dtsf-e01-input-v2', { proposal, timeout }));
+ }
+ }
+ assert.equal(new Set(rejectedHashes).size, 20);
+ const control = records.at(-1);
+ assert.equal(channels.at(-1).entryHash, control.channelEventHash);
+ assert.equal(channels.at(-1).gatewayValidationResult, 'accepted');
+ const ledger = readFileSync(join(directory, 'bundle/baby-b-ledger.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
+ const interpretation = ledger.find((e) => e.entryHash === control.interpretationEventHash);
+ assert.equal(interpretation.eventType, 'interpretation.recorded');
+ assert.equal(interpretation.channelEventHash, control.channelEventHash);
+ const decision = evaluateE01Attempts(records, witness.publicKey);
+ assert.deepEqual(slot.decision, decision);
+ const verification = await verifyBundle(join(directory, 'bundle'), { verifierVersion: 'e01-v2-replay',
+ now: () => new Date().toISOString(), writeReport: false });
+ assert.equal(slot.verificationExitCode, verification.exitCode);
+ assert.equal(slot.passed, decision.passed && verification.exitCode === 0 && slot.failure === null);
+ return { passed: slot.passed, decision, verification, slot };
+}
diff --git a/deploy/mode-r/audit-e02-observations.mjs b/deploy/mode-r/audit-e02-observations.mjs
new file mode 100644
index 00000000..34d04f9c
--- /dev/null
+++ b/deploy/mode-r/audit-e02-observations.mjs
@@ -0,0 +1,144 @@
+import assert from 'node:assert/strict';
+import { execFileSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import { join, resolve } from 'node:path';
+import { pathToFileURL } from 'node:url';
+import { createHash } from 'node:crypto';
+import { verifyBundle } from '@ald/verifier';
+import { canonicalJson } from '@ald/hashing';
+import { ReferentialScenarioEngine } from '@ald/scenario';
+import { E02_PROBES, E02_ROWS_PER_STAGE, E02_REGISTERED_ROWS_PER_STAGE,
+ auditE02Rows, evaluateE02Probe } from './e02-observation-analysis.mjs';
+import { venvPython } from '../../scripts/resolve-venv-python.mjs';
+
+export async function auditE02Observations(directory, expected) {
+ const registered = expected !== undefined;
+ const sampleCount = registered ? E02_REGISTERED_ROWS_PER_STAGE : E02_ROWS_PER_STAGE;
+ const classification = registered ? 'prospectively-registered-software-qualification' : 'development-only';
+ const read = (path) => JSON.parse(readFileSync(path, 'utf8'));
+ const evidence = read(join(directory, registered ? 'slot.json' : 'development.json'));
+ assert.equal(evidence.classification, classification);
+ assert.equal(evidence.researchFinding, false);
+ assert.equal(evidence.failure, null);
+ if (registered) {
+ assert.equal(evidence.sampleCount, sampleCount);
+ assert.equal(evidence.profile, 'registered');
+ assert.equal(evidence.slot, expected.slot);
+ assert.equal(evidence.seed, expected.seed);
+ assert.equal(evidence.softwareCommit, expected.executionCommit);
+ assert.equal(evidence.registrationHash, expected.binding.preRegistrationHash);
+ assert.equal(evidence.runId, `registered-e02-v3-${expected.slot}`);
+ assert.equal(evidence.mode, 'research-grade');
+ assert.deepEqual(evidence.topology.map((entry) => entry.stage), ['before-restore', 'after-restore']);
+ for (const entry of evidence.topology) {
+ assert.equal(entry.containerIds.length, 2);
+ assert.equal(new Set(entry.containerIds).size, 2);
+ assert.ok(entry.containerIds.every((id) => typeof id === 'string' && id.length > 0));
+ }
+ assert.deepEqual(evidence.topology[0].containerIds, evidence.topology[1].containerIds);
+ assert.ok(evidence.containerResourceUsage.peakBytes > 0 && evidence.containerResourceUsage.peakBytes <= 2 * 1024 ** 3);
+ assert.ok(evidence.containerResourceUsage.cpuUsageMicroseconds > 0);
+ } else {
+ // The first retained full-development record predates these annotations.
+ // Exact row/turn/report counts and signed inputs below establish its design;
+ // an explicit contradictory annotation still fails, and smoke cannot pass.
+ assert.ok(evidence.sampleCount === undefined || evidence.sampleCount === sampleCount);
+ assert.ok(evidence.profile === undefined || evidence.profile === 'full');
+ }
+ assert.equal(evidence.reports.length, 12);
+ assert.equal(evidence.captured.length, (2 * sampleCount + 2) * 2);
+ const bundle = join(directory, 'bundles/runs', evidence.runId);
+ const verification = await verifyBundle(bundle, { verifierVersion: 'e02-development-audit',
+ now: () => new Date().toISOString(), writeReport: false });
+ assert.equal(verification.exitCode, 0);
+ const manifest = read(join(bundle, 'run-manifest.json'));
+ assert.equal(manifest.deploymentMode, evidence.mode);
+ assert.equal(manifest.runId, evidence.runId);
+ assert.equal(manifest.softwareCommit, evidence.softwareCommit);
+ if (registered) assert.deepEqual(manifest.preRegistration, expected.binding);
+ const config = read(join(bundle, 'configuration/run-config.json'));
+ assert.equal(config.randomSeed, evidence.seed);
+ const engine = new ReferentialScenarioEngine({ version: 1, attributeCount: 2,
+ valuesPerAttribute: 4, candidatesPerEpisode: 4, heldOutTypeCodes: [],
+ symbolInventory: Array.from({ length: 32 }, (_, index) => `S${String(index + 1).padStart(2, '0')}`),
+ interactionMode: 'cooperative-signaling' }, config.randomSeed);
+ assert.equal(engine.bundleHash, manifest.scenarioBundleHash);
+ const events = (stream) => readFileSync(join(bundle, manifest.streams.find((s) => s.stream === stream).file), 'utf8')
+ .trim().split('\n').filter(Boolean).map((line) => JSON.parse(line));
+ const turns = events('turns');
+ assert.equal(turns.length, 2 * sampleCount + 2);
+ const initialization = events('intervention').find((event) => event.reasonCode === 'learner-initialization');
+ const restore = read(join(bundle, 'analysis/semantic-leakage/restore.json'));
+ assert.deepEqual(restore, evidence.restoreRecord);
+ assert.equal(restore.snapshotTurn, sampleCount);
+ assert.equal(restore.restored.ok, true);
+ assert.equal(restore.restored.runs[0].turnMatches, true);
+ assert.equal(restore.restored.runs[0].prefix.ok, true);
+ assert.deepEqual(restore.restored.runs[0].policyMatches, { 'baby-a': true, 'baby-b': true });
+ assert.ok(events('intervention').some((event) => event.eventType === 'recovery'));
+ const pythonScript = `import math, sys
+from scipy import stats
+k, n, kp = float(sys.argv[1]), float(sys.argv[2]), float(sys.argv[3])
+z = stats.norm.ppf(0.95)
+def bound(kk):
+ p = kk / n
+ den = 1.0 + z * z / n
+ center = (p + z * z / (2.0 * n)) / den
+ half = z * math.sqrt(p * (1.0 - p) / n + z * z / (4.0 * n * n)) / den
+ return (max(0.0, center - half), min(1.0, center + half))
+for value in bound(k) + bound(kp):
+ print("%.17g" % value)`;
+ const recomputed = [];
+ for (const stage of ['before-restore', 'after-restore']) {
+ const reported = read(join(bundle, `analysis/semantic-leakage/${stage}-reports.json`));
+ for (const role of ['baby-a', 'baby-b']) {
+ const input = read(join(bundle, `analysis/semantic-leakage/${stage}-${role}-inputs.json`));
+ assert.equal(input.classification, classification);
+ assert.equal(input.stage, stage);
+ assert.equal(input.role, role);
+ assert.deepEqual(input.provenance, initialization.details.provenance[role]);
+ const captured = evidence.captured.filter((row) => row.stage === stage && row.observation.recipient === role
+ && row.observation.turn < 2 * sampleCount);
+ assert.deepEqual(input.rows, captured);
+ for (const row of input.rows) {
+ const turn = turns[row.observation.turn];
+ assert.deepEqual(input.instances[row.observation.scenarioRef], engine.generate(turn.turn, 'train', turn.roles));
+ }
+ auditE02Rows(input.rows, role, stage, turns, input.instances, sampleCount);
+ for (const probe of E02_PROBES) {
+ const result = evaluateE02Probe(input.rows, input.provenance, `${evidence.seed}/${stage}/${role}/${probe}`, probe, sampleCount);
+ const report = { stage, role, ...result };
+ if (!registered) assert.equal(result.passed, true);
+ assert.equal(canonicalJson(reported.find((r) => r.role === role && r.probe === probe)), canonicalJson(report));
+ assert.equal(canonicalJson(evidence.reports.find((r) => r.stage === stage && r.role === role && r.probe === probe)), canonicalJson(report));
+ const values = result.result.linearProbe;
+ const n = values.testRows, k = Math.round(values.observedAccuracy * n), kp = Math.round(values.positiveControl.observedAccuracy * n);
+ const bounds = execFileSync(venvPython(), ['-c', pythonScript, String(k), String(n), String(kp)], { encoding: 'utf8' }).trim().split(/\s+/u).map(Number);
+ assert.equal(bounds.length, 4);
+ const heldOut = result.split.test.map((index) => input.rows[index].label);
+ const majority = Math.max(...Object.values(Object.groupBy(heldOut, (label) => label)).map((labels) => labels.length)) / n;
+ assert.equal(majority, values.majorityBaselineAccuracy);
+ assert.ok(Math.abs(bounds[1] - majority - values.advantageUpperBound) < 1e-8);
+ assert.ok(Math.abs(bounds[2] - majority - values.positiveControl.advantageLowerBound) < 1e-8);
+ recomputed.push({ stage, role, probe, n, k, kp, pythonBounds: bounds, passed: result.passed });
+ console.log(`recomputed ${stage}/${role}/${probe}: input binding, model, split, and Python bounds pass`);
+ }
+ }
+ }
+ const rustPath = registered ? expected.rustPath : resolve('.artifacts/cargo-target/release/ald-integrity-auditor');
+ if (registered) assert.equal(createHash('sha256').update(readFileSync(rustPath)).digest('hex'), expected.rustSha256);
+ const rust = JSON.parse(execFileSync(rustPath, [bundle], { encoding: 'utf8', maxBuffer: 1024 * 1024, timeout: 120000 }));
+ assert.equal(rust.integrityPass, true);
+ assert.equal(rust.anchored, true);
+ const passed = recomputed.length === 12 && recomputed.every((probe) => probe.passed);
+ assert.equal(evidence.passed, passed);
+ return { classification, researchFinding: false,
+ probesRecomputed: recomputed.length, independentWilsonReference: 'Python norm.ppf and direct Wilson formula',
+ estimatorReplay: 'same implementation, not independent model training', rust, passed,
+ probes: recomputed, bundleManifestHash: verification.bundleManifestHash };
+}
+
+if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
+ assert.equal(process.argv.length, 3, 'usage: audit-e02-observations.mjs ');
+ console.log(JSON.stringify(await auditE02Observations(process.argv[2])));
+}
diff --git a/deploy/mode-r/collect-e02-observations.mjs b/deploy/mode-r/collect-e02-observations.mjs
new file mode 100644
index 00000000..a29f43bf
--- /dev/null
+++ b/deploy/mode-r/collect-e02-observations.mjs
@@ -0,0 +1,266 @@
+import assert from 'node:assert/strict';
+import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { createHash } from 'node:crypto';
+import { join, resolve } from 'node:path';
+import { BaseAnchorPublisher, FakeChainTransport } from '@ald/anchor';
+import { InMemorySignerRegistry } from '@ald/hashing';
+import { createIsolatedAdapterFactory } from '@ald/isolation';
+import { PreRegistrationBindingSchema } from '@ald/types';
+import { buildRunConfig } from '@ald/lifecycle';
+import { createProductionRuntime } from '@ald/orchestrator';
+import { autoRestore, takeSnapshot } from '@ald/ops';
+import { ReferentialScenarioEngine, ScenarioBundleRegistry, registerGeneratorConfig } from '@ald/scenario';
+import { E02_ANALYSIS_VERSION, E02_LABELS, E02_PROBES, E02_ROWS_PER_STAGE,
+ E02_REGISTERED_ROWS_PER_STAGE, E02_REGISTERED_ANALYSIS_VERSION, auditE02Rows, evaluateE02Probe } from './e02-observation-analysis.mjs';
+
+export function e02ProbeEvaluation(profile, reportCount) {
+ if (profile === 'smoke') return 'not-run-short-transport-smoke';
+ if (reportCount === 12) return 'completed';
+ if (reportCount === 0) return 'not-reached';
+ return 'incomplete';
+}
+
+export async function collectE02Observations({ directory, seed, mode, profile, softwareCommit, registration, slot }) {
+ const registered = profile === 'registered';
+ assert.ok(['full', 'smoke', 'registered'].includes(profile));
+ assert.match(seed, /^[a-f0-9]{64}$/u);
+ assert.match(softwareCommit, /^[a-f0-9]{40}$/u);
+ assert.ok(['prototype', 'research-grade'].includes(mode));
+ if (registered) {
+ assert.equal(mode, 'research-grade');
+ assert.ok(Number.isInteger(slot) && slot >= 1 && slot <= 5);
+ registration = PreRegistrationBindingSchema.parse(registration);
+ assert.equal(registration.registrationClass, 'qualification');
+ assert.equal(registration.registrationAuthority, 'repository-native');
+ assert.equal(registration.preRunAnchor.anchorClass, 'simulated');
+ assert.match(directory, /^\/evidence\/registered-e02-v3-[1-5]$/u);
+ assert.equal(directory, '/evidence/registered-e02-v3-' + slot);
+ } else {
+ assert.equal(registration, undefined);
+ assert.equal(slot, undefined);
+ assert.match(directory, /^(evidence\/development|\/evidence)\/e02-[a-z0-9-]+$/u);
+ }
+ const sampleCount = registered ? E02_REGISTERED_ROWS_PER_STAGE : profile !== 'smoke' ? E02_ROWS_PER_STAGE : 4;
+ const classification = registered ? 'prospectively-registered-software-qualification' : 'development-only';
+ const analysisVersion = registered ? E02_REGISTERED_ANALYSIS_VERSION : E02_ANALYSIS_VERSION;
+ const runId = registered ? 'registered-e02-v3-' + slot : 'e02-development';
+ const root = resolve(directory);
+ const controllerSources = await Promise.all(['collect-e02-observations.mjs', 'e02-observation-analysis.mjs']
+ .map(async (file) => ({ file, sha256: createHash('sha256').update(await readFile(new URL(file, import.meta.url))).digest('hex') })));
+ await mkdir(root); // Every attempted run, including failures, reserves its directory.
+ const clock = { now: () => new Date().toISOString() };
+ const signers = InMemorySignerRegistry.generate(runId);
+ const instances = {};
+ const captured = new Map();
+ const reports = [];
+ const factories = [];
+ const topology = [];
+ let production;
+ let restoreRecord = null;
+ let containerResourceUsage = null;
+ let resourceMeasurementFailure = null;
+ let failure = null;
+ let failureStage = 'initialization';
+ const started = performance.now();
+ const bundleRoot = join(root, 'bundles');
+
+ function openRuntime() {
+ const chain = new FakeChainTransport({ endpointLabel: registered ? 'e02-registration-simulation' : 'e02-development-simulation' });
+ const publisher = new BaseAnchorPublisher({ transport: chain, anchorClass: 'simulated', clock,
+ evidence: { listRuns: () => production.runtime.listRuns().map((run) => run.runId),
+ insertAnchorReceipt: (receipt) => production.runtime.writerFor(receipt.runId).insertAnchorReceipt(receipt),
+ readCheckpoints: (id) => production.runtime.writerFor(id).readCheckpoints(id),
+ readAnchorReceipts: (id) => production.runtime.writerFor(id).readAnchorReceipts(id) },
+ anchorAddress: `0x${'42'.repeat(20)}`, finalityPolicy: '1-confirmation',
+ retry: { attempts: 2, initialBackoffMs: 0, maxBackoffMs: 0, sleep: async () => chain.mineBlock() },
+ confirmationPoll: { attempts: 2, intervalMs: 0 } });
+ const registry = new ScenarioBundleRegistry({ directory: join(bundleRoot, 'scenario-registry'), clock });
+ return createProductionRuntime({ databasePath: join(root, 'evidence.sqlite'), bundleRoot,
+ softwareCommit, clock, signerProvider: () => signers, allowUnanchored: false,
+ anchorPolicy: 'required', anchorPublisher: publisher, scenarioBundleRegistry: registry,
+ scenarioFactory: (config) => {
+ const engine = new ReferentialScenarioEngine({ version: 1, attributeCount: 2,
+ valuesPerAttribute: 4, candidatesPerEpisode: 4, heldOutTypeCodes: [],
+ symbolInventory: Array.from({ length: 32 }, (_, index) => `S${String(index + 1).padStart(2, '0')}`),
+ interactionMode: 'cooperative-signaling' }, config.randomSeed);
+ registerGeneratorConfig({ ...engine.config }, { registry });
+ const generate = engine.generate.bind(engine);
+ engine.generate = (...args) => {
+ const instance = generate(...args);
+ if (instances[instance.scenarioRef]) assert.deepEqual(instances[instance.scenarioRef], instance);
+ instances[instance.scenarioRef] = structuredClone(instance);
+ return instance;
+ };
+ return engine;
+ },
+ ...(mode === 'research-grade' ? { adapterFactoryFor: (_config, role) => {
+ const factory = createIsolatedAdapterFactory({ track: 'scratch-rl', transport: 'container',
+ endpoint: { host: role, port: 4318, attempts: 40, retryDelayMs: 250, timeoutMs: 1000, hostLabel: role },
+ timing: 'normalized', deadlineMs: 1000 });
+ factories.push(factory);
+ return factory;
+ } } : {}),
+ });
+ }
+
+ function captureAdapters() {
+ if (mode === 'research-grade') {
+ const containerIds = Object.values(production.runtime.adaptersFor(runId)).map((adapter) => adapter.isolation.containerId);
+ assert.equal(containerIds.length, 2);
+ assert.ok(containerIds.every((id) => typeof id === 'string' && id.length > 0));
+ assert.equal(new Set(containerIds).size, 2);
+ topology.push({ stage: topology.length === 0 ? 'before-restore' : 'after-restore', containerIds });
+ }
+ for (const [role, adapter] of Object.entries(production.runtime.adaptersFor(runId))) {
+ const observe = adapter.observe.bind(adapter);
+ adapter.observe = async (observation) => {
+ const key = `${role}/${observation.turn}`;
+ assert.equal(captured.has(key), false, `repeated observation: ${key}`);
+ const row = { observation: structuredClone(observation), delivered: false,
+ stage: observation.turn < sampleCount ? 'before-restore' : 'after-restore' };
+ captured.set(key, row);
+ const begin = performance.now();
+ try { await observe(observation); row.delivered = true; }
+ finally { row.observeDurationMs = performance.now() - begin; }
+ };
+ const act = adapter.act.bind(adapter);
+ adapter.act = async (budget) => {
+ const row = captured.get(`${role}/${budget.turn}`);
+ assert.ok(row);
+ assert.equal(row.budget, undefined, 'repeated action budget');
+ row.budget = structuredClone(budget);
+ return act(budget);
+ };
+ }
+ }
+
+ async function analyseStage(stage) {
+ const runtime = production.runtime;
+ const initialization = runtime.auditLog(runId).find((event) => event.reasonCode === 'learner-initialization');
+ const provenance = initialization.details.provenance;
+ const stageReports = [];
+ for (const role of ['baby-a', 'baby-b']) {
+ const rows = [...captured.values()].filter((row) => row.stage === stage && row.observation.recipient === role);
+ for (const row of rows) {
+ const truth = instances[row.observation.scenarioRef].groundTruth;
+ row.label = E02_LABELS[truth.attributeCodes[String(truth.targetTypeCode)][0]];
+ }
+ auditE02Rows(rows, role, stage, runtime.turnRecords(runId), instances, sampleCount);
+ await runtime.attachAnalysis({ runId, path: `analysis/semantic-leakage/${stage}-${role}-inputs.json`,
+ kind: 'semantic-leakage-battery', analysisVersion,
+ actorId: registered ? 'researcher:e02' : 'researcher:e02-development', reasonCode: 'e02-observed-inputs',
+ value: { classification, stage, role, rows, provenance: provenance[role],
+ instances: Object.fromEntries(rows.map((row) => [row.observation.scenarioRef, instances[row.observation.scenarioRef]])) } });
+ for (const probe of E02_PROBES) {
+ const result = evaluateE02Probe(rows, provenance[role], `${seed}/${stage}/${role}/${probe}`, probe, sampleCount);
+ const report = { stage, role, ...result };
+ reports.push(report);
+ stageReports.push(report);
+ console.log(`${stage} ${role} ${probe}: ${result.passed ? 'pass' : 'fail'}`);
+ }
+ }
+ await runtime.attachAnalysis({ runId, path: `analysis/semantic-leakage/${stage}-reports.json`,
+ kind: 'semantic-leakage-battery', analysisVersion,
+ actorId: registered ? 'researcher:e02' : 'researcher:e02-development', reasonCode: 'e02-observed-probes', value: stageReports });
+ }
+
+ async function sampleContainerResources(scope) {
+ const peakBytes = Number((await readFile('/sys/fs/cgroup/memory.peak', 'utf8')).trim());
+ const cpu = Object.fromEntries((await readFile('/sys/fs/cgroup/cpu.stat', 'utf8')).trim().split('\n')
+ .map((line) => { const [key, value] = line.split(' '); return [key, Number(value)]; }));
+ assert.ok(Number.isSafeInteger(peakBytes) && peakBytes > 0);
+ assert.ok(Number.isSafeInteger(cpu.usage_usec) && cpu.usage_usec > 0);
+ return { peakBytes, cpuUsageMicroseconds: cpu.usage_usec, scope };
+ }
+
+ try {
+ production = openRuntime();
+ failureStage = 'run-creation';
+ await production.runtime.createRun(buildRunConfig({ runId, experimentId: 'E02', randomSeed: seed,
+ deploymentMode: mode, babyA: { track: 'scratch-rl', modelRef: 'tabular-reinforce-v1' },
+ babyB: { track: 'scratch-rl', modelRef: 'tabular-reinforce-v1' }, learningSignal: 'extrinsic-task',
+ maxTurnsPerRun: 2 * sampleCount + 1, evaluationTurns: 1, checkpointEventInterval: 1024,
+ turnResponseBudgetMs: 1000, protocolGitCommit: softwareCommit,
+ ...(registered ? { registrationClass: 'qualification', preRegistrationHash: registration.preRegistrationHash } : {}) }),
+ registered ? { preRegistration: registration } : {});
+ captureAdapters();
+ for (const stage of ['before-restore', 'after-restore']) {
+ failureStage = `${stage}-collection`;
+ for (let index = 0; index < sampleCount; index++) {
+ await production.runtime.step(runId);
+ if ((index + 1) % 100 === 0) console.log(`${stage}: ${index + 1}/${sampleCount} turns`);
+ }
+ if (profile !== 'smoke') {
+ failureStage = `${stage}-analysis`;
+ await analyseStage(stage);
+ }
+ if (stage === 'before-restore') {
+ failureStage = 'snapshot';
+ const taken = await takeSnapshot(production.runtime, join(root, 'snapshots'), { clock, softwareCommit });
+ for (const factory of factories) await factory.dispose();
+ production.close();
+ production = undefined;
+ production = openRuntime();
+ failureStage = 'restore';
+ const restored = await autoRestore(() => production.runtime, { directory: join(root, 'snapshots'), bundleRoot });
+ assert.equal(restored.ok, true);
+ assert.equal(restored.runs.length, 1);
+ assert.equal(restored.runs[0].turnMatches, true);
+ assert.equal(restored.runs[0].prefix.ok, true);
+ assert.deepEqual(restored.runs[0].policyMatches, { 'baby-a': true, 'baby-b': true });
+ restoreRecord = { snapshotDigest: taken.snapshot.digest, snapshotTurn: taken.snapshot.runs[0].turn,
+ restored, scope: 'Runtime/database handle restart with original in-memory qualification signers; not a new operator or operating-system restart' };
+ captureAdapters();
+ }
+ }
+ failureStage = 'sealing';
+ await production.runtime.attachAnalysis({ runId, path: 'analysis/semantic-leakage/restore.json',
+ kind: 'semantic-leakage-battery', analysisVersion,
+ actorId: registered ? 'researcher:e02' : 'researcher:e02-development', reasonCode: 'e02-actual-runtime-restore', value: restoreRecord });
+ await production.runtime.runToCompletion(runId);
+ const bundle = production.runtime.bundleDirFor(runId);
+ const verification = JSON.parse(await readFile(join(bundle, 'verification-report.json'), 'utf8'));
+ assert.equal(verification.exitCode, 0);
+ assert.equal(production.runtime.turnRecords(runId).length, 2 * sampleCount + 2);
+ if (mode === 'research-grade') {
+ containerResourceUsage = await sampleContainerResources(
+ 'Nursery cgroup sampled after bundle verification, before cleanup/final-summary serialization; excludes learner containers and host-side audit',
+ );
+ }
+ failureStage = 'complete';
+ } catch (error) { failure = `${error.name}: ${error.message}`; console.error(failure); }
+ finally {
+ if (mode === 'research-grade' && containerResourceUsage === null) {
+ try {
+ containerResourceUsage = await sampleContainerResources(
+ `Nursery cgroup sampled after failure during ${failureStage}; excludes learner containers and host-side audit`,
+ );
+ } catch (error) {
+ resourceMeasurementFailure = `${error.name}: ${error.message}`;
+ }
+ }
+ const adapterDiagnostics = factories.flatMap((factory) => factory.adapters.map((adapter) => ({
+ isolation: adapter.isolation,
+ diagnostics: adapter.diagnostics,
+ transportStats: adapter.transportStats,
+ })));
+ let turnRecords = null;
+ try { turnRecords = production?.runtime.turnRecords(runId).length ?? null; }
+ catch { turnRecords = null; }
+ if (production) production.close();
+ for (const factory of factories) await factory.dispose();
+ const probeEvaluation = e02ProbeEvaluation(profile, reports.length);
+ await writeFile(join(root, registered ? 'slot.json' : 'development.json'), JSON.stringify({ classification, researchFinding: false,
+ runId, seed, mode, profile, sampleCount, softwareCommit, controllerSources,
+ ...(registered ? { slot, registrationHash: registration.preRegistrationHash } : {}),
+ sourceBoundary: registered ? 'Frozen sources checked by the registered host runner' : 'Unregistered development source; base commit does not assert a clean execution tree',
+ probeEvaluation,
+ captured: [...captured.values()], reports, restoreRecord, topology, failure,
+ executionProgress: { stage: failureStage, turnRecords, capturedRows: captured.size,
+ completedProbeReports: reports.length, restoreCompleted: restoreRecord !== null },
+ adapterDiagnostics, resourceMeasurementFailure,
+ passed: failure === null && reports.length === (profile !== 'smoke' ? 12 : 0) && reports.every((report) => report.passed),
+ wallMilliseconds: performance.now() - started, resourceUsage: process.resourceUsage(), containerResourceUsage }, null, 2));
+ }
+ return { passed: failure === null && reports.length === (profile !== 'smoke' ? 12 : 0) && reports.every((report) => report.passed), failure };
+}
diff --git a/deploy/mode-r/develop-e02-observations.mjs b/deploy/mode-r/develop-e02-observations.mjs
new file mode 100644
index 00000000..33301522
--- /dev/null
+++ b/deploy/mode-r/develop-e02-observations.mjs
@@ -0,0 +1,14 @@
+import assert from 'node:assert/strict';
+import { execFileSync } from 'node:child_process';
+import { collectE02Observations } from './collect-e02-observations.mjs';
+
+// Development only: no registration claim, no promotion to E02 outcomes.
+const [directory, seed, mode, profile = 'full'] = process.argv.slice(2);
+assert.ok(process.argv.length === 5 || process.argv.length === 6,
+ 'usage: develop-e02-observations.mjs [full|smoke]');
+assert.ok(['full', 'smoke'].includes(profile));
+const softwareCommit = mode === 'prototype'
+ ? execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
+ : process.env['ALD_SOFTWARE_COMMIT'];
+const result = await collectE02Observations({ directory, seed, mode, profile, softwareCommit });
+if (!result.passed) process.exitCode = 1;
diff --git a/deploy/mode-r/docker-compose.application-fault.v1.yml b/deploy/mode-r/docker-compose.application-fault.v1.yml
new file mode 100644
index 00000000..8219da77
--- /dev/null
+++ b/deploy/mode-r/docker-compose.application-fault.v1.yml
@@ -0,0 +1,5 @@
+services:
+ controller-scenario:
+ command: ["/app/deploy/mode-r/application-fault-controller.mjs"]
+ environment:
+ ALD_MODE_R_FAULT_CASE: ${ALD_MODE_R_FAULT_CASE:?set ALD_MODE_R_FAULT_CASE}
diff --git a/deploy/mode-r/docker-compose.application-late-callback.v1.yml b/deploy/mode-r/docker-compose.application-late-callback.v1.yml
new file mode 100644
index 00000000..436c025f
--- /dev/null
+++ b/deploy/mode-r/docker-compose.application-late-callback.v1.yml
@@ -0,0 +1,13 @@
+services:
+ model-adapter-a:
+ entrypoint: ["/usr/local/bin/node"]
+ command: ["/app/deploy/mode-r/application-delayed-model-adapter.mjs"]
+ environment:
+ ALD_MODE_R_DELAYED_MODEL_SOCKET: /run/ald-mode-r/model-a/model.sock
+ ALD_MODE_R_DELAYED_MODEL_METHOD: act
+ ALD_MODE_R_DELAYED_MODEL_MS: "1500"
+
+ controller-scenario:
+ command: ["/app/deploy/mode-r/application-late-callback-controller.mjs"]
+ environment:
+ ALD_MODE_R_LATE_CALLBACK_SETTLE_MS: "2500"
diff --git a/deploy/mode-r/docker-compose.application-lifecycle.v1.yml b/deploy/mode-r/docker-compose.application-lifecycle.v1.yml
new file mode 100644
index 00000000..043ab458
--- /dev/null
+++ b/deploy/mode-r/docker-compose.application-lifecycle.v1.yml
@@ -0,0 +1,4 @@
+services:
+ controller-scenario:
+ environment:
+ ALD_MODE_R_CONTROLLER_STAGE: ${ALD_MODE_R_CONTROLLER_STAGE:?set ALD_MODE_R_CONTROLLER_STAGE}
diff --git a/deploy/mode-r/docker-compose.application-uncertain-write.v1.yml b/deploy/mode-r/docker-compose.application-uncertain-write.v1.yml
new file mode 100644
index 00000000..cd62e1dc
--- /dev/null
+++ b/deploy/mode-r/docker-compose.application-uncertain-write.v1.yml
@@ -0,0 +1,13 @@
+services:
+ evidence-writer:
+ environment:
+ ALD_MODE_R_COMMIT_TURN_RESPONSE_DELAY_MS: "1500"
+
+ gateway:
+ environment:
+ ALD_MODE_R_GATEWAY_WRITER_TIMEOUT_MS: "1000"
+
+ controller-scenario:
+ command: ["/app/deploy/mode-r/application-uncertain-write-controller.mjs"]
+ environment:
+ ALD_MODE_R_UNCERTAIN_STAGE: ${ALD_MODE_R_UNCERTAIN_STAGE:?set ALD_MODE_R_UNCERTAIN_STAGE}
diff --git a/deploy/mode-r/docker-compose.application.v1.yml b/deploy/mode-r/docker-compose.application.v1.yml
new file mode 100644
index 00000000..713859a1
--- /dev/null
+++ b/deploy/mode-r/docker-compose.application.v1.yml
@@ -0,0 +1,383 @@
+x-app: &app
+ build:
+ context: ../..
+ dockerfile: deploy/mode-r/Dockerfile
+ user: "${ALD_MODE_R_UID:-1000}:${ALD_MODE_R_GID:-1000}"
+ read_only: true
+ cap_drop: [ALL]
+ security_opt: [no-new-privileges:true]
+ pids_limit: 64
+ restart: "no"
+
+x-node-host: &node-host
+ <<: *app
+ entrypoint: ["/usr/local/bin/node"]
+
+x-common-environment: &common-environment
+ ALD_MODE_R_CONFIG: /config/run-config.json
+ ALD_SOFTWARE_COMMIT: ${ALD_SOFTWARE_COMMIT:?set ALD_SOFTWARE_COMMIT}
+
+x-config-mount: &config-mount
+ type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT:?set ALD_MODE_R_APPLICATION_ROOT}/config/run-config.json
+ target: /config/run-config.json
+ read_only: true
+
+services:
+ model-adapter-a:
+ <<: *app
+ entrypoint: ["/usr/local/bin/node", "/app/packages/isolation/bin/ald-learner-host.js"]
+ command:
+ - --transport=unix
+ - --socket-path=/run/ald-mode-r/model-a/model.sock
+ - --track=no-learning
+ - --host-label=model-adapter-a
+ network_mode: none
+ tmpfs: ["/tmp:noexec,nosuid,size=16m"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/model-a
+ target: /run/ald-mode-r/model-a
+
+ model-adapter-b:
+ <<: *app
+ entrypoint: ["/usr/local/bin/node", "/app/packages/isolation/bin/ald-learner-host.js"]
+ command:
+ - --transport=unix
+ - --socket-path=/run/ald-mode-r/model-b/model.sock
+ - --track=no-learning
+ - --host-label=model-adapter-b
+ network_mode: none
+ tmpfs: ["/tmp:noexec,nosuid,size=16m"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/model-b
+ target: /run/ald-mode-r/model-b
+
+ baby-a:
+ <<: *node-host
+ entrypoint: ["/usr/local/bin/node", "/app/packages/isolation/bin/ald-baby-host.js"]
+ command:
+ - --transport=tcp
+ - --bind=0.0.0.0
+ - --port=4318
+ - --track=no-learning
+ - --host-label=baby-a
+ - --model-socket=/run/ald-mode-r/model-a/model.sock
+ - --model-host-label=model-adapter-a
+ depends_on: [model-adapter-a]
+ networks: [baby-a-gateway]
+ tmpfs: ["/tmp:noexec,nosuid,size=16m"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/model-a
+ target: /run/ald-mode-r/model-a
+
+ baby-b:
+ <<: *node-host
+ entrypoint: ["/usr/local/bin/node", "/app/packages/isolation/bin/ald-baby-host.js"]
+ command:
+ - --transport=tcp
+ - --bind=0.0.0.0
+ - --port=4318
+ - --track=no-learning
+ - --host-label=baby-b
+ - --model-socket=/run/ald-mode-r/model-b/model.sock
+ - --model-host-label=model-adapter-b
+ depends_on: [model-adapter-b]
+ networks: [baby-b-gateway]
+ tmpfs: ["/tmp:noexec,nosuid,size=16m"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/model-b
+ target: /run/ald-mode-r/model-b
+
+ signer-baby-a-ledger:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/domain-signer-host.mjs", "baby-a-ledger", "${ALD_MODE_R_RUN_ID:?set ALD_MODE_R_RUN_ID}"]
+ environment:
+ ALD_MODE_R_SIGNER_SOCKET: /run/ald-mode-r/signer-baby-a-ledger/signer.sock
+ ALD_MODE_R_PUBLIC_KEYS_ROOT: /public-keys
+ network_mode: none
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers/baby-a-ledger
+ target: /run/ald-mode-r/signer-baby-a-ledger
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/public-keys
+ target: /public-keys
+
+ signer-baby-b-ledger:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/domain-signer-host.mjs", "baby-b-ledger", "${ALD_MODE_R_RUN_ID:?set ALD_MODE_R_RUN_ID}"]
+ environment:
+ ALD_MODE_R_SIGNER_SOCKET: /run/ald-mode-r/signer-baby-b-ledger/signer.sock
+ ALD_MODE_R_PUBLIC_KEYS_ROOT: /public-keys
+ network_mode: none
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers/baby-b-ledger
+ target: /run/ald-mode-r/signer-baby-b-ledger
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/public-keys
+ target: /public-keys
+
+ signer-channel:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/domain-signer-host.mjs", "channel", "${ALD_MODE_R_RUN_ID:?set ALD_MODE_R_RUN_ID}"]
+ environment:
+ ALD_MODE_R_SIGNER_SOCKET: /run/ald-mode-r/signer-channel/signer.sock
+ ALD_MODE_R_PUBLIC_KEYS_ROOT: /public-keys
+ network_mode: none
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers/channel
+ target: /run/ald-mode-r/signer-channel
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/public-keys
+ target: /public-keys
+
+ signer-affect:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/domain-signer-host.mjs", "affect", "${ALD_MODE_R_RUN_ID:?set ALD_MODE_R_RUN_ID}"]
+ environment:
+ ALD_MODE_R_SIGNER_SOCKET: /run/ald-mode-r/signer-affect/signer.sock
+ ALD_MODE_R_PUBLIC_KEYS_ROOT: /public-keys
+ network_mode: none
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers/affect
+ target: /run/ald-mode-r/signer-affect
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/public-keys
+ target: /public-keys
+
+ signer-audit:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/domain-signer-host.mjs", "audit", "${ALD_MODE_R_RUN_ID:?set ALD_MODE_R_RUN_ID}"]
+ environment:
+ ALD_MODE_R_SIGNER_SOCKET: /run/ald-mode-r/signer-audit/signer.sock
+ ALD_MODE_R_PUBLIC_KEYS_ROOT: /public-keys
+ network_mode: none
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers/audit
+ target: /run/ald-mode-r/signer-audit
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/public-keys
+ target: /public-keys
+
+ witness-signer:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/domain-signer-host.mjs", "witness", "${ALD_MODE_R_RUN_ID:?set ALD_MODE_R_RUN_ID}"]
+ environment:
+ ALD_MODE_R_SIGNER_SOCKET: /run/ald-mode-r/signer-witness/signer.sock
+ ALD_MODE_R_PUBLIC_KEYS_ROOT: /public-keys
+ network_mode: none
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers/witness
+ target: /run/ald-mode-r/signer-witness
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/public-keys
+ target: /public-keys
+
+ evidence-writer:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/application-evidence-writer.mjs"]
+ environment:
+ <<: *common-environment
+ ALD_MODE_R_DATABASE: /evidence/evidence.sqlite
+ ALD_MODE_R_SIGNER_ROOT: /run/ald-mode-r/signers
+ ALD_MODE_R_WRITER_SOCKET_ROOT: /run/ald-mode-r/evidence
+ depends_on:
+ - signer-baby-a-ledger
+ - signer-baby-b-ledger
+ - signer-channel
+ - signer-affect
+ - signer-audit
+ - witness-signer
+ networks: [control-plane]
+ volumes:
+ - *config-mount
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/evidence
+ target: /evidence
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-controller
+ target: /run/ald-mode-r/evidence/controller
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-gateway
+ target: /run/ald-mode-r/evidence/gateway
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-checkpoint
+ target: /run/ald-mode-r/evidence/checkpoint
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-anchor
+ target: /run/ald-mode-r/evidence/anchor
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-audit
+ target: /run/ald-mode-r/evidence/audit
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers
+ target: /run/ald-mode-r/signers
+ read_only: true
+
+ gateway:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/application-gateway.mjs"]
+ environment:
+ <<: *common-environment
+ ALD_MODE_R_GATEWAY_WRITER: /run/ald-mode-r/evidence/gateway/writer.sock
+ ALD_MODE_R_GATEWAY_SOCKET: /run/ald-mode-r/gateway-controller/gateway.sock
+ ALD_MODE_R_BABY_A_RELAY: /run/ald-mode-r/gateway-baby-a/learner.sock
+ ALD_MODE_R_BABY_B_RELAY: /run/ald-mode-r/gateway-baby-b/learner.sock
+ ALD_MODE_R_GATEWAY_JOURNAL: /gateway-state
+ depends_on: [baby-a, baby-b, evidence-writer]
+ networks: [baby-a-gateway, baby-b-gateway, control-plane]
+ volumes:
+ - *config-mount
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-gateway
+ target: /run/ald-mode-r/evidence/gateway
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/gateway-controller
+ target: /run/ald-mode-r/gateway-controller
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/gateway-baby-a
+ target: /run/ald-mode-r/gateway-baby-a
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/gateway-baby-b
+ target: /run/ald-mode-r/gateway-baby-b
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/gateway-state
+ target: /gateway-state
+
+ checkpoint:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/application-checkpoint.mjs"]
+ environment:
+ <<: *common-environment
+ ALD_MODE_R_CHECKPOINT_WRITER: /run/ald-mode-r/evidence/checkpoint/writer.sock
+ ALD_MODE_R_WITNESS_SIGNER: /run/ald-mode-r/signer-witness/signer.sock
+ ALD_MODE_R_CHECKPOINT_SERVICE: /run/ald-mode-r/checkpoint-service/service.sock
+ depends_on: [evidence-writer, witness-signer]
+ networks: [control-plane]
+ volumes:
+ - *config-mount
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-checkpoint
+ target: /run/ald-mode-r/evidence/checkpoint
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/signers/witness
+ target: /run/ald-mode-r/signer-witness
+ read_only: true
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/checkpoint-service
+ target: /run/ald-mode-r/checkpoint-service
+
+ simulated-anchor:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/application-anchor.mjs"]
+ environment:
+ <<: *common-environment
+ ALD_MODE_R_ANCHOR_WRITER: /run/ald-mode-r/evidence/anchor/writer.sock
+ ALD_MODE_R_ANCHOR_SERVICE: /run/ald-mode-r/anchor-service/service.sock
+ depends_on: [evidence-writer]
+ networks: [control-plane]
+ volumes:
+ - *config-mount
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-anchor
+ target: /run/ald-mode-r/evidence/anchor
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/anchor-service
+ target: /run/ald-mode-r/anchor-service
+
+ audit-interpreter:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/application-audit-interpreter.mjs"]
+ environment:
+ <<: *common-environment
+ ALD_MODE_R_AUDIT_WRITER: /run/ald-mode-r/evidence/audit/writer.sock
+ ALD_MODE_R_AUDIT_SERVICE: /run/ald-mode-r/audit-service/service.sock
+ depends_on: [evidence-writer]
+ networks: [control-plane]
+ volumes:
+ - *config-mount
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-audit
+ target: /run/ald-mode-r/evidence/audit
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/audit-service
+ target: /run/ald-mode-r/audit-service
+
+ controller-scenario:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/application-controller.mjs"]
+ environment:
+ <<: *common-environment
+ ALD_MODE_R_PUBLIC_KEYS_ROOT: /public-keys
+ ALD_MODE_R_CONTROLLER_WRITER: /run/ald-mode-r/evidence/controller/writer.sock
+ ALD_MODE_R_GATEWAY_SOCKET: /run/ald-mode-r/gateway-controller/gateway.sock
+ ALD_MODE_R_BABY_A_RELAY: /run/ald-mode-r/gateway-baby-a/learner.sock
+ ALD_MODE_R_BABY_B_RELAY: /run/ald-mode-r/gateway-baby-b/learner.sock
+ ALD_MODE_R_CHECKPOINT_SERVICE: /run/ald-mode-r/checkpoint-service/service.sock
+ ALD_MODE_R_ANCHOR_SERVICE: /run/ald-mode-r/anchor-service/service.sock
+ ALD_MODE_R_AUDIT_SERVICE: /run/ald-mode-r/audit-service/service.sock
+ ALD_MODE_R_OUTPUT_ROOT: /output
+ depends_on: [gateway, checkpoint, simulated-anchor, audit-interpreter]
+ networks: [control-plane]
+ volumes:
+ - *config-mount
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/public-keys
+ target: /public-keys
+ read_only: true
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/evidence-controller
+ target: /run/ald-mode-r/evidence/controller
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/gateway-controller
+ target: /run/ald-mode-r/gateway-controller
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/gateway-baby-a
+ target: /run/ald-mode-r/gateway-baby-a
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/gateway-baby-b
+ target: /run/ald-mode-r/gateway-baby-b
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/checkpoint-service
+ target: /run/ald-mode-r/checkpoint-service
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/anchor-service
+ target: /run/ald-mode-r/anchor-service
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/runtime/audit-service
+ target: /run/ald-mode-r/audit-service
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/output
+ target: /output
+
+ offline-verifier:
+ <<: *node-host
+ command: ["/app/deploy/mode-r/application-offline-verifier.mjs"]
+ environment:
+ ALD_MODE_R_OUTPUT_ROOT: /output
+ ALD_MODE_R_PARENT_BUNDLE: ${ALD_MODE_R_PARENT_BUNDLE:-}
+ depends_on:
+ controller-scenario:
+ condition: service_completed_successfully
+ network_mode: none
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_APPLICATION_ROOT}/output
+ target: /output
+
+networks:
+ baby-a-gateway:
+ internal: true
+ baby-b-gateway:
+ internal: true
+ control-plane:
+ internal: true
diff --git a/deploy/mode-r/docker-compose.e02.yml b/deploy/mode-r/docker-compose.e02.yml
new file mode 100644
index 00000000..0ac8eb0d
--- /dev/null
+++ b/deploy/mode-r/docker-compose.e02.yml
@@ -0,0 +1,21 @@
+# Prospective E02 uses one slot at a time. Limits are per container, not
+# measured resource claims or replacements for the campaign CPU/storage ceiling.
+services:
+ baby-a:
+ cpus: 1
+ mem_limit: 512m
+ memswap_limit: 512m
+ ulimits:
+ cpu: { soft: 3600, hard: 3600 }
+ baby-b:
+ cpus: 1
+ mem_limit: 512m
+ memswap_limit: 512m
+ ulimits:
+ cpu: { soft: 3600, hard: 3600 }
+ nursery-study:
+ cpus: 2
+ mem_limit: 2g
+ memswap_limit: 2g
+ ulimits:
+ cpu: { soft: 3600, hard: 3600 }
diff --git a/deploy/mode-r/docker-compose.fort.yml b/deploy/mode-r/docker-compose.fort.yml
new file mode 100644
index 00000000..f5ee6945
--- /dev/null
+++ b/deploy/mode-r/docker-compose.fort.yml
@@ -0,0 +1,9 @@
+services:
+ nursery-study:
+ environment:
+ ALD_RUN_SIGNER_SEEDS_JSON_FILE: /run/secrets/ald-run-signer-seeds.json
+ volumes:
+ - type: bind
+ source: ${ALD_RUN_SIGNER_SEEDS_JSON_FILE:?run through fort run in files mode}
+ target: /run/secrets/ald-run-signer-seeds.json
+ read_only: true
diff --git a/deploy/mode-r/docker-compose.lv01-isolated-networks.v1.yml b/deploy/mode-r/docker-compose.lv01-isolated-networks.v1.yml
new file mode 100644
index 00000000..851b4325
--- /dev/null
+++ b/deploy/mode-r/docker-compose.lv01-isolated-networks.v1.yml
@@ -0,0 +1,13 @@
+networks:
+ baby-a-gateway:
+ ipam:
+ config:
+ - subnet: ${ALD_MODE_R_BABY_A_SUBNET:?ALD_MODE_R_BABY_A_SUBNET is required}
+ baby-b-gateway:
+ ipam:
+ config:
+ - subnet: ${ALD_MODE_R_BABY_B_SUBNET:?ALD_MODE_R_BABY_B_SUBNET is required}
+ control-plane:
+ ipam:
+ config:
+ - subnet: ${ALD_MODE_R_CONTROL_PLANE_SUBNET:?ALD_MODE_R_CONTROL_PLANE_SUBNET is required}
diff --git a/deploy/mode-r/docker-compose.lv01.v1.yml b/deploy/mode-r/docker-compose.lv01.v1.yml
new file mode 100644
index 00000000..2eea94d9
--- /dev/null
+++ b/deploy/mode-r/docker-compose.lv01.v1.yml
@@ -0,0 +1,80 @@
+# LV01 must be combined with docker-compose.application.v1.yml. This overlay
+# selects the frozen recurrent track and imposes the prospective local limits;
+# it is not evidence that the selected topology has been qualified.
+services:
+ model-adapter-a:
+ command: ["--transport=unix", "--socket-path=/run/ald-mode-r/model-a/model.sock", "--track=scratch-rl", "--host-label=model-adapter-a"]
+ environment:
+ ALD_MODE_R_DELAYED_MODEL_TRACK: scratch-rl
+ cpus: 1.0
+ mem_limit: 768m
+ memswap_limit: 768m
+ model-adapter-b:
+ command: ["--transport=unix", "--socket-path=/run/ald-mode-r/model-b/model.sock", "--track=scratch-rl", "--host-label=model-adapter-b"]
+ cpus: 1.0
+ mem_limit: 768m
+ memswap_limit: 768m
+ baby-a:
+ command: ["--transport=tcp", "--bind=0.0.0.0", "--port=4318", "--track=scratch-rl", "--host-label=baby-a", "--model-socket=/run/ald-mode-r/model-a/model.sock", "--model-host-label=model-adapter-a"]
+ cpus: 0.5
+ mem_limit: 512m
+ memswap_limit: 512m
+ baby-b:
+ command: ["--transport=tcp", "--bind=0.0.0.0", "--port=4318", "--track=scratch-rl", "--host-label=baby-b", "--model-socket=/run/ald-mode-r/model-b/model.sock", "--model-host-label=model-adapter-b"]
+ cpus: 0.5
+ mem_limit: 512m
+ memswap_limit: 512m
+ evidence-writer:
+ cpus: 0.5
+ mem_limit: 512m
+ memswap_limit: 512m
+ gateway:
+ cpus: 0.5
+ mem_limit: 512m
+ memswap_limit: 512m
+ checkpoint:
+ cpus: 0.25
+ mem_limit: 256m
+ memswap_limit: 256m
+ simulated-anchor:
+ cpus: 0.25
+ mem_limit: 256m
+ memswap_limit: 256m
+ audit-interpreter:
+ cpus: 0.25
+ mem_limit: 256m
+ memswap_limit: 256m
+ controller-scenario:
+ environment:
+ ALD_MODE_R_CONTROLLER_STAGE: ${ALD_MODE_R_CONTROLLER_STAGE:-complete}
+ cpus: 1.0
+ mem_limit: 768m
+ memswap_limit: 768m
+ offline-verifier:
+ cpus: 0.5
+ mem_limit: 512m
+ memswap_limit: 512m
+ signer-baby-a-ledger:
+ cpus: 0.1
+ mem_limit: 128m
+ memswap_limit: 128m
+ signer-baby-b-ledger:
+ cpus: 0.1
+ mem_limit: 128m
+ memswap_limit: 128m
+ signer-channel:
+ cpus: 0.1
+ mem_limit: 128m
+ memswap_limit: 128m
+ signer-affect:
+ cpus: 0.1
+ mem_limit: 128m
+ memswap_limit: 128m
+ signer-audit:
+ cpus: 0.1
+ mem_limit: 128m
+ memswap_limit: 128m
+ witness-signer:
+ cpus: 0.1
+ mem_limit: 128m
+ memswap_limit: 128m
diff --git a/deploy/mode-r/docker-compose.yml b/deploy/mode-r/docker-compose.yml
new file mode 100644
index 00000000..675b7689
--- /dev/null
+++ b/deploy/mode-r/docker-compose.yml
@@ -0,0 +1,141 @@
+x-domain-signer: &domain-signer
+ build:
+ context: ../..
+ dockerfile: deploy/mode-r/Dockerfile
+ entrypoint: ["/usr/local/bin/node", "/app/deploy/mode-r/domain-signer-host.mjs"]
+ user: "${ALD_MODE_R_NURSERY_UID:-1000}:${ALD_MODE_R_NURSERY_GID:-1000}"
+ read_only: true
+ cap_drop: [ALL]
+ security_opt: [no-new-privileges:true]
+ pids_limit: 32
+ network_mode: none
+
+services:
+ baby-a:
+ build:
+ context: ../..
+ dockerfile: deploy/mode-r/Dockerfile
+ command:
+ - --transport=tcp
+ - --bind=0.0.0.0
+ - --port=4318
+ - --track=${ALD_LEARNER_TRACK:-no-learning}
+ - --host-label=baby-a
+ read_only: true
+ tmpfs:
+ - /tmp:noexec,nosuid,size=16m
+ cap_drop: [ALL]
+ security_opt: [no-new-privileges:true]
+ pids_limit: 64
+ networks: [baby-a-only]
+
+ baby-b:
+ build:
+ context: ../..
+ dockerfile: deploy/mode-r/Dockerfile
+ command:
+ - --transport=tcp
+ - --bind=0.0.0.0
+ - --port=4318
+ - --track=${ALD_LEARNER_TRACK:-no-learning}
+ - --host-label=baby-b
+ read_only: true
+ tmpfs:
+ - /tmp:noexec,nosuid,size=16m
+ cap_drop: [ALL]
+ security_opt: [no-new-privileges:true]
+ pids_limit: 64
+ networks: [baby-b-only]
+
+ nursery:
+ build:
+ context: ../..
+ dockerfile: deploy/mode-r/Dockerfile
+ entrypoint: ["/usr/local/bin/node", "/app/deploy/mode-r/verify-isolation.mjs"]
+ command: [both]
+ read_only: true
+ tmpfs:
+ - /tmp:noexec,nosuid,size=16m
+ cap_drop: [ALL]
+ security_opt: [no-new-privileges:true]
+ pids_limit: 64
+ networks: [baby-a-only, baby-b-only]
+
+ nursery-study:
+ build:
+ context: ../..
+ dockerfile: deploy/mode-r/Dockerfile
+ entrypoint: ["/usr/local/bin/node", "/app/deploy/mode-r/run-study.mjs"]
+ user: "${ALD_MODE_R_NURSERY_UID:-1000}:${ALD_MODE_R_NURSERY_GID:-1000}"
+ read_only: true
+ tmpfs:
+ - /tmp:noexec,nosuid,size=16m
+ cap_drop: [ALL]
+ security_opt: [no-new-privileges:true]
+ pids_limit: 64
+ environment:
+ ALD_SOFTWARE_COMMIT: ${ALD_SOFTWARE_COMMIT:-unknown}
+ ALD_MODE_R_SIGNER_SOCKET_ROOT: /signers
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_EVIDENCE_DIR:-/tmp/ald-mode-r-evidence}
+ target: /evidence
+ - type: bind
+ source: ${ALD_MODE_R_SIGNER_SOCKET_ROOT:-/tmp/ald-mode-r-signers}
+ target: /signers
+ read_only: true
+ networks: [baby-a-only, baby-b-only]
+
+ signer-baby-a-ledger:
+ <<: *domain-signer
+ command: ["baby-a-ledger", "mode-r-study-${ALD_LEARNER_TRACK:-no-learning}"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_SIGNER_SOCKET_ROOT:-/tmp/ald-mode-r-signers}/baby-a-ledger
+ target: /signer
+
+ signer-baby-b-ledger:
+ <<: *domain-signer
+ command: ["baby-b-ledger", "mode-r-study-${ALD_LEARNER_TRACK:-no-learning}"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_SIGNER_SOCKET_ROOT:-/tmp/ald-mode-r-signers}/baby-b-ledger
+ target: /signer
+
+ signer-channel:
+ <<: *domain-signer
+ command: ["channel", "mode-r-study-${ALD_LEARNER_TRACK:-no-learning}"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_SIGNER_SOCKET_ROOT:-/tmp/ald-mode-r-signers}/channel
+ target: /signer
+
+ signer-affect:
+ <<: *domain-signer
+ command: ["affect", "mode-r-study-${ALD_LEARNER_TRACK:-no-learning}"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_SIGNER_SOCKET_ROOT:-/tmp/ald-mode-r-signers}/affect
+ target: /signer
+
+ signer-audit:
+ <<: *domain-signer
+ command: ["audit", "mode-r-study-${ALD_LEARNER_TRACK:-no-learning}"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_SIGNER_SOCKET_ROOT:-/tmp/ald-mode-r-signers}/audit
+ target: /signer
+
+ signer-witness:
+ <<: *domain-signer
+ command: ["witness", "mode-r-study-${ALD_LEARNER_TRACK:-no-learning}"]
+ volumes:
+ - type: bind
+ source: ${ALD_MODE_R_SIGNER_SOCKET_ROOT:-/tmp/ald-mode-r-signers}/witness
+ target: /signer
+
+networks:
+ baby-a-only:
+ internal: true
+ baby-b-only:
+ internal: true
diff --git a/deploy/mode-r/domain-signer-host.mjs b/deploy/mode-r/domain-signer-host.mjs
new file mode 100644
index 00000000..173f244b
--- /dev/null
+++ b/deploy/mode-r/domain-signer-host.mjs
@@ -0,0 +1,28 @@
+import { InMemorySignerRegistry, createDomainSignerRpcServer } from '@ald/hashing';
+import { SIGNER_DOMAINS } from '@ald/types';
+import { mkdir, rename, writeFile } from 'node:fs/promises';
+
+const [domain, runId] = process.argv.slice(2);
+if (!SIGNER_DOMAINS.includes(domain) ||
+ !/^(?:mode-r-(?:study|application)-[a-z0-9-]+|lv01-(?:development|recurrent-lifecycle|late-callback|five-rejection-safety|detector)-v\d+-p\d{4}|lv01-paired-development-v\d+-p\d{4}-(?:normal|disabled|constant|random|shuffled|ledger-consistent|ledger-shuffled)|lv01-(?:commitment-window-fault|malformed-proposal)-v\d+-p\d{4}-normal|lv01-application-fault-v\d+-p\d{4}-(?:baby-peer-death|anchor-service-death))$/u.test(runId ?? '')) {
+ throw new Error('invalid qualification signer identity');
+}
+
+// Ephemeral qualification keys only. A selected registered run needs
+// prospective per-domain Fort provisioning and recovery before execution.
+const signer = InMemorySignerRegistry.generate(runId, [domain]).signer(domain);
+const socketPath = process.env['ALD_MODE_R_SIGNER_SOCKET'] ?? '/signer/signer.sock';
+const publicKeyRoot = process.env['ALD_MODE_R_PUBLIC_KEYS_ROOT'];
+if (publicKeyRoot !== undefined) {
+ await mkdir(publicKeyRoot, { recursive: true, mode: 0o755 });
+ const target = `${publicKeyRoot}/${domain}.json`;
+ const temporary = `${target}.${String(process.pid)}.tmp`;
+ await writeFile(temporary, `${JSON.stringify({
+ domain,
+ keyId: signer.keyId,
+ publicKey: signer.publicKey,
+ })}\n`, { mode: 0o644 });
+ await rename(temporary, target);
+}
+const server = await createDomainSignerRpcServer(socketPath, runId, signer);
+process.on('SIGTERM', () => server.close());
diff --git a/deploy/mode-r/e01-corpus.mjs b/deploy/mode-r/e01-corpus.mjs
new file mode 100644
index 00000000..e38eb8d7
--- /dev/null
+++ b/deploy/mode-r/e01-corpus.mjs
@@ -0,0 +1,123 @@
+import { hashCanonical, verifyHashSignature } from '@ald/hashing';
+import { decodeFrameLine, FrameAssembler } from '@ald/isolation';
+import { evaluateHostIsolationAttacks, runHiddenStateCorrelationAttack, runActiveTransportAttacks } from '@ald/redteam';
+
+export const E01_ATTEMPT_DOMAIN = 'dtsf-e01-attempt-v2';
+export const E01_TRANSPORT_SAMPLES = 40;
+export const E01_STORAGE_PATHS = [
+ ['filesystem', '/run/ald-peer-canary'],
+ ['shared-cache', '/run/ald-shared-cache/canary'],
+ ['vector-store', '/run/ald-vector-store/canary'],
+ ['replay-buffer', '/run/ald-replay-buffer/canary'],
+ ['snapshot', '/run/ald-snapshot/canary'],
+];
+
+export function e01GatewayCorpus() {
+ const texts = [
+ ['english', 'hello partner'], ['non-latin', 'مرحبا'],
+ ['unicode', '☃'], ['emoji', '😀'], ['url', 'https://example.invalid/'],
+ ['code', 'console.log(1)'], ['tool-text', 'send_message(partner)'],
+ ['json-text', '{"tool":"send"}'], ['whitespace', ' '],
+ ['leading-space', ' S01'], ['trailing-space', 'S01 '],
+ ['empty-symbol', ''], ['unregistered-symbol', 'S99'],
+ ].map(([id, text]) => ({ id, proposal: { kind: 'emit_symbols', publicArtifact: { symbols: [text] } } }));
+ return [...texts,
+ { id: 'overlength', proposal: { kind: 'emit_symbols', publicArtifact: { symbols: Array(5).fill('S01') } } },
+ { id: 'model-identifier', proposal: { kind: 'emit_symbols', publicArtifact: { symbols: ['S01'], modelGeneratedIdentifier: 'extra' } } },
+ { id: 'json-extension', proposal: { kind: 'emit_symbols', publicArtifact: { symbols: ['S01'] }, tool: 'send' } },
+ ];
+}
+
+export function e01AttemptIds() {
+ return [
+ 'detector-controls',
+ ...e01GatewayCorpus().map(({ id }) => `gateway-${id}`),
+ 'gateway-silence', ...[1, 2, 3].map((i) => `gateway-retry-${i}`),
+ ...['baby-a', 'baby-b'].flatMap((role) => E01_STORAGE_PATHS.map(([id]) => `host-${role}-${id}`)),
+ ...Array.from({ length: E01_TRANSPORT_SAMPLES }, (_, i) =>
+ ['accepted', 'rejected'].map((label) => `transport-${label}-${i + 1}`)).flat(),
+ 'gateway-allowed-control',
+ ];
+}
+
+/** Deliberately exposed fixtures qualify the detectors, not a live escape. */
+export async function runE01DetectorControls() {
+ const probe = { permissionModel: false, fsRead: 'allowed', clipboard: 'allowed', childProcess: 'allowed',
+ worker: 'allowed', network: 'allowed', envKeys: ['PLANTED_MARKER'], argvCount: 1, processId: 1 };
+ const host = evaluateHostIsolationAttacks('prototype', [probe, { ...probe, processId: 2 }]);
+ let time = 0;
+ const transport = await runActiveTransportAttacks({ deploymentMode: 'prototype', samplesPerCondition: 3,
+ timingTolerance: { maxAbsoluteMeanDifference: 0 }, sizeTolerance: { maxAbsoluteMeanDifference: 0 },
+ now: () => time, conditions: [1, 20].map((duration) => {
+ let sample = 0;
+ return { label: `planted-${duration}`, execute: async () => {
+ const measured = duration + sample++;
+ time += measured;
+ return { sizeBytes: measured, responseBody: `planted-${duration}` };
+ } };
+ }) });
+ const correlation = runHiddenStateCorrelationAttack();
+ return { id: 'detector-controls', classification: 'synthetic-detector-fixtures', host, transport, correlation,
+ passed: !host.passed && host.attempts.every((a) => !a.blocked && a.extractedByRecipient) &&
+ !transport.timingDecision.withinTolerance && !transport.sizeDecision.withinTolerance &&
+ !transport.errorDecision.withinTolerance && correlation.detectedBeforeClaim };
+}
+
+/** Validate a complete measured slot; this function makes no observations. */
+export function evaluateE01Attempts(records, publicKey) {
+ const expected = e01AttemptIds();
+ const issues = [];
+ if (JSON.stringify(records.map((r) => r.id)) !== JSON.stringify(expected)) issues.push('attempt-corpus');
+ for (const record of records) {
+ const { entryHash, writerSignature, ...unsigned } = record;
+ if (hashCanonical(E01_ATTEMPT_DOMAIN, unsigned) !== entryHash ||
+ !verifyHashSignature(entryHash, writerSignature, publicKey)) issues.push(`signature:${record.id}`);
+ if (record.passed !== true) issues.push(`disposition:${record.id}`);
+ if (record.id === 'detector-controls' && (record.classification !== 'synthetic-detector-fixtures' ||
+ record.host?.passed !== false || record.host?.attempts?.length !== 5 ||
+ record.host?.attempts?.some((a) => a.blocked !== false || a.extractedByRecipient !== true) ||
+ record.transport?.timingDecision?.withinTolerance !== false ||
+ record.transport?.sizeDecision?.withinTolerance !== false ||
+ record.transport?.errorDecision?.withinTolerance !== false ||
+ record.correlation?.detectedBeforeClaim !== true ||
+ !Number.isFinite(record.correlation?.observedCmiBits) || !Number.isFinite(record.correlation?.boundBits) ||
+ record.correlation.observedCmiBits <= record.correlation.boundBits)) issues.push('detector-controls');
+ if (record.id.startsWith('gateway-') && record.id !== 'gateway-allowed-control' &&
+ (record.delivered !== false || record.rejected !== true || record.channelEventHashes?.length !== 1 ||
+ record.recipientObservation?.delivery !== null)) issues.push(`gateway:${record.id}`);
+ if (record.id.startsWith('gateway-retry-') &&
+ record.pauseRequested !== (record.id === 'gateway-retry-3')) issues.push(`retry:${record.id}`);
+ if (record.id.startsWith('host-')) {
+ const p = record.probe;
+ if (!p || p.permissionModel !== true || p.fsRead !== 'denied' || p.clipboard !== 'denied' ||
+ p.childProcess !== 'denied' || p.worker !== 'denied' || p.network !== 'refused' ||
+ !Array.isArray(p.envKeys) || p.envKeys.length !== 0) issues.push(`host:${record.id}`);
+ const expectedPath = E01_STORAGE_PATHS.find(([id]) => record.id.endsWith(`-${id}`))?.[1];
+ if (record.readPath !== expectedPath) issues.push(`host-path:${record.id}`);
+ }
+ if (record.id.startsWith('transport-')) {
+ if (!Number.isFinite(record.durationMs) || record.durationMs < 0 || record.sizeBytes !== 8192 ||
+ record.frameHashes?.length !== 1 || record.responses?.length !== 1) issues.push(`transport:${record.id}`);
+ if (record.label === 'rejected' && JSON.stringify(record.responses) !== '[{"error":{"code":"adapter-error"}}]') issues.push(`error-body:${record.id}`);
+ if (record.label === 'accepted' && record.responses?.[0]?.ok !== 1) issues.push(`accepted-body:${record.id}`);
+ if (record.label !== record.id.split('-')[1] || record.outcome !==
+ (record.label === 'accepted' ? 'returned' : 'host-error')) issues.push(`transport-label:${record.id}`);
+ try {
+ const assembler = new FrameAssembler();
+ const responses = record.frames.map((line) => assembler.push(decodeFrameLine(line, 8192)))
+ .filter((message) => message?.kind === 'res').map((message) => message.payload);
+ if (record.frames.length !== 1 ||
+ record.frames.reduce((sum, line) => sum + Buffer.byteLength(line, 'utf8') + 1, 0) !== record.sizeBytes ||
+ JSON.stringify(record.frames.map((line) => hashCanonical('dtsf-e01-frame-v2', line))) !== JSON.stringify(record.frameHashes) ||
+ JSON.stringify(responses) !== JSON.stringify(record.responses)) issues.push(`frame-capture:${record.id}`);
+ } catch { issues.push(`frame-capture:${record.id}`); }
+ }
+ }
+ const control = records.find((r) => r.id === 'gateway-allowed-control');
+ if (control?.delivered !== true || control?.receiverResponded !== true) issues.push('delivery-positive-control');
+ const groups = ['accepted', 'rejected'].map((label) => records.filter((r) => r.label === label));
+ const means = groups.map((group) => group.reduce((sum, r) => sum + r.durationMs, 0) / group.length);
+ const timingDifferenceMs = Math.abs(means[0] - means[1]);
+ if (groups.some((g) => g.length !== E01_TRANSPORT_SAMPLES) || !Number.isFinite(timingDifferenceMs) || timingDifferenceMs > 100) issues.push('timing');
+ return { passed: issues.length === 0, issues, attempts: records.length, timingDifferenceMs };
+}
diff --git a/deploy/mode-r/e02-observation-analysis.mjs b/deploy/mode-r/e02-observation-analysis.mjs
new file mode 100644
index 00000000..f82d3a15
--- /dev/null
+++ b/deploy/mode-r/e02-observation-analysis.mjs
@@ -0,0 +1,131 @@
+import assert from 'node:assert/strict';
+import { SeededPrng, canonicalJson, hashCanonical } from '@ald/hashing';
+import { evaluateSemanticLeakage } from '@ald/leakage';
+import { assertObservationHygiene, hashObservation } from '@ald/scenario';
+import { HASH_DOMAINS } from '@ald/types';
+
+export const E02_ROWS_PER_STAGE = 816;
+export const E02_REGISTERED_ROWS_PER_STAGE = 2016;
+export const E02_ANALYSIS_VERSION = 'e02-observation-qualification-v1';
+export const E02_REGISTERED_ANALYSIS_VERSION = 'e02-observation-qualification-v2';
+export const E02_PROBES = ['metadata', 'identifier', 'timing'];
+export const E02_LABELS = ['red', 'green', 'blue', 'yellow'];
+
+// Offline labels never enter the learner observation or configuration.
+// Sensory values, target flags, type codes and outcome success are NOT features.
+function checkSampleCount(sampleCount) {
+ assert.ok([E02_ROWS_PER_STAGE, E02_REGISTERED_ROWS_PER_STAGE].includes(sampleCount));
+}
+
+export function e02Features(row, probe, sampleCount = E02_ROWS_PER_STAGE) {
+ checkSampleCount(sampleCount);
+ const { observation, budget } = row;
+ assertObservationHygiene(observation);
+ assert.deepEqual(Object.keys(observation).sort(), ['encoding', 'payload', 'recipient', 'runId', 'scenarioRef', 'turn']);
+ assert.equal(observation.encoding, 'opaque-numeric');
+ assert.deepEqual(Object.keys(budget).sort(), budget.role === 'receiver'
+ ? ['availableActions', 'candidateRefs', 'responseBudgetMs', 'role', 'turn']
+ : ['availableActions', 'responseBudgetMs', 'role', 'turn']);
+ assert.equal(budget.turn, observation.turn);
+ assert.ok(['sender', 'receiver'].includes(budget.role));
+ assert.equal(budget.responseBudgetMs, 1000);
+ assert.deepEqual(budget.availableActions, budget.role === 'receiver' ? ['select_object'] : ['emit_symbols']);
+ const refs = budget.candidateRefs ?? [];
+ assert.equal(refs.length, budget.role === 'receiver' ? 4 : 0);
+ assert.equal(new Set(refs).size, refs.length);
+ const hex = (value, prefix, length) => {
+ assert.equal(typeof value, 'string');
+ assert.match(value, new RegExp(`^${prefix}[a-f0-9]{${length}}$`, 'u'));
+ return [...value.slice(prefix.length)].map((nibble) => parseInt(nibble, 16) / 15);
+ };
+ const identifiers = [...hex(observation.scenarioRef, 'scn:', 16),
+ ...Array.from({ length: 4 }, (_, index) => refs[index] === undefined
+ ? Array(12).fill(0) : hex(refs[index], 'o:', 12)).flat()];
+ assert.ok(Number.isFinite(row.observeDurationMs) && row.observeDurationMs >= 0);
+ if (probe === 'identifier') return identifiers;
+ if (probe === 'timing') return [Math.log1p(row.observeDurationMs)];
+ assert.equal(probe, 'metadata');
+ const payload = observation.payload;
+ assert.equal(payload.length, 4);
+ assert.ok(payload.every((entry) => Array.isArray(entry) && entry.every(Number.isFinite)));
+ return [observation.turn / (2 * sampleCount), budget.role === 'receiver' ? 1 : 0,
+ payload.length / 4, ...payload.map((entry) => entry.length / 4),
+ Buffer.byteLength(canonicalJson(observation)) / 1024,
+ budget.availableActions.length / 4, refs.length / 4, ...identifiers];
+}
+
+export function auditE02Rows(rows, role, stage, turnRecords, instances, sampleCount = E02_ROWS_PER_STAGE) {
+ checkSampleCount(sampleCount);
+ assert.ok(['baby-a', 'baby-b'].includes(role));
+ assert.ok(['before-restore', 'after-restore'].includes(stage));
+ assert.equal(rows.length, sampleCount);
+ const offset = stage === 'before-restore' ? 0 : sampleCount;
+ const recordByTurn = new Map(turnRecords.map((record) => [record.turn, record]));
+ assert.equal(recordByTurn.size, turnRecords.length);
+ const refs = new Set();
+ for (const [index, row] of rows.entries()) {
+ const observation = row.observation;
+ assert.equal(observation.recipient, role);
+ assert.equal(observation.turn, index + offset);
+ assert.equal(row.stage, stage);
+ assert.equal(row.delivered, true);
+ assert.equal(refs.has(observation.scenarioRef), false);
+ refs.add(observation.scenarioRef);
+ const record = recordByTurn.get(observation.turn);
+ assert.ok(record);
+ assert.equal(record.runId, observation.runId);
+ assert.equal(record.scenarioRef, observation.scenarioRef);
+ assert.equal(record.phase, 'running');
+ assert.equal(row.budget.role, record.roles.sender === role ? 'sender' : 'receiver');
+ assert.equal(record.observationHashes[role === 'baby-a' ? 'babyA' : 'babyB'], hashObservation(observation));
+ const instance = instances[observation.scenarioRef];
+ assert.ok(instance);
+ assert.equal(instance.stateHash, hashCanonical(HASH_DOMAINS.scenarioState, {
+ scenarioRef: instance.scenarioRef, groundTruth: instance.groundTruth, observations: instance.observations,
+ }));
+ assert.equal(record.scenarioStateHash, instance.stateHash);
+ const truth = instance.groundTruth;
+ assert.equal(row.label, E02_LABELS[truth.attributeCodes[String(truth.targetTypeCode)][0]]);
+ assert.deepEqual(observation.payload, instance.observations[role]);
+ assert.deepEqual(row.budget.candidateRefs ?? [], row.budget.role === 'receiver' ? instance.candidateRefs : []);
+ for (const probe of E02_PROBES) e02Features(row, probe, sampleCount);
+ }
+ assert.equal(new Set(rows.map((row) => row.label)).size, 4);
+}
+
+// Reproduce the existing estimator's declared stratified split for inspection.
+// This uses the same PRNG; it is not an independent statistical implementation.
+export function e02Split(rows, seed, sampleCount = E02_ROWS_PER_STAGE) {
+ checkSampleCount(sampleCount);
+ const labels = [...new Set(rows.map((row) => row.label))].sort();
+ assert.equal(labels.length, 4);
+ const train = [], test = [];
+ for (const [labelIndex, label] of labels.entries()) {
+ const indices = rows.flatMap((row, index) => row.label === label ? [index] : []);
+ assert.ok(indices.length >= 4);
+ const shuffled = new SeededPrng(`${seed}/split/${labelIndex}`).shuffle(indices);
+ const count = Math.floor(indices.length / 4);
+ test.push(...shuffled.slice(0, count));
+ train.push(...shuffled.slice(count));
+ }
+ assert.equal(new Set([...train, ...test]).size, rows.length);
+ assert.ok(test.length >= (sampleCount === E02_REGISTERED_ROWS_PER_STAGE ? 501 : 200));
+ return { train, test };
+}
+
+export function evaluateE02Probe(rows, provenance, seed, probe, sampleCount = E02_ROWS_PER_STAGE) {
+ checkSampleCount(sampleCount);
+ assert.equal(rows.length, sampleCount);
+ assert.equal(provenance.track, 'scratch-rl');
+ const split = e02Split(rows, seed, sampleCount);
+ const input = { provenance,
+ frozenFeatures: rows.map((row) => ({ label: row.label, features: e02Features(row, probe, sampleCount) })),
+ preRegistration: { seed, confidence: 0.95, permutations: 20,
+ maximumAccuracyAdvantage: 0.1, minimumTestRows: sampleCount === E02_REGISTERED_ROWS_PER_STAGE ? 501 : 200,
+ positiveControlMinimumAdvantage: 0.2 } };
+ const result = evaluateSemanticLeakage(input);
+ assert.equal(result.linearProbe.trainRows, split.train.length);
+ assert.equal(result.linearProbe.testRows, split.test.length);
+ return { probe, input, split, result, passed: result.classification === 'strict-ungrounded-eligible'
+ && result.linearProbe.negativeBoundDecision === 'below-bound' && result.linearProbe.positiveControl.detected };
+}
diff --git a/deploy/mode-r/e02-slot-contract.mjs b/deploy/mode-r/e02-slot-contract.mjs
new file mode 100644
index 00000000..9988c428
--- /dev/null
+++ b/deploy/mode-r/e02-slot-contract.mjs
@@ -0,0 +1,52 @@
+import assert from 'node:assert/strict';
+import { compileRegistrationPacket } from '@ald/analysis';
+import { PreRegistrationBindingSchema } from '@ald/types';
+import { E02_REGISTERED_ROWS_PER_STAGE } from './e02-observation-analysis.mjs';
+
+export function e02RootBuildInputs(packageJson) {
+ for (const hook of ['preinstall', 'install', 'postinstall', 'prepare', 'prebuild', 'postbuild']) {
+ assert.equal(packageJson.scripts[hook], undefined, `unregistered root lifecycle hook: ${hook}`);
+ }
+ return { name: packageJson.name, type: packageJson.type, engines: packageJson.engines,
+ dependencies: packageJson.dependencies, devDependencies: packageJson.devDependencies,
+ buildCommand: packageJson.scripts.build };
+}
+
+// A second boundary inside the container; the host also verifies Git ancestry
+// and every frozen source before starting any registered learner observation.
+export function validateE02SlotContract(packet, candidate, slot, seed, packetPath) {
+ assert.ok(Number.isInteger(slot) && slot >= 1 && slot <= 5);
+ assert.match(seed, /^[a-f0-9]{64}$/u);
+ assert.equal(packet.artifact.experimentId, 'E02');
+ assert.equal(packet.artifact.registrationClass, 'qualification');
+ assert.equal(packet.researchFinding, false);
+ const bindings = Object.fromEntries(packet.artifact.bindings.map((entry) => [entry.key, entry.content]));
+ const compiled = compileRegistrationPacket({ experimentId: 'E02', registrationClass: 'qualification', bindings });
+ assert.deepEqual(compiled.artifact, packet.artifact);
+ assert.equal(compiled.preRegistrationHash, packet.preRegistrationHash);
+ assert.equal(bindings.selectedSeedPrefix.primary.length, 5);
+ assert.equal(new Set(bindings.selectedSeedPrefix.primary.map((entry) => entry.scenario)).size, 5);
+ assert.deepEqual(bindings.selectedSeedPrefix.primary.map((entry) => entry.slot), [1, 2, 3, 4, 5]);
+ assert.deepEqual(bindings.selectedSeedPrefix.primary[slot - 1], { slot, scenario: seed });
+ assert.equal(bindings.runConfigurations.length, 1);
+ const config = bindings.runConfigurations[0];
+ assert.equal(config.rowsPerRolePerStage, E02_REGISTERED_ROWS_PER_STAGE);
+ assert.equal(config.totalTurnsPerSlot, 2 * E02_REGISTERED_ROWS_PER_STAGE + 2);
+ assert.equal(config.deploymentMode, 'research-grade');
+ assert.equal(config.learnerTrack, 'scratch-rl');
+ assert.equal(config.turnResponseBudgetMs, 1000);
+ const binding = PreRegistrationBindingSchema.parse(candidate);
+ assert.equal(binding.registrationClass, 'qualification');
+ assert.equal(binding.registrationAuthority, 'repository-native');
+ assert.equal(binding.preRegistrationHash, packet.preRegistrationHash);
+ assert.match(packetPath, /^protocols\/e02-registration\.v[123]\.json$/u);
+ assert.equal(binding.repositoryRegistration.path, packetPath);
+ assert.equal(binding.repositoryRegistration.artifactSha256, packet.preRegistrationHash);
+ assert.equal(binding.preRunAnchor.anchorClass, 'simulated');
+ assert.equal(binding.preRunAnchor.network, 'base-sepolia');
+ assert.equal(binding.preRunAnchor.chainId, 84532);
+ assert.equal(binding.preRunAnchor.status, 'confirmed');
+ assert.ok(Number.isSafeInteger(binding.preRunAnchor.blockNumber) && binding.preRunAnchor.blockNumber >= 0);
+ assert.equal(binding.preRunAnchor.inputData, `0x${packet.preRegistrationHash.slice(7)}`);
+ return binding;
+}
diff --git a/deploy/mode-r/lv01-detector-contract.mjs b/deploy/mode-r/lv01-detector-contract.mjs
new file mode 100644
index 00000000..55a6c3a3
--- /dev/null
+++ b/deploy/mode-r/lv01-detector-contract.mjs
@@ -0,0 +1,30 @@
+import assert from 'node:assert/strict';
+
+import { RECURRENT_ARCHITECTURE } from '@ald/learners';
+
+export const LV01_DETECTOR_ROWS_PER_ROLE_PER_STAGE = 2_016;
+export const LV01_DETECTOR_STAGES = ['before-restore', 'after-restore'];
+
+export function validateLv01DetectorConfig(config) {
+ assert.equal(config.experimentId, 'LV01');
+ assert.equal(config.deploymentMode, 'research-grade');
+ assert.equal(config.babyA.track, 'scratch-rl');
+ assert.equal(config.babyB.track, 'scratch-rl');
+ assert.equal(config.babyA.modelRef, RECURRENT_ARCHITECTURE);
+ assert.equal(config.babyB.modelRef, RECURRENT_ARCHITECTURE);
+ assert.equal(config.maxTurnsPerRun, LV01_DETECTOR_ROWS_PER_ROLE_PER_STAGE);
+ assert.equal(config.evaluationTurns, 0);
+ assert.equal(config.turnResponseBudgetMs, 1_000);
+ assert.ok(config.ledgerValuePlan !== undefined);
+}
+
+export function validateLv01DetectorObservation(result) {
+ assert.equal(result.classification, 'lv01-selected-detector-observation-stage');
+ assert.equal(result.researchFinding, false);
+ assert.equal(result.learnerArchitecture, RECURRENT_ARCHITECTURE);
+ assert.equal(result.failure, undefined);
+ assert.equal(result.state, 'sealed');
+ assert.equal(result.turnCount, LV01_DETECTOR_ROWS_PER_ROLE_PER_STAGE);
+ assert.equal(result.captured.length, result.turnCount * 2);
+ assert.ok(result.captured.every((entry) => entry.delivered));
+}
diff --git a/deploy/mode-r/package.json b/deploy/mode-r/package.json
new file mode 100644
index 00000000..af722c21
--- /dev/null
+++ b/deploy/mode-r/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@ald/mode-r-verifier",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@ald/analysis": "workspace:*",
+ "@ald/anchor": "workspace:*",
+ "@ald/checkpoint": "workspace:*",
+ "@ald/evidence": "workspace:*",
+ "@ald/gateway": "workspace:*",
+ "@ald/hashing": "workspace:*",
+ "@ald/isolation": "workspace:*",
+ "@ald/learners": "workspace:*",
+ "@ald/leakage": "workspace:*",
+ "@ald/lifecycle": "workspace:*",
+ "@ald/orchestrator": "workspace:*",
+ "@ald/ops": "workspace:*",
+ "@ald/redteam": "workspace:*",
+ "@ald/scenario": "workspace:*",
+ "@ald/types": "workspace:*",
+ "@ald/verifier": "workspace:*"
+ }
+}
diff --git a/deploy/mode-r/run-e01-qualification.mjs b/deploy/mode-r/run-e01-qualification.mjs
new file mode 100644
index 00000000..e1517a27
--- /dev/null
+++ b/deploy/mode-r/run-e01-qualification.mjs
@@ -0,0 +1,239 @@
+import assert from 'node:assert/strict';
+
+import { createIsolatedAdapterFactory } from '@ald/isolation';
+import {
+ buildConformanceRunConfig,
+ loadLearnerContract,
+ RecordingLedgerClient,
+} from '@ald/learners';
+import {
+ evaluateHostIsolationAttacks,
+ runActiveTransportAttacks,
+ runSideChannelRedTeamSuite,
+ SIDE_CHANNEL_ATTACK_CATEGORIES,
+} from '@ald/redteam';
+import { fixedTokenInventory } from '@ald/types';
+
+const PORT = 4318;
+const slot = Number(process.argv[2]);
+const seed = process.argv[3];
+if (!Number.isInteger(slot) || slot < 1 || typeof seed !== 'string' || !/^[a-f0-9]{64}$/u.test(seed)) {
+ throw new Error('usage: run-e01-qualification.mjs <64-hex-seed>');
+}
+const runId = `run-e01-qualified-${String(slot).padStart(2, '0')}`;
+
+function factoryFor(host, label) {
+ return createIsolatedAdapterFactory({
+ track: 'no-learning',
+ transport: 'container',
+ endpoint: {
+ host,
+ port: PORT,
+ attempts: 40,
+ retryDelayMs: 250,
+ timeoutMs: 500,
+ hostLabel: label,
+ },
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ });
+}
+
+async function initialized(host, role) {
+ const factory = factoryFor(host, role);
+ const adapter = factory.create();
+ const config = buildConformanceRunConfig('no-learning', {
+ deploymentMode: 'research-grade',
+ runId,
+ seed: `${seed}-${role}`,
+ learningSignal: 'none',
+ });
+ await adapter.init({
+ runId,
+ role,
+ babyId: role === 'baby-a' ? 'A' : 'B',
+ config,
+ learnerContract: loadLearnerContract('no-learning'),
+ seed: `${seed}-private-${role}`,
+ symbolInventory: fixedTokenInventory(config.symbolInventorySize),
+ ledger: new RecordingLedgerClient(runId, role),
+ });
+ return { adapter, config, factory };
+}
+
+async function transportObservation(adapter, execute) {
+ const before = adapter.transportStats?.bytesIn ?? 0;
+ await execute();
+ const after = adapter.transportStats?.bytesIn ?? 0;
+ return {
+ sizeBytes: after - before,
+ responseBody: '{"turnComplete":true}',
+ };
+}
+
+function transportAttackOptions(adapter) {
+ return {
+ // 47 observations per label plus the six Gateway attacks equals exactly
+ // 100 recipient-visible attempts in each registered topology slot.
+ samplesPerCondition: 47,
+ timingTolerance: { maxAbsoluteMeanDifference: 100 },
+ sizeTolerance: { maxAbsoluteMeanDifference: 0 },
+ conditions: [
+ {
+ label: 'accepted',
+ execute: () =>
+ transportObservation(adapter, () =>
+ adapter.observe({
+ runId,
+ turn: 2,
+ recipient: 'baby-a',
+ encoding: 'opaque-numeric',
+ payload: [[0, 1, 1], [1, 0, 0]],
+ scenarioRef: `scenario:e01:${seed}`,
+ }),
+ ),
+ },
+ {
+ label: 'rejected',
+ execute: () =>
+ transportObservation(adapter, async () => {
+ try {
+ await adapter.receive({
+ runId,
+ turn: 2,
+ logicalSender: 'baby-b',
+ carrier: 'fixed-token',
+ publicArtifact: { symbols: 'invalid' },
+ channelEventHash: `sha256:${'0'.repeat(64)}`,
+ });
+ throw new Error('malformed delivery unexpectedly reached the adapter');
+ } catch (error) {
+ assert.equal(error?.code, 'host-error');
+ }
+ }),
+ },
+ ],
+ };
+}
+
+function exposedProbe(processId) {
+ return {
+ permissionModel: false,
+ fsRead: 'allowed',
+ clipboard: 'allowed',
+ childProcess: 'allowed',
+ worker: 'allowed',
+ network: 'allowed',
+ envKeys: ['PATH'],
+ argvCount: 1,
+ processId,
+ };
+}
+
+async function detectorPositiveControls() {
+ const host = evaluateHostIsolationAttacks('prototype', [
+ exposedProbe(1),
+ exposedProbe(2),
+ ]);
+ let time = 0;
+ let pendingDuration = 0;
+ const transport = await runActiveTransportAttacks({
+ deploymentMode: 'prototype',
+ samplesPerCondition: 3,
+ timingTolerance: { maxAbsoluteMeanDifference: 0 },
+ sizeTolerance: { maxAbsoluteMeanDifference: 0 },
+ now: () => {
+ time += pendingDuration;
+ pendingDuration = 0;
+ return time;
+ },
+ conditions: [
+ {
+ label: 'accepted',
+ execute: async () => {
+ pendingDuration = 1;
+ return { sizeBytes: 128, responseBody: '{"status":"accepted"}' };
+ },
+ },
+ {
+ label: 'rejected',
+ execute: async () => {
+ pendingDuration = 20;
+ return { sizeBytes: 32, responseBody: '{"status":"rejected"}' };
+ },
+ },
+ ],
+ });
+ return {
+ hostExposureDetected: !host.passed && host.attempts.every((attempt) => !attempt.blocked),
+ timingLeakDetected: !transport.timingDecision.withinTolerance,
+ sizeLeakDetected: !transport.sizeDecision.withinTolerance,
+ errorShapeLeakDetected: !transport.errorDecision.withinTolerance,
+ };
+}
+
+const a = await initialized('baby-a', 'baby-a');
+const b = await initialized('baby-b', 'baby-b');
+try {
+ assert.equal(a.adapter.isolation.boundary, 'separate-container');
+ assert.equal(b.adapter.isolation.boundary, 'separate-container');
+ assert.ok(a.adapter.isolation.containerId);
+ assert.ok(b.adapter.isolation.containerId);
+ assert.notEqual(a.adapter.isolation.containerId, b.adapter.isolation.containerId);
+
+ const [probeA, probeB] = await Promise.all([
+ a.adapter.probeIsolation({
+ readPath: '/run/ald-peer-secret',
+ connect: { host: 'baby-b', port: PORT, timeoutMs: 300 },
+ }),
+ b.adapter.probeIsolation({
+ readPath: '/run/ald-peer-secret',
+ connect: { host: 'baby-a', port: PORT, timeoutMs: 300 },
+ }),
+ ]);
+ const report = await runSideChannelRedTeamSuite({
+ config: a.config,
+ hostProbes: [probeA, probeB],
+ transport: transportAttackOptions(a.adapter),
+ });
+ const positiveControls = await detectorPositiveControls();
+ const primaryAttackAttempts =
+ report.gateway.attempts.length + report.transport.timing.totalSamples;
+ const allPositiveControlsDetected = Object.values(positiveControls).every(Boolean);
+ const passed =
+ report.passed &&
+ report.claimEligible &&
+ primaryAttackAttempts === 100 &&
+ report.host.attempts.length === 5 &&
+ Object.keys(report.categories).length === SIDE_CHANNEL_ATTACK_CATEGORIES.length &&
+ allPositiveControlsDetected;
+ const output = {
+ schemaVersion: 1,
+ slot,
+ scenarioSeed: seed,
+ runId,
+ topology: {
+ mode: 'research-grade',
+ boundary: 'separate-container',
+ containerIds: [a.adapter.isolation.containerId, b.adapter.isolation.containerId],
+ distinctLearnerContainers:
+ a.adapter.isolation.containerId !== b.adapter.isolation.containerId,
+ directPeerRoutes: [probeA.network, probeB.network],
+ },
+ primaryAttackAttempts,
+ hostCapabilityAttempts: report.host.attempts.length,
+ registeredCategories: [...SIDE_CHANNEL_ATTACK_CATEGORIES],
+ categoryDecisions: report.categories,
+ gateway: report.gateway,
+ host: report.host,
+ transport: report.transport,
+ correlationDetector: report.correlation,
+ positiveControls,
+ allPositiveControlsDetected,
+ passed,
+ };
+ process.stdout.write(`${JSON.stringify(output)}\n`);
+ if (!passed) process.exitCode = 1;
+} finally {
+ await Promise.all([a.factory.dispose(), b.factory.dispose()]);
+}
diff --git a/deploy/mode-r/run-e01-v2-slot.mjs b/deploy/mode-r/run-e01-v2-slot.mjs
new file mode 100644
index 00000000..c715f84d
--- /dev/null
+++ b/deploy/mode-r/run-e01-v2-slot.mjs
@@ -0,0 +1,217 @@
+import assert from 'node:assert/strict';
+import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+import { BaseAnchorPublisher, FakeChainTransport } from '@ald/anchor';
+import { EvidenceCheckpointService } from '@ald/checkpoint';
+import { SqliteEvidenceWriter, exportRunBundle, openEvidenceDatabase } from '@ald/evidence';
+import { SymbolGatewayImpl } from '@ald/gateway';
+import { hashCanonical, InMemorySignerRegistry } from '@ald/hashing';
+import { ContainerHostTransport, RemoteLearnerAdapter, decodeFrameLine, FrameAssembler, LineReader } from '@ald/isolation';
+import { loadLearnerContract } from '@ald/learners';
+import { buildRunConfig } from '@ald/lifecycle';
+import { CLAIM_BOUNDARY_STATEMENTS, GENESIS_HASH, HASH_DOMAINS, fixedTokenInventory } from '@ald/types';
+import { verifyBundle } from '@ald/verifier';
+
+import { E01_ATTEMPT_DOMAIN, E01_STORAGE_PATHS, E01_TRANSPORT_SAMPLES, e01GatewayCorpus, evaluateE01Attempts, runE01DetectorControls } from './e01-corpus.mjs';
+
+const [slotText, seed, outputRoot, softwareCommit, registrationFile] = process.argv.slice(2);
+const slot = Number(slotText);
+assert.ok(Number.isInteger(slot) && slot >= 1 && slot <= 5);
+assert.match(seed, /^[a-f0-9]{64}$/u);
+assert.match(softwareCommit, /^[a-f0-9]{40}$/u);
+assert.equal(outputRoot, '/evidence');
+const development = registrationFile === 'development';
+const binding = development ? undefined : JSON.parse(await readFile(registrationFile, 'utf8'));
+const runId = `${development ? 'development' : 'registered'}-e01-v2-${slot}`;
+const runRoot = join(outputRoot, runId);
+await mkdir(runRoot); // Deliberately refuses to overwrite any earlier attempt.
+const clock = { now: () => new Date().toISOString() };
+const signers = InMemorySignerRegistry.generate(runId);
+const witness = signers.signer('witness');
+const database = openEvidenceDatabase(join(runRoot, 'evidence.sqlite'));
+const writer = new SqliteEvidenceWriter({ database, signers, clock, softwareCommit });
+const contract = loadLearnerContract('no-learning');
+const config = buildRunConfig({
+ runId, experimentId: 'E01', randomSeed: seed, deploymentMode: 'research-grade',
+ babyA: { track: 'no-learning' }, babyB: { track: 'no-learning' }, learningSignal: 'none',
+ maxSymbolsPerMessage: 4, maxConsecutiveRejections: 3, turnResponseBudgetMs: 1000,
+ protocolGitCommit: softwareCommit, preRegistrationHash: binding?.preRegistrationHash ?? GENESIS_HASH,
+ promptBundleHash: hashCanonical(HASH_DOMAINS.promptBundle, { 'no-learning': contract.text }),
+});
+const { configurationHash } = writer.registerRun(config);
+const checkpoints = new EvidenceCheckpointService({ evidence: writer, signers, clock, softwareCommit });
+const gateway = new SymbolGatewayImpl({ runId, config, seed, symbolInventory: fixedTokenInventory(32) }, writer);
+const records = [];
+const adapters = [];
+let failure = null;
+let topology = null;
+
+async function record(observation) {
+ const unsigned = { ...observation, runId, slot, scenarioSeed: seed, recordedAt: clock.now(), writerKeyId: witness.keyId };
+ const entryHash = hashCanonical(E01_ATTEMPT_DOMAIN, unsigned);
+ const signed = { ...unsigned, entryHash, writerSignature: await witness.sign(entryHash) };
+ await writer.appendAnalysisAttachment({
+ runId, path: `analysis/red-team-observation/${observation.id}.json`, kind: 'red-team-observation',
+ analysisVersion: 'e01-explicit-v2', value: signed, actorId: 'nursery:e01', reasonCode: 'measured-isolation-attempt',
+ });
+ records.push(signed);
+}
+
+async function connect(role) {
+ const transport = new ContainerHostTransport({ host: role, port: 4318, attempts: 40, retryDelayMs: 250, timeoutMs: 500, hostLabel: role });
+ const observed = { frames: [], responses: [], errors: [] };
+ const assembler = new FrameAssembler();
+ const lines = new LineReader(8192, (line) => {
+ observed.frames.push(line);
+ try {
+ const message = assembler.push(decodeFrameLine(line, 8192));
+ if (message?.kind === 'res') observed.responses.push(message.payload);
+ } catch (error) { observed.errors.push(error.message); }
+ }, (error) => observed.errors.push(error.message));
+ const monitoredTransport = {
+ boundary: transport.boundary, hostLabel: role, current: () => undefined,
+ terminate: () => transport.terminate(),
+ open: async () => {
+ const channel = await transport.open();
+ return {
+ kind: channel.kind, write: (lines) => channel.write(lines), close: () => channel.close(),
+ closeCode: () => channel.closeCode?.(), onClose: (handler) => channel.onClose(handler),
+ onLine: (handler) => channel.onLine((chunk) => {
+ lines.push(chunk);
+ handler(chunk);
+ }),
+ };
+ },
+ };
+ const adapter = new RemoteLearnerAdapter({ track: 'no-learning', transport: monitoredTransport, timing: 'normalized', deadlineMs: 1000 });
+ adapters.push(adapter);
+ const ledger = { turn: 0, append: (draft, options) => writer.appendLedgerEvent({ runId,
+ babyId: role === 'baby-a' ? 'A' : 'B', turn: ledger.turn, draft, ...options }) };
+ await adapter.init({ runId, role, babyId: role === 'baby-a' ? 'A' : 'B', config,
+ learnerContract: contract, seed: `${seed}:${role}`, symbolInventory: fixedTokenInventory(32), ledger });
+ return { adapter, observed, ledger };
+}
+
+function draft(turn) {
+ return { eventType: 'intention.recorded', contentSchema: 'agent-native-ledger', subjectId: 'symbol:S01',
+ content: { artifactRef: `proposal:${turn}` }, blindingNonce: `nonce:${turn}`, evidenceRefs: [] };
+}
+
+try {
+ await checkpoints.createCheckpoint(runId, 'run-initialized');
+ await record(await runE01DetectorControls());
+ const a = await connect('baby-a');
+ const b = await connect('baby-b');
+ topology = { containerIds: [a.adapter.isolation.containerId, b.adapter.isolation.containerId], boundary: 'separate-container' };
+ assert.ok(topology.containerIds.every(Boolean));
+ assert.notEqual(...topology.containerIds);
+ let turn = 0;
+ async function attack(id, proposal, timeout = false, reset = true) {
+ turn += 1;
+ if (reset) gateway.resetRejectionCounter();
+ const before = writer.readEvents(runId, 'channel').length;
+ const result = timeout ? await gateway.rejectForTimeout({ turn, sender: 'baby-a', recipient: 'baby-b' }, 'baby-a')
+ : await gateway.submitProposal({ turn, sender: 'baby-a', recipient: 'baby-b' }, { proposal, privateLedgerDraft: draft(turn) });
+ const events = writer.readEvents(runId, 'channel').slice(before);
+ const delivery = gateway.deliveryFor(turn, 'baby-b');
+ await record({ id, inputHash: hashCanonical('dtsf-e01-input-v2', { proposal: proposal ?? null, timeout }),
+ rejected: result.kind === 'rejected', delivered: delivery !== undefined,
+ recipientObservation: { delivery: delivery ?? null }, channelEventHashes: events.map((e) => e.entryHash),
+ pauseRequested: result.kind === 'rejected' && result.pauseRequested,
+ passed: result.kind === 'rejected' && delivery === undefined && events.length === 1 });
+ }
+ for (const { id, proposal } of e01GatewayCorpus()) await attack(`gateway-${id}`, proposal);
+ await attack('gateway-silence', undefined, true);
+ for (let retry = 1; retry <= 3; retry += 1) {
+ await attack(`gateway-retry-${retry}`, { kind: 'emit_symbols', publicArtifact: { symbols: ['S99'] } }, false, retry === 1);
+ }
+ for (const [role, peer, connected] of [['baby-a', 'baby-b', a], ['baby-b', 'baby-a', b]]) {
+ for (const [id, readPath] of E01_STORAGE_PATHS) {
+ const probe = await connected.adapter.probeIsolation({ readPath, connect: { host: peer, port: 4318, timeoutMs: 300 } });
+ await record({ id: `host-${role}-${id}`, readPath, probe,
+ passed: probe.permissionModel && probe.fsRead === 'denied' && probe.clipboard === 'denied' &&
+ probe.childProcess === 'denied' && probe.worker === 'denied' && probe.network === 'refused' && probe.envKeys.length === 0 });
+ }
+ }
+ for (let sample = 1; sample <= E01_TRANSPORT_SAMPLES; sample += 1) {
+ for (const label of ['accepted', 'rejected']) {
+ const frameStart = a.observed.frames.length;
+ const responseStart = a.observed.responses.length;
+ const started = performance.now();
+ let outcome = 'returned';
+ if (label === 'accepted') {
+ await a.adapter.observe({ runId, turn: 100 + sample, recipient: 'baby-a', encoding: 'opaque-numeric', payload: [[0, 1, 1]], scenarioRef: `scenario:e01:${seed}` });
+ } else {
+ const variants = [ { symbols: 'invalid' }, { symbols: [17] }, { symbols: [], tool: 'extra' }, { symbols: null } ];
+ try {
+ await a.adapter.receive({ runId, turn: 100 + sample, logicalSender: 'baby-b', carrier: 'fixed-token',
+ publicArtifact: variants[(sample - 1) % variants.length], channelEventHash: GENESIS_HASH });
+ } catch (error) { outcome = error.code; }
+ }
+ const durationMs = performance.now() - started;
+ const frames = a.observed.frames.slice(frameStart);
+ const responses = a.observed.responses.slice(responseStart);
+ await record({ id: `transport-${label}-${sample}`, label, durationMs,
+ sizeBytes: frames.reduce((sum, line) => sum + Buffer.byteLength(line, 'utf8') + 1, 0),
+ frames, frameHashes: frames.map((line) => hashCanonical('dtsf-e01-frame-v2', line)), responses,
+ outcome, passed: label === 'accepted' ? outcome === 'returned' : outcome === 'host-error' });
+ }
+ }
+ assert.deepEqual(a.observed.errors, []);
+ assert.deepEqual(b.observed.errors, []);
+ gateway.resetRejectionCounter();
+ turn += 1;
+ const accepted = await gateway.submitProposal({ turn, sender: 'baby-a', recipient: 'baby-b' },
+ { proposal: { kind: 'emit_symbols', publicArtifact: { symbols: ['S01'] } }, privateLedgerDraft: draft(turn) });
+ const delivery = accepted.kind === 'accepted' ? accepted.delivery : undefined;
+ let receiverResponded = false;
+ let interpretationEventHash = null;
+ if (delivery) {
+ b.ledger.turn = turn;
+ const interpretation = await b.adapter.receive(delivery);
+ receiverResponded = interpretation.channelEventHash === delivery.channelEventHash;
+ if (receiverResponded) interpretationEventHash = (await gateway.submitInterpretation(
+ { turn, sender: 'baby-a', recipient: 'baby-b' }, 'baby-b', interpretation)).entryHash;
+ }
+ await record({ id: 'gateway-allowed-control', delivered: delivery !== undefined, receiverResponded,
+ channelEventHash: delivery?.channelEventHash ?? null, interpretationEventHash,
+ passed: accepted.kind === 'accepted' && receiverResponded });
+} catch (error) { failure = `${error.name}: ${error.message}`; }
+
+await writeFile(join(runRoot, 'attempt-status.json'), `${JSON.stringify({ failure, recordedAttempts: records.length })}\n`);
+try {
+ const decision = evaluateE01Attempts(records, witness.publicKey);
+ if (failure !== null) decision.passed = false;
+ const checkpoint = await checkpoints.createCheckpoint(runId, 'run-sealed');
+ const chain = new FakeChainTransport({ endpointLabel: 'e01-v2-local-simulation' });
+ const publisher = new BaseAnchorPublisher({ transport: chain, anchorClass: 'simulated', clock,
+ evidence: { listRuns: () => [runId], insertAnchorReceipt: (r) => writer.insertAnchorReceipt(r),
+ readCheckpoints: (id) => writer.readCheckpoints(id), readAnchorReceipts: (id) => writer.readAnchorReceipts(id) },
+ anchorAddress: `0x${'42'.repeat(20)}`, finalityPolicy: '1-confirmation',
+ retry: { attempts: 2, initialBackoffMs: 0, maxBackoffMs: 0, sleep: async () => chain.mineBlock() },
+ confirmationPoll: { attempts: 2, intervalMs: 0 },
+ });
+ const anchor = await publisher.anchorAndConfirm(checkpoint);
+ writer.appendExperimentRecord({ version: 1, recordVersion: 1, runId, experimentId: 'E01', deploymentMode: 'research-grade',
+ learnerContractVersion: '1', runConfigRef: configurationHash, protocolGitCommit: softwareCommit,
+ preRegistrationHash: config.preRegistrationHash, checkpointManifestRef: checkpoint.checkpointHash,
+ anchorTxRef: anchor.transactionHash, verifierReportRef: 'verification-report.json',
+ disposition: decision.passed ? 'valid' : 'invalid', claimBoundaryStatement: CLAIM_BOUNDARY_STATEMENTS['research-grade'], deviations: [],
+ analysisAttachmentRefs: writer.readAnalysisAttachments(runId).map((a) => a.descriptor.sha256) });
+ const bundleDir = join(runRoot, 'bundle');
+ await exportRunBundle(writer, runId, bundleDir, { softwareCommit, learnerContracts: [{ track: 'no-learning', version: '1', text: contract.text }],
+ ...(binding ? { preRegistration: binding } : {}) });
+ await checkpoints.writeProofFiles(runId, bundleDir, { maxInclusionPerTree: 512 });
+ const verification = await verifyBundle(bundleDir, { verifierVersion: 'e01-v2', now: clock.now, writeReport: true });
+ const result = { schemaVersion: 2, classification: development ? 'development-only' : 'prospectively-registered-software-qualification',
+ researchFinding: false, slot, scenarioSeed: seed, runId, softwareCommit, topology, witnessPublicKey: witness.publicKey,
+ registrationHash: config.preRegistrationHash, records, decision, failure, verificationExitCode: verification.exitCode,
+ checkpointHash: checkpoint.checkpointHash, anchor, passed: decision.passed && verification.exitCode === 0 };
+ await writeFile(join(runRoot, 'slot.json'), `${JSON.stringify(result, null, 2)}\n`);
+ process.stdout.write(`${JSON.stringify({ runId, slot, passed: result.passed, decision, failure, verificationExitCode: verification.exitCode })}\n`);
+ if (!result.passed) process.exitCode = 1;
+} finally {
+ await Promise.all(adapters.map((a) => a.dispose()));
+ database.close();
+}
diff --git a/deploy/mode-r/run-e02-slot.mjs b/deploy/mode-r/run-e02-slot.mjs
new file mode 100644
index 00000000..09766321
--- /dev/null
+++ b/deploy/mode-r/run-e02-slot.mjs
@@ -0,0 +1,14 @@
+import assert from 'node:assert/strict';
+import { readFile } from 'node:fs/promises';
+import { collectE02Observations } from './collect-e02-observations.mjs';
+import { validateE02SlotContract } from './e02-slot-contract.mjs';
+
+const [slotText, seed, softwareCommit] = process.argv.slice(2);
+assert.equal(process.argv.length, 5, 'usage: run-e02-slot.mjs ');
+const slot = Number(slotText);
+const packet = JSON.parse(await readFile('/evidence/registration-packet.json', 'utf8'));
+const binding = JSON.parse(await readFile('/evidence/registration-binding.json', 'utf8'));
+const registration = validateE02SlotContract(packet, binding, slot, seed, 'protocols/e02-registration.v3.json');
+const result = await collectE02Observations({ directory: `/evidence/registered-e02-v3-${slot}`,
+ seed, mode: 'research-grade', profile: 'registered', softwareCommit, registration, slot });
+if (!result.passed) process.exitCode = 1;
diff --git a/deploy/mode-r/run-e03-pilot-slot.mjs b/deploy/mode-r/run-e03-pilot-slot.mjs
new file mode 100644
index 00000000..e9610df1
--- /dev/null
+++ b/deploy/mode-r/run-e03-pilot-slot.mjs
@@ -0,0 +1,169 @@
+import assert from 'node:assert/strict';
+import { readFile, mkdir, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+import { BaseAnchorPublisher, FakeChainTransport } from '@ald/anchor';
+import { hashCanonical } from '@ald/hashing';
+import { createProductionRuntime } from '@ald/orchestrator';
+import { HASH_DOMAINS, PreRegistrationArtifactSchema } from '@ald/types';
+
+const [runId, softwareCommit] = process.argv.slice(2);
+assert.equal(process.argv.length, 4, 'usage: run-e03-pilot-slot.mjs ');
+assert.match(softwareCommit, /^[a-f0-9]{40}$/u);
+
+const registration = JSON.parse(await readFile('/evidence/registration.json', 'utf8'));
+const artifact = PreRegistrationArtifactSchema.parse(registration.artifact);
+const registrationHash = hashCanonical(HASH_DOMAINS.preRegistration, artifact);
+assert.equal(registration.preRegistrationHash, registrationHash);
+assert.equal(artifact.experimentId, 'E03');
+const stage = artifact.parameters.stage;
+assert.ok(['blinded-pilot', 'full-qualification'].includes(stage));
+const runPattern = stage === 'blinded-pilot'
+ ? /^e03-pilot-(?:v[23]-)?(?:disabled|constant|random|shuffled|normal|oracle)-s\d{3}$/u
+ : /^e03-full-(?:disabled|constant|random|shuffled|normal|oracle)-s\d{3}$/u;
+assert.match(runId, runPattern);
+const topology = artifact.parameters.executionBinding?.topology;
+assert.equal(topology?.mode, 'prototype');
+assert.equal(topology.learnerContainersPerSlot, 0);
+assert.equal(topology.nurseryContainersPerSlot, 1);
+assert.equal(topology.sharedNurseryProcess, true);
+assert.equal(topology.adapterTransport, 'in-process');
+assert.equal(topology.adapterTiming, 'immediate');
+assert.equal(topology.turnResponseBudgetMs, 2_000);
+assert.match(process.env.HOSTNAME, /^[a-f0-9]{12}$/u);
+const registered = registration.runs.find((run) => run.config.runId === runId);
+assert.ok(registered && (stage === 'full-qualification' || registered.use === 'primary'));
+const { config, condition, slot } = registered;
+assert.equal(config.communicationCondition, condition);
+assert.equal(config.preRegistrationHash, registrationHash);
+assert.equal(config.evaluationTurns, 200);
+assert.equal(config.turnResponseBudgetMs, 2_000);
+assert.equal(config.deploymentMode, 'prototype');
+assert.equal(config.babyA.track, 'no-learning');
+assert.equal(config.babyB.track, 'no-learning');
+
+const root = join('/evidence', runId);
+await mkdir(root); // Original pilot evidence is single-use, including failed attempts.
+const clock = { now: () => new Date().toISOString() };
+let production;
+let failure = null;
+let failureStage = 'initialization';
+let observations = null;
+let nurseryResourceUsage = null;
+let resourceMeasurementFailure = null;
+const started = performance.now();
+
+async function measureNursery() {
+ const peakBytes = Number((await readFile('/sys/fs/cgroup/memory.peak', 'utf8')).trim());
+ const cpuStat = await readFile('/sys/fs/cgroup/cpu.stat', 'utf8');
+ const cpuUsageMicroseconds = Number(/^usage_usec (\d+)$/mu.exec(cpuStat)?.[1]);
+ assert.ok(Number.isSafeInteger(peakBytes) && peakBytes > 0);
+ assert.ok(Number.isSafeInteger(cpuUsageMicroseconds) && cpuUsageMicroseconds > 0);
+ return { peakBytes, cpuUsageMicroseconds, scope: 'nursery-container-cgroup' };
+}
+
+try {
+ const transport = new FakeChainTransport({ endpointLabel: `e03-${stage}-simulated-commitment` });
+ const evidence = {
+ listRuns: () => production.runtime.listRuns().map((run) => run.runId),
+ insertAnchorReceipt: (receipt) => production.runtime.writerFor(receipt.runId).insertAnchorReceipt(receipt),
+ readCheckpoints: (id) => production.runtime.writerFor(id).readCheckpoints(id),
+ readAnchorReceipts: (id) => production.runtime.writerFor(id).readAnchorReceipts(id),
+ };
+ const publisher = new BaseAnchorPublisher({
+ transport, anchorClass: 'simulated', evidence, clock,
+ anchorAddress: `0x${'44'.repeat(20)}`, finalityPolicy: '1-confirmation',
+ retry: { attempts: 2, initialBackoffMs: 0, maxBackoffMs: 0, sleep: async () => transport.mineBlock() },
+ confirmationPoll: { attempts: 2, intervalMs: 0 },
+ });
+ production = createProductionRuntime({
+ databasePath: join(root, 'evidence.sqlite'), bundleRoot: join(root, 'bundles'),
+ softwareCommit, clock, allowUnanchored: false, anchorPolicy: 'required',
+ anchorPublisher: publisher,
+ });
+ failureStage = 'run-creation';
+ await production.runtime.createRun(config);
+ const adapters = production.runtime.adaptersFor(runId);
+ assert.ok(Object.values(adapters).every((adapter) =>
+ adapter.isolation === undefined || adapter.isolation.boundary === 'in-process'));
+ failureStage = 'collection';
+ const summary = await production.runtime.runToCompletion(runId, {
+ onTurn: (result) => {
+ if (result.phase === 'evaluating' && (result.turn + 1) % 50 === 0) {
+ console.log(`${runId}: evaluation turn ${result.turn + 1}/201`);
+ }
+ },
+ });
+ assert.equal(summary.state, 'sealed');
+ failureStage = 'audit';
+ const records = production.runtime.turnRecords(runId);
+ const evaluation = records.filter((record) => record.phase === 'evaluating');
+ assert.equal(records.filter((record) => record.phase === 'running').length, 1);
+ assert.equal(evaluation.length, 200);
+ assert.ok(evaluation.every((record) => record.communicationCondition === condition));
+ const channel = production.runtime.transcript(runId);
+ assert.ok(channel.every((event) => event.communicationCondition === condition));
+ const accepted = channel.filter((event) => event.gatewayValidationResult === 'accepted');
+ assert.ok(accepted.length > 0, 'a registered control must carry accepted channel events');
+ if (condition === 'oracle') {
+ assert.ok(accepted.every((event) => event.origin === 'gateway-control'));
+ } else {
+ assert.ok(accepted.every((event) => event.origin === 'baby'));
+ }
+ if (condition === 'disabled') {
+ assert.ok(accepted.every((event) => event.deliveryReceipt === undefined));
+ } else {
+ assert.ok(accepted.every((event) => event.deliveryReceipt !== undefined));
+ }
+ if (condition === 'constant') {
+ assert.equal(new Set(accepted.map((event) => event.publicArtifactHash)).size, 1);
+ }
+ if (condition === 'random') {
+ assert.ok(new Set(accepted.map((event) => event.publicArtifactHash)).size > 1);
+ }
+ const receipts = production.runtime.writerFor(runId).readAnchorReceipts(runId);
+ assert.equal(receipts.length, 1);
+ assert.equal(receipts[0].status, 'confirmed');
+ const bundleDir = production.runtime.bundleDirFor(runId);
+ const verification = JSON.parse(await readFile(join(bundleDir, 'verification-report.json'), 'utf8'));
+ assert.equal(verification.exitCode, 0);
+ observations = {
+ runId, condition, slot, scenarioSeed: config.randomSeed,
+ seedBindings: config.seedBindings,
+ configurationHash: production.runtime.getRun(runId).configurationHash,
+ scenarioStateHashes: evaluation.map((record) => record.scenarioStateHash),
+ evaluationTurns: evaluation.length,
+ agreements: evaluation.filter((record) => record.outcome.success === true).length,
+ channelEvents: channel.length, acceptedChannelEvents: accepted.length,
+ checkpoints: production.runtime.checkpoints(runId).length,
+ nurseryContainerId: process.env.HOSTNAME, nurseryProcessId: process.pid,
+ roleProcessIds: { 'baby-a': process.pid, 'baby-b': process.pid },
+ externalLearnerContainerCount: 0,
+ anchorReceiptCount: receipts.length, verifierExitCode: verification.exitCode,
+ state: summary.state,
+ };
+} catch (error) {
+ failure = `${error.name}: ${error.message}`;
+} finally {
+ try { nurseryResourceUsage = await measureNursery(); }
+ catch (error) { resourceMeasurementFailure = `${error.name}: ${error.message}`; }
+ try { production?.close(); }
+ catch (error) { failure = `${failure ?? ''} close failed: ${error.message}`; }
+ const passed = failure === null && resourceMeasurementFailure === null && observations !== null;
+ await writeFile(join(root, 'slot.json'), `${JSON.stringify({
+ schemaVersion: 1, experimentId: 'E03', stage,
+ classification: stage === 'blinded-pilot'
+ ? 'original-registered-pilot-slot'
+ : 'original-registered-full-qualification-slot',
+ researchFinding: false, scientificDisposition: 'not-tested',
+ externalSpend: 0, publicChainTransaction: false,
+ softwareCommit, registrationHash, runId, condition, slot,
+ failure, failureStage, observations, resourceMeasurementFailure,
+ nurseryResourceUsage, processResourceUsage: process.resourceUsage(),
+ wallMilliseconds: performance.now() - started, passed,
+ claimBoundary: stage === 'blinded-pilot'
+ ? 'Registered E03 Prototype-Mode blinded-pilot feasibility/variance input only; no Research-Grade isolation, chance-control, or language-emergence finding.'
+ : 'Registered E03 Prototype-Mode full control qualification input only; no Research-Grade isolation or language-emergence finding.',
+ }, null, 2)}\n`, { flag: 'wx' });
+ if (!passed) process.exitCode = 1;
+}
diff --git a/deploy/mode-r/run-e03-topology-slot.mjs b/deploy/mode-r/run-e03-topology-slot.mjs
new file mode 100644
index 00000000..7c01b08a
--- /dev/null
+++ b/deploy/mode-r/run-e03-topology-slot.mjs
@@ -0,0 +1,189 @@
+import assert from 'node:assert/strict';
+import { readFile, mkdir, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+import { BaseAnchorPublisher, FakeChainTransport } from '@ald/anchor';
+import { deriveSeedHex } from '@ald/hashing';
+import { createIsolatedAdapterFactory } from '@ald/isolation';
+import { buildRunConfig } from '@ald/lifecycle';
+import { createProductionRuntime } from '@ald/orchestrator';
+
+const [condition, softwareCommit, profile = 'v1'] = process.argv.slice(2);
+const conditions = ['disabled', 'constant', 'random', 'shuffled', 'normal', 'oracle'];
+assert.ok([4, 5].includes(process.argv.length),
+ 'usage: run-e03-topology-slot.mjs [v1|prototype-v2]');
+assert.ok(conditions.includes(condition));
+assert.match(softwareCommit, /^[a-f0-9]{40}$/u);
+assert.ok(['v1', 'prototype-v2'].includes(profile));
+
+const runId = profile === 'v1'
+ ? `e03-topology-development-${condition}`
+ : `e03-topology-prototype-v2-${condition}`;
+const root = `/evidence/${runId}`;
+const seedRoot = profile === 'v1'
+ ? 'ald-e03-topology-development-v1'
+ : 'ald-e03-topology-prototype-v2';
+const scenarioSeed = deriveSeedHex(seedRoot, 'scenario');
+const seed = (component) => deriveSeedHex(seedRoot, `${condition}/${component}`);
+const clock = { now: () => new Date().toISOString() };
+const factories = [];
+let production;
+let failure = null;
+let failureStage = 'initialization';
+let observations = null;
+let containerResourceUsage = null;
+let resourceMeasurementFailure = null;
+const started = performance.now();
+
+await mkdir(root); // An attempted development slot is single-use and retained on failure.
+
+async function sampleContainerResources() {
+ const peakBytes = Number((await readFile('/sys/fs/cgroup/memory.peak', 'utf8')).trim());
+ const cpu = Object.fromEntries((await readFile('/sys/fs/cgroup/cpu.stat', 'utf8')).trim().split('\n')
+ .map((line) => { const [key, value] = line.split(' '); return [key, Number(value)]; }));
+ assert.ok(Number.isSafeInteger(peakBytes) && peakBytes > 0);
+ assert.ok(Number.isSafeInteger(cpu.usage_usec) && cpu.usage_usec > 0);
+ return { peakBytes, cpuUsageMicroseconds: cpu.usage_usec, scope: 'nursery-container-cgroup' };
+}
+
+try {
+ const transport = new FakeChainTransport({ endpointLabel: 'e03-development-simulated-commitment' });
+ const evidence = {
+ listRuns: () => production.runtime.listRuns().map((run) => run.runId),
+ insertAnchorReceipt: (receipt) => production.runtime.writerFor(receipt.runId).insertAnchorReceipt(receipt),
+ readCheckpoints: (id) => production.runtime.writerFor(id).readCheckpoints(id),
+ readAnchorReceipts: (id) => production.runtime.writerFor(id).readAnchorReceipts(id),
+ };
+ const publisher = new BaseAnchorPublisher({
+ transport, anchorClass: 'simulated', evidence, clock,
+ anchorAddress: `0x${'42'.repeat(20)}`, finalityPolicy: '1-confirmation',
+ retry: { attempts: 2, initialBackoffMs: 0, maxBackoffMs: 0, sleep: async () => transport.mineBlock() },
+ confirmationPoll: { attempts: 2, intervalMs: 0 },
+ });
+ production = createProductionRuntime({
+ databasePath: join(root, 'evidence.sqlite'), bundleRoot: join(root, 'bundles'),
+ softwareCommit, clock, allowUnanchored: false, anchorPolicy: 'required',
+ anchorPublisher: publisher,
+ ...(profile === 'v1' ? { adapterFactoryFor: (_config, role) => {
+ const factory = createIsolatedAdapterFactory({
+ track: 'no-learning', transport: 'container',
+ endpoint: { host: role, port: 4318, attempts: 40, retryDelayMs: 250, timeoutMs: 1_000, hostLabel: role },
+ timing: 'normalized', deadlineMs: 2_000,
+ });
+ factories.push(factory);
+ return factory;
+ } } : {}),
+ });
+ failureStage = 'run-creation';
+ const config = buildRunConfig({
+ runId, experimentId: 'E03', randomSeed: scenarioSeed,
+ seedBindings: {
+ version: 1, scenario: scenarioSeed,
+ babyA: seed('baby-a'), babyB: seed('baby-b'), gateway: seed('gateway'), analysis: seed('analysis'),
+ },
+ deploymentMode: profile === 'v1' ? 'research-grade' : 'prototype',
+ registrationClass: 'qualification',
+ babyA: { track: 'no-learning', modelRef: 'uniform-random-v1', trainingIsolation: 'independent' },
+ babyB: { track: 'no-learning', modelRef: 'uniform-random-v1', trainingIsolation: 'independent' },
+ learningSignal: 'none', communicationCondition: condition,
+ maxTurnsPerRun: 1, evaluationTurns: 200, evaluationSeeds: 1,
+ turnResponseBudgetMs: 2_000, checkpointEventInterval: 1_024,
+ protocolGitCommit: softwareCommit,
+ });
+ await production.runtime.createRun(config);
+ const adapters = production.runtime.adaptersFor(runId);
+ const containerIds = profile === 'v1'
+ ? Object.fromEntries(['baby-a', 'baby-b'].map((role) => [role, adapters[role].isolation.containerId]))
+ : null;
+ if (profile === 'v1') assert.equal(new Set(Object.values(containerIds)).size, 2);
+ else {
+ assert.ok(Object.values(adapters).every((adapter) =>
+ adapter.isolation === undefined || adapter.isolation.boundary === 'in-process'));
+ assert.match(process.env.HOSTNAME, /^[a-f0-9]{12}$/u);
+ }
+ failureStage = 'collection';
+ const summary = await production.runtime.runToCompletion(runId, {
+ onTurn: (result) => {
+ if (result.phase === 'evaluating' && (result.turn + 1) % 50 === 0) {
+ console.log(`${condition}: evaluation turn ${result.turn + 1}/201`);
+ }
+ },
+ });
+ assert.equal(summary.state, 'sealed');
+ failureStage = 'audit';
+ const records = production.runtime.turnRecords(runId);
+ const evaluation = records.filter((record) => record.phase === 'evaluating');
+ assert.equal(records.filter((record) => record.phase === 'running').length, 1);
+ assert.equal(evaluation.length, 200);
+ assert.ok(evaluation.every((record) => record.communicationCondition === condition));
+ const channel = production.runtime.transcript(runId);
+ assert.ok(channel.every((event) => event.communicationCondition === condition));
+ const accepted = channel.filter((event) => event.gatewayValidationResult === 'accepted');
+ assert.ok(accepted.length > 0, 'a control topology must carry accepted channel events');
+ if (condition === 'oracle') {
+ assert.ok(accepted.every((event) => event.origin === 'gateway-control'));
+ } else {
+ assert.ok(accepted.every((event) => event.origin === 'baby'));
+ }
+ if (condition === 'disabled') {
+ assert.ok(accepted.every((event) => event.deliveryReceipt === undefined));
+ } else {
+ assert.ok(accepted.every((event) => event.deliveryReceipt !== undefined));
+ }
+ if (condition === 'constant') {
+ assert.equal(new Set(accepted.map((event) => event.publicArtifactHash)).size, 1);
+ }
+ if (condition === 'random') {
+ assert.ok(new Set(accepted.map((event) => event.publicArtifactHash)).size > 1);
+ }
+ const receipts = production.runtime.writerFor(runId).readAnchorReceipts(runId);
+ assert.equal(receipts.length, 1);
+ assert.equal(receipts[0].status, 'confirmed');
+ const bundleDir = production.runtime.bundleDirFor(runId);
+ const verification = JSON.parse(await readFile(join(bundleDir, 'verification-report.json'), 'utf8'));
+ assert.equal(verification.exitCode, 0);
+ const manifest = JSON.parse(await readFile(join(bundleDir, 'run-manifest.json'), 'utf8'));
+ assert.equal(manifest.deploymentMode, profile === 'v1' ? 'research-grade' : 'prototype');
+ observations = {
+ runId, condition, scenarioSeed, seedBindings: config.seedBindings,
+ configurationHash: production.runtime.getRun(runId).configurationHash,
+ scenarioStateHashes: evaluation.map((record) => record.scenarioStateHash),
+ evaluationTurns: evaluation.length,
+ acceptedChannelEvents: accepted.length,
+ rejectedChannelEvents: channel.filter((event) => event.gatewayValidationResult === 'rejected').length,
+ successes: evaluation.filter((record) => record.outcome.success === true).length,
+ checkpoints: production.runtime.checkpoints(runId).length,
+ ...(profile === 'v1'
+ ? { containerIds }
+ : { nurseryContainerId: process.env.HOSTNAME, nurseryProcessId: process.pid,
+ roleProcessIds: { 'baby-a': process.pid, 'baby-b': process.pid },
+ externalLearnerContainerCount: 0 }),
+ anchorReceiptCount: receipts.length, verifierExitCode: verification.exitCode,
+ state: summary.state,
+ };
+} catch (error) {
+ failure = `${error.name}: ${error.message}`;
+} finally {
+ try { containerResourceUsage = await sampleContainerResources(); }
+ catch (error) { resourceMeasurementFailure = `${error.name}: ${error.message}`; }
+ try { production?.close(); }
+ catch (error) { failure = `${failure ?? ''} close failed: ${error.message}`; }
+ for (const factory of factories) {
+ try { await factory.dispose(); }
+ catch (error) { failure = `${failure ?? ''} adapter disposal failed: ${error.message}`; }
+ }
+ const passed = failure === null && resourceMeasurementFailure === null && observations !== null;
+ await writeFile(join(root, 'slot.json'), `${JSON.stringify({
+ schemaVersion: 1, experimentId: 'E03', classification: 'development-only-topology-qualification',
+ researchFinding: false, externalSpend: 0, publicChainTransaction: false,
+ softwareCommit, condition, ...(profile === 'v1' ? {} : { profile }),
+ failure, failureStage, observations,
+ resourceMeasurementFailure, containerResourceUsage,
+ processResourceUsage: process.resourceUsage(), wallMilliseconds: performance.now() - started,
+ passed,
+ claimBoundary: profile === 'v1'
+ ? 'Six-condition transport, control, anchor, and verifier mechanics only; no E03 pilot or research finding; not full Research-Grade writer/signer isolation.'
+ : 'Prototype-Mode in-process control, oracle, simulated-anchor, and verifier mechanics only; no Research-Grade isolation claim, pilot, or research finding.',
+ }, null, 2)}\n`, { flag: 'wx' });
+ if (!passed) process.exitCode = 1;
+}
diff --git a/deploy/mode-r/run-lv01-paired-case.mjs b/deploy/mode-r/run-lv01-paired-case.mjs
new file mode 100644
index 00000000..8767ad2a
--- /dev/null
+++ b/deploy/mode-r/run-lv01-paired-case.mjs
@@ -0,0 +1,223 @@
+import assert from 'node:assert/strict';
+import { execFileSync, spawnSync } from 'node:child_process';
+import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { createHash } from 'node:crypto';
+import { join, resolve } from 'node:path';
+
+import { createLv01PairedCasePlan, LV01_BRANCHES, verifyLv01PairedCase } from '@ald/orchestrator';
+
+const [mode, version] = process.argv.slice(2);
+assert.ok(mode === '--run' || mode === '--check', 'usage: run-lv01-paired-case.mjs --run|--check ');
+assert.match(version ?? '', /^[1-9]\d*$/u);
+
+const packetPath = `protocols/lv01-paired-development-allocation.v${version}.json`;
+const packet = existsSync(packetPath) ? JSON.parse(readFileSync(packetPath, 'utf8')) : null;
+const parentRunId = `lv01-development-v${version}-p0001`;
+const parentRoot = join('evidence/lv01', `development-v${version}`, parentRunId);
+const root = join('evidence/lv01', `paired-development-v${version}`);
+const commit = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
+
+function networkAllocation(runOrdinal) {
+ const thirdOctet = 1 + (Number(version) * 8) + runOrdinal;
+ assert.ok(thirdOctet <= 255, 'LV01 isolated-network allocation requires a new version range');
+ return {
+ babyA: `10.250.${thirdOctet}.0/28`,
+ babyB: `10.250.${thirdOctet}.16/28`,
+ controlPlane: `10.250.${thirdOctet}.32/28`,
+ };
+}
+
+function requirePacket() {
+ assert.ok(packet !== null, `missing ${packetPath}`);
+ assert.equal(packet.studyId, 'LV01');
+ assert.equal(packet.classification, 'prospective-development-only-paired-case');
+ assert.equal(packet.status, 'design-locked-not-executed');
+ assert.equal(packet.researchFinding, false);
+ assert.equal(packet.externalSpend, 0);
+ assert.equal(packet.parentRunId, parentRunId);
+ assert.deepEqual(packet.branchOrder, LV01_BRANCHES);
+ assert.deepEqual(packet.networkAllocation.parent, networkAllocation(0));
+ assert.deepEqual(packet.networkAllocation.children,
+ Object.fromEntries(LV01_BRANCHES.map((branch, index) => [branch, networkAllocation(index + 1)])));
+ assert.equal(packet.sourceFreeze.tree,
+ execFileSync('git', ['rev-parse', `${packet.sourceFreeze.commit}^{tree}`], { encoding: 'utf8' }).trim());
+ for (const artifact of packet.sourceFreeze.artifacts) {
+ const bytes = execFileSync('git', ['show', `${packet.sourceFreeze.commit}:${artifact.path}`]);
+ const hash = `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
+ assert.equal(hash, artifact.sha256,
+ `source freeze changed for ${artifact.path}`);
+ assert.equal(`sha256:${createHash('sha256').update(readFileSync(artifact.path)).digest('hex')}`, hash,
+ `working source differs from frozen ${artifact.path}`);
+ }
+ assert.ok(packet.sourceFreeze.artifacts.some((entry) => entry.path === 'deploy/mode-r/run-lv01-paired-case.mjs'));
+}
+
+function requirePairedCaseIdentities() {
+ const scope = packet.actionDrawScope;
+ assert.ok(scope !== undefined && scope !== null,
+ `packet ${packetPath} carries no actionDrawScope for the shared LV01 action draw`);
+ assert.ok(['development', 'qualification', 'shadow', 'generalization', 'pilot'].includes(scope.stage),
+ 'paired packet actionDrawScope.stage is invalid');
+ assert.ok(scope.slotKind === 'primary' || scope.slotKind === 'reserve',
+ 'paired packet actionDrawScope.slotKind is invalid');
+ assert.match(scope.slotIndex ?? '', /^\d{4}$/u,
+ 'paired packet actionDrawScope.slotIndex must be a zero-padded slot number');
+ assert.ok(['dev', 'within-support', 'novel-composition'].includes(scope.partition),
+ 'paired packet actionDrawScope.partition is invalid');
+ const treatments = packet.ledgerTreatments;
+ assert.ok(treatments !== undefined && treatments !== null,
+ `packet ${packetPath} carries no ledgerTreatments for the ledger branches`);
+ assert.deepEqual(Object.keys(treatments).sort(), ['ledger-consistent', 'ledger-shuffled'],
+ 'paired packet ledgerTreatments must cover exactly the two ledger branches');
+ const slices = {};
+ for (const branch of ['ledger-consistent', 'ledger-shuffled']) {
+ const slice = treatments[branch];
+ assert.ok(slice !== undefined && slice !== null, `paired packet ledgerTreatments.${branch} is missing`);
+ assert.equal(typeof slice.selectedToken, 'string', `paired packet ledgerTreatments.${branch}.selectedToken is invalid`);
+ assert.ok(slice.selectedToken.length > 0, `paired packet ledgerTreatments.${branch}.selectedToken is empty`);
+ assert.equal(typeof slice.deliveredToken, 'string', `paired packet ledgerTreatments.${branch}.deliveredToken is invalid`);
+ assert.ok(slice.deliveredToken.length > 0, `paired packet ledgerTreatments.${branch}.deliveredToken is empty`);
+ assert.match(slice.batchCommitment ?? '', /^sha256:[0-9a-f]{64}$/u,
+ `paired packet ledgerTreatments.${branch}.batchCommitment is invalid`);
+ assert.equal(typeof slice.sourceCaseId, 'string', `paired packet ledgerTreatments.${branch}.sourceCaseId is invalid`);
+ assert.ok(slice.sourceCaseId.length > 0, `paired packet ledgerTreatments.${branch}.sourceCaseId is empty`);
+ slices[branch] = { selectedToken: slice.selectedToken, deliveredToken: slice.deliveredToken,
+ batchCommitment: slice.batchCommitment, sourceCaseId: slice.sourceCaseId };
+ }
+ return {
+ actionDrawScope: { stage: scope.stage, slotKind: scope.slotKind,
+ slotIndex: scope.slotIndex, partition: scope.partition },
+ ledgerTreatments: slices,
+ };
+}
+
+function prepare(rootPath) {
+ for (const directory of [
+ 'config', 'evidence', 'gateway-state', 'output', 'runtime/model-a', 'runtime/model-b', 'runtime/public-keys',
+ ...['baby-a-ledger', 'baby-b-ledger', 'channel', 'affect', 'audit', 'witness'].map((name) => `runtime/signers/${name}`),
+ ...['controller', 'gateway', 'checkpoint', 'anchor', 'audit'].map((name) => `runtime/evidence-${name}`),
+ 'runtime/gateway-controller', 'runtime/gateway-baby-a', 'runtime/gateway-baby-b',
+ 'runtime/checkpoint-service', 'runtime/anchor-service', 'runtime/audit-service',
+ ]) mkdirSync(join(rootPath, directory), { recursive: true, mode: 0o700 });
+}
+
+function json(path) { return JSON.parse(readFileSync(path, 'utf8')); }
+
+function lastCheckpoint(bundle) {
+ const checkpointRoot = join(bundle, 'checkpoints');
+ const files = readdirSync(checkpointRoot)
+ .filter((name) => /^\d{6}\.json$/u.test(name)).sort()
+ .map((name) => join(checkpointRoot, name));
+ assert.ok(files.length > 0, 'parent bundle has no checkpoint');
+ return json(files.at(-1));
+}
+
+function compose(rootPath, runId, networks) {
+ const project = `ald-lv01-paired-v${version}-${runId.slice(-12)}`;
+ const command = ['compose', '--project-name', project,
+ '--file', 'deploy/mode-r/docker-compose.application.v1.yml',
+ '--file', 'deploy/mode-r/docker-compose.lv01.v1.yml',
+ '--file', 'deploy/mode-r/docker-compose.lv01-isolated-networks.v1.yml'];
+ const environment = {
+ ...process.env, ALD_MODE_R_APPLICATION_ROOT: resolve(rootPath), ALD_SOFTWARE_COMMIT: commit,
+ ALD_MODE_R_RUN_ID: runId, ALD_MODE_R_CONTROLLER_STAGE: 'lv01-paired-branch',
+ ALD_MODE_R_PARENT_BUNDLE: `/output/bundles/runs/${parentRunId}`,
+ ALD_MODE_R_UID: String(process.getuid?.() ?? 1000), ALD_MODE_R_GID: String(process.getgid?.() ?? 1000),
+ ALD_MODE_R_BABY_A_SUBNET: networks.babyA,
+ ALD_MODE_R_BABY_B_SUBNET: networks.babyB,
+ ALD_MODE_R_CONTROL_PLANE_SUBNET: networks.controlPlane,
+ };
+ let result;
+ let teardown;
+ try {
+ result = spawnSync('docker', [...command, 'up', '--build', '--abort-on-container-exit', '--exit-code-from', 'offline-verifier'], {
+ cwd: resolve('.'), encoding: 'utf8', env: environment,
+ });
+ } finally {
+ teardown = spawnSync('docker', [...command, 'down', '--remove-orphans'], {
+ cwd: resolve('.'), encoding: 'utf8', env: environment,
+ });
+ }
+ writeFileSync(join(rootPath, 'output', 'compose-output.log'), [
+ result?.stdout ?? '', result?.stderr ?? '', '\n-- teardown --\n', teardown?.stdout ?? '', teardown?.stderr ?? '',
+ ].join(''), { flag: 'wx', mode: 0o600 });
+ assert.equal(result.status, 0, `branch Compose failed: ${(result.stderr ?? result.stdout ?? '').split('\n')[0]}`);
+ assert.equal(teardown.status, 0, `branch Compose teardown failed: ${(teardown.stderr ?? teardown.stdout ?? '').split('\n')[0]}`);
+}
+
+function runBranch(branchPlan, parentBundle, networks) {
+ const childRoot = join(root, branchPlan.config.runId);
+ assert.equal(existsSync(childRoot), false, `single-use branch evidence already exists: ${branchPlan.config.runId}`);
+ prepare(childRoot);
+ const parentDestination = join(childRoot, 'output', 'bundles', 'runs', parentRunId);
+ cpSync(parentBundle, parentDestination, { recursive: true, errorOnExist: true });
+ assert.deepEqual(json(join(parentDestination, 'run-manifest.json')), json(join(parentBundle, 'run-manifest.json')),
+ 'copied parent bundle changed before child launch');
+ writeFileSync(join(childRoot, 'config', 'run-config.json'), `${JSON.stringify(branchPlan.config, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
+ compose(childRoot, branchPlan.config.runId, networks);
+ const output = join(childRoot, 'output');
+ const result = json(join(output, 'lv01-paired-branch-result.json'));
+ assert.equal(result.state, 'sealed');
+ assert.equal(result.turnCount, 1);
+ const bundle = join(output, 'bundle');
+ const turn = readFileSync(join(bundle, 'turn-records.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
+ assert.equal(turn.length, 1);
+ const interventions = readFileSync(join(bundle, 'intervention-log.jsonl'), 'utf8').trim().split('\n').map(JSON.parse);
+ const prediction = interventions.find((entry) => entry.reasonCode === 'lv01-paired-pre-receiver-action-prediction-committed');
+ assert.ok(prediction, 'branch has no pre-action LV01 prediction commitment');
+ assert.deepEqual(prediction.details.preStateCommitment, branchPlan.config.lv01PairedCase.preStateCommitment);
+ const draw = interventions.find((entry) => entry.reasonCode === 'lv01-shared-action-draw-committed');
+ assert.ok(draw, 'branch has no shared action-draw commitment');
+ return {
+ branch: branchPlan.branch,
+ scenarioHash: turn[0].scenarioStateHash,
+ receiverDrawCommitment: draw.details.drawCommitment,
+ preStateCommitment: branchPlan.config.lv01PairedCase.preStateCommitment,
+ predictionCommitment: prediction.details.commitment,
+ actionRecordedAfterPrediction: true,
+ restoredBeforeAction: true,
+ runId: branchPlan.config.runId,
+ };
+}
+
+requirePacket();
+if (mode === '--check') {
+ assert.equal(existsSync(root), false, 'paired-case evidence already exists; audit retained evidence instead');
+ console.log(`LV01 paired development v${version} is configured but unexecuted`);
+ process.exit(0);
+}
+
+assert.equal(execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8' }).trim(), '', 'paired collector requires a clean committed source tree');
+assert.equal(existsSync(root), false, 'paired-case evidence is single-use');
+const pairedCaseIdentities = requirePairedCaseIdentities();
+mkdirSync(root, { recursive: false, mode: 0o700 });
+let stage = 'parent-launch';
+try {
+ const parentLaunch = spawnSync(process.execPath, ['deploy/mode-r/run-lv01-slot.mjs', '--run', version], { cwd: resolve('.'), encoding: 'utf8' });
+ assert.equal(parentLaunch.status, 0, `parent fixture failed: ${(parentLaunch.stderr ?? parentLaunch.stdout ?? '').split('\n')[0]}`);
+ const parentBundle = join(parentRoot, 'output', 'bundle');
+ const parentConfig = json(join(parentBundle, 'configuration', 'run-config.json'));
+ const checkpoint = lastCheckpoint(parentBundle);
+ const plan = createLv01PairedCasePlan({ parent: parentConfig, parentCheckpointHash: checkpoint.checkpointHash,
+ babyAInitialPolicyRef: 'policies/baby-a-latest.json', babyBInitialPolicyRef: 'policies/baby-b-latest.json',
+ childRunIdPrefix: `lv01-paired-development-v${version}-p0001`, ...pairedCaseIdentities });
+ const branches = plan.branches.map((branch, index) => {
+ stage = `branch:${branch.branch}`;
+ return runBranch(branch, parentBundle, packet.networkAllocation.children[branch.branch]);
+ });
+ const verification = verifyLv01PairedCase(branches);
+ writeFileSync(join(root, 'paired-case-receipt.json'), `${JSON.stringify({ schemaVersion: 1,
+ classification: 'lv01-seven-branch-development-fixture', researchFinding: false,
+ scientificDisposition: 'not-tested', parentRunId, parentCheckpointHash: checkpoint.checkpointHash,
+ preStateCommitment: plan.preStateCommitment, branches, ...verification,
+ claimBoundary: 'One local seven-branch software fixture. It is not a pilot or behavioral result.' }, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
+ console.log(`LV01 paired development complete: ${verification.caseCommitment}; no research finding`);
+} catch (error) {
+ writeFileSync(join(root, 'paired-case-failure.json'), `${JSON.stringify({
+ schemaVersion: 1, classification: 'lv01-seven-branch-development-fixture', researchFinding: false,
+ scientificDisposition: 'not-tested', parentRunId, stage,
+ failure: `${error.name}: ${error.message.split('\n')[0]}`,
+ claimBoundary: 'The local paired development fixture did not complete. No pilot or behavioral result exists.',
+ }, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
+ throw error;
+}
diff --git a/deploy/mode-r/run-lv01-slot.mjs b/deploy/mode-r/run-lv01-slot.mjs
new file mode 100644
index 00000000..eb927763
--- /dev/null
+++ b/deploy/mode-r/run-lv01-slot.mjs
@@ -0,0 +1,376 @@
+import assert from 'node:assert/strict';
+import { execFileSync, spawn, spawnSync } from 'node:child_process';
+import { createHash } from 'node:crypto';
+import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { join, resolve } from 'node:path';
+
+import { deriveSeedHex } from '@ald/hashing';
+import { loadLearnerContract, promptBundleHash, RECURRENT_ARCHITECTURE } from '@ald/learners';
+import { buildRunConfig } from '@ald/lifecycle';
+import { ReferentialScenarioEngine } from '@ald/scenario';
+import { fixedTokenInventory } from '@ald/types';
+
+const [mode, version] = process.argv.slice(2);
+assert.ok(mode === '--run' || mode === '--check', 'usage: run-lv01-slot.mjs --run|--check ');
+assert.match(version ?? '', /^[1-9]\d*$/u);
+
+const allocationPath = `protocols/lv01-development-resource-allocation.v${version}.json`;
+const evidenceRoot = `evidence/lv01/development-v${version}`;
+const runId = `lv01-development-v${version}-p0001`;
+const slotRoot = join(evidenceRoot, runId);
+const resultPath = join(slotRoot, 'output', 'lv01-fixture-result.json');
+const resourcePath = join(slotRoot, 'output', 'lv01-fixture-resources.json');
+const authorityPath = join(slotRoot, 'output', 'lv01-authority-observation.json');
+const composeLogPath = join(slotRoot, 'output', 'compose-output.log');
+const authorityFailurePath = join(slotRoot, 'output', 'lv01-authority-failure.json');
+const commit = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
+const sha256 = (path) => `sha256:${createHash('sha256').update(readFileSync(path)).digest('hex')}`;
+const allocation = existsSync(allocationPath)
+ ? JSON.parse(readFileSync(allocationPath, 'utf8'))
+ : null;
+const controllerStage = process.env.ALD_MODE_R_CONTROLLER_STAGE ?? 'lv01-fixture';
+assert.equal(controllerStage, 'lv01-fixture',
+ 'LV01 slot runner accepts only the fixture controller stage; detector observation is owned by the outer collector');
+
+const APPLICATION_SERVICES = [
+ 'model-adapter-a', 'model-adapter-b', 'baby-a', 'baby-b',
+ 'signer-baby-a-ledger', 'signer-baby-b-ledger', 'signer-channel',
+ 'signer-affect', 'signer-audit', 'witness-signer', 'evidence-writer',
+ 'gateway', 'checkpoint', 'simulated-anchor', 'audit-interpreter',
+ 'controller-scenario', 'offline-verifier',
+];
+
+function networkAllocation(runOrdinal = 0) {
+ const thirdOctet = 1 + (Number(version) * 8) + runOrdinal;
+ assert.ok(thirdOctet <= 255, 'LV01 isolated-network allocation requires a new version range');
+ return {
+ babyA: `10.250.${thirdOctet}.0/28`,
+ babyB: `10.250.${thirdOctet}.16/28`,
+ controlPlane: `10.250.${thirdOctet}.32/28`,
+ };
+}
+
+function inspectServiceAccess(containerId, service, target) {
+ const probe = spawnSync('docker', [
+ 'exec', containerId, '/usr/local/bin/node', '-e',
+ "process.exit(require('node:fs').existsSync(process.argv[1]) ? 0 : 1)", target,
+ ], { encoding: 'utf8' });
+ const exitCode = probe.status ?? -1;
+ return { service, target, exitCode, denied: exitCode === 1 };
+}
+
+function projectContainerIds(project, all = false) {
+ return execFileSync('docker', ['ps', ...(all ? ['--all'] : []), '--quiet',
+ '--filter', `label=com.docker.compose.project=${project}`], { encoding: 'utf8' })
+ .trim().split('\n').filter(Boolean);
+}
+
+function authorityObservation(project) {
+ const ids = projectContainerIds(project, true);
+ if (ids.length !== APPLICATION_SERVICES.length) return null;
+ const containers = JSON.parse(execFileSync('docker', ['inspect', ...ids], { encoding: 'utf8' }));
+ const services = new Map(containers.map((container) => [
+ container.Config.Labels['com.docker.compose.service'], container,
+ ]));
+ assert.deepEqual([...services.keys()].sort(), [...APPLICATION_SERVICES].sort(),
+ 'selected application services differ from the expected 17-role topology');
+ if (['controller-scenario', 'baby-a', 'baby-b'].some((service) =>
+ !services.get(service).State.Running)) return null;
+ for (const service of APPLICATION_SERVICES) {
+ const container = services.get(service);
+ assert.equal(container.HostConfig.ReadonlyRootfs, true, `${service} root must be read-only`);
+ assert.ok(container.HostConfig.CapDrop.includes('ALL'), `${service} must drop all capabilities`);
+ }
+ const mountTargets = (service) => new Set(services.get(service).Mounts.map((mount) => mount.Destination));
+ const controllerMounts = mountTargets('controller-scenario');
+ assert.ok(![...controllerMounts].some((target) => target.startsWith('/run/ald-mode-r/model-')),
+ 'controller must not mount private model state');
+ assert.ok(![...controllerMounts].some((target) => target.startsWith('/run/ald-mode-r/signer-')),
+ 'controller must not mount signer capabilities');
+ assert.ok(mountTargets('baby-a').has('/run/ald-mode-r/model-a'));
+ assert.ok(!mountTargets('baby-a').has('/run/ald-mode-r/model-b'));
+ assert.ok(mountTargets('baby-b').has('/run/ald-mode-r/model-b'));
+ assert.ok(!mountTargets('baby-b').has('/run/ald-mode-r/model-a'));
+ const networks = (service) => Object.keys(services.get(service).NetworkSettings.Networks)
+ .filter((network) => network !== 'none').sort();
+ for (const service of ['model-adapter-a', 'model-adapter-b', 'signer-baby-a-ledger',
+ 'signer-baby-b-ledger', 'signer-channel', 'signer-affect', 'signer-audit',
+ 'witness-signer', 'offline-verifier']) {
+ assert.equal(networks(service).length, 0,
+ `${service} must have no network route`);
+ }
+ assert.equal(networks('baby-a').some((network) => networks('baby-b').includes(network)), false,
+ 'the two learner roles must not share a network');
+ const access = [
+ ['controller-scenario', '/run/ald-mode-r/model-a'],
+ ['controller-scenario', '/run/ald-mode-r/model-b'],
+ ['baby-a', '/run/ald-mode-r/model-b'],
+ ['baby-b', '/run/ald-mode-r/model-a'],
+ ].map(([service, target]) => inspectServiceAccess(services.get(service).Id, service, target));
+ assert.ok(access.every((entry) => entry.denied), 'private-model access probe unexpectedly succeeded');
+ return {
+ schemaVersion: 1,
+ classification: 'lv01-selected-application-authority-observation',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId,
+ services: APPLICATION_SERVICES.map((service) => {
+ const container = services.get(service);
+ return {
+ service,
+ containerId: container.Id,
+ running: container.State.Running,
+ processId: container.State.Pid,
+ readOnlyRoot: container.HostConfig.ReadonlyRootfs,
+ droppedCapabilities: container.HostConfig.CapDrop,
+ networks: networks(service),
+ mountTargets: [...mountTargets(service)].sort(),
+ };
+ }),
+ deniedPrivateModelAccess: access,
+ claimBoundary: 'A read-only live Docker inspection and negative filesystem probes for the selected LV01 application topology. It does not test malformed envelopes, recurrent state restoration, branch chronology, failure recovery, or behavioral outcomes.',
+ };
+}
+
+function verifyAllocation() {
+ assert.ok(allocation !== null, `missing ${allocationPath}`);
+ assert.equal(allocation.studyId, 'LV01');
+ assert.equal(allocation.classification, 'prospective-development-only-allocation');
+ assert.equal(allocation.status, 'design-locked-not-executed');
+ assert.equal(allocation.researchFinding, false);
+ assert.equal(allocation.externalSpend, 0);
+ assert.equal(allocation.allocation.smallFixture.runId, runId);
+ assert.equal(allocation.allocation.smallFixture.trainingCases, 64);
+ assert.equal(allocation.allocation.smallFixture.validationFitCases, 24);
+ assert.equal(allocation.allocation.smallFixture.validationSelectionCases, 24);
+ assert.equal(allocation.allocation.smallFixture.withinSupportTestCases, 24);
+ assert.deepEqual(allocation.networkAllocation, networkAllocation());
+ assert.equal(allocation.seedDerivation.slots.length, 6);
+ for (const slot of allocation.seedDerivation.slots) {
+ const derive = (purpose) => deriveSeedHex(allocation.seedDerivation.root,
+ ...allocation.seedDerivation.parts.map((part) => {
+ if (part === 'four-digit slot index') return slot.index;
+ if (part === 'purpose') return purpose;
+ return part;
+ }));
+ assert.equal(slot.scenario, derive('scenario'), 'scenario seed derivation');
+ assert.equal(slot.babyA, derive('learner/baby-a'), 'baby A seed derivation');
+ assert.equal(slot.babyB, derive('learner/baby-b'), 'baby B seed derivation');
+ assert.equal(slot.gateway, derive('gateway'), 'gateway seed derivation');
+ assert.equal(slot.analysis, derive('analysis'), 'analysis seed derivation');
+ }
+ assert.equal(execFileSync('git', ['rev-parse', `${allocation.sourceFreeze.commit}^{tree}`], { encoding: 'utf8' }).trim(), allocation.sourceFreeze.tree);
+ assert.equal(JSON.parse(execFileSync('git', ['show', `${allocation.sourceFreeze.commit}:package.json`], { encoding: 'utf8' })).version, allocation.sourceFreeze.version);
+ for (const artifact of allocation.sourceFreeze.artifacts) {
+ const bytes = execFileSync('git', ['show', `${allocation.sourceFreeze.commit}:${artifact.path}`]);
+ assert.equal(`sha256:${createHash('sha256').update(bytes).digest('hex')}`, artifact.sha256);
+ }
+ assert.ok(allocation.sourceFreeze.artifacts.some((artifact) => artifact.path === 'deploy/mode-r/run-lv01-slot.mjs'), 'allocation source closure does not bind this runner; compile a fresh version');
+}
+
+function config() {
+ const slot = allocation.seedDerivation.slots[0];
+ const planHash = (path) => sha256(path);
+ const base = buildRunConfig({
+ runId: allocation.allocation.smallFixture.runId,
+ experimentId: 'LV01',
+ randomSeed: slot.scenario,
+ seedBindings: { version: 1, scenario: slot.scenario, babyA: slot.babyA, babyB: slot.babyB, gateway: slot.gateway, analysis: slot.analysis },
+ deploymentMode: 'research-grade',
+ registrationClass: 'qualification',
+ babyA: { track: 'scratch-rl', modelRef: RECURRENT_ARCHITECTURE, trainingIsolation: 'independent' },
+ babyB: { track: 'scratch-rl', modelRef: RECURRENT_ARCHITECTURE, trainingIsolation: 'independent' },
+ learningSignal: 'extrinsic-task', communicationCondition: 'normal', maxTurnsPerRun: 64,
+ evaluationTurns: 24, evaluationSeeds: 1, turnResponseBudgetMs: 1_000,
+ checkpointEventInterval: 64, checkpointTimeIntervalMs: 300_000, protocolGitCommit: commit,
+ interventionPlan: { version: 1, heldOutTypeCodes: [0, 5, 10, 15] },
+ ledgerValuePlan: {
+ version: 1,
+ designCommitmentHash: planHash('protocols/lv01-study-design.v1.json'),
+ analysisCommitmentHash: planHash('protocols/lv01-analysis-plan.v1.json'),
+ seedResourceCommitmentHash: planHash('protocols/lv01-seed-resource-policy.v1.json'),
+ predictionFunctionVersion: 'lv01-ledger-value-prediction/v1',
+ partitionContractVersion: 'lv01-within-support/v1',
+ },
+ });
+ const scenario = new ReferentialScenarioEngine({
+ version: 1, symbolInventory: fixedTokenInventory(32), interactionMode: base.interactionMode,
+ heldOutTypeCodes: [0, 5, 10, 15],
+ }, base.randomSeed);
+ return {
+ ...base,
+ promptBundleHash: promptBundleHash(['scratch-rl'].map((track) => loadLearnerContract(track))),
+ scenarioBundleHash: scenario.bundleHash,
+ };
+}
+
+function prepareDirectories() {
+ for (const directory of [
+ 'config', 'evidence', 'gateway-state', 'output', 'runtime/model-a', 'runtime/model-b', 'runtime/public-keys',
+ ...['baby-a-ledger', 'baby-b-ledger', 'channel', 'affect', 'audit', 'witness'].map((domain) => `runtime/signers/${domain}`),
+ ...['controller', 'gateway', 'checkpoint', 'anchor', 'audit'].map((name) => `runtime/evidence-${name}`),
+ 'runtime/gateway-controller', 'runtime/gateway-baby-a', 'runtime/gateway-baby-b',
+ 'runtime/checkpoint-service', 'runtime/anchor-service', 'runtime/audit-service',
+ ]) mkdirSync(join(slotRoot, directory), { recursive: true, mode: 0o700 });
+}
+
+function bytes(value) {
+ const match = /^(\d+(?:\.\d+)?)\s*(B|KiB|MiB|GiB|TiB)$/u.exec(value.trim());
+ if (match === null) return null;
+ const scale = { B: 1, KiB: 2 ** 10, MiB: 2 ** 20, GiB: 2 ** 30, TiB: 2 ** 40 }[match[2]];
+ return Math.round(Number(match[1]) * scale);
+}
+
+function resourceSnapshot(project) {
+ const capturedAt = new Date().toISOString();
+ const ids = projectContainerIds(project);
+ if (ids.length === 0) return [];
+ try {
+ return execFileSync('docker', ['stats', '--no-stream', '--format', '{{json .}}', ...ids], {
+ encoding: 'utf8',
+ }).trim().split('\n').filter(Boolean).flatMap((line) => {
+ const stat = JSON.parse(line);
+ const memory = bytes(String(stat.MemUsage ?? '').split('/')[0] ?? '');
+ const cpuPercent = Number.parseFloat(String(stat.CPUPerc ?? '').replace('%', ''));
+ return [{ capturedAt, container: String(stat.Name ?? 'unknown'), cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : null,
+ memoryBytes: memory, pids: Number.parseInt(String(stat.PIDs ?? ''), 10) || null }];
+ });
+ } catch { return []; }
+}
+
+function summarizeResources(samples) {
+ const services = new Map();
+ for (const sample of samples) {
+ for (const row of sample.rows) {
+ const prior = services.get(row.container) ?? { maxCpuPercent: 0, maxMemoryBytes: 0, maxPids: 0, estimatedCpuSeconds: 0, lastAt: null };
+ const now = Date.parse(row.capturedAt);
+ if (prior.lastAt !== null && row.cpuPercent !== null) prior.estimatedCpuSeconds += (row.cpuPercent / 100) * ((now - prior.lastAt) / 1_000);
+ prior.maxCpuPercent = Math.max(prior.maxCpuPercent, row.cpuPercent ?? 0);
+ prior.maxMemoryBytes = Math.max(prior.maxMemoryBytes, row.memoryBytes ?? 0);
+ prior.maxPids = Math.max(prior.maxPids, row.pids ?? 0);
+ prior.lastAt = now;
+ services.set(row.container, prior);
+ }
+ }
+ return [...services.entries()].map(([container, value]) => ({ container, ...value, lastAt: undefined }));
+}
+
+async function runComposeWithMeasurements(compose, environment) {
+ const startedAt = new Date().toISOString();
+ const project = compose[compose.indexOf('--project-name') + 1];
+ const samples = [];
+ let authority = null;
+ let authorityFailure = null;
+ const sample = () => {
+ try { samples.push({ capturedAt: new Date().toISOString(), rows: resourceSnapshot(project) }); }
+ catch { /* Resource observation must not alter the fixture outcome. */ }
+ if (authority === null && authorityFailure === null) {
+ try { authority = authorityObservation(project); }
+ catch (error) { authorityFailure = `${error.name}: ${error.message}`; }
+ }
+ };
+ const child = spawn('docker', [...compose, 'up', '--build', '--abort-on-container-exit', '--exit-code-from', 'offline-verifier'], {
+ env: environment, stdio: ['ignore', 'pipe', 'pipe'],
+ });
+ let output = '';
+ child.stdout.on('data', (chunk) => { output += chunk; });
+ child.stderr.on('data', (chunk) => { output += chunk; });
+ const timer = setInterval(sample, 1_000);
+ try {
+ const code = await new Promise((resolve, reject) => {
+ child.once('error', reject);
+ child.once('close', resolve);
+ });
+ sample();
+ writeFileSync(resourcePath, `${JSON.stringify({
+ schemaVersion: 1, classification: 'lv01-development-resource-observation', researchFinding: false,
+ scientificDisposition: 'not-tested', runId, startedAt, endedAt: new Date().toISOString(),
+ samplingIntervalMs: 1_000, sampleCount: samples.length, samples,
+ services: summarizeResources(samples),
+ claimBoundary: 'Sampled local Docker observations support only bounded development resource accounting; they are not a calibrated resource authorization or scientific result.',
+ }, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
+ assert.ok(authority !== null, authorityFailure ?? 'selected authority observation was never available');
+ writeFileSync(authorityPath, `${JSON.stringify(authority, null, 2)}\n`, {
+ flag: 'wx', mode: 0o600,
+ });
+ if (code !== 0) throw new Error(`docker compose exited with ${String(code)}: ${output.split('\n')[0]}`);
+ return output;
+ } finally {
+ clearInterval(timer);
+ const teardown = spawnSync('docker', [...compose, 'down', '--remove-orphans'], {
+ env: environment, encoding: 'utf8',
+ });
+ output += `\n-- teardown --\n${teardown.stdout ?? ''}${teardown.stderr ?? ''}`;
+ writeFileSync(composeLogPath, output, { flag: 'wx', mode: 0o600 });
+ if (authority === null) {
+ writeFileSync(authorityFailurePath, `${JSON.stringify({
+ schemaVersion: 1,
+ classification: 'lv01-selected-application-authority-observation',
+ researchFinding: false,
+ scientificDisposition: 'not-tested',
+ runId,
+ failure: authorityFailure ?? 'no live selected-application authority observation was available',
+ }, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
+ }
+ assert.equal(teardown.status, 0, `LV01 fixture teardown failed: ${(teardown.stderr ?? teardown.stdout ?? '').split('\n')[0]}`);
+ }
+}
+
+verifyAllocation();
+if (mode === '--check') {
+ assert.equal(existsSync(slotRoot), false, 'fixture evidence already exists; audit the retained attempt instead');
+ console.log(`LV01 development fixture v${version} is configured but unexecuted`);
+ process.exit(0);
+}
+
+assert.equal(execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8' }).trim(), '', 'LV01 fixture requires a clean committed source tree');
+assert.equal(existsSync(slotRoot), false, 'LV01 fixture evidence is single-use');
+prepareDirectories();
+let resolvedConfig;
+try { resolvedConfig = config(); }
+catch (error) {
+ writeFileSync(join(slotRoot, 'fixture-failure.json'), `${JSON.stringify({
+ schemaVersion: 1, classification: 'lv01-development-topology-fixture', researchFinding: false,
+ scientificDisposition: 'not-tested', runId, stage: 'configuration',
+ failure: `${error.name}: ${error.message}`,
+ claimBoundary: 'The development fixture failed before Docker launch. No pilot, scientific result, or resource measurement exists.',
+ }, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
+ throw error;
+}
+writeFileSync(join(slotRoot, 'config', 'run-config.json'), `${JSON.stringify(resolvedConfig, null, 2)}\n`, { mode: 0o600 });
+const environment = {
+ ...process.env,
+ ALD_MODE_R_APPLICATION_ROOT: resolve(slotRoot), ALD_SOFTWARE_COMMIT: commit,
+ ALD_MODE_R_RUN_ID: runId,
+ ALD_MODE_R_UID: String(process.getuid?.() ?? 1000), ALD_MODE_R_GID: String(process.getgid?.() ?? 1000),
+ ALD_MODE_R_CONTROLLER_STAGE: controllerStage,
+ ALD_MODE_R_BABY_A_SUBNET: allocation.networkAllocation.babyA,
+ ALD_MODE_R_BABY_B_SUBNET: allocation.networkAllocation.babyB,
+ ALD_MODE_R_CONTROL_PLANE_SUBNET: allocation.networkAllocation.controlPlane,
+};
+const compose = ['compose', '--project-name', `ald-lv01-development-v${version}`,
+ '--file', 'deploy/mode-r/docker-compose.application.v1.yml',
+ '--file', 'deploy/mode-r/docker-compose.lv01.v1.yml',
+ '--file', 'deploy/mode-r/docker-compose.lv01-isolated-networks.v1.yml'];
+let result;
+let fixture;
+try {
+ result = await runComposeWithMeasurements(compose, environment);
+ assert.ok(existsSync(resultPath), 'controller did not write the LV01 fixture result');
+ fixture = JSON.parse(readFileSync(resultPath, 'utf8'));
+ assert.equal(fixture.state, 'sealed');
+ assert.equal(fixture.trainingTurns, 64);
+ assert.equal(fixture.evaluationTurns, 24);
+ assert.equal(fixture.researchFinding, false);
+} catch (error) {
+ writeFileSync(join(slotRoot, 'fixture-failure.json'), `${JSON.stringify({
+ schemaVersion: 1, classification: 'lv01-development-topology-fixture', researchFinding: false,
+ scientificDisposition: 'not-tested', runId,
+ stage: existsSync(resultPath) ? 'result-validation' : 'container-execution',
+ failure: `${error.name}: ${error.message.split('\n')[0]}`,
+ claimBoundary: 'The development fixture reached the selected container topology but did not complete. No pilot, scientific result, or resource measurement exists.',
+ }, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
+ throw error;
+}
+assert.ok(result.length >= 0);
+console.log(`LV01 development fixture complete: ${fixture.runId}; no research finding`);
diff --git a/deploy/mode-r/run-study.mjs b/deploy/mode-r/run-study.mjs
new file mode 100644
index 00000000..971b6c0b
--- /dev/null
+++ b/deploy/mode-r/run-study.mjs
@@ -0,0 +1,274 @@
+import assert from 'node:assert/strict';
+import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+import { BaseAnchorPublisher, FakeChainTransport } from '@ald/anchor';
+import { connectDomainSignerRegistryRpc } from '@ald/hashing';
+import { createIsolatedAdapterFactory } from '@ald/isolation';
+import { RECURRENT_ARCHITECTURE } from '@ald/learners';
+import { buildRunConfig } from '@ald/lifecycle';
+import {
+ FORT_SIGNER_SEEDS_FILE_ENV,
+ createProductionRuntime,
+ signerProviderFromFortEnvironment,
+} from '@ald/orchestrator';
+import { SIGNER_DOMAINS } from '@ald/types';
+
+// Live Mode R qualification for the recurrent ALD-045 and ALD-046 paths.
+
+const track = process.argv[2] ?? 'no-learning';
+const allowedTracks = new Set([
+ 'no-learning',
+ 'scratch-rl',
+ 'self-supervised',
+ 'hybrid',
+]);
+if (!allowedTracks.has(track)) {
+ throw new Error(`unsupported Mode R study track: ${track}`);
+}
+
+const outputRoot = '/evidence';
+const softwareCommit = process.env['ALD_SOFTWARE_COMMIT'] ?? 'unknown';
+const runId = `mode-r-study-${track}`;
+const clock = { now: () => new Date().toISOString() };
+const transport = new FakeChainTransport({
+ network: 'base-sepolia',
+ endpointLabel: 'local-qualification-fake-chain',
+});
+const fortSignerProvider =
+ process.env[FORT_SIGNER_SEEDS_FILE_ENV] === undefined
+ ? undefined
+ : signerProviderFromFortEnvironment();
+const remoteSignerRegistry = fortSignerProvider === undefined
+ ? await (async () => {
+ const root = process.env['ALD_MODE_R_SIGNER_SOCKET_ROOT'];
+ if (!root) throw new Error('Mode R qualification signer sockets are required');
+ const sockets = Object.fromEntries(SIGNER_DOMAINS.map((domain) => [
+ domain,
+ join(root, domain, 'signer.sock'),
+ ]));
+ for (let attempt = 0; attempt < 40; attempt += 1) {
+ try {
+ return await connectDomainSignerRegistryRpc(runId, sockets);
+ } catch (error) {
+ if (attempt === 39) throw error;
+ await new Promise((resolve) => setTimeout(resolve, 250));
+ }
+ }
+ throw new Error('Mode R qualification signers did not start');
+ })()
+ : undefined;
+
+let production;
+const evidence = {
+ listRuns: () => production.runtime.listRuns().map((run) => run.runId),
+ insertAnchorReceipt: (receipt) =>
+ production.runtime.writerFor(receipt.runId).insertAnchorReceipt(receipt),
+ readCheckpoints: (id) => production.runtime.writerFor(id).readCheckpoints(id),
+ readAnchorReceipts: (id) =>
+ production.runtime.writerFor(id).readAnchorReceipts(id),
+};
+const publisher = new BaseAnchorPublisher({
+ transport,
+ anchorClass: 'simulated',
+ evidence,
+ clock,
+ anchorAddress: `0x${'42'.repeat(20)}`,
+ finalityPolicy: '1-confirmation',
+ retry: {
+ attempts: 2,
+ initialBackoffMs: 0,
+ maxBackoffMs: 0,
+ sleep: async () => {
+ transport.mineBlock();
+ },
+ },
+ confirmationPoll: { attempts: 2, intervalMs: 0 },
+});
+
+const factories = [];
+const recurrentTrack = track === 'scratch-rl' || track === 'self-supervised';
+production = createProductionRuntime({
+ databasePath: join(outputRoot, `${runId}.sqlite`),
+ bundleRoot: join(outputRoot, 'bundles'),
+ softwareCommit,
+ signerProvider: fortSignerProvider ?? (() => remoteSignerRegistry),
+ allowUnanchored: false,
+ anchorPublisher: publisher,
+ anchorPolicy: 'required',
+ adapterFactoryFor: (config, role) => {
+ const declaredTrack =
+ role === 'baby-a' ? config.babyA.track : config.babyB.track;
+ const factory = createIsolatedAdapterFactory({
+ track: declaredTrack,
+ transport: 'container',
+ endpoint: {
+ host: role,
+ port: 4318,
+ attempts: 40,
+ retryDelayMs: 250,
+ timeoutMs: 1_000,
+ hostLabel: role,
+ },
+ timing: 'normalized',
+ deadlineMs: 2_000,
+ ...(recurrentTrack
+ ? {
+ learnerOptions: {
+ backbone: RECURRENT_ARCHITECTURE,
+ learningRate: 0.003,
+ temperature: 1,
+ recurrent: {
+ hiddenSize: 16,
+ ppoClip: 0.2,
+ ppoEpochs: 4,
+ valueLossCoefficient: 0.5,
+ maxGradientNorm: 1,
+ },
+ },
+ }
+ : {}),
+ });
+ factories.push(factory);
+ return factory;
+ },
+});
+
+try {
+ const learningSignal =
+ track === 'no-learning'
+ ? 'none'
+ : track === 'self-supervised'
+ ? 'self-supervised'
+ : 'extrinsic-task';
+ const config = buildRunConfig({
+ runId,
+ experimentId: track === 'self-supervised' ? 'E11' : 'E03',
+ randomSeed: `qualification-${track}`,
+ deploymentMode: 'research-grade',
+ babyA: {
+ track,
+ modelRef: recurrentTrack ? RECURRENT_ARCHITECTURE : `qualification-${track}`,
+ trainingIsolation: 'independent',
+ },
+ babyB: {
+ track,
+ modelRef: recurrentTrack ? RECURRENT_ARCHITECTURE : `qualification-${track}`,
+ trainingIsolation: 'independent',
+ },
+ learningSignal,
+ communicationCondition: 'normal',
+ maxTurnsPerRun: 4,
+ evaluationTurns: 4,
+ checkpointEventInterval: 2,
+ protocolGitCommit: softwareCommit,
+ });
+
+ await production.runtime.createRun(config);
+ const initialized = production.runtime.getRun(runId);
+ assert.equal(initialized?.deploymentMode, 'research-grade');
+ assert.equal(initialized?.state, 'running');
+
+ const summary = await production.runtime.runToCompletion(runId);
+ assert.equal(summary.state, 'sealed');
+ const records = production.runtime.turnRecords(runId);
+ assert.equal(records.filter((record) => record.phase === 'running').length, 4);
+ assert.equal(records.filter((record) => record.phase === 'evaluating').length, 4);
+ const checkpoints = production.runtime.checkpoints(runId);
+ const receipts = production.runtime.writerFor(runId).readAnchorReceipts(runId);
+ assert.equal(receipts.length, 1);
+ assert.equal(receipts[0]?.status, 'confirmed');
+ assert.equal(transport.submissions.length, 1);
+
+ const bundleDir = join(outputRoot, 'bundles', 'runs', runId);
+ const verification = JSON.parse(
+ await readFile(join(bundleDir, 'verification-report.json'), 'utf8'),
+ );
+ assert.equal(verification.exitCode, 0);
+ const manifest = JSON.parse(
+ await readFile(join(bundleDir, 'run-manifest.json'), 'utf8'),
+ );
+ assert.equal(manifest.deploymentMode, 'research-grade');
+ let recurrentPolicy;
+ if (recurrentTrack) {
+ const initialPolicies = await Promise.all(
+ ['baby-a', 'baby-b'].map((role) =>
+ readFile(join(bundleDir, 'policies', `${role}-policy-initial.json`), 'utf8')
+ .then(JSON.parse),
+ ),
+ );
+ const latestPolicies = await Promise.all(
+ ['baby-a', 'baby-b'].map((role) =>
+ readFile(join(bundleDir, 'policies', `${role}-latest.json`), 'utf8')
+ .then(JSON.parse),
+ ),
+ );
+ const recurrentModel = (policy) =>
+ policy.track === 'self-supervised' ? policy.model.model : policy.model;
+ const initialModels = initialPolicies.map(recurrentModel);
+ const latestModels = latestPolicies.map(recurrentModel);
+ assert.equal(initialModels[0]?.architecture, RECURRENT_ARCHITECTURE);
+ assert.equal(initialModels[1]?.architecture, RECURRENT_ARCHITECTURE);
+ assert.notDeepEqual(initialModels[0]?.parameters, initialModels[1]?.parameters);
+ assert.equal(initialModels[0]?.parameterCount, initialModels[1]?.parameterCount);
+ assert.ok(latestModels.every((model) => model.updateCount > 0));
+ const policyHashes = ['babyA', 'babyB'].map((ledgerName) =>
+ production.runtime
+ .ledgers(runId)[ledgerName]
+ .filter((event) => event.eventType === 'policy.checkpointed')
+ .map((event) => event.content.policyHash),
+ );
+ assert.ok(policyHashes.every((hashes) => hashes.at(-1) === hashes.at(-2)));
+ recurrentPolicy = {
+ architecture: RECURRENT_ARCHITECTURE,
+ parameterCount: initialModels[0].parameterCount,
+ hiddenSize: initialModels[0].options.hiddenSize,
+ independentInitialParameters: true,
+ bothPoliciesUpdated: true,
+ evaluationPolicyHashesConstant: true,
+ };
+ }
+
+ const result = {
+ schemaVersion: 1,
+ classification: 'mode-r-topology-qualification',
+ researchFinding: false,
+ publicChainTransaction: false,
+ anchorTransport: 'local-qualification-fake-chain',
+ signerBoundary: fortSignerProvider === undefined
+ ? 'six-ephemeral-container-signers'
+ : 'nursery-fort-file',
+ runId,
+ track,
+ softwareCommit,
+ state: summary.state,
+ trainingTurns: 4,
+ evaluationTurns: 4,
+ eventCounts: {
+ babyA: production.runtime.ledgers(runId).babyA.length,
+ babyB: production.runtime.ledgers(runId).babyB.length,
+ channel: production.runtime.transcript(runId).length,
+ turns: records.length,
+ },
+ checkpointCount: checkpoints.length,
+ anchorReceiptCount: receipts.length,
+ verifierExitCode: verification.exitCode,
+ ...(recurrentPolicy === undefined ? {} : { recurrentPolicy }),
+ containerIds: Object.fromEntries(
+ ['baby-a', 'baby-b'].map((role) => [
+ role,
+ production.runtime.adaptersFor(runId)[role].isolation.containerId,
+ ]),
+ ),
+ };
+ await mkdir(outputRoot, { recursive: true });
+ await writeFile(
+ join(outputRoot, `${runId}-summary.json`),
+ `${JSON.stringify(result, null, 2)}\n`,
+ 'utf8',
+ );
+ process.stdout.write(`${JSON.stringify(result)}\n`);
+} finally {
+ production.close();
+ await Promise.all(factories.map((factory) => factory.dispose()));
+}
diff --git a/deploy/mode-r/verify-isolation.mjs b/deploy/mode-r/verify-isolation.mjs
new file mode 100644
index 00000000..8bf76e01
--- /dev/null
+++ b/deploy/mode-r/verify-isolation.mjs
@@ -0,0 +1,310 @@
+import assert from 'node:assert/strict';
+
+import { createIsolatedAdapterFactory } from '@ald/isolation';
+import {
+ buildConformanceRunConfig,
+ loadLearnerContract,
+ RecordingLedgerClient,
+} from '@ald/learners';
+import { fixedTokenInventory } from '@ald/types';
+import {
+ SIDE_CHANNEL_ATTACK_CATEGORIES,
+ runSideChannelRedTeamSuite,
+} from '@ald/redteam';
+
+const PORT = 4318;
+const mode = process.argv[2] ?? 'both';
+const requestedTrack = process.argv[3] ?? 'no-learning';
+
+function factoryFor(host, label, track = requestedTrack) {
+ return createIsolatedAdapterFactory({
+ track,
+ transport: 'container',
+ endpoint: {
+ host,
+ port: PORT,
+ attempts: 40,
+ retryDelayMs: 250,
+ timeoutMs: 500,
+ hostLabel: label,
+ },
+ timing: 'normalized',
+ deadlineMs: 1_000,
+ });
+}
+
+async function initialized(host, role, track = requestedTrack) {
+ const factory = factoryFor(host, role, track);
+ const adapter = factory.create();
+ const learningSignal =
+ track === 'self-supervised' ? 'self-supervised' :
+ track === 'no-learning' ? 'none' : 'extrinsic-task';
+ const config = buildConformanceRunConfig(track, {
+ deploymentMode: 'research-grade',
+ runId: `mode-r-${mode}-${track}`,
+ seed: `mode-r-${mode}-${track}-${role}`,
+ learningSignal,
+ });
+ await adapter.init({
+ runId: config.runId,
+ role,
+ babyId: role === 'baby-a' ? 'A' : 'B',
+ config,
+ learnerContract: loadLearnerContract(track),
+ seed: `private-${role}`,
+ symbolInventory: fixedTokenInventory(config.symbolInventorySize),
+ ledger: new RecordingLedgerClient(config.runId, role),
+ });
+ return { adapter, config, factory };
+}
+
+function assertClosedProbe(probe, peer) {
+ assert.equal(probe.permissionModel, true);
+ assert.equal(probe.fsRead, 'denied');
+ assert.equal(probe.clipboard, 'denied');
+ assert.equal(probe.childProcess, 'denied');
+ assert.equal(probe.worker, 'denied');
+ assert.equal(probe.network, 'refused', `direct route to ${peer} must be absent`);
+ assert.deepEqual(probe.envKeys, []);
+}
+
+async function exercise(adapter, role) {
+ await adapter.observe({
+ runId: `mode-r-${mode}`,
+ turn: 1,
+ recipient: role,
+ encoding: 'opaque-numeric',
+ payload: [[0, 1, 1], [1, 0, 0]],
+ scenarioRef: 'scenario:mode-r-smoke',
+ });
+ const proposal = await adapter.act({
+ turn: 1,
+ role: 'sender',
+ responseBudgetMs: 100,
+ availableActions: ['emit_symbols'],
+ });
+ assert.equal(proposal.proposal.kind, 'emit_symbols');
+}
+
+async function exerciseTraining(adapter, role, track) {
+ const runId = `mode-r-${mode}-${track}`;
+ await adapter.observe({
+ runId,
+ turn: 1,
+ recipient: role,
+ encoding: 'opaque-numeric',
+ payload: [[0, 1, 1], [1, 0, 0]],
+ scenarioRef: `scenario:mode-r-training-${track}`,
+ });
+ await adapter.act({
+ turn: 1,
+ role: 'sender',
+ responseBudgetMs: 100,
+ availableActions: ['emit_symbols'],
+ });
+ await adapter.onOutcome({
+ runId,
+ turn: 1,
+ role: 'sender',
+ success: true,
+ reward: track === 'self-supervised' ? null : 1,
+ payload: [1],
+ });
+
+ const before = JSON.stringify(adapter.exportPolicy());
+ let counterpartStateRead = false;
+ const batch = {
+ runId,
+ turns: [1],
+ learningSignal:
+ track === 'self-supervised' ? 'self-supervised' : 'extrinsic-task',
+ };
+ Object.defineProperty(batch, 'counterpartState', {
+ enumerable: true,
+ get() {
+ counterpartStateRead = true;
+ throw new Error('counterpart state crossed the training boundary');
+ },
+ });
+ await adapter.updatePolicy(batch);
+ const after = JSON.stringify(adapter.exportPolicy());
+ assert.equal(counterpartStateRead, false);
+ assert.notEqual(after, before, `${track} must perform a real local-buffer update`);
+ assert.equal(after.includes('counterpart state'), false);
+ return { counterpartStateRead, policyChanged: true };
+}
+
+async function transportObservation(adapter, execute) {
+ const before = adapter.transportStats?.bytesIn ?? 0;
+ await execute();
+ const after = adapter.transportStats?.bytesIn ?? 0;
+ return {
+ sizeBytes: after - before,
+ // This is the fixed completion tick visible to the peer; host response
+ // content and errors remain inside the Nursery-side proxy.
+ responseBody: '{"turnComplete":true}',
+ };
+}
+
+function transportAttackOptions(adapter) {
+ return {
+ samplesPerCondition: 3,
+ timingTolerance: { maxAbsoluteMeanDifference: 100 },
+ sizeTolerance: { maxAbsoluteMeanDifference: 0 },
+ conditions: [
+ {
+ label: 'accepted',
+ execute: () =>
+ transportObservation(adapter, () =>
+ adapter.observe({
+ runId: `mode-r-${mode}`,
+ turn: 2,
+ recipient: 'baby-a',
+ encoding: 'opaque-numeric',
+ payload: [[0, 1, 1], [1, 0, 0]],
+ scenarioRef: 'scenario:mode-r-timing',
+ }),
+ ),
+ },
+ {
+ label: 'rejected',
+ execute: () =>
+ transportObservation(adapter, async () => {
+ try {
+ await adapter.receive({
+ runId: `mode-r-${mode}`,
+ turn: 2,
+ logicalSender: 'baby-b',
+ carrier: 'fixed-token',
+ publicArtifact: { symbols: 'invalid' },
+ channelEventHash: `sha256:${'0'.repeat(64)}`,
+ });
+ throw new Error('malformed delivery unexpectedly reached the adapter');
+ } catch (error) {
+ assert.equal(error?.code, 'host-error');
+ }
+ }),
+ },
+ ],
+ };
+}
+
+async function runBoth() {
+ const a = await initialized('baby-a', 'baby-a');
+ const b = await initialized('baby-b', 'baby-b');
+ try {
+ assert.equal(a.adapter.isolation.boundary, 'separate-container');
+ assert.equal(b.adapter.isolation.boundary, 'separate-container');
+ assert.equal(a.adapter.isolation.timingNormalization, 'normalized');
+ assert.equal(b.adapter.isolation.timingNormalization, 'normalized');
+ assert.ok(a.adapter.isolation.containerId);
+ assert.ok(b.adapter.isolation.containerId);
+ assert.notEqual(
+ a.adapter.isolation.containerId,
+ b.adapter.isolation.containerId,
+ );
+
+ const [probeA, probeB] = await Promise.all([
+ a.adapter.probeIsolation({
+ readPath: '/run/ald-peer-secret',
+ connect: { host: 'baby-b', port: PORT, timeoutMs: 300 },
+ }),
+ b.adapter.probeIsolation({
+ readPath: '/run/ald-peer-secret',
+ connect: { host: 'baby-a', port: PORT, timeoutMs: 300 },
+ }),
+ ]);
+ assertClosedProbe(probeA, 'baby-b');
+ assertClosedProbe(probeB, 'baby-a');
+ const sideChannels = await runSideChannelRedTeamSuite({
+ config: a.config,
+ hostProbes: [probeA, probeB],
+ transport: transportAttackOptions(a.adapter),
+ });
+ assert.equal(sideChannels.passed, true);
+ assert.equal(sideChannels.claimEligible, true);
+ await Promise.all([
+ exercise(a.adapter, 'baby-a'),
+ exercise(b.adapter, 'baby-b'),
+ ]);
+ process.stdout.write(`${JSON.stringify({
+ mode: 'research-grade',
+ containers: [a.adapter.isolation.containerId, b.adapter.isolation.containerId],
+ directNetworkRoutes: 'refused',
+ filesystem: 'denied',
+ clipboard: 'denied',
+ childProcess: 'denied',
+ worker: 'denied',
+ environmentKeys: 0,
+ timingNormalization: 'normalized',
+ activeSideChannelCategories: SIDE_CHANNEL_ATTACK_CATEGORIES.length,
+ transportSamples: sideChannels.transport.timing.totalSamples,
+ timingWithinTolerance:
+ sideChannels.transport.timingDecision.withinTolerance,
+ sizeWithinTolerance: sideChannels.transport.sizeDecision.withinTolerance,
+ errorShapeWithinTolerance:
+ sideChannels.transport.errorDecision.withinTolerance,
+ })}\n`);
+ } finally {
+ await Promise.all([a.factory.dispose(), b.factory.dispose()]);
+ }
+}
+
+async function runSurvivor() {
+ const b = await initialized('baby-b', 'baby-b');
+ try {
+ const probe = await b.adapter.probeIsolation({
+ connect: { host: 'baby-a', port: PORT, timeoutMs: 300 },
+ });
+ assert.equal(probe.network, 'refused');
+ await exercise(b.adapter, 'baby-b');
+ process.stdout.write(`${JSON.stringify({
+ killed: 'baby-a',
+ survivor: 'baby-b',
+ survivorResponsive: true,
+ directNetworkRoute: 'refused',
+ })}\n`);
+ } finally {
+ await b.factory.dispose();
+ }
+}
+
+async function runTraining() {
+ assert.ok(
+ ['scratch-rl', 'self-supervised', 'hybrid'].includes(requestedTrack),
+ `unsupported training track: ${requestedTrack}`,
+ );
+ const a = await initialized('baby-a', 'baby-a', requestedTrack);
+ const b = await initialized('baby-b', 'baby-b', requestedTrack);
+ try {
+ assert.equal(a.adapter.isolation.boundary, 'separate-container');
+ assert.equal(b.adapter.isolation.boundary, 'separate-container');
+ assert.ok(a.adapter.isolation.containerId);
+ assert.ok(b.adapter.isolation.containerId);
+ assert.notEqual(a.adapter.isolation.containerId, b.adapter.isolation.containerId);
+ const results = await Promise.all([
+ exerciseTraining(a.adapter, 'baby-a', requestedTrack),
+ exerciseTraining(b.adapter, 'baby-b', requestedTrack),
+ ]);
+ process.stdout.write(`${JSON.stringify({
+ mode: 'research-grade-training',
+ track: requestedTrack,
+ containers: [a.adapter.isolation.containerId, b.adapter.isolation.containerId],
+ updateSource: 'private-local-buffer',
+ counterpartStateRead: results.some((result) => result.counterpartStateRead),
+ policiesChanged: results.every((result) => result.policyChanged),
+ })}\n`);
+ } finally {
+ await Promise.all([a.factory.dispose(), b.factory.dispose()]);
+ }
+}
+
+if (mode === 'both') {
+ await runBoth();
+} else if (mode === 'survivor') {
+ await runSurvivor();
+} else if (mode === 'training') {
+ await runTraining();
+} else {
+ throw new Error(`unknown verification mode: ${mode}`);
+}
diff --git a/docs/anchor-staging.md b/docs/anchor-staging.md
new file mode 100644
index 00000000..83e8d207
--- /dev/null
+++ b/docs/anchor-staging.md
@@ -0,0 +1,162 @@
+# Public-Chain Anchor Staging (ALD-020 / ALD-022)
+
+Manual runbook for submitting checkpoint digests to Base Sepolia (ALD-020)
+and staging the mainnet opt-in path (ALD-022) with
+`scripts/anchor-public-submit.mjs`. All research campaign runs stay on
+`anchorClass: "simulated"`; public-chain operation is outside the approved
+research profile and needs a prospective governance amendment per
+`SPECIFICATION.md` §13.4 before any production use.
+
+## Prerequisites
+
+- `pnpm install --frozen-lockfile && pnpm run build`
+- A Base RPC endpoint per network, each stored as one URL on a single line in
+ its own file (for example `~/.ald/sepolia-rpc.url`). Only the host label is
+ ever recorded; userinfo, path, and query credentials never reach output.
+- A dedicated, low-balance anchor wallet key file per network (see below).
+ Mainnet additionally requires a managed signer or hardware-backed key per
+ SPEC §19 ADR-02 — a file key is a staging-only stand-in, never production
+ custody.
+- For Sepolia: testnet ETH on the wallet from a Base Sepolia faucet. One
+ zero-value anchor costs well under 0.001 test ETH.
+- For mainnet: explicit user approval in chat before any broadcast. No
+ exceptions, including staging.
+
+Generate a wallet (mode 0600, refuses to overwrite; prints `{ "keyFile", "address" }` JSON):
+
+```sh
+node scripts/anchor-public-submit.mjs --generate-key --key-file ~/.ald/sepolia-anchor.key
+```
+
+## ALD-020: Base Sepolia submission
+
+1. Dry-run (read-only; broadcasts nothing):
+
+```sh
+node scripts/anchor-public-submit.mjs --dry-run --network base-sepolia \
+ --rpc-url-file ~/.ald/sepolia-rpc.url \
+ --from 0x --to 0x \
+ --checkpoint-hash sha256:<64 hex> \
+ --out /tmp/ald020-dryrun.json
+```
+
+A `--checkpoint-manifest ` may replace `--checkpoint-hash`; the
+manifest `checkpointHash` is then bound into the evidence.
+
+2. Broadcast, wait for 1 confirmation, verify calldata, write the receipt:
+
+```sh
+node scripts/anchor-public-submit.mjs --broadcast --network base-sepolia \
+ --rpc-url-file ~/.ald/sepolia-rpc.url \
+ --key-file ~/.ald/sepolia-anchor.key \
+ --to 0x \
+ --checkpoint-hash sha256:<64 hex> \
+ --out reports/research/ald-020-sepolia-anchor-receipt.json
+```
+
+3. Verify on the public explorer at the printed
+`https://sepolia.basescan.org/tx/` URL:
+ - status success, chain 84532 (Base Sepolia);
+ - `from` is the anchor wallet, `to` is the project address;
+ - input data is exactly `0x`, 66 characters, nothing else.
+4. Link the receipt and explorer URL in `BACKLOG.md` ALD-020 and the
+conformance matrix, then check the box.
+
+Receipt shape can be checked dry (no network) before the live run:
+
+```sh
+node -e "import('./scripts/anchor-faucet-watch.mjs').then(m => console.log(JSON.stringify(m.readAndAssertReceipt(process.argv[1]))))" /tmp/ald020-receipt.json
+```
+
+It refuses wrong networks, wrong chain ids, and explorer URLs that merely
+contain (rather than start with) `https://sepolia.basescan.org/tx/0x`.
+
+If a broadcast run is interrupted after printing the transaction hash, check
+the explorer before re-running: a fresh process signs at a fresh nonce and
+would pay for a second transaction for the same checkpoint.
+
+## ALD-022: mainnet staging (no broadcast without approval)
+
+Default-off is enforced twice: `BaseAnchorPublisher` throws
+`MainnetAnchoringDisabledError` before any RPC call unless constructed with
+`allowMainnet: true` AND `ALD_ALLOW_MAINNET_ANCHORING=true`, and the staging
+script applies the same rule to every mainnet invocation, including dry-runs.
+Covered by `packages/anchor/__tests__/publisher.test.ts`
+(mainnet policy switch) and `scripts/__tests__/anchor-staging.test.ts`.
+
+Staging dry-run (read-only; the farthest this path goes without approval):
+
+```sh
+ALD_ALLOW_MAINNET_ANCHORING=true node scripts/anchor-public-submit.mjs --dry-run \
+ --network base-mainnet --allow-mainnet \
+ --rpc-url-file ~/.ald/mainnet-rpc.url \
+ --from 0x --to 0x \
+ --checkpoint-hash sha256:<64 hex> \
+ --out /tmp/ald022-dryrun.json
+```
+
+Without both opt-ins the command refuses with exit 2 (gate error
+`MAINNET_ANCHORING_DISABLED`; only the human-readable message is printed)
+and makes zero mainnet RPC calls.
+
+Mainnet broadcast additionally requires `--confirm-mainnet-broadcast`, which
+must be passed only after explicit user approval in chat, and then waits for
+the `safe-tag` depth (192 Base blocks) before reporting anchored-final:
+
+```sh
+ALD_ALLOW_MAINNET_ANCHORING=true node scripts/anchor-public-submit.mjs --broadcast \
+ --network base-mainnet --allow-mainnet --confirm-mainnet-broadcast \
+ --rpc-url-file ~/.ald/mainnet-rpc.url \
+ --key-file ~/.ald/mainnet-anchor.key \
+ --to 0x \
+ --checkpoint-hash sha256:<64 hex> \
+ --out reports/research/ald-022-mainnet-anchor-receipt.json
+```
+
+Status: staging-ready. ALD-022 criterion 2 stays open until an approved
+staging broadcast produces a mainnet receipt.
+
+## Faucet-watch resume (R2-C, ALD-020)
+
+`scripts/anchor-faucet-watch.mjs` is the one-command resume path: it checks
+the anchor wallet balance and, only when funded, broadcasts, verifies the
+Sepolia receipt, checks the ALD-020 box, syncs BACKLOG/README/RESEARCH counts,
+regenerates the conformance matrix, verifies gates, and commits the named
+files. Push is opt-in (`--push`, only with push authorization).
+
+```sh
+node scripts/anchor-faucet-watch.mjs \
+ --rpc-url-file ~/.ald/sepolia-rpc.url \
+ --key-file ~/.ald/sepolia-anchor.key \
+ --to 0x \
+ --checkpoint-hash sha256:<64 hex> \
+ --receipt-out reports/research/ald-020-sepolia-anchor-receipt.json
+```
+
+Safety shape (not configurable): no `--network` input exists; the RPC chain
+id must be exactly 84532; the receipt must report `base-sepolia`; a zero
+balance is never funded. Mainnet is unreachable through this script.
+`--check-only` reports the funding decision without broadcasting.
+`--watch --interval-seconds --max-checks ` repeats the check bounded;
+each funded check broadcasts at most once (receipt files are never
+overwritten, and re-running after a close-out refuses the checked box).
+`--receipt-out` must resolve inside the repository (symlink escapes are
+refused) so the close-out commit only ever stages repo files.
+
+Not funded yet: the command prints `NOT_FUNDED` with balance, required total
+(estimate x 2 margin), and address, then exits 0. Fund the printed address
+from a Base Sepolia faucet and re-run; nothing else changes until funds land.
+
+## Troubleshooting
+
+- `RPC chain id ... does not match`: the RPC file points at the wrong network.
+- Empty balance on Sepolia: fund the printed wallet address from a faucet
+ and re-run the dry-run.
+- `eth_estimateGas` failure: the node rejected the estimate call (not a
+ funding problem) — check the RPC endpoint and the `from`/`to` addresses,
+ then re-run.
+- Key file permission errors: the loader requires mode 0600 and a
+ group/other-writable-free parent directory (`chmod 600` the file,
+ `chmod go-w` the directory); the generator already creates both correctly.
+- `Refusing to overwrite`: evidence files are write-once; choose a new
+ `--out` path instead of replacing a receipt.
diff --git a/docs/api-reference.md b/docs/api-reference.md
new file mode 100644
index 00000000..5f349db5
--- /dev/null
+++ b/docs/api-reference.md
@@ -0,0 +1,60 @@
+# Twin route API reference
+
+The host prefixes each path with its twin name (`/nursery`, `/baby-a`, or
+`/baby-b`); the tables show the unprefixed pack route. Every request supplies
+`x-ald-role` and `x-ald-service-token`. Human calls may also supply
+`x-ald-actor`, which defaults to the authenticated role. Mode P's development
+token for role `` is `dev-`; it is not a Mode R credential model.
+
+Every success is JSON `{ "ok": true, ...data }` with status 200 or 201. Every
+failure is JSON `{ "ok": false, "error": { "code", "message", "details"? } }`
+with one of `INVALID_REQUEST`, `UNAUTHENTICATED`, `FORBIDDEN`, `NOT_FOUND`,
+`DUPLICATE_ID`, `CHANNEL_REJECTED`, or `CONFLICT`.
+
+## Nursery routes
+
+`researcher-operator` inherits `researcher-viewer` access. `:id` is a
+path-safe run ID.
+
+| Route | Authorized role | Request | Success data |
+|---|---|---|---|
+| `POST /runs` | researcher-operator | a `RunConfig`, or `{ config: RunConfig, preRegistration?: PreRegistrationBinding }` | 201 `{ run: RunSummary }` |
+| `POST /runs/:id/step` | internal-controller | empty body | `{ turnResult, run: RunSummary }`; seals automatically when the step enters `sealing` |
+| `GET /runs` | researcher-viewer | none | `{ runs: RunSummary[] }` |
+| `GET /runs/:id` | researcher-viewer | none | `{ run: RunSummary }` |
+| `GET /runs/:id/transcript` | researcher-viewer | none | `{ transcript: ChannelEvent[] }` |
+| `GET /runs/:id/ledgers` | researcher-viewer | none | viewer: `{ ledgers: AuditLedgers, agentNativeEventCounts }`; operator: `{ ledgers: NativeLedgers, auditLedgers }` |
+| `GET /runs/:id/audit` | researcher-viewer | none | `{ audit: InterventionEvent[] }` |
+| `GET /runs/:id/checkpoints` | researcher-viewer | none | `{ checkpoints: CheckpointManifest[] }` |
+| `GET /runs/:id/anchors` | researcher-viewer | none | `{ anchors: AnchorReceipt[] }` |
+| `GET /runs/:id/telemetry` | researcher-viewer | none | `{ telemetry: TelemetryRecord[] }` |
+| `GET /runs/:id/replay` | researcher-viewer | none | `{ replayDigest, scenario, readOnly: true, overrideAllowed: false }` |
+| `GET /runs/:id/observations` | researcher-operator | none | `{ observations }`; the private read is audited |
+| `POST /runs/:id/pause` | researcher-operator | `{ reasonCode: string, details?: object }` | `{ run: RunSummary }` |
+| `POST /runs/:id/resume` | researcher-operator | `{ reasonCode: string, details?: object }` | `{ run: RunSummary }` |
+| `POST /runs/:id/abort` | researcher-operator | `{ reasonCode: string, details?: object }` | `{ run: RunSummary }` |
+| `POST /runs/:id/annotate` | researcher-operator | `{ reasonCode: string, details?: object }` | `{ event: InterventionEvent }`; creates a checkpoint and deviation reference |
+| `GET /runs/:id/verification-report` | researcher-viewer | none | `{ report: VerificationReport }` from the exported file |
+| `POST /runs/:id/verify` | researcher-operator | empty body | `{ report: VerificationReport }`; exports, writes proofs, then verifies |
+| `POST /session/snapshot` | researcher-operator | empty body | 201 `{ snapshotId: string }` |
+| `POST /session/restore` | researcher-operator | `{ snapshotId: string }` | `{ runs: RunSummary[] }` recovered from that session index |
+| `GET /session/delta?since=` | researcher-viewer | query parameter `since` | `{ since, changed: RunSummary[] }` |
+
+## Baby routes
+
+The same six routes exist on both `/baby-a` and `/baby-b`. A request that
+names the counterpart anywhere in its body is forbidden. `observe`, `act`,
+`deliver`, and `outcome` require the run to be `running` or `evaluating`.
+
+| Route | Authorized role | Request | Success data |
+|---|---|---|---|
+| `POST /observe` | internal-controller | `{ runId, observation: Observation }`, whose recipient is this Baby | `{}` after hygiene validation and delivery |
+| `POST /act` | internal-gateway | `{ runId, turnBudget: TurnBudget }` | `{ envelope: TurnProposalEnvelope }` |
+| `POST /deliver` | internal-gateway | `{ runId, delivery: DeliveredChannelArtifact }` | `{ ledgerDraft: LedgerDraftEnvelope }` |
+| `POST /outcome` | internal-controller | `{ runId, outcome: OutcomeEvent }` | `{}` |
+| `GET /ledger?runId=` | researcher-viewer | query parameter `runId` | `{ ledger: AuditLedgerEntry[], agentNativeEventCount: number }`; never returns native content |
+| `POST /reset` | internal-controller | `{ runId }` | currently returns 409 `CONFLICT`; reset occurs only during run creation |
+
+The authoritative schemas are exported by `@ald/types`. New routes must also
+be added to the exhaustive authorization/envelope matrix and this document;
+`scripts/check-api-docs.mjs` enforces that correspondence.
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 00000000..c7b73103
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,37 @@
+# Architecture overview
+
+The system has three twin surfaces: `baby-a`, `baby-b`, and `nursery`. A Baby
+receives only a private numeric observation, acts through the Symbol Gateway,
+receives only a validated public artifact, and writes only its own native
+ledger. There is deliberately no Baby-to-Baby route. The Nursery owns run
+lifecycle, deterministic scenarios, evidence export, checkpointing,
+verification, operator interventions, and the Research Console.
+
+One committed turn follows this boundary:
+
+1. the Scenario Engine deterministically produces role-specific observations;
+2. the active Baby adapter returns a proposal and private intention draft;
+3. the Symbol Gateway validates the carrier and commits the public channel
+ event plus the sender ledger event atomically through the Evidence Writer;
+4. the recipient adapter receives only the committed public artifact and
+ returns its interpretation draft;
+5. the Nursery evaluates the task outcome and records signed outcome and turn
+ evidence; and
+6. the Checkpoint Service commits ordered Merkle roots for every evidence
+ stream, which the Anchor Publisher may submit to Base.
+
+SQLite WAL and append-only constraints are the local source of truth. Bundle
+export is a deterministic projection of that store. The independent verifier
+uses only an exported bundle, public keys, and anchor receipts; it does not
+trust the live runtime or its database.
+
+Prototype Mode keeps logical state separation in one process. Research-Grade
+Mode places the two learner hosts in distinct hardened containers and distinct
+internal networks, with the Nursery attached to both networks as the only
+test controller. The exact six allowed mode differences are represented by
+`MODE_COMPARISON`; all protocol, scenario, evidence, and learner behavior is
+otherwise shared.
+
+Operational entry points are documented in [the API reference](api-reference.md),
+[the Mode R runbook](mode-r-runbook.md), and
+[the snapshot/restore runbook](snapshot-restore-runbook.md).
diff --git a/docs/audit-cost-and-utility.md b/docs/audit-cost-and-utility.md
new file mode 100644
index 00000000..09e1b28c
--- /dev/null
+++ b/docs/audit-cost-and-utility.md
@@ -0,0 +1,142 @@
+# Audit Cost and Utility: Local Software Qualification
+
+Status: completed local benchmark; A03 remains in progress
+Evidence class: software qualification, not an empirical research result
+Protocol: `protocols/audit-cost-utility.v1.json`
+Receipt: `reports/research/audit-cost-utility-receipt.json`
+
+## Question and boundary
+
+This benchmark asks what the current audit artifacts cost to store and verify on the
+local qualification host, and whether the two existing verifier implementations
+reject the already-prespecified integrity challenges. It does not test whether the
+agents communicate successfully, whether ledger interpretations are truthful, or
+whether a public-chain anchor improves scientific validity.
+
+Five immutable E13 software-qualification bundles were selected by run ID and by the
+content-tree hashes already captured in the data/claim manifest. They contain 260
+turns and 1,828 total stream events. All five remain excluded from pilot,
+confirmatory, replication, and hypothesis-estimation use. Both verifiers accepted
+their local contents only under the explicit unanchored qualification boundary. No
+RPC endpoint was supplied and no public transaction was authorized.
+
+## Comparators
+
+The comparison separates storage models from executable verification work:
+
+| Comparator | Exact meaning | What it can establish |
+|---|---|---|
+| Payload lower bound | JSON serialization of each event's `content` value | Approximate application payload volume only |
+| Ordinary-log proxy | JSONL with stream, sequence, turn when present, event type, and content | Declared storage comparator; it has no implemented integrity guarantee |
+| Signed streams | Actual five stream files, including links, hashes, signer metadata, signatures where applicable, and witness fields | Stored signed/linked record cost, but not complete bundle cost |
+| Whole-input SHA-256 | One trusted digest over sorted paths and bytes of the same verifier input | Detects later byte changes relative to that trusted digest; does not validate sequence, signatures, lineage, receipts, or truth |
+| Rust auditor | Independent local parsing, canonical hashes, event signatures, chain/checkpoint and receipt bindings, and attachment hashes | Cross-implementation integrity check with narrower coverage than the production verifier |
+| TypeScript production verifier | Complete bundle verification, including the exported proof files and experiment-record bindings | Current production verification behavior; public-chain facts still require an RPC reader |
+
+The ordinary-log proxy is deliberately not promoted into a new logging system. Its
+purpose is to make the storage denominator explicit. Likewise, the full bundle ratio
+must not be labeled "cryptographic overhead": the bundle also carries policy state,
+prompts, configuration, and experiment metadata.
+
+## Method
+
+The protocol fixed one warm-up and five measured subprocess invocations per bundle
+for each verifier. Twenty whole-input SHA-256 measurements were made per bundle. The
+monotonic wall clock includes process startup for both verifiers. Runs were serial;
+filesystem cache state was not controlled. Medians and p95 values are descriptive for
+this six-logical-CPU, approximately 7.5-GiB host. They are not inferential performance
+estimates and should not be generalized to a study deployment.
+
+The benchmark re-hashed verifier inputs before and after measurement and aborted on
+any change. `verification-report.json` was excluded from verifier input and included
+only in the captured-directory total. The generated receipt records executable and
+compiled-verifier hashes, host details, every timing sample summary, input-tree
+hashes, and per-verifier coverage counters.
+
+## Storage result
+
+| Layer or component | Bytes across five bundles | Bytes per turn | Share of verifier input |
+|---|---:|---:|---:|
+| Payload lower bound | 546,418 | 2,101.61 | 2.65% |
+| Ordinary-log proxy | 687,916 | 2,645.83 | 3.33% |
+| Actual signed streams | 2,053,633 | 7,898.59 | 9.94% |
+| Integrity core | 12,117,263 | 46,604.86 | 58.68% |
+| Policy state | 6,437,525 | 24,759.71 | 31.17% |
+| Experiment metadata and analysis | 41,793 | 160.74 | 0.20% |
+| Complete verifier input | 20,650,214 | 79,423.90 | 100.00% |
+
+Actual signed streams are 2.985× the declared ordinary-log proxy, an incremental
+1,365,717 bytes or 198.53% for these five small runs. Complete verifier inputs are
+30.019× the proxy. That larger ratio is driven principally by integrity structures
+and policy snapshots: proof files alone account for 11,795,564 bytes, while
+checkpoints account for 309,373 bytes. This small-run format stores many individual
+proof files, so the ratio is sensitive to checkpoint frequency, run length, and file
+layout. It is not a stable asymptotic estimate.
+
+## Offline verification result
+
+| Bundle | Events | Proof files checked by production verifier | Whole-input SHA-256 median | Production verifier median | Rust auditor median |
+|---|---:|---:|---:|---:|---:|
+| Fixed glyph | 333 | 2,736 | 48.964 ms | 982.747 ms | 39.159 ms |
+| Fixed token | 345 | 3,095 | 49.712 ms | 1,027.571 ms | 41.201 ms |
+| Generative bitmap | 378 | 3,140 | 52.751 ms | 1,156.726 ms | 47.084 ms |
+| Generative canvas | 381 | 3,146 | 52.136 ms | 1,069.228 ms | 47.236 ms |
+| Generative tone | 391 | 3,274 | 53.950 ms | 1,108.121 ms | 49.963 ms |
+
+Both verifiers accepted all five local unanchored bundles. The production verifier
+checked 15,391 exported proof files in total. The Rust auditor checked the same 1,828
+stream events and 171 checkpoints but does not implement the exported proof-file
+pass; its faster time is therefore evidence about a narrower independent audit, not
+an equivalent-work performance advantage. Whole-input hashing reads the complete
+input tree but supplies only byte-change detection relative to an already trusted
+digest.
+
+## Integrity-challenge utility
+
+The benchmark binds to the prior frozen integrity-challenge receipt rather than
+creating new mutation definitions after observing performance. Both implementations
+accepted the unchanged fixture and rejected each of six challenges: event-content
+mutation, attachment-byte mutation, lineage injection, wrong-chain substitution,
+false receipt, and an unanchored tail. This yields 12 observed rejections in 12
+implementation-by-case challenges.
+
+That count is a deterministic conformance result from one fixture per case. It is not
+a statistical sensitivity estimate, does not sample an attacker distribution, and
+does not show that ordinary logs would fail every such mutation. A trusted whole-file
+digest would detect changed bytes, but it would not independently adjudicate the
+semantic and structural rules that the verifiers apply.
+
+## What remains for A03
+
+A03 stays `IN_PROGRESS`. Its local software-qualification component is complete, but
+three preconditions remain:
+
+1. measure actual deterministic simulated-commitment submission, confirmation, and
+ receipt-verification latency; monetary fee remains not applicable;
+2. repeat cost measurement on prospectively registered pilot and confirmatory
+ bundles at the selected checkpoint schedule and study scale; and
+3. execute the registered ordinary-log, signed-log, and full-audit comparison on the
+ same study inputs before drawing an incremental scientific-value conclusion.
+
+The present public-anchor result is exactly `not-measured`: zero transactions, null
+latency, and null fee. Local fake-chain receipts cannot close that gap.
+
+## Reproduction
+
+After building the TypeScript workspace and the release Rust auditor, regenerate the
+host-specific receipt deliberately with:
+
+```sh
+pnpm run benchmark:audit-cost
+```
+
+Routine checks validate the stored receipt without rerunning timing:
+
+```sh
+pnpm run audit:audit-cost
+```
+
+Regeneration changes descriptive timing values and should be committed only as a new
+declared benchmark snapshot. The receipt checker verifies the protocol hash, evidence
+boundary, bundle count, storage accounting, timing ordering, verifier coverage
+distinction, mutation count, and the absence of a fabricated public anchor result.
diff --git a/docs/carrier-perception-qualification.md b/docs/carrier-perception-qualification.md
new file mode 100644
index 00000000..d3cab709
--- /dev/null
+++ b/docs/carrier-perception-qualification.md
@@ -0,0 +1,28 @@
+# Carrier Perception Qualification
+
+This qualification covers a deliberately narrow part of the E13 analysis path. It
+tests deterministic carrier-specific distances and held-out nearest-prototype
+scoring for bitmap, canvas, and tone marks on synthetic fixtures. The protocol and
+generated receipt bind the implementation to exact source hashes and to a clean,
+detached v0.1.71 validation.
+
+The bitmap distance is normalized cell Hamming distance. Canvas marks are rasterized
+on the registered 16-by-16 grid and compared over a fixed plus-or-minus-one-cell
+translation window. Tone marks use a normalized sequence edit cost with graded pitch
+and duration substitutions. The evaluation uses deterministic distance, family, and
+identifier ordering and reports exact structural novelty separately from distance.
+
+The first canvas positive control failed under raw raster Hamming: a translated
+within-family stroke was ranked farther away than a crossing-family stroke. That
+failure is retained in the receipt. The fixed translation window corrected this
+specific diagnostic defect without redefining a transformed mark as an exact copy.
+
+All three synthetic carriers recover two intended families with two novel queries
+each. Six negative controls reject cross-carrier comparison, malformed carrier data,
+ambiguous identifiers, and uncovered query labels. These are deterministic software
+controls, so their perfect accuracy is not an empirical learner result.
+
+The qualification does not show that either learner acquired this metric, inferred
+a transformation rule, generalized on production-held-out forms, communicated more
+successfully, or avoided forbidden metadata, timing, envelope, container, dimension,
+sample-rate, or compression paths. B09 and E13 therefore remain open.
diff --git a/docs/carrier-side-feature-qualification.md b/docs/carrier-side-feature-qualification.md
new file mode 100644
index 00000000..ab53ef24
--- /dev/null
+++ b/docs/carrier-side-feature-qualification.md
@@ -0,0 +1,26 @@
+# Carrier Side-Feature and Reference-Topology Qualification
+
+The versioned protocol binds two related software checks on exact candidate
+v0.1.73. First, 28 deliberately malformed bitmap, canvas, and tone submissions
+exercise dimension, metadata, raw-media, sample-rate, compression, and container
+paths through the real Gateway. Each carrier also has an accepted and delivered
+control. Every attack must receive its exact registered reason, create one audit
+event, create no receiver delivery, and omit the raw artifact from that evidence.
+
+Second, the current two-container Mode R reference topology measures six accepted
+versus rejected transport samples and checks normalized timing, fixed wire size,
+constant error shape, direct-network refusal, filesystem/clipboard/process/worker
+denial, empty learner environments, crash survival, and independent local updates
+for the scratch-RL, self-supervised, and hybrid tracks.
+
+The candidate passed a fresh frozen install, 152 focused tests, all 1,803 repository
+tests, Rust tests and lint, secret scanning, dependency audit, and the complete Mode R
+container path. Raw logs are stored in ignored evidence storage and bound by hashes
+in the protocol and receipt.
+
+This is not a powered leakage estimate and not an E01 or E13 result. The structural
+attacks execute at the Gateway, while the Mode R measurement uses the current
+fixed-token reference topology. The final registered carrier, learner, deployment,
+sample count, tolerances, and negative-bound method have not been selected. B09 and
+B12 therefore remain open until the same checks are run prospectively on that exact
+study topology with eligible evidence.
diff --git a/docs/causal-ledger-and-leakage-protocol.md b/docs/causal-ledger-and-leakage-protocol.md
new file mode 100644
index 00000000..d22a777e
--- /dev/null
+++ b/docs/causal-ledger-and-leakage-protocol.md
@@ -0,0 +1,110 @@
+# Causal Ledger and Leakage Protocol
+
+Status: D06 design frozen; no study outcomes
+
+The machine-readable source of truth is
+`protocols/causal-ledger-and-leakage.v1.json`. This document explains the
+decision boundary and the independent operating-characteristic receipt in
+`reports/research/leakage-design-validation.tsv`.
+
+## Ledger chronology and claims
+
+E15/E16 predictions must be genuinely prospective. Training ends first. The
+receiver policies freeze, and only training-period native-ledger events may
+feed the versioned prediction function. The validation-selected comparator,
+ordered probe schedule, native predictions, comparator predictions, and
+function version are committed to the intervention chain and checkpointed
+before the first held-out outcome exists.
+
+H2 is behavioral and causal: on paired held-out cases it estimates the
+seed-level target-action probability change caused by a ledger-consistent
+message rather than a shuffled valid message. Receiver observations, candidate
+order, policy bytes, recurrent state, scenario slot, and delivery envelope must
+match within a pair. Probe cases are repeated measures; the seed/run is the
+independent unit.
+
+H4 is predictive and incremental: it compares the frozen native-ledger
+predictor with one non-ledger comparator selected on validation data, then
+scores both once on untouched test cases using multiclass Brier score. The
+candidate baselines are uniform, validation-majority, transcript-only,
+task-history, and frozen-policy-state predictors. The oracle is a diagnostic
+positive control and cannot be selected. Learner-native state, generated
+post-run interpretations, and human coding remain separate evidence classes.
+
+The implementation candidate in `@ald/analysis` enforces this chronology as
+separate APIs. Five eligible comparators fit on a validation-fit fold, selection
+uses a disjoint validation-selection fold, the winner is refit on all validation
+rows, and outcome-free test predictions are hash-committed before the scoring API
+accepts labels. Exact object-key checks reject undeclared information fields. The
+oracle is constructed only as a post-outcome detector-positive diagnostic and is not
+a member of the selectable comparator type.
+
+Exact detached candidate `541142d` passed a frozen install, the 6-test focused suite,
+all 1,777 tests in 141 files, three Rust auditor tests and clippy, a 657-file secret
+scan, and the dependency audit. A deterministic 40-row fit, 20-row selection, and
+12-row scoring fixture exercised all five comparators, selected the planted
+task-history signal, and passed seven negative controls. The perfect native prediction
+was constructed from fixture labels and is therefore only an oracle-like scoring
+positive control. A separate exact runtime qualification now establishes production
+chronology and evidence binding; eligible repository registration, execution, aggregate
+analysis, and independent review remain required before blocker B10 can close.
+
+## Leakage decision principle
+
+Absence of a significant association does not establish absence of leakage. A
+negative result is eligible only when it has all three of the following:
+
+1. an explicit allowed/forbidden information set and practical margin;
+2. a powered uncertainty bound below that margin; and
+3. a planted violation detected through the same analysis boundary.
+
+E01 forbids all routes around the normalized Gateway observation: peer
+process, filesystem, network, IPC, cache, environment, tools, errors, timing,
+and variable envelope size. Each attacked path needs a detector-positive
+fixture. A passing audit means no prohibited delivery in the tested topology;
+it is not a universal isolation claim.
+
+E02 targets four-way human semantic labels. A strict track may receive only
+the declared numeric sensory data, candidate set/order, and configured carrier
+payload. Human labels, target answers, semantic identifiers, captions, paths,
+OCR text, target-coded metadata, text tokenizers, and text-aligned encoders are
+forbidden. The primary rule uses at least 200 untouched test rows and requires
+the one-sided 95% Wilson upper bound on accuracy advantage over the held-out
+majority baseline to be at most 0.10. A one-hot target-feature fixture must
+have a lower advantage bound of at least 0.20. The shuffled-label interval is
+diagnostic only.
+
+E13 distinguishes communication from leakage. Ink density, stroke structure,
+pitch, duration, and reusable normalized form identity are the intended
+carrier. Their association with referents is a form-use/bandwidth diagnostic,
+not a side channel. Hidden metadata, timing, variable envelopes, undeclared
+dimensions/sample rates, container artifacts, and recognizable prior glyph
+semantics remain forbidden and must be probed separately.
+
+E20 permits only a fixed post-outcome display and the declared binary outcome
+context. Referent identity, target action, task identity, pre-outcome delivery,
+timing, sequences, combinations, and envelope variation are forbidden. For
+each seed, analysis subtracts the mean of 1,000 within-outcome permutation CMI
+values from observed Miller-Madow conditional mutual information. With at
+least 75 eligible seeds and 1,000 windows per seed, the primary negative gate
+is a one-sided 95% seed-level Student-t upper bound below 0.02 bits. The
+percentile seed bootstrap is sensitivity-only.
+
+## Outcome-blind design validation
+
+The Python/scipy receipt contains six exact operating-characteristic calculations.
+At E02's 200-row test size, a true chance probe clears the 0.10 advantage
+margin with probability 0.9157, boundary false clearance is 0.0426, and the
+deterministic one-hot positive control is detected with probability 1. At
+E20's 75-seed design, assuming normally distributed seed excess CMI with SD no
+greater than 0.04 bits, zero excess clears with probability 0.9959, boundary
+Type I error is 0.05, and a 0.04-bit planted mean is rejected as non-clear with
+probability above 0.99999999.
+
+The E20 variance assumption is a design constraint, not a finding. A blinded
+pilot must estimate it before registration. If the uncertainty bound for SD
+exceeds 0.04 bits, D07 must increase the seed count before outcomes are
+unblinded. The portable audit checks hashes, decision floors, required
+comparators, information sets, positive controls, and implementation markers;
+`pnpm audit:causal-leakage:live` additionally reproduces the Python receipt byte for
+byte.
diff --git a/docs/causal-prediction-runtime-qualification.md b/docs/causal-prediction-runtime-qualification.md
new file mode 100644
index 00000000..1432ff00
--- /dev/null
+++ b/docs/causal-prediction-runtime-qualification.md
@@ -0,0 +1,25 @@
+# Causal Prediction Runtime Qualification
+
+The versioned protocol binds the E16 prediction path to exact candidate v0.1.75.
+For each accepted evaluation delivery, the production runtime constructs the permitted
+prediction information after the Gateway fixes the artifact and before the receiver
+acts. It commits the validation-selected baseline and native-ledger distributions,
+checkpoints that intervention, and later attaches a score bound to the recorded action
+and outcome hashes.
+
+The hashed run configuration fixes the comparator-selection commitment, native
+prediction-function version, and accepted-evaluation-delivery eligibility rule.
+Creation and restart reject a missing provider, and creation or evaluation rejects a
+mismatched selection, configured function version, or returned function version. A
+prediction provider without a plan and a plan outside E16 are also rejected.
+
+The exact detached candidate passed a frozen pnpm 12.3.4 install, 10 focused tests,
+all 1,808 tests across 144 files, Rust tests and clippy, a 678-file secret scan, and
+the dependency audit. The consolidated gate took 183.69 seconds with 917,932 KiB
+peak RSS, and the checkout remained clean. Raw ignored logs and the tracked-file
+manifest are content-bound in the protocol and receipt.
+
+This is software qualification, not an E16 result. It does not externally register
+the exact validation corpus or study configuration, execute an eligible run, produce
+aggregate inference, or provide independent review. B10 therefore remains open for
+those empirical and external requirements.
diff --git a/docs/cryptographic-separation-policy.md b/docs/cryptographic-separation-policy.md
new file mode 100644
index 00000000..f1c5488e
--- /dev/null
+++ b/docs/cryptographic-separation-policy.md
@@ -0,0 +1,44 @@
+# Cryptographic Research Separation Policy
+
+Status: mandatory review gate for E40 software and evidence.
+
+## Boundary
+
+`@ald/crypto-research` implements ALD-069 research instrumentation. Its scheme
+identifiers, nonce commitments, novelty registry comparisons, and eavesdropper
+measurements describe experimental artifacts only. They do not provide or claim
+confidentiality, integrity, authenticity, forward secrecy, key security, or
+production-ready encryption.
+
+Production evidence integrity remains exclusively the responsibility of:
+
+- **ALD-009:** domain-separated SHA-256 event hashing and per-stream Ed25519
+ signing in `@ald/hashing`;
+- **ALD-019:** reviewed Base transaction signing, key handling, and anchoring in
+ `@ald/anchor`.
+
+No learned encoding, generated codebook, cipher artifact, E40 nonce, or module
+from `packages/crypto-research` may be imported into `packages/hashing` or
+`packages/anchor`. The repository's `lint:crypto-boundary` check enforces this
+dependency direction on every full check.
+
+## Required E40 reporting
+
+Every E40 attachment must report these dimensions separately:
+
+1. artifact novelty relative to the supplied prior-artifact registry;
+2. recovery outcomes for each pre-registered Eve architecture;
+3. cryptographic security as `not-established`.
+
+Uniqueness, a fresh salt or nonce, and low recovery against one implemented Eve
+must never change the third value. E40 uses synthetic, non-sensitive messages
+only. Any future security claim requires an external threat model, formal
+argument, expert review, and reviewed production implementation outside this
+research harness.
+
+## Review gate
+
+Gate G5 / ALD-077 may mark E40 *software readiness* only when the ALD-069 tests
+and the import-boundary lint pass. That readiness does not change E40's
+`Not started` notebook status, satisfy its cryptographic-review checklist, or
+constitute an experiment result.
diff --git a/docs/data-and-claim-inventory.md b/docs/data-and-claim-inventory.md
new file mode 100644
index 00000000..6218b8d8
--- /dev/null
+++ b/docs/data-and-claim-inventory.md
@@ -0,0 +1,60 @@
+# Data and Claim Inventory
+
+Status: A01 evidence-cutoff inventory complete; no research result
+
+The machine-readable source is
+`reports/research/data-claim-manifest.json`. It captures every exported run bundle
+present under local ignored evidence storage at the v0.1.104 cutoff on 2026-09-13 without copying raw events,
+messages, observations, native ledgers, or secrets into Git.
+
+This frozen inventory predates the registered E02 v1 attempt. That attempt later
+failed before analysis and is tracked separately until its partial evidence and
+disposition can be reconciled. New
+exports can make the live whole-tree comparison report a stale snapshot; the
+portable check continues to validate the frozen cutoff, not newly collected data.
+
+## Inventory result
+
+The snapshot contains 140 bundles in 22 collections and 259,037,705 bundle bytes.
+Of 140 exports, 139 have a recorded verifier exit code of zero; one partial
+unregistered development export has no recorded verification report. A pass does
+not make a collection current or scientific: 33 are historical qualification exports
+whose old intervention-tree declaration is incompatible with the current verifier,
+61 belong to development, failed, or superseded diagnostics, and 46 have tracked bounded software
+qualification support. All 140 are excluded from empirical estimates.
+
+Ninety-two bundle records show anchor confirmation, but every such confirmation
+in this snapshot is a local simulated receipt. Confirmed public-chain
+anchors: zero. The manifest uses separate fields for these values and its audit fails
+if any local receipt is relabeled public.
+
+| Evidence use | Bundles | Permitted claim |
+|---|---:|---|
+| Exact/current bounded software qualification | 46 | Only the claim boundary in the matching tracked receipt |
+| Historical qualification | 33 | Historical software behavior; no inheritance of current verifier status |
+| Development, failed, or superseded diagnostic | 61 | Development behavior, failure history, and debugging provenance only |
+| Pilot | 0 | None |
+| Confirmatory | 0 | None |
+| Replication | 0 | None |
+| Empirical research estimate | 0 | None |
+
+Every bundle entry records its path, run/experiment identifiers, deployment and
+software metadata, manifest and optional verification-report hashes, complete relative-path
+content commitment, file/byte counts, verification basis, explicit exclusion, and
+allowed claim use. Collection entries link exact qualification groups to their
+tracked receipts. Diagnostics remain visible even when their bundle-level verifier
+reported success, because a green bundle cannot erase a failed or superseded
+collection-level purpose.
+
+## Reproduction
+
+`pnpm run audit:data-claims` validates the tracked manifest without requiring the
+ignored evidence corpus, so clean checkouts and CI can enforce claim boundaries.
+`pnpm run audit:data-claims:live` additionally re-hashes every locally captured file
+and fails if the evidence snapshot differs. `pnpm run build:data-claim-manifest` is an
+intentional snapshot update and must be accompanied by a review of every new
+collection's evidence class and inclusion rule.
+
+Any future pilot, confirmatory, or replication run reopens A01. It must use its
+prospective D07 seed domain and receive an explicit inclusion decision; mere presence,
+a verifier pass, or an anchor receipt never promotes it automatically.
diff --git a/docs/e01-explicit-qualification.md b/docs/e01-explicit-qualification.md
new file mode 100644
index 00000000..c46516f8
--- /dev/null
+++ b/docs/e01-explicit-qualification.md
@@ -0,0 +1,86 @@
+# E01 explicit isolation qualification
+
+This procedure closes the explicit-corpus and signed-evidence gap left by v1.
+It is software qualification using local synthetic tasks and simulated anchors,
+not an agent-language result or independent public timestamp. Historical v1
+packets and observations remain unchanged.
+
+## Corpus and decision
+
+Each of five freshly recreated two-learner container topologies records:
+
+| Record group | Records per slot | Required observation |
+| --- | ---: | --- |
+| Explicit prohibited content, silence, retries | 20 | Gateway rejection with no recipient delivery |
+| Filesystem, cache, vector store, replay, snapshot | 10 | Both roles deny each path; clipboard/process/worker access denied, environment empty, direct peer connection refused |
+| Accepted and malformed transport calls | 80 | Captured raw 8 KiB frames; identical generic malformed-error bodies; absolute condition-mean timing difference at most 100 ms |
+| Allowed delivery control | 1 | Real receiver interpretation bound to the accepted channel event |
+| Planted detector fixtures | 1 | Host exposure, timing, size, error-shape and hidden-state-correlation detectors identify the deliberately leaky fixtures |
+
+The 112 records are individually witness-signed. Every attachment is bound by an
+intervention event with an inclusion proof in the final signed checkpoint. The
+checkpoint receives a hash-only simulated anchor. Both the TypeScript verifier and
+the separately implemented Rust auditor must accept the complete bundle.
+
+The Nursery sees actual success and error responses. It is not the other learner.
+Their bodies are not claimed identical across success and failure; error bodies
+are compared across malformed inputs. Raw captured frames are retained so their
+decoded bodies and byte counts can be recomputed. A refused filesystem operation
+does not count as permission denial. Storage tests exercise the named denied
+paths; they do not claim to test a deployed external vector database or cache.
+Planted fixtures qualify detectors and are not observations of real escapes.
+
+## Prospective execution
+
+Use the repository's pinned pnpm toolchain. Development runs use separate ignored
+directories and can never be promoted into qualification outcomes.
+
+1. Validate and commit implementation with a patch bump.
+2. Run `node scripts/register-e01-v2.mjs --compile`; commit the exact packet with
+ the next patch bump. The packet binds the complete runtime source set, explicit
+ corpus, fresh scenario seeds, decision margins and exclusion rules.
+3. Run `node scripts/register-e01-v2.mjs --activate`; commit its simulated pre-run
+ binding with the next patch bump. `--check` reproduces the full binding.
+4. On the clean execution commit, build the Rust auditor with
+ `CARGO_TARGET_DIR=.artifacts/cargo-target cargo build --locked --release --manifest-path tools/integrity-auditor/Cargo.toml`,
+ then run `node scripts/run-e01-v2.mjs` once. The runner refuses overwrite and
+ changed registered sources. A measured failure fails its slot; infrastructure
+ failure stops execution and retains available logs and evidence. No seed rerun.
+5. Run `node scripts/run-e01-v2.mjs --audit` against retained original evidence.
+ Reconcile every notebook criterion before changing E01's status.
+
+`node scripts/check-e01-v2-mutations.mjs ` checks altered
+temporary copies, including a missing per-attempt proof, and preserves the source.
+Portable corpus regression tests are under `scripts/__tests__/`.
+
+## Registered result, 2026-09-13
+
+The one registered v2 execution passed all five slots on clean commit
+`4ba1f27561d2ae20261a2e14a5a91e34fe602d56`. The retained-evidence audit then
+recomputed each signed record, raw frame, channel disposition, inclusion proof,
+and bundle result. All five bundles passed both verifier implementations.
+There are 560 signed records, 685 events, ten checkpoints, and 560 attachments.
+The five absolute condition-mean timing differences were 0.285137, 0.066581,
+0.051949, 0.305719, and 0.215077 ms (rounded here; exact values are retained),
+below the registered 100 ms bound. These are qualification measurements, not
+confidence bounds or a statistical proof of zero timing capacity.
+
+The full build/execution/cleanup took 508.951 seconds. The tracked result is
+`reports/research/e01-v2-qualification-receipt.json`; original slot records and
+bundles remain under ignored `evidence/qualification/e01-v2/`. Ten distinct
+learner container IDs establish fresh topology instances across the five slots.
+Development attempts and their failures remain separate and research-excluded.
+
+`pnpm run audit:qualification-e01` checks the portable v1/v2 receipts and their
+historical source identity. `pnpm run audit:qualification-e01:live` additionally
+requires and verifies the original ignored evidence; it does not rerun experiments.
+The runner refuses to overwrite the completed v2 execution. Future protocol changes
+require a new prospective version and seed domain.
+
+## Interpretation limits
+
+Signatures prove consistency of retained bytes under the recorded keys, not that
+the operator is independent or incapable of fabricating observations. Simulated
+anchors use no real funds and do not provide public consensus or an independent
+timestamp. Passing this finite corpus supports only its tested topology and
+threat model, not universal resistance to side channels or behavioral emergence.
diff --git a/docs/e02-observation-qualification.md b/docs/e02-observation-qualification.md
new file mode 100644
index 00000000..81977ac9
--- /dev/null
+++ b/docs/e02-observation-qualification.md
@@ -0,0 +1,294 @@
+# E02 observation qualification: implementation and evidence boundary
+
+Status: registered v1 and v2 attempts both failed before restore and probe
+evaluation. V1 exposed the nested deadline race; v2 corrected that race but exposed
+an uncovered post-action timeout path after 161 recorded turns. E02 is not qualified.
+The v2 packet and its fresh seeds are consumed and must not be reused.
+The current candidate implements one audited timeout-forfeit path for every adapter
+method used during a turn. It makes a timed-out remote adapter unreachable, pauses
+the attempt immediately, and refuses resume—including after runtime recovery—so a
+late host completion cannot affect another turn. The five-rejection pause remains
+the ceiling for non-remote timeout rejections. Local synthetic and real framed-
+adapter deadline fixtures pass. Exact v0.1.115 also passed the full repository and
+ordinary two-container Mode R gates, with source and logs bound in the
+[v3 readiness qualification](../reports/research/e02-v3-readiness-qualification.md).
+The v3 packet is repository-registered under
+`sha256:d35e6b118e4c811f80b426680a0b2f4b0c9b8da3e59bdc56293e557ef979f248`.
+Its five identifiers and seeds are disjoint from v1/v2. A matching deterministic
+simulated commitment records three confirmations and zero currency. The seeds remain
+unused. Exact v0.1.119 passed the full 1,963-test gate, release-auditor hash check,
+and ordinary two-container Mode R preflight. The
+[execution-gate receipt](../reports/research/e02-v3-execution-gate.md) authorizes one
+sequential v3 attempt. That attempt started on exact commit `9ec08ec` (rewritten as `4ee6ffd`) at
+2026-09-13T20:49:51Z. Slot 1 is running under a no-restart service; the
+[start receipt](../reports/research/e02-v3-execution-start.json) is not a completion
+or result.
+
+The collector uses actual scratch learners and the production SQLite-backed
+Nursery, not a sink that pretends to be a learner. The explicit `prototype` mode
+runs them in-process. The explicit `research-grade` mode uses isolated containers
+and the unchanged 1,000 ms normalized response budget. Neither development mode
+may be promoted to registered observations later.
+
+## Development analysis design
+
+The asset-free referential generator has two attributes, four values per attribute,
+four candidate objects, and independent sender/receiver presentation shuffles.
+The offline four-class label maps the target's first attribute to `red`, `green`,
+`blue`, or `yellow`. These are researcher-only class names, not delivered words or
+a claim that the learners recognize human color terms.
+
+Each full development slot collects 816 training turns, takes a real policy/cursor/prefix
+snapshot, closes and reopens the runtime against the original database, restores,
+and collects another 816 turns. Two additional sealing turns are excluded from
+the analysis by design. Each stage/role contributes 816 rows. This addresses the
+provisional 800-row allocation's rounding risk: per-class floor rounding can leave
+800 rows with fewer than 200 test cases, whereas 816 gives at least 201 across
+four represented classes. This minimum does not by itself establish adequate
+assurance for the complete planned 60-probe qualification.
+
+| Probe | Frozen features | Excluded information |
+| --- | --- | --- |
+| Metadata | Turn index, sender/receiver role, numeric-array dimensions, serialized observation byte count, action count, candidate count, opaque ID nibbles | Sensory values, target flag, target type, outcome, offline label |
+| Identifier | Scenario-reference nibbles and candidate-reference nibbles in delivered order, with zero padding where no candidates are exposed | Sensory values, target, outcomes, timing |
+| Timing | Log-transformed measured duration of the actual `observe` call | Payload, identifiers, target, outcomes |
+
+Timing is measured by the Nursery. It is a round-trip proxy, not a learner-side
+clock trace or a complete scheduling-channel analysis. Observation and action-budget
+schemas contain no scenario timestamp. Initialization configuration, contracts,
+and provenance must also be inventoried; they are not per-scenario feature rows.
+
+Three feature sets × two roles × two stages produce 12 separately reported probes.
+Each uses the existing 400-epoch linear softmax implementation, a frozen seeded
+stratified 75/25 split, and at least 200 untouched test rows. The one-sided 95%
+Wilson upper accuracy bound minus held-out majority accuracy must be at most
+0.10. The same learner/split with an appended offline one-hot target must have a
+one-sided lower advantage of at least 0.20. Twenty label-shuffle fits are diagnostic
+only. Every required probe must pass; no seed, role, stage, or feature is selected
+after observing its result. The intersection rule does not turn these into a
+universal absence-of-leakage claim.
+
+### Prospective sample-size criticism
+
+A separate ideal-null calculation, not a fit to the development held-out results,
+examines 60 required decisions (five slots × 12 probes). With independent Bernoulli
+test correctness at 0.25 and a fixed baseline of 0.25, the exact binomial probability
+of failing the stated Wilson bound is about 0.06792 at 201 test rows, versus
+0.0005204 at 500. The union bound gives an all-60-pass probability of at least
+0.9688 in the latter reference model, without assuming independence between probes.
+This is a conditional design calculation, not measured operating characteristics
+for the trained, stratified production probes.
+
+The prospective implementation therefore uses 2,016 observations per role per stage,
+yielding at least 501 held-out rows after four-class rounding. Neither margin nor
+feature membership changes. The turn-index feature is normalized by twice the
+declared per-stage sample count. The measured development resource envelope is
+recorded. The registered five-slot execution was attempted; historical development
+uses 816 rows and the original normalization.
+
+### Registered v1 attempt disposition
+
+The attempt started from exact clean commit `895d59a90b66cf74603d58f1962c58f98e29dfb9` (rewritten as `93dc55ef84f503abbdd8307e15fb656d880bfb84`).
+Slot 1 retained 465 turn records and 930 delivered adapter-entry observations. At
+turns 460–464, five consecutive learner response timeouts triggered the configured
+safety pause. The next turn was correctly refused because a paused run does not
+accept turns. No snapshot/restore or leakage probe report was produced, and slots
+2–5 were never attempted.
+
+The terminal receipt is tracked; the partial database, bundle, and log remain under
+ignored evidence storage. The raw slot summary's historical `probeEvaluation` value
+said `executed`, but its empty `reports` array and absent restore record establish
+that evaluation was not reached. Future summaries derive that field from completed
+reports. The failed seed set remains consumed.
+
+The timing boundary was reproduced without study seeds. The prior Mode R proxy
+padded turn-path calls to the 1,000 ms normalization deadline while the Nursery
+independently raced the same call against that boundary. The correction makes the
+trusted normalized adapter the single deadline authority through remote work,
+parsing, policy synchronization, and fixed-schedule release. The Nursery retains
+its deadline for adapters without that authority. Genuine late completion and the
+five-rejection safety pause remain enforced. A new prospective packet, binding, run
+identifiers, and seed root are still required before another registered attempt.
+
+### Registered v2 attempt disposition
+
+The attempt started from exact clean commit
+`7ed10f9f844f6a51c257f1f57829a57bbdf754ab` (rewritten as `40bb76fcb46d1ce0960da8ce5a60b10c3514ecbf`). Slot 1 retained 161 turn records,
+324 delivered observations, 162 accepted channel events, and one checkpoint. One
+deadline exceedance and one dropped response occurred after both turn-161
+observations, the accepted sender event, receiver interpretation, and receiver
+action-side ledger writes, but before the turn record. No safety event, restore,
+probe report, sealing, or final export completed; slots 2–5 were not attempted.
+
+The original error did not persist the exact method. The recorded ordering localizes
+it to the post-action turn path. The wrapper receipt's zero slot summaries result
+from a later assertion against the failed slot, not from absence of the partial
+evidence. The current correction routes `observe`, sender and receiver `act`,
+`receive`, and `onOutcome` deadline failures through one audited forfeit path,
+records the method and role, and preserves the five-consecutive-rejection pause.
+Remote framed-adapter fixtures additionally show immediate boundary quarantine and
+no-resume recovery for each method. Exact v0.1.115 passed those cases within the
+1,959-test repository gate and separately passed the ordinary two-container Mode R
+lifecycle. The receipt discloses that delayed-method injection was framed in-process,
+not injected inside a container. These checks permit prospective v3 registration;
+they do not qualify E02 itself.
+
+### Reproducible design calculation
+
+The conditional sample-size calculation can be reproduced without any observed
+development data using the repo `.venv` Python/scipy sidecar:
+
+```python
+import numpy as np
+from scipy import stats
+z = stats.norm.ppf(0.95)
+for n in (201, 300, 400, 500, 501, 502, 503, 504, 600):
+ k = np.arange(n + 1)
+ p = k / n
+ upper = (p + z*z/(2*n) + z*np.sqrt(p*(1-p)/n + z*z/(4*n*n))) / (1 + z*z/n)
+ critical = int(np.max(k[upper <= 0.35]))
+ false_failure = stats.binom.sf(critical, n, 0.25)
+ print(dict(n=n, critical=critical, false_failure=false_failure,
+ union_bound=max(0.0, 1 - 60*false_failure)))
+```
+
+The exact bound is discrete and need not improve at every adjacent sample count;
+all possible rounded test counts must be checked before selecting the final packet.
+
+## Evidence and recovery
+
+Adapter-entry observations and action budgets are retained with actual delivery
+disposition and duration. Input rows are reconciled with signed turn-record
+observation hashes, scenario-state hashes, generator instances and offline labels.
+Inputs, initialization provenance, exact feature vectors, split indices, results,
+and restore evidence become checkpoint-bound analysis attachments.
+
+The audit replays the same estimator and independently recomputes Wilson bounds
+using Python's normal quantile (`scipy.stats.norm.ppf`) and the direct formula. It separately verifies the whole
+bundle with TypeScript and Rust. Same-code estimator replay is not an independent
+implementation of model training; different implementations are not independent
+human review. Qualification signers are ephemeral and remain in memory during
+runtime-handle restoration. No private signing material is exported, and this is
+not a new-operator or operating-system restart.
+
+## OCR and numeric observations
+
+The numeric baseline contains no images, filenames, captions, text-aligned encoder,
+or tokenizer. The existing structural detector is not OCR. A separate offline
+Tesseract 5.5.3 English/PSM 6 audit records engine/model hashes, exact input hashes,
+stdout, stderr, return codes and all errors for the 32 frozen image fixtures.
+
+The first development OCR run detected characters in all ten visible-text fixtures,
+failed on all five deliberately malformed images, and produced false character
+detections on three of seven text-free controls. The separate structural quarantine
+correctly quarantined 25 attacks and approved seven controls. These are different
+measurements: quarantine success does not imply OCR success. An empty result or
+engine failure is not proof of a text-free image. No OCR threshold was tuned from
+these observations, and no multilingual or future-image guarantee is claimed.
+
+## Development commands
+
+Use the pinned toolchain (Node.js >=24.20 <25 via MacPorts, pnpm 12.3.4) to build dependencies. Never overwrite a
+previous output directory or promote these commands' outputs to registered results.
+
+```text
+node deploy/mode-r/develop-e02-observations.mjs evidence/development/e02-new-name prototype full
+node deploy/mode-r/audit-e02-observations.mjs evidence/development/e02-new-name
+node scripts/audit-e02-ocr.mjs evidence/development/e02-new-ocr.json
+```
+
+Container development uses the same entry point with an `/evidence/e02-name`
+directory, explicit `research-grade` mode, scratch learner hosts, and the base
+software commit supplied by the Compose environment. `smoke` is a distinct ten-turn
+transport/recovery check: it runs no leakage probe and cannot qualify E02. The full
+audit rejects smoke outputs as insufficient. Historical E00/E01 evidence and
+registration packets remain unchanged.
+
+## Prospective execution and resource boundary
+
+`collect-e02-observations.mjs` is shared by the development and registered entry
+points. Registered mode requires an exact E02 packet, its matching repository-native
+simulated binding, a listed slot and seed, and the isolated `research-grade` topology.
+Development profiles cannot accept a registration or be promoted after collection.
+The host runner refuses dirty sources, changed build inputs, reused attempt roots,
+and overwritten receipts. Failed statistical probes remain in the five-slot decision;
+infrastructure failure stops with partial evidence retained and no replacement seed.
+
+The committed-candidate Mode R smoke used the shared collector, ran ten real turns
+with an actual restore, and completed in 71.221 seconds. Both verifiers accepted its
+bundle. The two adapters reported zero timeouts and zero protocol violations; the
+SQLite channel record contains no timeout reason code. The separate in-process
+smoke also passed both verifiers. These are smoke observations, not leakage-test
+denominators. The resource report binds the committed-candidate JSON and preserves
+the distinction between Node process maxRSS, Nursery cgroup peak memory and
+unmeasured learner or host-side resource consumption.
+
+The planned schedule is serial, about 8.397 projected hours per slot including a
+linear analysis/replay allowance, with a ten-hour Nursery-command timeout; the
+post-run audit is outside that wall timeout and inside the host CPU budget. Docker enforces
+512 MiB/one CPU per learner and 2 GiB/two CPUs for the Nursery, no swap allowance,
+and one CPU-hour per container process. The host controller is capped at five CPU
+hours (`maximumHostControllerCpuHours: 5` in the registration packet). The primary-process reservation is 20 CPU-hours, plus a two-hour planning
+allowance for build and audit subprocesses, and 3 GiB planned evidence storage.
+These estimates do not resolve the whole campaign's resource-allocation blocker.
+No response deadline is shortened to make the run cheaper.
+
+The resource envelope has a portable arithmetic/source check and an explicit live
+comparison with retained development evidence. The v2 sequence is:
+
+```text
+pnpm run audit:e02-resources:live
+pnpm run build:registration-e02
+# Commit the packet with the next patch version and reconcile readiness.
+pnpm run activate:registration-e02
+# Commit the binding with the next patch version and validate the clean tree.
+pnpm run qualify:e02
+pnpm run audit:qualification-e02:live
+```
+
+The same sequence is no longer executable for v1 because the attempt consumed its
+seed set and failed. V2 uses new paths, identifiers, and seeds and preserves v1; no
+observed slot is relabeled or rerun to obtain a pass.
+
+The v1 packet binds all eleven classes to implementation commit
+`0fdcdb0ede7a332e7e82546e569e55e0bb17f155`, including the exact Rust binary.
+Its canonical hash is
+`sha256:b59c0e22e8c4cc84331723a9266055d653c8341dbd9a326a11aa5ec7ad2fc413`.
+Compilation alone does not establish committed activation or a passing slot.
+
+The packet commit is `4d7068b5ccc1c8259239923d88ebff10fb85a3b1`. The matching
+repository-native binding records a successful local simulated commitment, not
+a public transaction. Execution may begin only from a clean descendant containing
+the committed binding and after the full consolidated suite passes.
+
+The prospective v2 packet is committed at
+`f1f63b4607d1b68033f7d8934e56b0b6559dde2b` (rewritten as `095b9e227ca4eedf1f5ff6c4da0ee1b54f38c232`) with canonical hash
+`sha256:3d925374d7fd309ddcbb694def69c3bb7d3dd1f93a6742070f84fa32d0a9189d`.
+Its matching binding records a confirmed deterministic simulation at block 1 and
+transaction-shaped identifier
+`0x041e98a23a16d0eb1de9481fe81361d1a66863a71b4ba350df4c06996c53ee69`.
+This is prospective local ordering evidence only: no public transaction, public
+timestamp, real funds, or external custody is claimed.
+
+## Registered v1 execution record
+
+The original attempt started at 2026-09-13 14:16:52 UTC on clean execution commit
+`895d59a90b66cf74603d58f1962c58f98e29dfb9` (rewritten as `93dc55ef84f503abbdd8307e15fb656d880bfb84`). That exact commit passed 1,933 tests,
+Rust tests/clippy, a 747-file secret scan and the complete consolidated suite.
+The first slot recorded 465 turns before the safety pause and then terminated. This
+is a failed qualification attempt, not a behavioral result.
+
+The bounded user service `ald-e02-qualification-v1.service` has no automatic
+restart, a 55-hour outer runtime limit and a five-minute cleanup allowance.
+Its controller was designed to run and audit the five slots serially and wrote the
+terminal receipt to `reports/research/e02-qualification-receipt.json`. Individual evidence
+and logs remain under `evidence/qualification/e02-v1`. The user manager is
+session-bound; it is now stopped with exit status 1. Never restart this attempt into
+the same reserved seed set.
+
+Read-only monitoring:
+
+```text
+systemctl --user status ald-e02-qualification-v1.service
+journalctl --user -u ald-e02-qualification-v1.service --no-pager -n 30
+```
diff --git a/docs/e03-design-simulation.json b/docs/e03-design-simulation.json
new file mode 100644
index 00000000..a3742b88
--- /dev/null
+++ b/docs/e03-design-simulation.json
@@ -0,0 +1,64 @@
+{
+ "version": 2,
+ "claimBoundary": "outcome-blind-design-simulation",
+ "scope": "single-control-equivalence-component",
+ "registrationFacingReceipt": "reports/research/statistical-validation.tsv",
+ "samplingModel": "normal-seed-rate-with-binomial-episode-variance",
+ "seed": "ald-e03-v1-design-check",
+ "repetitions": 30000,
+ "mean": 0.25,
+ "episodesPerSeed": 200,
+ "equivalenceBounds": [
+ 0.2,
+ 0.3
+ ],
+ "alpha": 0.01,
+ "minimumPower": 0.9,
+ "rows": [
+ {
+ "maximumBetweenSeedSd": 0.05,
+ "primarySeeds": 25,
+ "totalSeedRateSd": 0.05863019699779287,
+ "equivalentReplicates": 27280,
+ "repetitions": 30000,
+ "estimatedPower": 0.9093333333333333,
+ "monteCarloStandardError": 0.0016577718200667447,
+ "monteCarloLower95": 0.9060841005660025,
+ "passesMinimumPower": true
+ },
+ {
+ "maximumBetweenSeedSd": 0.1,
+ "primarySeeds": 75,
+ "totalSeedRateSd": 0.10458250331675946,
+ "equivalentReplicates": 27587,
+ "repetitions": 30000,
+ "estimatedPower": 0.9195666666666666,
+ "monteCarloStandardError": 0.0015701784635535566,
+ "monteCarloLower95": 0.9164891168781016,
+ "passesMinimumPower": true
+ },
+ {
+ "maximumBetweenSeedSd": 0.15,
+ "primarySeeds": 155,
+ "totalSeedRateSd": 0.15309310892394862,
+ "equivalentReplicates": 27327,
+ "repetitions": 30000,
+ "estimatedPower": 0.9109,
+ "monteCarloStandardError": 0.0016448018117694299,
+ "monteCarloLower95": 0.9076761884489319,
+ "passesMinimumPower": true
+ },
+ {
+ "maximumBetweenSeedSd": 0.2,
+ "primarySeeds": 300,
+ "totalSeedRateSd": 0.2023301757029831,
+ "equivalentReplicates": 28462,
+ "repetitions": 30000,
+ "estimatedPower": 0.9487333333333333,
+ "monteCarloStandardError": 0.0012732948801639989,
+ "monteCarloLower95": 0.9462376753682119,
+ "passesMinimumPower": true
+ }
+ ],
+ "passes": true
+}
diff --git a/docs/e03-seed-manifest.json b/docs/e03-seed-manifest.json
new file mode 100644
index 00000000..e3137d8f
--- /dev/null
+++ b/docs/e03-seed-manifest.json
@@ -0,0 +1,879 @@
+{
+ "version": 2,
+ "claimBoundary": "prospective-seed-allocation-only",
+ "seedRoot": "ald-seed-allocation-v1",
+ "stage": "blinded-pilot",
+ "seedDomain": "blinded-pilot",
+ "primarySeeds": 20,
+ "reserveSeeds": 0,
+ "conditions": [
+ "disabled",
+ "constant",
+ "random",
+ "shuffled",
+ "normal",
+ "oracle"
+ ],
+ "entries": [
+ {
+ "slot": 1,
+ "use": "primary",
+ "scenarioSeed": "85bac8f1ea68b302fcb0083e950d8cbebcb0855dc1ccf8b56c229a4b5281e419",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "d3cb3942ef3aac8dc680ba49b126562d492fc7ac09bdeb24b15caf94f4d8013b",
+ "babyB": "32acebd279acec64f89cde400b3bcd373eca2e43d852c5950bb9e1a2420ca2b1",
+ "gateway": "e80570bf5a58ff9ca1729960498b1c416138f77dc38355b3c3fa131959985596",
+ "analysis": "ed4bdee40a7c19f1c0af9128d937b775f3d9d12e060a91d65ed2671a33e53a8b"
+ },
+ "constant": {
+ "babyA": "8348532f5e7228f7f181c12c26bcb6fa139c9482cf18b6085232992a5ed27bd9",
+ "babyB": "4ac994b39e628fc82d1d3b8518fd567103085ce074e07704a31f62a0193ba61d",
+ "gateway": "e8086f5f8830de4dde2ecf38fed5382401747ff70aaf9860ad32dc64dc8d0eef",
+ "analysis": "ddd35350bde25119ef4f054a81cc9b9a7021563377586f4797bc009278804f40"
+ },
+ "random": {
+ "babyA": "c93665af3cf2118e655c7bbf2f60207e928626bff46924edbd28da7bdcdc9590",
+ "babyB": "6f12c8b9758f547ff172093b380a4b7b48b72acb1659090dbb24d5591c3873be",
+ "gateway": "08a7484113f46168694c0947617e019cc695404a0e577043d6ad23bc1b1f64e3",
+ "analysis": "8f895683a11b9cb34fd1e1fe550e7a5470e958fb3a37942fbe6540ef07205454"
+ },
+ "shuffled": {
+ "babyA": "c103a5e88fcddd6372e6b47b37facd978de86b1c8c6386fef784fbbc4d765f53",
+ "babyB": "63d6c7e949d6c6d46adb44a6f729f361fd22f456f5a0610c0d7e8778828b7a13",
+ "gateway": "5c866f0b79cac45dc2c507609524f7993ebc1a3b700391ab2e57fd3e3b395cc3",
+ "analysis": "f172b391ed9f40fd7df388261d289dc44656a8ce8b3c68dcdf4e9d356dd5e4f6"
+ },
+ "normal": {
+ "babyA": "12621c04b3b7c0e1c87bf3945c8cf287e7d4cbe511b2522375c23c1da1e61990",
+ "babyB": "b389747683dde1468d8e8229434e2671a8cc56e1318da21d821e03eee2a104de",
+ "gateway": "49f0091f963ba76255ca3b56519d02dd7379f781d539804b518b33eb1ed51978",
+ "analysis": "5d34807c6899b48e2a9d0e08957ef6c2d5bef60028a5286367ff6a38a519007b"
+ },
+ "oracle": {
+ "babyA": "adb3a8ebef2ba51f3d3e80f542f239eeac17ba35014a28c8a3e5b5f76e5d4742",
+ "babyB": "e66134d90f3268b4fcb2ec8d47802fc097901b7fd928ec007b411bb569289534",
+ "gateway": "92864fdc12a7296c0e893a6d3353a5b9bb20a650188c4647cedc53eff875dbf0",
+ "analysis": "927e29fc2eb245ff43e69ad0214f0766de1ffa068186ccb1344f0f668cfab5e2"
+ }
+ }
+ },
+ {
+ "slot": 2,
+ "use": "primary",
+ "scenarioSeed": "80c78d2a15e910b95dc357d31cd9b59794350d62b93ab0587acbecbbc5a99535",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "314921e1fba7e18d83fe213579013161a7b3c48a0e2f954a4fd1c6987d757169",
+ "babyB": "ff4d196ce27b0967cc34a4602e9caf7bd27fb29308dfd0c99ce83ffa9890f301",
+ "gateway": "52449cf32b478b7d1f78bbe82c9b9f2279b3004e1b1b96162394e129e69da01d",
+ "analysis": "a3e9a7c5d9fe9f37cf084ac9e135f52811b048d6f213957fa44770550d47ce7a"
+ },
+ "constant": {
+ "babyA": "9cb3363bb0dee9831e55576011b5aa6a57b034a691529326613ad682d30644d1",
+ "babyB": "7adccc2c6c723c2da4fe5410a91f78bafa0da60a03017928636297c85e7a599c",
+ "gateway": "36bcd8beb45cec21ba77874f807583eb973f1807872674a6269ada3b75893592",
+ "analysis": "20a48e305e8f7181db85d80ff5dc15cca351a3e0cd85f9849d6141d70d71bd10"
+ },
+ "random": {
+ "babyA": "ab9e659316999e5c4492cab8c86b5ceec753e258f5524486677a7fc2c18872b5",
+ "babyB": "a11baac97641bceed2c6a96fa901e14a019690da67228c141b2cda0992e16d97",
+ "gateway": "14b34b2a495cc3ed95ec62e319f24aa397e8e54f2ace177018d29565a15fc4f5",
+ "analysis": "0ffd9c99ab1d99d739e7e39cd7ebcd43819c2f43c2ee9221bc5e18a0f10f8b81"
+ },
+ "shuffled": {
+ "babyA": "1efd1d4727d112fcef388a49cb5dfb3404391db71cee83b66b269dcc31294e0a",
+ "babyB": "fdb86d2f6ebd07db73012e853f6a7eefc01108ef06f732bc05591e10faaf5a64",
+ "gateway": "36d1e68635fe17221138e24d447ee1ce4e3aab03dd5cd99879d2ce6240b65e1a",
+ "analysis": "638c40664c8473b7d73ea67fa4881c537dc93241be0ef4cc7c1e763e3f965582"
+ },
+ "normal": {
+ "babyA": "d17bbcb3059b12291047c4a23a36e96684d77fc396cc6d356123b61dda24db0c",
+ "babyB": "f2d7f161a898440d16469d182a9360b228c9748f3af981d25b9a78b36697701b",
+ "gateway": "36bb5598db8510e33d2ec6608203591df83f57b88c2fa40a81fe798698d9b675",
+ "analysis": "09b5a5ef4c134140bcea450ab94a6e02a02ffea1c5ca06070f1ff142bf4d0634"
+ },
+ "oracle": {
+ "babyA": "a582cf1b990836bbce755443bb067aa7741ff6ca2593075f0c2aaa299ca7cf1d",
+ "babyB": "10647db9d69beebeab1f9a25dfe17e8ca2e06ecff00bac93535dd08291bb6bd3",
+ "gateway": "77db49b709470a70af61d04105e45b259df231bb8e5c7db9602fdd9b395ad123",
+ "analysis": "4689986e129eb3c902d3fbc57711efb072d15cd02947f1c4b670802f4f180e31"
+ }
+ }
+ },
+ {
+ "slot": 3,
+ "use": "primary",
+ "scenarioSeed": "f1a9691d251a4998ba7f0f6a7ec056d5f492539899e4e3b52bf24ee808316e84",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "327aa7654057077f30aefd94328584b85315191aefe8f0a93abad309784d7812",
+ "babyB": "766b66fcbfebbd07a9c69a7f7be22a27e9a5bb28acd1a8f6f9947f1c41f37c20",
+ "gateway": "5ec0a715acc9184d31f868c6ab463852d55b22ead3a2cb8538042ade68508c24",
+ "analysis": "7d55c5e1a91242818ac2132aa870bc7ec1268d54dd2c9a74a1c564ede4c30cd9"
+ },
+ "constant": {
+ "babyA": "1cec06e5d36b9a2cb1e44a509105c30482929bfe8e1238bf4267edf04b0bdcc0",
+ "babyB": "1199a337ecd028d16783794c502da6df5572ef753881e577fb4a606560c0ee18",
+ "gateway": "b9bbca2059722cfedae83b55a17b3d0048c816ce47bdb2c982da2933cc3e56b2",
+ "analysis": "ef2fd979ff3dc20036d4db6bef1eb78aacbf1b163eca4bfb1837196899e35a60"
+ },
+ "random": {
+ "babyA": "abb82e7b42817bbbb8dca7faeb6b13f65d581b8cd7e3bd16aba91e3a23aa915f",
+ "babyB": "69a18603d43b3a98624d908e2614ef4e34061b22c0791c18fe18d2eb65639143",
+ "gateway": "3eb918986f2264c92c0d0c9721391c6b92eceae2cff468d5962bb197aaf5e4eb",
+ "analysis": "66610214e306bc8c86d891cc240e1e829cc714fac8df4ed90167f0a7fbcbcae1"
+ },
+ "shuffled": {
+ "babyA": "2e3bc228e0109b28de75e54b98bcea1794b34aa912088dcaf962eca510e5e82d",
+ "babyB": "3d5d57aba8de6df2847b5e8a9ab83dfcba3fe0ecfbbf9483fd44cacd0d7b720a",
+ "gateway": "af7ffd39affa274a10655384b40981c11fe42366d0a403e709906eb77b655159",
+ "analysis": "df447b912ad6d9ceddccc81220a3e97ce021fcf53aafa3c25dcd5e297a930a80"
+ },
+ "normal": {
+ "babyA": "5f04516d4dbd2ed74c1632289c672324675d2a0df4f36a57c05ce6cbd775f560",
+ "babyB": "61812fb2f04abf380a0f18f9768491e6d559a544773f5dc7d5881d36568c7906",
+ "gateway": "25da172ff4dadc980b6be0d716678436181cb0af03f6c78abc428dd9ece7109f",
+ "analysis": "b307ae7e0776a81e5d0cfe858af59ce013368e5b9040fd3ee093040de4736bd4"
+ },
+ "oracle": {
+ "babyA": "5754e70a593e487801206419636a9f8d061a341085f386b07119560375bdc70e",
+ "babyB": "5a59c55b7cfe77238f643fc6aa225cf5091e714a78bedb05c85c6b80f54b6bfb",
+ "gateway": "06b4806beec00276fb2f7f099b2bc0b6de2fda2fe2359fc784bcf01e4ce2bf24",
+ "analysis": "1880ddb0974010fb9b4c63c99ea11f709ba76e6befc8a7184c01d5aea8cbf9b2"
+ }
+ }
+ },
+ {
+ "slot": 4,
+ "use": "primary",
+ "scenarioSeed": "9a76cb44e802dc16a7fbbc6bbbead25819ae7fe84b5a95ff522acc515fbfb4bc",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "e8fbc33c2ebbd7e03ff3761e36c6c758aba408a291dc96899077e2ee1ea2534d",
+ "babyB": "ddab14ad9a0655ec27b462888141477f53700c6bc4341d27210db226b855094c",
+ "gateway": "29dc2f974814171aae0235ca34eb74bc39dc0cda75ae5634836ca28b92b2ccb9",
+ "analysis": "c6042ed9b8a7063aff861d5b56fb856bc09e0e28bcfcae290ea9b7b577f9b380"
+ },
+ "constant": {
+ "babyA": "c8d4cea1d589e829851e354cd01f02a01f07ebfad98d094b7dd3f7bc4a4c53b1",
+ "babyB": "f70a0f14cec863d93f7ae1f0ff6be3e67bd494ccc0d30774a63af1591264eaf4",
+ "gateway": "3d7f60fa2934cd8ea93fd8b805ee609ba5f0e7082d3eec11d3a0781068dc066a",
+ "analysis": "e6f2d4892fedd4a19fe26c82d72c31cdfdde6b0df8c7f9265b2f7a610b8bb2fa"
+ },
+ "random": {
+ "babyA": "708f9ec74f20a3fe5bb694aeb98c6becc2e98771b45d1f7932f2d5d3ec585a87",
+ "babyB": "84a4d2d7a689877982cfa712840770b887db976c4807560ae090981d6451b0b4",
+ "gateway": "1f5afa6dae0efa9651b81eb07977810b7f66b0eab17db63b43e0dda76064c3fe",
+ "analysis": "7776a4d568e88338c19d34290b342a18290181122ff6ae43415b2671cfb487e9"
+ },
+ "shuffled": {
+ "babyA": "2a744be238835601cb3f6cd6ae51f75f2b1380393120d1a522fb3a577ff18528",
+ "babyB": "027ecd4d90b69c6c17b8f1ebfdb398a7ef073caf7d261a5b9f9165cd9cb4f179",
+ "gateway": "6de1fd4524d5f861715558ca2588753850c734b2935f0e4b42fd98abcc45f4da",
+ "analysis": "fe15e03bf8c36da9344f8c26d60eca97cba881bc4e71f0c5cb089fa9de5b0b9b"
+ },
+ "normal": {
+ "babyA": "eb5809ac69dc26313bf36cf68c67bfab908669568cbeb614a2f10c5917094ce7",
+ "babyB": "e3114a5d73e26a8a68b8500d052ca5db64f7909bf09f5a1a3fcc4534407fd910",
+ "gateway": "cf675fde0707cee75efb75674dea867bdc419e3fa0f9de297c5f9b83018f2091",
+ "analysis": "08a9dafbaaac1e7e9cef7b0cdc984f6a8ec3eebbe0237093aa93f8eeea07e95a"
+ },
+ "oracle": {
+ "babyA": "8a539305043b0615673c6051890be66a9c6e9147330d06f1e071c0e3dc944c7c",
+ "babyB": "ef2862080bc07ac89a91796bfb79629c01b9546cbbb79d49df1d8d497374e4e8",
+ "gateway": "e160dc509825b39a064ac086ea1c3a2e70f3e396c942c2178bb1962a9c9ae87f",
+ "analysis": "b5dd15acb77fb48d1579dbcb01154c8b9fedc862e2eb0b342be67dc88be1bd4b"
+ }
+ }
+ },
+ {
+ "slot": 5,
+ "use": "primary",
+ "scenarioSeed": "d6100aa8f640e7839d553a621fd7695598aeb3a135df1c9e498e3b4230563c03",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "49838c610ef5186f70d78a4f3003407d771633c5d225584eba5a592dccd34950",
+ "babyB": "69869cd25905da5f39d697a357416b3a398469c29ff8f65718fc1bb50361783f",
+ "gateway": "78ca31267004003ee1f4c8ee4c0fd2a3d0d0980968b023114a871563c73a39c1",
+ "analysis": "1632c6da841dea969258b68cf8f6bcfb8f0660b2cf43ff408c14ad04a05a6618"
+ },
+ "constant": {
+ "babyA": "f7a9961eac56a97c8e4fdc15b92e199b89b0afb8dc9e2fef69d127c8d53956a4",
+ "babyB": "d6803c149d5151ff9ffb61c664df065d5fc4d742b256d6b4caa94bf47a73ab5d",
+ "gateway": "80a417030c11d5bb972d1fa62f4988124618c25f50e7ddb43d9451206aa4a326",
+ "analysis": "a5a67ce2c8f491c8c56e915ec5c1c2a1ff0594f9748c5d88e00fc85e7c9b6069"
+ },
+ "random": {
+ "babyA": "ae87b6cf1530c97b2cccb67dba2397db05d9fccdc16592bdeaa1043c94d4a2f6",
+ "babyB": "ea6c4673bc035c0a422abde4c5ad72c878067898fd2f2447f131e13f5da1c22d",
+ "gateway": "a81ff2e188d00cd6c4aa959cb61e65110362f2943260d0d500db4616763c1787",
+ "analysis": "b5952879d45aa60bc6e901789821190295c105b3d8eef8804da60a50ef6f5d49"
+ },
+ "shuffled": {
+ "babyA": "466d4f361bc4ac259e7c4d74e17698a1de74fc011a4bae7f6bc150cccbd1991c",
+ "babyB": "2f6e986615c5acd84f9446806aa9964b41348d2b573513c8882cae3a561be374",
+ "gateway": "a9ea4ca5278f5e97a8035b02d3286d1b9ba8d17f7d39e9ef0d4d6ee3dc41bcc0",
+ "analysis": "0c544cafbcae8dff803673be55ab280c76a5cc71017258cab7ee8ac14e0cc619"
+ },
+ "normal": {
+ "babyA": "bdae64e9cb09799e24239f2bf09169a222651c0f30fd270488a21c7c6b5dc018",
+ "babyB": "b5736628dac59dce29850e420ed84a31eae54807b01d029713d5ac06624953c4",
+ "gateway": "7e7dcabe316d2fda0b1344036d0cbf221bb6fc47da6906c0a7aa3edebbe53ccd",
+ "analysis": "036ee99487f87efe2841ebe228f3703120747c5843a25a6f698bcb5dedfe6ca2"
+ },
+ "oracle": {
+ "babyA": "0ae0200208c4e8eb3532faa3c2fc37c44def85aed9ea279c32afb63f58ceecd9",
+ "babyB": "5302663ae30820e4cf3fb4626e2849dd963f151ce0b043208457c767475206bc",
+ "gateway": "738cba24a370025dd868a1e4b074a4f4ffb4fdc246ed4d7414b4a68459017930",
+ "analysis": "593a8e16c86188ebcbbaf426f50fd91b2206f6de2a0453a1a091ece77de103a0"
+ }
+ }
+ },
+ {
+ "slot": 6,
+ "use": "primary",
+ "scenarioSeed": "9ae13690d4c136e0ad37de8b8dd713f6084749ceaa6cd9697d1ae30bb1e734f9",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "d39ed42cee932637b92dbf3c7813412d95a3afc5839337310867982cf272d492",
+ "babyB": "ee07c31c12b584b51dbe2d5a41bd2a135cc61d5cadd6f0992d6a15410269fd40",
+ "gateway": "a0ce9e19bd01f69b4355a6dc4d24dd5b97c48a84a959f15c1752b37f91d0216f",
+ "analysis": "74ba09577f4403f7a78dbee9f354a504e09a0b0e51f8a899dc3e7ce220294d74"
+ },
+ "constant": {
+ "babyA": "0cf1aa10cb5bb1744da990e0b54495047841169233271ceb453bfc84a06e5ba1",
+ "babyB": "b3959046072ae32ed37b93674429ec9b36d241b2df2aa887bf0fabbb454c1d50",
+ "gateway": "e3c454595e1f933fcba61d1efcbecbb2914d96b98f676ebab7182befd1130db6",
+ "analysis": "90ab1779509bf931aac1d5e96017082239adae659a6e1a61607509eba154dd57"
+ },
+ "random": {
+ "babyA": "54f514a408e4cb83c5be62a5692d96e2e0d87c2ed401e3da8ea9f3886a30060c",
+ "babyB": "51111036d409eb20067277072316b0e3f394bbb3147cf319b4e9052987dc5838",
+ "gateway": "48ac48be36f319e559a6a2c408def88360f157946a01548f395f2932132d1283",
+ "analysis": "4ab7368c81aae12581f48061c293640c7480c65cab13d318ecfe30b9f6cf3f6d"
+ },
+ "shuffled": {
+ "babyA": "2853e5ab9072805fa3773eeaba774be1e555dd443993571b0cfb9dae6a4fe9ab",
+ "babyB": "6bfbc4fdebd2025861006dc10d89e072c258e10263d8c42ae3534faf3f538099",
+ "gateway": "cf64b065a92db0febd0401675d5b71cc203de17c8d80174c199229226fd6b864",
+ "analysis": "8a93985e4c08145105b549a1b6755c1376178b2b6fb15ee1af1c4f78cf6687cb"
+ },
+ "normal": {
+ "babyA": "878f59176f51af8ca027ebf5f43356efaa7f356d715116dac5ba13d27aa6173e",
+ "babyB": "afe05c5dc3a2c515382885dc06c5e9ed093f153ce82e0d9087a0b369836fd237",
+ "gateway": "28820f9f5b5d14107f2d20abff774c8d517340a308ad399c3650f771482dac36",
+ "analysis": "7538e2606ab954be3e09b9f59dee25cb1ecd3e54b1d4a137da6eaa6cb9a81121"
+ },
+ "oracle": {
+ "babyA": "f846021f45268aa973a7f45aecb174b49a4160629cfaebdb1914be694ccc0129",
+ "babyB": "aaa74b02d51c67ac8b60591dd4523e32866e81a10800a1d02136b5eb1c64abdd",
+ "gateway": "68383c631c3edd6c93dc7d80495172077b6fcd5b8dc5f9381afb08c7a0beffdb",
+ "analysis": "1ca555aa65f81514ce12de9c0be7755bd04c7d871cfa425431cc018209c52a76"
+ }
+ }
+ },
+ {
+ "slot": 7,
+ "use": "primary",
+ "scenarioSeed": "08f1cb838f3aec2f795791b33ab83753532790be5e8513b12b8e35e739772a9b",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "00ee7a751d3c5eb6b272524e13c6fe07322ef8cd719fa2bd673f01ecbf9e45dc",
+ "babyB": "4bf6017733c46adc8657ab99ec42a3097036af694101e37bd8833180dbf2b0cf",
+ "gateway": "0199a250933246ed2557fc446dfc7dae507ba9aaac0094517ea05cc2f5062b59",
+ "analysis": "d9ed606be26bdb58c350a77be1d129ca2dbd410769b633125b80e6955e659361"
+ },
+ "constant": {
+ "babyA": "15d3ca07a3b6639dc8cbcb523c562121d61c6283735d735143efd5d04727489c",
+ "babyB": "0ed81e0876b3a74df0ef59887cad338289cf0a762a348eefaf5d25878de25a42",
+ "gateway": "6b1083ffb18b885251eed1901847a833c2fa503cfa1f3611361671f3c1815d54",
+ "analysis": "f370ac3acae2ba1a1f0f6a1463b6f465c7ddf1ce8fa23e5bdd6afae4f712c7db"
+ },
+ "random": {
+ "babyA": "2f626a211c87c59c88e08b63fe57ef40ddfba7a5f1954f9a4af76f8a8133439d",
+ "babyB": "64ddb5ffe6a0269c3cecd6ff275989d0e8acf45c3a6e49378bdc4254d33a18eb",
+ "gateway": "053c4b7e280a099e698bc77bb1a34e8f8be9adb3309bfeeb38c22d7225810c64",
+ "analysis": "abb3c3f9b21377b581df715b5179274bcd44fa38712325543a0b62618883d2e4"
+ },
+ "shuffled": {
+ "babyA": "a42068a0528879cfd0e9a630aeb50d494bd6e232b864d614b119245bace9bd12",
+ "babyB": "f8cc10fa46081cb43f7cc2232912f195d532ec29455ed4bae5fcbf105af27692",
+ "gateway": "2279150d4231032dccaccdbc495e6da1c10970cd51dcd0e457c4262a3ec3437e",
+ "analysis": "1c5859d7fad5288a8b735b0d6d6335636458009fb991cba193c7a8f9f37a0b0a"
+ },
+ "normal": {
+ "babyA": "c0a0d73fcd681a745e18230804dc8b359026737f5cf9c1ea82681d4d48eeb3ee",
+ "babyB": "056be9063a390ea4af9ab6997c6155dfc76d7bc5e1e496ce2343677be8f4d2d2",
+ "gateway": "7bca61b53506766f78f47a9f9fdd8e2f024e675870ffd155f458ec0b5f980054",
+ "analysis": "18d1b56b030a0be4f88854e3cfe721153fa2a4bf0388ccbfb3c1f860c1086f96"
+ },
+ "oracle": {
+ "babyA": "57114c303b95ce046a16b767449e2b82de40c4e1afda9c1020592547f590d341",
+ "babyB": "34fc719d57c7f3a59c8af589c2cec393feba64b862a41e62ae78b8c55cfac1f5",
+ "gateway": "e03474e8d52104eeafcee71b01d0e8a41c7415f96f434f20280c691981c00405",
+ "analysis": "63b90cca14e69c6f475ea64a06ed8a3bb97fb4789fa44a20932c4f4de6aab24f"
+ }
+ }
+ },
+ {
+ "slot": 8,
+ "use": "primary",
+ "scenarioSeed": "54db93d7463085ae6fa9911538acce50279ec5653f59f6ff3d9a746afa3b8ab4",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "9bb29cdf8922288e8eab346c5e67b3a60355e05b5a9675e6d479eaa2b31c5ed1",
+ "babyB": "152f0dc33195084b7a35f2ed669acff0bc51b36f8ac6c3d9521989937aec8bf7",
+ "gateway": "f9464d7571711be8a187a0a3f919af19d39236d1a4b3d7256287c9b6103d60bb",
+ "analysis": "961edae55c023d3d71eab9f55117cd54b5bd9c8dadc0db512fc51179d2a1c79e"
+ },
+ "constant": {
+ "babyA": "e0df3ff6d29c664af919e578ae9ce5e79d789edcaf1b3aaf54079fd79c08eaab",
+ "babyB": "016f4cfdae95c5090f94c759d1afdee9dc32d5ba62d61c56391f92f6b3a7f785",
+ "gateway": "0d6db9cec7cb970768ce9fb6eb992e9d6e6576daee0ba50dd3b327524c69e2fa",
+ "analysis": "3c5b33382c374bb3f8e5f90d9fd35a66b5f22aa4a50a02b01e079837e3fded1a"
+ },
+ "random": {
+ "babyA": "ccda7bfee43e38bdaed983ba63028e1ef1cf4e5350b3a5ca3a8961fcb8a6f834",
+ "babyB": "3b12ee4bb3fbdded96c9fde9a3309d381f2fba14e7760c685142d547eb2c6885",
+ "gateway": "b6bb143c4c94bdf98315f7b069dadd791d3c0b0b7c3238eca78204b6f61b5f2a",
+ "analysis": "aa219d977999c92f87fe61ce13194653b5718d9d48882655bf7f47dec63f1aea"
+ },
+ "shuffled": {
+ "babyA": "a8d5149ee6abe353d6c6260f92006db70dc5e96b544b66c63f1439de92d41a21",
+ "babyB": "e8b095f1b77691ecb32ab04b0b76bc169154bc2146c3af7750cbfd8c70106481",
+ "gateway": "ea2d59a394128f34cbe2797e992ced0f1b137fed4209b9be1ac0eefd2da66210",
+ "analysis": "2d2dd56b14d6f0766e9e28bb055578da398f144c33da88d3d685d79bef1ad13c"
+ },
+ "normal": {
+ "babyA": "1455fbed0ff5f7c23c78cb069626240ae8c6dadf2109e1590630b87f70626578",
+ "babyB": "5e72da768521022ea37dfebe020d0c8431335fab0421995b1aa34e53810ec672",
+ "gateway": "9bf8a553f22a66470885e36361f2f8c142976df5e7d9d9b3cb4103e949ec14d5",
+ "analysis": "c9543e34f65304c16f037225ec41f1cfa8a8da2b14760c03c4447cdef8c8b3ef"
+ },
+ "oracle": {
+ "babyA": "e7dae60e7c1ecbde6970272333292f9e9b1921b7aef825a74572bd7ea83d86da",
+ "babyB": "a503e150b02ebfcb236b4364b12eb3c55aefb9eef193008e95f3612c5bff3c07",
+ "gateway": "aa3036c2e6608c088adfa307a48a194a35588c87a756fc2cb9169b51107a6bf9",
+ "analysis": "98246a1828c96e2924378634c057d1306af699b01b040f505a2ba8e8056453e3"
+ }
+ }
+ },
+ {
+ "slot": 9,
+ "use": "primary",
+ "scenarioSeed": "a941d1c14fc381dab903ff6350b8251b4bbcb8f82c60b8481651fdfce5a3442d",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "d2d144cd624bd56e1ef296b4e4733d552827459538e2ac0c4d111593032110fb",
+ "babyB": "6617a0c23b4e4d8fdf5b41faf57f2255060e054dada00c64732e27fc08cd4a44",
+ "gateway": "279c31828691a0a214e6ca3b073c382f5bc6e15230f969e04e37b555d55621c3",
+ "analysis": "9c51a1af1de3444e2b7a82e460fa338fa51fa12e70a73bc255cbd4eeac526380"
+ },
+ "constant": {
+ "babyA": "3d245955b2d688f3263298130e3b7fe6864827b69d97859598cba8ebf2d4a178",
+ "babyB": "6d0851c0e69d4e769482cc845e09d9a707807e27ca35bd55498835fbbb5d82d0",
+ "gateway": "2c966641a6b61abacb6f382c98c9dafd8e70876911e2fb949722f490de678387",
+ "analysis": "4ac42be74c068bf35579dc9ade5677babe29d5557106525cf2264337e4e5f3ff"
+ },
+ "random": {
+ "babyA": "2c75558449a4c3d28df1aabde0d0dfae30b036bd4e16cff519315d87ccbf96a4",
+ "babyB": "38518ec78b741b949ddfbe261cb2396805dcb3f59686375e49288f5359b9deb1",
+ "gateway": "373a64ac8ed187a3f6bd7191392e837bf5d69b279409bd8659277dc09f7a9f3d",
+ "analysis": "782fca4d09dce21e1b240db55426ec8784133feb6396d03467d0d47097c362c2"
+ },
+ "shuffled": {
+ "babyA": "ffa5053d2113c17b8c42a45a9f1dcab1af0489af78c65f261e5768cce9f31623",
+ "babyB": "18051058b5b04e14dcfc1d66e5f29e32166afc6df9bd108beef15788271ba5d1",
+ "gateway": "884104778add05031a8aa82e75ab3892d5201a69d0df4d74b900319139a5b718",
+ "analysis": "692ff01851bb02cfe208fdcde49bc507a5d0125d7402fb75dd5466cf1a86b374"
+ },
+ "normal": {
+ "babyA": "d6509a3d468c8e2a4856806d08f411d42c4559a2554477af479a317a9e232091",
+ "babyB": "7ce405d03c2fc8ca0cc6dd0dfe56d97fd2448e6fa6a9dc5587be44afa3646e74",
+ "gateway": "157cf8d347f28dd42c39acf1b4d3dff19ef9ef2906f746af4a563926d6cdcbd6",
+ "analysis": "64793a1ebe63ae183525539a37bcd46f889c91224a8f29811252e4db1a0f7fa0"
+ },
+ "oracle": {
+ "babyA": "2b2583c5e63db5ec54e1bffd50e82418aca8c52570f1591a37a116934795d103",
+ "babyB": "3baeaa7d65becfc4644ad0bc3359f19d2978abf3e72a48be963e2b556fb12bf1",
+ "gateway": "f5f9d045c67e725c10f3fe15058bf7321c892f7d37eddb6222d537a1fbe4604f",
+ "analysis": "4315ae1b532c7bc2ac37501ed4abae48f447e9691db688431a86a5260e4cc52d"
+ }
+ }
+ },
+ {
+ "slot": 10,
+ "use": "primary",
+ "scenarioSeed": "608d3c901bc2774335643514672dcc9d170e8eb6a045b63ec9e4ed3342ffd5ab",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "6ce10abdc804571cb1a3a6de5e89eddf03d9e11e524fc61303215c4945bb61ae",
+ "babyB": "6d138dbaf40a4210bad9b081a5f5f625a61eed954a214902debc28184388d55f",
+ "gateway": "d71e50fb96b9e7a226e2eaec227fb7a9be97791fde55287ed03c8b75aa4e016b",
+ "analysis": "5bfbb3a48fcd53f2642d0d1489bd4b4b7bbe3e5b9382f98e526a83e99868303f"
+ },
+ "constant": {
+ "babyA": "2567c049c50f7304f605df1104b459bba7fba86c98d3bb0312a491ef99ea07c5",
+ "babyB": "edca3a60d548e72ec8edd319b24b5b0ea5b86d86f8dd52813fe8e194f8dc31ee",
+ "gateway": "72541c0f411bafdd18fb18b18ab7ae7b436767d6cfa6d4000bd2a90b92293523",
+ "analysis": "2513245172de410532b08a983c91644eb5947d72c9b0f0dcccaab61469c3889e"
+ },
+ "random": {
+ "babyA": "35eb983012bcfb24f80e4bc7104737059204295e48baa4c64e384722a92f2d0e",
+ "babyB": "d81e7dbe4a92c912b53187087f0c3f5d6e708df623b6c19ee170dae54b1336fa",
+ "gateway": "925a1b3449dc7324c0f10eaa93510691d0e2649e28246a77d2a53ce5e401ff0e",
+ "analysis": "d603ecd3cbc283286eb47068e4cfefe818f173ba0fc80baa4dceb91e014d6a58"
+ },
+ "shuffled": {
+ "babyA": "abacba738bbcd20ade3ec2088df8143093c4fdf75134bbf00da48af154899e2f",
+ "babyB": "7cf3092561eea98ad694af29e3d3fbd6423c7a2e79c2999d80a10d2ee9709699",
+ "gateway": "d7be691ffaef6a575afe40a8e628f4aab1a9294bed25bc6af950889a60555bd2",
+ "analysis": "7da56d4bf75dd78ea746f741080756a242eead42b968c282d6f64a59ae9e15e2"
+ },
+ "normal": {
+ "babyA": "b7610df9eb52e52b10ff0587581f431b8b10576bb0cb99cb4b27a85e15566739",
+ "babyB": "7141a21d68799b11e8072406b58e4e5737bcf004f488b4cff5cf43e8f18f66c6",
+ "gateway": "17be7fa6ef59188f23c6d6c9fb1fbeb8696343f127dfa7ead6abb36eb2f675a1",
+ "analysis": "b360c6520dbbcd1ac5823273bf301a7b8b6a5f4ef210e8d317a3fc15d0412b92"
+ },
+ "oracle": {
+ "babyA": "91eacf6f2dc6b063e27957920373c39747d613a6dd0f0a38ec28e93628c38552",
+ "babyB": "5d13e0c89555cb547925a9455b4989048ff060fce96598e40132586f751eeaef",
+ "gateway": "4bcf047064894a609d9fd5c5bf811518ec4290936ddb8f4116f43a0c19fc8a2f",
+ "analysis": "b6814f11d82e6b7efde21cf9bc120d967f4257ec9e03bcbce759a0cacefe8797"
+ }
+ }
+ },
+ {
+ "slot": 11,
+ "use": "primary",
+ "scenarioSeed": "092fa80972b9fe8adefb6b3f5d2884c9a269ea93e54c91c41cf5a97ddd4a0ccb",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "5a348bced00d254ce4843c52624056c2b512c158e4557bffe45228f380bda6ee",
+ "babyB": "6ba05af09b359e75483f3126cc0e3ded3a77b0b76c8d11d84f96752cde77056c",
+ "gateway": "b05150035597f0f51e19497aea1916575fa37e90956f5afe87811808f0cac484",
+ "analysis": "b175eb028138b50dccdb98e1829772c6fae70c055cf1b0e047dd658356256b89"
+ },
+ "constant": {
+ "babyA": "a3b3dcf0baeee7cd9851abc49f56e8fbc43f96408736da6344b4f1aba3849c02",
+ "babyB": "ca0e34ff41af00b35af427ad5afb0990c41a34e247cf179de8aef43d95c06278",
+ "gateway": "9519a3dfe2aa6642fc81b432a691787e98afa2ed290b5815ad31bb3dddb49a87",
+ "analysis": "3ca9bd6772ac12198d3ad70a954bef9b2a3774851ec4897e94e73d763c4a75ed"
+ },
+ "random": {
+ "babyA": "ed79e6a3cd74128fd2d03576b0af621526e251823e1a33d665c5b77ab5126e20",
+ "babyB": "3408e83cbae614e49e6d2312d2232d4c27d1f6bed36eaaac47dda3e7753f726b",
+ "gateway": "40cd9a2802c4352d61cb8e557ef36a58ec017923810b374056ae4ee990481954",
+ "analysis": "3c770bf158d030775496e1cd387fda7b2ef2f7f9cd0f23d3dc0c85ebbff41edb"
+ },
+ "shuffled": {
+ "babyA": "279be4e4469d4afbee07e4cb0d7b2116f729920476ce9a60bc22c108e6b60412",
+ "babyB": "89a88c9c74f3848690b353030d23b9a9e553daeeb67dd2e8992799d3ffe61029",
+ "gateway": "4be4c735c8d7dbb131073e2fb8dd1d1303641b2cec273cef525300ab2dfda71b",
+ "analysis": "1645dbf06245a9a1db1f394c2305f942ceff2e33ad331049fbba9d2cf80e1404"
+ },
+ "normal": {
+ "babyA": "fc0ef9fdf59d4821c6f84d5ac9999746bda1d4289c6c14566e26ca77b23f0a88",
+ "babyB": "efcef56ed57d8d8e83fc277b2573065a84e4cf5cfcc50c014361ad3b027c8da1",
+ "gateway": "3a513b837c5c27c7c1723fc16d6e677132a8656fcd0e8cf049d0b1e488a518fa",
+ "analysis": "be57e2fb548b37d27ee0b7be43279a26e06e0179b74d5f7952b47973987b9ac8"
+ },
+ "oracle": {
+ "babyA": "f76f16bb60d0d3bd45dcd9c1423768463b2502cfac1608edd17bb7e7e82da074",
+ "babyB": "5d3043885fffbb1f376d960f5f65cf8c8f67fac74ccbae0fd5710c8216920144",
+ "gateway": "24a5b6e1e9340623d25611186eadf18f37b4fb73fd03c97fc3f0748a22be969d",
+ "analysis": "edcd3a7c8f45e8b0e5153156def7c6c23415f3a53c228f9efd3d75fcaab2cae8"
+ }
+ }
+ },
+ {
+ "slot": 12,
+ "use": "primary",
+ "scenarioSeed": "0de3ea2466e2f52285eb308d84f16c51fcf6f5ff75530823f16eb014a9373c99",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "4ab500665db073dd185cfd6dd836f359bb62f67719cb24e192cd2396808318ba",
+ "babyB": "4da871f7c808dcc2a0aede737dde1590818ba5a8599af77c29ebb3ee90a8e7fc",
+ "gateway": "0af94535846fc1e4b1a11c75a68429f8e7ea330510589de6b94abcd984629297",
+ "analysis": "cd4311cb38d13f0210ac1a948a8749d0d8b8c22df139ef85502eed070776c565"
+ },
+ "constant": {
+ "babyA": "671565c1dc4d9aec9025b1842d9ccd8d12dde15c3d0f9e4b6419df66ec127cc9",
+ "babyB": "af8e1f1d32ad923b2140fedbbea7b02e661ccccfea5db5256a0faaa7a6572206",
+ "gateway": "073977e2f64d76647b9413d2c285de30e3af40e539f121bb04137a803931de9f",
+ "analysis": "3f03a666a1d3698a30c0ff19124f76bd41b117a936e182e6b49e567bd346a990"
+ },
+ "random": {
+ "babyA": "d8f837d62d4dda5d279fe47ed8c93262404d86a8f7f0e155db6d077676fc331d",
+ "babyB": "c8fe7449794e31d6a87d022de4ebcf883b9745a598b40468f560053b9b3ee528",
+ "gateway": "faa80b019cea6db8d5df94ffacd4af2049aa1f6f7c8ec2d06fbdcf6fd58e5e08",
+ "analysis": "24230e65695ea2d88faf79233fff502ded7e53f69826b69cb7089a9989a3035f"
+ },
+ "shuffled": {
+ "babyA": "9fb6260acadd33f563b2dab2d06c87af55c8878cf03cb764356c93a791557ac8",
+ "babyB": "41a631e78bacadcbb46d116c446c68d5b76b51619235da9cc833e0cf720098b3",
+ "gateway": "6bc011f597fa26db7eba77c8059ea34e3af44391b755e165967677d8f83307e8",
+ "analysis": "778bf6c3c129285e85f631b7e4d727b724948e1c4adb47866a4ef690bdc27c3c"
+ },
+ "normal": {
+ "babyA": "dd6adb8276f9c784ee69c9d39a6ed4b9adfb40830a04ca5a1e9b3e9bb6bd07af",
+ "babyB": "bd1dcba1523d1c0d8fa70d3ed8ae6833fdecd38f4a2a87bc9779585169163080",
+ "gateway": "53d12c0c6bdd747d2b4a73c696c0199b6156cc60b53ea2fd6091e3328b97a970",
+ "analysis": "0fa1cd4a45829858eb208cfff73bbde3bf8d0cc0a07425a85c13d861fc6b3827"
+ },
+ "oracle": {
+ "babyA": "bfb3fb3427e96e9fad5b0f4a006406223a244d02038a54e28389176915c31664",
+ "babyB": "76bc730c2dffdc5368044a21be378060d542ba0f95164763fe26ddee836058d3",
+ "gateway": "a08b5335756e51488cb5b38ae56c10c508e2b1b91c551de4515f0fbdf1afabac",
+ "analysis": "fea576b399730c98fdbf7685944edc77096cb5614600cd54fc4856ce94a865c3"
+ }
+ }
+ },
+ {
+ "slot": 13,
+ "use": "primary",
+ "scenarioSeed": "7ca3ae4a7ed89cc2c07f47dbe2c3b517fe63ed77e4a94feaa4c52913c5d60467",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "29fe5e610c9893cf9970a5d44b5d88d33146d219787a0f2f48d1e897f21b6236",
+ "babyB": "9711402c147bed4654ba914286f5b71ddc55edbbdad61b1c152b566a7612ceaf",
+ "gateway": "244b3148f0854a7b29ec844d497a044c4ef160710bbf61af302214de894c5c46",
+ "analysis": "0c72270ddef30495959b2fcb1a74ddbbd46715570ae42b9dec9d716e27d83941"
+ },
+ "constant": {
+ "babyA": "906c6aaf6e69ee3acc1ac0b0a7c0ca4fac83edb2274207282f661778f1d70e14",
+ "babyB": "e98980b633d41c0ab4fdca8c685b273608b061c822e95e3a70c563cecb998b5d",
+ "gateway": "3b5571726dcb8f7f041d8ec4264ec60b2d411c8ae32752814ba76648cbc5a7fd",
+ "analysis": "ffb0c38f6241c58b06a492119a98d6f3c39b205102efd8b42ceec8890e6adef6"
+ },
+ "random": {
+ "babyA": "d7890725662b0574bfb7e6159ce9c3c87924f25881a02a0de74d709f16cf588b",
+ "babyB": "060671aa3c4a81185b1e9408be214843c50f121f86f605b4fdbd850bbec2f459",
+ "gateway": "cd60809814e903f8c7993c0da3d971a55b55c6be1c5ae513a02a7abfbf009c7a",
+ "analysis": "73acd98fefda9f8902ffbf3978e41f8e69fc8acbccd6dab6d4344f7e16212b37"
+ },
+ "shuffled": {
+ "babyA": "694dd6386279d31743c78b30ff293fbfa48f094d3889e33da157b698ba6a7be9",
+ "babyB": "efb0445bf620124e67b34c88b5f1ea972eb11e7670f06cecbf7dfd397065c188",
+ "gateway": "cc50ce5bc1f29f0a7547208544af9d5b14dc8a34801b239155d9bba038436a87",
+ "analysis": "909b80f269609b00b1e57b83fdef23ef5368938b5f32f8f66bcc3033d37b2869"
+ },
+ "normal": {
+ "babyA": "ab5307814ec17d5847ffa75bb99ac65e577c382c40bb44c4b035a1e02ecc6755",
+ "babyB": "ad87a0f7b2ba1b955baff331a0771a12e2a4229947a6c367d9ab76e6f60ee42c",
+ "gateway": "a2818f1d7f93c26810826a5eda019448fee0f2324d5cbe69edaae2ba9e961335",
+ "analysis": "a01c97ce3228b20c0127ed6ee73cd5c8b4d75e2f879cf816e36ee81ef0cd7f83"
+ },
+ "oracle": {
+ "babyA": "34a4ad1c9ccd66c25e04d66ade77066b874378e189208ed9384840d24e46fb5d",
+ "babyB": "4878195b3f5f9ac767166165318cd475968ee2c4bcbdbaefeb50521652871484",
+ "gateway": "0ef47858517196a7206fc09daffd5896c6c131683a9ad5f8a07fcdd028873163",
+ "analysis": "b1f58ee5d36bbd603aaa94521cc946403929d976264909ec0eaecbe624d315c9"
+ }
+ }
+ },
+ {
+ "slot": 14,
+ "use": "primary",
+ "scenarioSeed": "43bdd025d48ab96472e72cd3fc7176be9c574f169c22a956e2e7b6abb263bd65",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "7c3d8ab5a882d2aabd92af9076281101e0081438594823c7a8c29d83e16f8895",
+ "babyB": "4e78422aca1d35b51f31473e2af5dc4c0630e7e784e0918378251110b890c018",
+ "gateway": "284f260cc9cf3502a0769a10684c708ada3b5a314dbf9ea8581f9214e1ed1548",
+ "analysis": "5de6708dd041d90f4013c7052ec9ebaa124cb8d976d608253fad46aa569ac06d"
+ },
+ "constant": {
+ "babyA": "61febedd508a09a756f52ea5221c13cc668a43bf4da3c09048084d33f7608884",
+ "babyB": "65f4c85a8088a5e492be82393960ea241f4483b5fbc1848b0d0770cfd91fb3c1",
+ "gateway": "87a54bfdf541eb71877e2f773030859ae158f2f9fb6557e5258af981ee6f1f2a",
+ "analysis": "14a126c7009cfe4a30c19fdbd92375fb3e92c505b9c669a9876655b65c3ce784"
+ },
+ "random": {
+ "babyA": "241ef8597e57c525f7fefa0da85cd1a810f4b85bb6689c4fcdf1be01f56817c5",
+ "babyB": "037f4866bc5a07a37263729443b5414ed09cfde86d69ab1275e612ada95bc88c",
+ "gateway": "a16fb013b75be5747f9df682e8e88ccbf0e5966706d0a9e9c69dbc12737d9f70",
+ "analysis": "7784f6496e14ba3f893f208df3b2e23dab6c3e2314a6c70a5b5434ba39ca67e5"
+ },
+ "shuffled": {
+ "babyA": "a3a52506fe353e07d0252e65424e212e3dc99c5d583b8f96ecbb3d98146ed799",
+ "babyB": "9d68b819c6bdb1d399d10f79ea49a83b76b15fba95a511109970efdaed7d90a7",
+ "gateway": "eb6f983068504dd3fa682be70b05a4c604696df01c5bb4d9493aee286c496388",
+ "analysis": "ea940539694af5299c74ca3d6e5b81d0a53c010510a680492a1ccebc220e9e44"
+ },
+ "normal": {
+ "babyA": "da91e7a2920ca515bde61ee11f080e8a94fec8b512e6d8fa8d59bcf3d444d6f1",
+ "babyB": "2331949c210085b3e49e39d36c5c55a0be3e219608e545c56e68eb7eee3bf8ce",
+ "gateway": "f2a5f0ac858c07e5031f8fe0d03a0849b8ce4c45288b69e07dfdbc3a3141a94e",
+ "analysis": "e5bab6c249f74e2cbaa57f0d5033d6963ad1f609b060dd67a5ee62494a04caa0"
+ },
+ "oracle": {
+ "babyA": "11f4cb5ed07ac3ade626a8537c2fad5f2053706bd1c484a2d4c2ba2d878cab2d",
+ "babyB": "4b64da9a456b2c2654fda11196d4dfd93668f33d8c2119d94c9d912f84c40536",
+ "gateway": "d2006b9da1cdac039df1d0f9fbcca3f757d29afbc70e9adcc479d8635bbc7df7",
+ "analysis": "f45503ad8b3ceaa58a48cf5d6055e6367f40bd5a00283833e90a21b1c226e578"
+ }
+ }
+ },
+ {
+ "slot": 15,
+ "use": "primary",
+ "scenarioSeed": "c2bbe38716e2f3175db0aa5913f436a5c8c2ba0c0da237d1ef38136c970cc934",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "e7618dbdeb88168f932613ab7f79dbcdec7f867066bebf33469a16e4c893c4f0",
+ "babyB": "0c26ca6adbae2a450e75a7a1dd11413afd947166223a6bf5e7f97f3d5864f1dc",
+ "gateway": "bc30b024733e02f7fe0a53c44b4084163eec4c30fca9ff1b5c20213e93998fc3",
+ "analysis": "b8649e1c11478c90adfe965444c161b310524df97c44f010dc561178ad5741be"
+ },
+ "constant": {
+ "babyA": "b2691c130a5ade788a08a20a716888ec7d9c6517ea231f416a1b3320e263be4d",
+ "babyB": "86f2de8bc89bf3c35953ea914a7fcff4a949b2d541ccaf8f02ce653761512ba6",
+ "gateway": "da9ba497e6ae11f814a361bb1d13df1c929f5d61c2cbd29b0f13cfa97225dd30",
+ "analysis": "c381a1f8fa8247eee1e81410f544913893e5ce1ae179d898e9a9ed1bcee867d8"
+ },
+ "random": {
+ "babyA": "b3c868703b04833d11b826da82db8be4139fb2125f30009f142ceb81c6c68a61",
+ "babyB": "984f247e1e8aed9202092fcd84265fefcd6dafde9277e3ace0a25a2b9054b096",
+ "gateway": "20cb897a1a14a1fbffdd7c9e2eb7ebac193deaa4a5c1eb61ecf89e8189ee91c0",
+ "analysis": "b924ff16def533a139944a957fcc97d362683ad869be370526f17afac13d989f"
+ },
+ "shuffled": {
+ "babyA": "821974da7087584fdb57a65bff0275eaf261f7483230b1c3ba189d2e6c09e1c1",
+ "babyB": "8a8261841a6f096651fa138e6dcf6574c53f6d8f0709d7298327413f4100b9ff",
+ "gateway": "aa759dbf83ad15282a3cbbed6e17c102f0b852de92d14b1aa894740faa170efb",
+ "analysis": "f283f256ea82875c110f929cf544f49c657723fc095353fbdbfc24055077ce66"
+ },
+ "normal": {
+ "babyA": "a2bddf7627ee0f998d3e17dcd297421caabdf05dd63bb6e84cbde5cbe4a19d8a",
+ "babyB": "5b99837fcc564d03ff6f8d15fa69cc86166f9691366a8663ee52e0ad9590b2d4",
+ "gateway": "c363ec2a09d705b7a8575de68895cd0773d7027ec1b4f8d51c99378727d57ecb",
+ "analysis": "5be10424defd70b7a93fdcecdb3bf2708c9ba3621701087b3fc02a954e6878da"
+ },
+ "oracle": {
+ "babyA": "21bdc4048c32058f446db00e6aa7ebe7bf07baaa8d7b1ea17094d51e8c096c26",
+ "babyB": "f711ff9d9163d0ae329b64b1f5a6bd4506b80899ef16f5c4bea7cb7db485c46a",
+ "gateway": "20f77f227625d1d7da64764a5c2cf8ab41b39c46f86f54425db293145b71ba39",
+ "analysis": "ef6e8561b45d1f0ed26c614dbacfc3c4e5f279dacfd8757f477893534937e75e"
+ }
+ }
+ },
+ {
+ "slot": 16,
+ "use": "primary",
+ "scenarioSeed": "23b9ea82fcd55028fb8e3340696df9b3ec4404ec26f350ccd6d014bb06fda4d5",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "6c2e06ec02d273c5e8bf6a51b54513f46b1b7dc8b86c0f890cfb21dfb4b936fe",
+ "babyB": "d4a937721b9a602f14822da24255b4882c51f77efc533fbb30e513c466c3f916",
+ "gateway": "af610ab9ddb92e31d2e0f618a4b8492dfc85566029c6dc18ba66b3b13fca3c89",
+ "analysis": "0b78840011290e5bf12e08258b4bd481b3f69179e006d9de2f5210732686f3e3"
+ },
+ "constant": {
+ "babyA": "42fd86470354184802678b9e5ab050ab952c16385015c0b16504df552d3dabac",
+ "babyB": "099ac65d1d9035f8e7a06612385032fbb182711bf0fe80560fcd7309927bbc46",
+ "gateway": "7f48efee205e174de6695288759f81addf2aa46d860870072771ef505b4d4b40",
+ "analysis": "3e8685674f8eac90be8892b085c393edf6402bb7709c322b11ef19e1bf15878d"
+ },
+ "random": {
+ "babyA": "c2cc08d8b18073140c35cfec9ba58234aed8d074955e4cf990d6adf27b842849",
+ "babyB": "2267eb53c577bedaeae4c8ca59b74debb66913f71f375d2b4875b449507eb66d",
+ "gateway": "a75da13f62b7ab5c2894ed6111655de80b3b9e40126065988b9c5677bf0364dc",
+ "analysis": "6a1a9f5d1f28a04ea0c0a673586a05977d9e94da6283855fc7370ce669eab307"
+ },
+ "shuffled": {
+ "babyA": "88562af25a9829af20c5fc498c211fb73913639980c9f8cf8831bcc7899d9daf",
+ "babyB": "1ea680334a80449f477725af49a966ffa36ae59d3ca5805e1286e95695595b17",
+ "gateway": "4477d356641d393ffe4a8d6a1262b2e297d0d1750b95f8918f8eee79af829fed",
+ "analysis": "50b74a55b2173defe99e403c75c6d2dc9e2017c1fd1904edfb837fca746a2d1d"
+ },
+ "normal": {
+ "babyA": "29bcf1596c12e441f7082629b5a5be532116851f23f91888330bd027d8e705e1",
+ "babyB": "7aff575195cb896a587bb2306467986e2e1794d6f011de632b9b596bd8536029",
+ "gateway": "affc22ab2d5f2b431caf7da9532e294b5c40db0b374e075b1db56397db80b373",
+ "analysis": "d92147c6eb3fa08319db5f23b74c87e89c352053ba1e62a9c6e470e156acf4ee"
+ },
+ "oracle": {
+ "babyA": "cd762d95aadda4f6964922ef279739b37ed3600f86cb7c24162e0ef504f4649b",
+ "babyB": "6ed1aebaefa2fbc42d395ef5a97bdcddacd96bcd9570f513007b2b7e4b1cff97",
+ "gateway": "fc2b30ff5080fc3b605e706457f289f3124bfbe267beef9a846c8dff26314ac2",
+ "analysis": "ba320535369adcc61e6805c3806a3a5b3bdb55d5d01ef122c68e09bb3493f88c"
+ }
+ }
+ },
+ {
+ "slot": 17,
+ "use": "primary",
+ "scenarioSeed": "d82447ff9b5a721beb86d5d9f02186ec3d90d738e5b78b697d1beee1f7260e95",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "d7ab7e598c026ed1a3bb4be024bd5523cfcbb2416d2284368ade3eb655abb044",
+ "babyB": "4f11e3166ad9177b6278db505c17336e4f47524dccafa208d858ab1c23b2079b",
+ "gateway": "267782bbbbc3a94d178bd93bcef8f9b387f9671be06ea218fe04ee4e295f8d9b",
+ "analysis": "f4481f3380b10ffd5231a07b78b127cb6db0b8b9446b4b94cf2c5215edb39682"
+ },
+ "constant": {
+ "babyA": "e059ca784ab70f8e9e444b1cf5e1944ef8718e93f94f752a15f93cbe5f30fab5",
+ "babyB": "d4b4cfa91b5dbe944ae2a374e6d963fc6ac18004155afa353cb2f68072374d86",
+ "gateway": "570b6f6f09353be5fb9db507e30a305caf95700ff28cc12f3d76a08c565fe52a",
+ "analysis": "09830233463b7f104393702e449a613fa7a019adabeb1aee898b330b0b4ff0c2"
+ },
+ "random": {
+ "babyA": "950974e3e99716a6d93f698d2fc262025942440d3acf1eb253265981c945bcf6",
+ "babyB": "7cba7e09e6612e969b72895e3137a3d1db18ae24d6ae2678edc1db6995f4e251",
+ "gateway": "c56d86e1148f674a55f0ecbdaf1c65cecb04c33f1cafc41157bd0b5fe536065c",
+ "analysis": "c38db7af2cc73c6e7dc1c58fa563853eb790e9df9150298f914b886113ece6b7"
+ },
+ "shuffled": {
+ "babyA": "cca635618d102192c2d684a9dbb80f27bf0358ff94b8a3f0ab388d091c344135",
+ "babyB": "bca6c481ad4a232543469a0aab7269145a289081cc86f2e945f0b319b85247c5",
+ "gateway": "c766c76facc4fdd06be3d1bf941a2e8b77dd6dc1cdfd98354b3ba10434e6fabf",
+ "analysis": "0aed809facd94e4101f2dfae4fdb5cabfef073d780fd3607266b42bd56455789"
+ },
+ "normal": {
+ "babyA": "f27d1063277206c9027c1063f27aca1f0d54b3e0af919eea8e34b524386107ca",
+ "babyB": "e75f0740993835f76245c877febfeddc069d6d09205f07773b37cb2aaf79f4f2",
+ "gateway": "0dd278fbb6874116afbbe915880b47754f656498ad559b3b028d7178fece84b3",
+ "analysis": "b01e129f37150f16c1c02ad1054ac4d07ac622ef74cfc8b6749e820fcf1a598d"
+ },
+ "oracle": {
+ "babyA": "153cce80c4a04a39b6f47446b902684198df8dd7bac5928d82d1b10ff7e0835a",
+ "babyB": "823d016cba2e97ffffdb3120617b0f1399255c256414f1a3ef2d33fc614db25e",
+ "gateway": "2e3a128d015a8552afc12d6c781af6f96172e9a4242a8aefecdf820cab3f0c27",
+ "analysis": "d204124dfb33f82f51600dd64b86c84c8a11ff236557572d1868f4f378b27567"
+ }
+ }
+ },
+ {
+ "slot": 18,
+ "use": "primary",
+ "scenarioSeed": "e2919db34f6e450df5243bf9971e72d89317bf7a12e5320326d105d9a1e35f30",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "44f0f8e876b2c368f8d1e5afe3b852f39f6c3c577acd9370c8924aaa36b6a40f",
+ "babyB": "1b8ecbd51f3495d31e21c910bed0a7b9f56f7516b59c3ab1a178179bc97d5f0e",
+ "gateway": "6d8c61a422b7a5759ff8d5581743c774e6e43c672e525f914865525c423ad4fc",
+ "analysis": "faab1ca2883ae4c53fa30ff47fecce621e98c2014c691df646bbf147253e9c30"
+ },
+ "constant": {
+ "babyA": "0ce174ce50c6944e582dcf578c5be9674b50aa791f55c0a2e45140ddd47dff01",
+ "babyB": "79c0973fa8c5e25756a65a8d3d7e98ba121134b2696c39925b25e84269c5b2cd",
+ "gateway": "1e4778473ec0b441c61225e5dc28deabb126ab58bbec49c50df5a1840a15b82e",
+ "analysis": "169ae40fa650ab5a0354aafcb4726496dfaeb247af3cbac6d851a925c306bd04"
+ },
+ "random": {
+ "babyA": "12aa647fa79e789fc6d1730fea8a9d4e6eccebc365a970e7f75f1f0c02c9f29d",
+ "babyB": "50843c24c7268bad3426fa5982772c4b89002b6ee2177ec4975eed0b31f2b024",
+ "gateway": "761ac50c712dc4f8c50841dfefd5b47801979498649551f50742049dfcf85b7c",
+ "analysis": "18de757984600b4c2c65237b6f99432440a5bde521183057425d4b690074d119"
+ },
+ "shuffled": {
+ "babyA": "256f7a90961f359f492dea6b1d94cecaf9580edbcf4a685df75700c23afbab23",
+ "babyB": "3678221a499d4378e839359fb67290eaf1a9e5bd4800929c4ec4a7dc34dac41d",
+ "gateway": "6fea2286cacd369086e7a79a0217ca9590068b639fbee5159cad5f14ed4cbda9",
+ "analysis": "b78fe2be950d1676441a5a2d96d0ca3f57154c6bf569967c588512233afef3a0"
+ },
+ "normal": {
+ "babyA": "a04318e37ad3c7d38925171958c0f5382a488d14740881445bd3a3e82cb7e342",
+ "babyB": "bf1ab075835b46866f5eeba726bac99c2da84393255689fe96d49b930fc38b11",
+ "gateway": "0424ba32421a35cb61e6473164aef15fd4e6bc5171a6e8f307dd50c1b18876b3",
+ "analysis": "20259df2d775f663fde5c5c4bbb0ac910ead0a037c7c50b5124b183c70b51050"
+ },
+ "oracle": {
+ "babyA": "60686c654961d6a0fcee0140d2b3b1e54c42ca55bf8f589684dc9a67fbf455bf",
+ "babyB": "f90b5d9becc3ae5d8feace2bcc55e7a0b13ead919c8a96fac1934a0674eb5da7",
+ "gateway": "64bf9842baa4d0052921bf495d0e1f7d71ca22cb64cddaa05fd806fa5cb5553c",
+ "analysis": "3cd0c6dde27d57faa985ff3537522cc1c0aaac56f11d72959e370b09308e42ca"
+ }
+ }
+ },
+ {
+ "slot": 19,
+ "use": "primary",
+ "scenarioSeed": "66a73900ce01d77198a3f34270765086e8f4328a3bb22dd9f3ea7578de4ac00c",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "276b5bc30dd0bcab02abf58eecb244ca14d069daaf7fb36c921f6a872bef41aa",
+ "babyB": "fc2fd79b0f19a86e5598f0bd11a241a17e986685d55aa6099cd8e3379cb946cf",
+ "gateway": "cedaf402d4b0c29880a09f0323175530a15c646a5a50a2b91ab4c0509f69ddb3",
+ "analysis": "55ed2107c3c378f6b1de20ea17fc3fd6648964acd7a9bbe82f2c875ee158bc78"
+ },
+ "constant": {
+ "babyA": "117a3eaea4cd5dafa79acda218a504eb3e9626a2f0445dabf758b608c9363c5e",
+ "babyB": "3af26876347741be192bae93f539763a094b0b59e9b44d4c1d7f2c95dff074f6",
+ "gateway": "1187a1445ea1ea93825c6b6979fcd4106e2c9671187ce971a6b389f3e6c95a85",
+ "analysis": "903731dfb9fa765052d33ef00e2b2d9f7ee97d2df048b152d5e16624080693e4"
+ },
+ "random": {
+ "babyA": "51df295e18da09fb7c4e213339bfea2acd5db81be182ed032379bedd3c59586f",
+ "babyB": "d6d39829904c83ce72d298071beab22ee3ff775d829b27bf674c91224b509ac0",
+ "gateway": "c38c5f36fd9f698d51be30526cc8d8cbdf957df839d755134b214300da33a0f0",
+ "analysis": "d14c84af7da8bbb666940a3ca6a05d75b0a41587654ef9a9263c34d5b7ba3f67"
+ },
+ "shuffled": {
+ "babyA": "5f7bebd67a627435031ba0abc427d70a01040a74eaa6cae95bf8c05160e92fc1",
+ "babyB": "7824ad9e0365575f33765eb6847eaeadb67f38d0c9606c0971fd329073cd248f",
+ "gateway": "60e5c0bb0619f4e4a46f46be2560224f9cdd54d4870ed7ad0d934fcbaa6392f8",
+ "analysis": "d33f7f6f55db9adf61ded193a6a176717ce2bb9ff0f50ca10723ed2a1126445b"
+ },
+ "normal": {
+ "babyA": "79c79bb30ae5e5f8d62aa40943c6960d29423eda0a09ad958ad2cb9d229c3717",
+ "babyB": "c0d617e18c822b131a4cb26560d1135ece0afbef7ddcb7a1017ac6d05ae6ddf5",
+ "gateway": "573b336f39deed20b345d15eacbfd85664b79d743fc9beaff4736410ad0b9bf4",
+ "analysis": "7abf561d0ffab88defa399d81485e7d532a5aa46d36bd9181e1eb02a4730d6bb"
+ },
+ "oracle": {
+ "babyA": "32e27218b3908eb5e241373aa38f5121c403ba5e9bb42799af613a258849d48d",
+ "babyB": "dbc6ba3a2408260426148d59a6f68411af543b3c447fe7f148f252fb1be0759e",
+ "gateway": "e83c533a79ca270c8be083752bed2b7e6e8421f56a3c9aa58e57c6223618b699",
+ "analysis": "7e01994f6973d6b52f765efe1ad792f24766217b4e3d1a8f2488eba84bcf46dd"
+ }
+ }
+ },
+ {
+ "slot": 20,
+ "use": "primary",
+ "scenarioSeed": "bc6f6c59fd32c68182f6e34fd3958ee533712bcded9e2fbb5456936d2daa6235",
+ "conditionSeeds": {
+ "disabled": {
+ "babyA": "62231be5e5f4540bfe2fb7b0e0029a088effe2af3ebbed09db7f4013ce82c141",
+ "babyB": "996585918aa6de1a6ed38514f9148ec5fbc9fbc49676ac66d287c5715a4f7e3e",
+ "gateway": "f03bddb0d0425b0a65ba21f970fc1c285ef68b71deed6596bb6eccadc369a546",
+ "analysis": "5b7d8df11a78d45ad61063db95be1825e7b83077defd50f1598662af821b80ab"
+ },
+ "constant": {
+ "babyA": "443be5d1ff8793f116b359cacee7dd8e5b294458cf0a6f628f5c4525e776b8f2",
+ "babyB": "b9b72afed53b5c148da46a74e35ac6351d861ee45af01d83357dc6b3b0049536",
+ "gateway": "d16eb3f828b3b6ec9fab5bb5f15360e850b78a3c239252fe64e4abec572d0342",
+ "analysis": "aa7e4ce337081ba07ebddd8d03f5f34e8c6135c2dae52749f00157f5bf0e1b1b"
+ },
+ "random": {
+ "babyA": "545f2f47d27fb17d32017c01db3969afa8d8813444df85ada21ab59c5a9c9965",
+ "babyB": "c990dcfd9654bbfa05db608ebcef838c6901a1986c6614983005552c7fe43227",
+ "gateway": "9cad0d0fbd1010f374d8a814143914b998ef1bd6879223526a757def0e4f016a",
+ "analysis": "5ba82d13a0aa921b955114669b0fab65a8a265b0d11488fbd522473ab3161d41"
+ },
+ "shuffled": {
+ "babyA": "7e619d3b02d0efa45c6b786f3ce3acab6edd2cd3ddee552c0c7873da985b0421",
+ "babyB": "23d09dbbf1b5cc7d6b17705adb5285083144bb4164c8097b7df47a7a8b013531",
+ "gateway": "e284e0244dcd382035f7144e2b02c98765fa29ee56aa81877a321abfb7064df6",
+ "analysis": "d87f46f049c0feae6ba89ec9328d3c06ab443d6ef9f6dc1b8ca440f5c940f18c"
+ },
+ "normal": {
+ "babyA": "03ac723c1cd0692ae5b1cb39a42e87c675b7e9f6229da5755c7230fb725ae4be",
+ "babyB": "5d5ed4577d0461305e69276ae3074f09eda89f510d8dcc4cd2a392493bda1c64",
+ "gateway": "3a7213f987820d906cda5aaf1bef99b6de3c86f8f21a33a4d5ac013191efa197",
+ "analysis": "02179fca7d6db8d4344a9303ef01443821ed566f1a7be1c14e7d23c175b5dc13"
+ },
+ "oracle": {
+ "babyA": "47f762b6b4dcddbc0c7194aff8a7f6b56882f38d86df27500b6474770f8dea7c",
+ "babyB": "b2adec6d3623562c0f8366812001c74a915592e38133dd3ad53a90f175b144e5",
+ "gateway": "f4f839f9cf44016a792c890d0bfef95fc69a84d69a7c166f68fb512ebf16bf47",
+ "analysis": "192850cc644b931a78eba515b232d5394f4938ee51073aabba8cfb2e65979396"
+ }
+ }
+ }
+ ]
+}
diff --git a/docs/evidence-bundle-format.md b/docs/evidence-bundle-format.md
new file mode 100644
index 00000000..472214e3
--- /dev/null
+++ b/docs/evidence-bundle-format.md
@@ -0,0 +1,250 @@
+# Evidence Bundle Format
+
+> **Status:** Implementation contract for ALD-016 (export) and ALD-015 (verifier).
+> Authoritative layout: [LEDGER-INTEGRITY-DESIGN.md §13](../LEDGER-INTEGRITY-DESIGN.md#13-evidence-bundle)
+> and [SPECIFICATION.md §13.2](../SPECIFICATION.md#132-evidence-bundle-structure).
+> This document fixes the byte-level details those sections leave open so that a
+> third party can write an independent verifier.
+
+## 1. Layout
+
+```text
+/
+ run-manifest.json canonical JSON, RunManifestSchema
+ baby-a-ledger.jsonl one canonical signed LedgerEvent per line
+ baby-b-ledger.jsonl one canonical signed LedgerEvent per line
+ channel-transcript.jsonl one canonical signed ChannelEvent per line
+ affect-transcript.jsonl present only if affect events exist
+ audit-ledger.jsonl present only if generated-analysis entries exist
+ turn-records.jsonl one canonical signed TurnRecord per line
+ intervention-log.jsonl one canonical InterventionEvent per line (unsigned chain)
+ checkpoints/
+ 000000.json canonical signed CheckpointManifest, sequence 0
+ 000001.json
+ proofs/
+ inclusion/--at-.json InclusionProofSchema
+ consistency/--.json ConsistencyProofSchema
+ anchors/
+ base-receipts.json canonical JSON array of AnchorReceiptSchema
+ configuration/
+ run-config.json canonical JSON RunConfig
+ prompts/
+ learner-contract.