diff --git a/notes/swarm-survivor-repo-agent-plans-2026-07-06.md b/notes/swarm-survivor-repo-agent-plans-2026-07-06.md new file mode 100644 index 0000000..add63e5 --- /dev/null +++ b/notes/swarm-survivor-repo-agent-plans-2026-07-06.md @@ -0,0 +1,757 @@ +# Swarm Survivor Repo-Agent Plans + +Generated 2026-07-06 from the post-MiMo swarm chatter/traces and one read-only grounding sub-agent per survivor task. This is a handoff packet, not a changelog. The already one-shotted complaints are intentionally absent. + +Survivors: + +- CrossingReceipt / Verse contract +- Hermodr/Eve/Sai live provider surface +- Heimdall deadbolt / revocation +- Mimir live frame proof +- Ymir Unity runtime proof +- Weksa language-project state + +## 1. CrossingReceipt / Verse Contract + +Owner agent: Bifrost, with Heimdall and CultLib consultation. + +Grounded repos: + +- `E:/Projects/Bifrost` +- `E:/Projects/CultLib` +- `E:/Projects/Heimdall` +- `E:/Projects/Fensalir` + +Grounded source: + +- `E:/Projects/CultLib/packages/cultcache-ts/src/types.ts` +- `E:/Projects/CultLib/packages/cultcache-ts/src/document.ts` +- `E:/Projects/CultLib/packages/cultmesh-ts/src/index.ts` +- `E:/Projects/Bifrost/tools/bifrost-bridge.mjs` +- `E:/Projects/Bifrost/tools/cultmesh-bridge-commands.mjs` +- `E:/Projects/Bifrost/tools/agent-transport.mjs` +- `E:/Projects/Bifrost/tools/governance-threads.mjs` +- `E:/Projects/Bifrost/tools/provider-advertisement.mjs` +- `E:/Projects/Bifrost/docs/verse-service-contract.md` +- `E:/Projects/Heimdall/src/contracts.ts` +- `E:/Projects/Heimdall/src/store/types.ts` +- `E:/Projects/Heimdall/src/verse-witness.ts` + +### Current Mechanism + +Bifrost already has a real typed Discord command loop. `bifrost.bridge.discord_post_command.v1` is written into `.bifrost/provider-store.cc`, `cultmesh-bridge-commands.mjs` pumps the command through `bifrost-bridge.mjs discord-post`, and then writes `bifrost.bridge.discord_post_receipt.v1`. + +The split is that Bifrost's broader bridge, governance, and transport receipt paths are not unified. `bifrost-bridge.mjs` still returns stdout receipts and has `beginBridgeAction()` stubbed. `agent-transport.mjs` and `governance-threads.mjs` call `recordTransportReceiptOrThrow()` and `recordGovernanceReceiptOrThrow()`, but those are also stubs. The machine says receipts are typed truth, but several crossings still only prove gate presence or stdout output. + +CultCacheTS is not the owner here; the real CultCache/CultMesh substrate is in CultLib. Heimdall supplies capability/grant/session truth, but should not own crossing receipts. Fensalir/Eve lower receipt state later; they do not decide whether a crossing happened. + +### Authority Map + +Owner: Bifrost owns `bifrost.crossing_receipt.v1`, because Bifrost owns governed public crossings. + +Inputs: typed command/request/topic documents, Bifrost identity, source kind/id, authority reference, work item/motion/topic/request ids, target surface, target locator, Epiphany run/lane/agent identity when present, Heimdall capability/account reference, external actuator result. + +Outputs: one canonical typed `bifrost.crossing_receipt.v1` document per crossing attempt, plus optional surface-specific receipt documents such as `bifrost.bridge.discord_post_receipt.v1`. + +Invariants: + +- no visible or durable public crossing completes without a `CrossingReceipt`; +- no receipt exists without command/source/authority provenance; +- Heimdall references are references, not bearer tokens; +- receipt lifecycle is auditable: `requested`, `running`, `completed`, `failed`, `cancelled`; +- Discord, GitHub, Reddit, governance, and transport receipts share one primitive; +- provider advertisement exposes receipt schema and store location through CultMesh/Odin; +- Eve/Fensalir render or command surfaces, but never own crossing meaning. + +Derived state: Discord mirror comments, stdout JSON, provider readiness rows, HTML/Razor pages, Reddit flair, GitHub URLs, Fensalir UI lowerings. + +Forbidden writers: VoidBot, repo Personas, raw `gh`, raw Discord scripts, Reddit scripts, Eve renderers, Fensalir lowering, Odin discovery, and Heimdall grant store must not decide crossing completion. + +### Contract Shape + +Add `bifrost.crossing_receipt.v1` as the general receipt. Surface-specific receipts may remain, but should reference the canonical receipt. + +Core fields: + +- `receiptId` +- `commandId` +- `crossingKind`: `github.draft_pr`, `github.pr_comment`, `discord.post`, `discord.dm`, `reddit.post`, `governance.topic`, `agent_transport.request`, `future_surface.request` +- `status` +- `ok` +- `requestedAt`, `startedAt`, `completedAt` +- `actor`: Bifrost identity and actor kind/name +- `source`: source kind/id, topicId, requestId, workItemId, motionId, source channel/message ids +- `authority`: authorityRef, Heimdall capability ref, access revision or claim `jti` if available, grant snapshot/ref, policy decision id +- `epiphany`: optional runId, laneId, agentIdentity +- `target`: surface, repo/channel/subreddit/pr/thread locator +- `externalReceipt`: url, id, commitSha, changedPaths, messageId, thingId, transport +- `error`: code/message when failed +- `supersedes` and `relatedReceiptIds` for retries and mirrors + +This should stay an event-proof document, not a giant state document. + +### Implementation Plan + +1. Add a Bifrost receipt contract module. + Likely first file: `E:/Projects/Bifrost/tools/bifrost-crossing-documents.mjs`. + Export schema definition, parsers, lifecycle status enum, `buildCrossingReceipt()`, and `openCrossingReceiptStore()`. + +2. Use `.bifrost/bridge-receipts.cc` for canonical receipt history. + Keep Discord command docs in `.bifrost/provider-store.cc`, but write canonical receipt history into `.bifrost/bridge-receipts.cc`, matching the witness already advertised in Bifrost provider metadata. + +3. Replace `beginBridgeAction()` in `bifrost-bridge.mjs`. + It should create/request, mark running, complete/fail. Use it for GitHub draft PRs, PR comments, Discord post/DM, Reddit post, and future-surface requests. + +4. Wire stubbed receipt functions. + Implement `recordTransportReceiptOrThrow()` in `agent-transport.mjs` and `recordGovernanceReceiptOrThrow()` in `governance-threads.mjs` using the same receipt module. + +5. Fold Discord command receipts upward. + Update `cultmesh-bridge-commands.mjs` so every completed/failed `bifrost.bridge.discord_post_receipt.v1` creates or references a canonical `bifrost.crossing_receipt.v1`. + +6. Advertise the contract. + Update `provider-advertisement.mjs` and `docs/verse-service-contract.md` to include `bifrost.crossing_receipt.v1`, `.bifrost/bridge-receipts.cc`, command boundaries, and Eve operator rows for receipt gaps. + +7. Add Heimdall capability reference semantics. + Heimdall should expose stable references: claim `jti`, `account_id`, `access_revision`, `exp`, grant id/ref, and revoked/expired behavior. Bifrost records these as references/snapshots and fails closed when references are stale or revoked. + +8. Lower receipt visibility later. + Fensalir/Eve should render receipt queues and gaps only after Bifrost owns the typed receipt state. No policy in the renderer. + +### Tests And Smokes + +Add or extend Bifrost tests in `E:/Projects/Bifrost/tests/Bifrost.Web.Tests/BridgeCliTests.cs`: + +- bridge actions write `bifrost.crossing_receipt.v1` on success; +- bridge actions write failed receipts on actuator failure; +- dry-run behavior is explicit and locked; +- GitHub PR comment fake `gh` receipt appears in `.bifrost/bridge-receipts.cc`; +- Discord write-smoke/process/receipt links surface-specific receipt to canonical receipt; +- agent transport enqueue/claim/release/close writes canonical receipts; +- governance open/comment/approve/promote writes canonical receipts; +- removed HTTP env fails before receipt mutation; +- missing or expired Heimdall capability ref fails closed. + +Update `ProviderAdvertisementTests.cs` to require the receipt schema, store path, and operator gap rows. + +Heimdall follow-up tests should prove Bifrost can distinguish expired/revoked capability refs without receiving provider tokens. + +### Risks + +The failure mode is preserving surface-specific receipts as peers beside the canonical receipt. Make `CrossingReceipt` the owner and demote surface receipts to details. + +Do not let Heimdall become receipt owner. Heimdall owns grants/capabilities; Bifrost owns crossings. + +Do not put canonical receipt history only in `.bifrost/provider-store.cc`. That store already mixes advertisements and command docs. + +## 2. Hermodr / Eve / Sai Live Provider Surface + +Owner agent: Sai first, then Odin/Hermodr and Eve as consumers/lowerers. + +Grounded repos: + +- `E:/Projects/Odin` +- `E:/Projects/Eve` +- `E:/Projects/Sai` + +Grounded source: + +- `E:/Projects/Odin/src/hermodr-daemon.cjs` +- `E:/Projects/Odin/src/odin/provider-ingress.cjs` +- `E:/Projects/Odin/src/odin/interfaces.cjs` +- `E:/Projects/Odin/src/odin/surface.cjs` +- `E:/Projects/Eve/docs/surface-contract-v1.md` +- `E:/Projects/Eve/docs/provider-advertisement-contract.md` +- `E:/Projects/Eve/web/surface.js` +- `E:/Projects/Eve/packages/eve-browser-lowering/src/index.ts` +- `E:/Projects/Sai/src/eve.js` +- `E:/Projects/Sai/src/sai.js` +- `E:/Projects/Eve/web/fixtures/sai-vn-surface.json` + +### Current Mechanism + +The contract is mostly ready. Odin owns discovery/schema/provider catalog. Hermodr already exposes browser lowering over Odin/CultMesh via `/hermodr/verse/catalog`, `/hermodr/surface/:providerId`, `/hermodr/document/:providerId`, and `/hermodr/commands/eve`. Eve already tries Hermodr live providers before local fixture providers. Sai already has a useful projection module in `src/eve.js` that emits `gamecult.eve.surface.v1` with `vn.stage`, `story.continue`, `story.choose`, `story.jump`, and `style.patch`. + +The missing organ is Sai-owned provider runtime/state. `src/sai.js` owns browser-local Ink playback and choice/continue behavior, but it is not a daemon, not shared state, and not a receipt authority. The current Sai fixture under Eve is static surface-state, not provider truth. + +### Authority Map + +Owner: + +- Sai provider daemon owns story session state, accepted command effects, surface version, style tokens, and receipts. +- Odin owns discovery and aggregation only. +- Hermodr owns browser lowering and command publication only. +- Eve owns rendering semantics and command envelope shape only. + +Inputs: compiled Ink JSON, Sai visual manifest, `gamecult.eve.command.v1` docs, optional style patches, CultMesh/Odin RUDP snapshot requests. + +Outputs: `gamecult.eve.provider_advertisement.v1`, `gamecult.eve.surface_state.v1`, `sai.visual_novel.command_receipt.v1` or `gamecult.eve.command_receipt.v1`, optional visible story-state document. + +Invariants: + +- fixture JSON is not state authority; +- browser DOM player cannot own live provider state; +- Hermodr cannot invent provider catalog entries; +- Odin cannot synthesize Sai scene truth from snapshots; +- Hermodr returning `202` is not command proof; provider receipt plus surface version change is proof. + +Forbidden writers: + +- Eve fixture fallback must not mutate or synthesize live Sai state; +- Hermodr must not apply `story.choose`; +- Odin must not turn local fixture files into daemon truth; +- Sai static embedder must not pretend browser-local Ink state is shared provider state. + +### Implementation Plan + +1. Add a Sai provider daemon. + Likely file: `E:/Projects/Sai/src/provider-daemon.cjs`. + It loads `examples/static-site/story.ink.json` and `visual-manifest.json` for the first proof, uses `src/eve.js#createVisualNovelSurface`, and owns a session state. Start with the smallest coherent `.cc` or in-memory-plus-store path; do not make DOM playback the state owner. + +2. Publish provider-owned CultMesh documents. + Provider id candidate: `sai.visual_novel.survivor` or `gamecult.home.vn`. + Store candidate: `E:/Projects/Sai/.voidbot/status/cultmesh/sai-vn-provider.cc` or `E:/Projects/Sai/state/sai-vn-provider.cc`. + Advertisement must include schemas, witnesses, surface document, command route, command schemas, receipt schema, and receipt key pattern. + +3. Let Odin discover Sai through existing paths. + Prefer live RUDP provider publication into Odin. For the first smoke only, an explicit `cultmesh-store:file://...` import is acceptable if it is marked smoke scaffolding. Do not add raw path discovery. + +4. Extend Hermodr only if receipt visibility is missing. + Existing `POST /hermodr/commands/eve` can publish command docs. Add a narrow receipt read/poll route only if needed. Hermodr must not apply the command or cache final state. + +5. Apply commands in Sai. + On `story.choose { index: 0 }`, Sai provider calls Ink `ChooseChoiceIndex(0)`, advances visible line, updates path/speaker/line/choices/variables, increments `version`, republishes `gamecult.eve.surface_state.v1`, and writes a receipt with `commandId`, `previousVersion`, `nextVersion`, status, timestamp, effect summary, and denial reason if any. + +6. Prove through browser. + Start Sai provider, Odin, and Hermodr. Open Eve browser through Hermodr, confirm Sai appears in `/hermodr/verse/catalog`, click a choice, then verify provider receipt and newer surface state through `/hermodr/surface/`. + +### Likely Files To Touch + +Sai: + +- `E:/Projects/Sai/src/provider-daemon.cjs` +- `E:/Projects/Sai/src/eve.js` +- `E:/Projects/Sai/src/sai.js` +- `E:/Projects/Sai/scripts/smoke-live-provider.mjs` +- `E:/Projects/Sai/package.json` + +Odin: + +- `E:/Projects/Odin/src/hermodr-daemon.cjs`, only if receipt readback is missing. +- `E:/Projects/Odin/src/odin/documents.cjs`, only if receipt schema registration is needed. + +Eve: + +- Ideally no change to `E:/Projects/Eve/web/surface.js`. +- Change `E:/Projects/Eve/packages/eve-browser-lowering/src/index.ts` only if command affordance or receipt display is visually missing. + +### Tests And Smokes + +Sai smoke: + +- initial surface version N has first line and one choice; +- publish `story.choose`; +- provider writes completed receipt; +- surface version N+1 has changed line/path/choices; +- Hermodr can read both new surface and receipt. + +Odin/Hermodr smoke: + +- `/hermodr/verse/catalog` lists the Sai provider; +- `/hermodr/surface/` returns provider-owned state; +- `POST /hermodr/commands/eve` creates a command doc and does not mutate state itself. + +Eve smoke: + +- reference browser renders through Hermodr, not `web/fixtures/sai-vn-surface.json`; +- reload shows provider state after the command. + +### Acceptance Gate + +The task is not done until: + +1. Sai publishes live advertisement and surface into CultMesh. +2. Odin discovers it without raw path scraping. +3. Hermodr lists it from `/hermodr/verse/catalog`. +4. Eve browser renders it through `/hermodr/surface/`. +5. `story.choose` changes Sai-owned scene state. +6. Sai writes provider-owned receipt. +7. Reloading the browser shows provider state, not repaired local UI state. + +## 3. Heimdall Deadbolt / Revocation + +Owner agent: Heimdall. + +Grounded repos: + +- `E:/Projects/Heimdall` +- `E:/Projects/Bifrost` + +Grounded source: + +- `E:/Projects/Heimdall/src/store/types.ts` +- `E:/Projects/Heimdall/src/store/schema.ts` +- `E:/Projects/Heimdall/src/store/in-memory.ts` +- `E:/Projects/Heimdall/src/store/postgres.ts` +- `E:/Projects/Heimdall/src/claims.ts` +- `E:/Projects/Heimdall/src/app.ts` +- `E:/Projects/Heimdall/src/verifier.ts` +- `E:/Projects/Heimdall/src/verse-witness.ts` +- `E:/Projects/Heimdall/src/verse-state.ts` +- `E:/Projects/Bifrost/docs/jurisdiction-map.md` +- `E:/Projects/Bifrost/src/Bifrost.Web/Features/Bridge/BridgeActionService.cs` + +### Current Mechanism + +Heimdall already gives grants a `status: "active" | "revoked"` and `listActiveGrants` filters only active grants. Sessions, however, are persisted only as `id/account/app/expires/claims/accessRevision`; there is no session status or revocation field. `issueAccessClaim` signs access and refresh JWTs, then upserts the session row. + +The critical fault line is `/sessions/refresh`: it verifies the signed refresh token and mints a new access/refresh pair using `sid` and `access_revision`, but does not load and validate the session row first. Managed credential resolution also lacks a deadbolt; it uses app-secret plus `accountId/provider` and returns or refreshes provider access tokens from linked identity custody. + +Heimdall already has audit events and Verse witness descriptors, but `verse-state.ts` currently advertises an empty command list. Bifrost consumes Heimdall claims/capability references, but currently checks shape, not live revocation state. + +### Authority Map + +Owner: Heimdall owns revocation decisions for sessions, grants, linked provider credentials, and capability references. + +Inputs: operator or app-authenticated command, target type, account id, app slug, session id, grant id, provider, reason code, actor/provenance, optional external receipt/correlation id. + +Outputs: updated authoritative row state, monotonic revocation record, audit event, redacted CultCache/CultMesh witness, optional HTTP revocation feed/introspection response, app-consumable deadbolt receipt. + +Invariant: + +- a revoked session cannot refresh; +- a revoked grant cannot contribute facts; +- a revoked managed credential cannot resolve or refresh; +- a revoked completion cannot redeem; +- a consumer with the revocation feed cannot accept a dead capability reference locally. + +Derived state: JWKS, health, Eve/CultMesh projections, Bifrost receipts, app-local revocation caches. + +Forbidden writers: Bifrost, Odin, Eve renderers, app dashboards, and browser callback pages must not mutate Heimdall revocation state except through explicit Heimdall command APIs. + +### Implementation Plan + +1. Add canonical revocation state. + Touch `src/store/schema.ts`, `src/store/types.ts`, `src/store/in-memory.ts`, and `src/store/postgres.ts`. + Add `sessions.status`, `revoked_at`, `revoked_by`, `revocation_reason`, and likely `revocation_id`. + Add completion revocation fields or model completions through session revocation. + Add credential status or a `credential_revocations` table. + Add store methods: `findSession`, `revokeSession`, `revokeAccountSessions`, `revokeGrant`, `revokeLinkedIdentityCredential`, `listRevocationsSince`. + +2. Make session row the refresh gate. + In `/v1/apps/{appSlug}/sessions/refresh`, after JWT signature validation, load the session by `sid`. + Reject if missing, wrong account/app, expired, revoked, or `session.accessRevision !== refreshClaim.access_revision`. + On refresh, update only active sessions. Do not let session upsert resurrect a revoked row. + +3. Add command endpoints. + Proposed endpoints: + - `POST /v1/admin/revocations/sessions` + - `POST /v1/admin/revocations/accounts/{accountId}/sessions` + - `POST /v1/admin/revocations/grants/{grantId}` + - `POST /v1/apps/{appSlug}/managed-credentials/revoke` + + Use a scoped operator/app command secret, not the generic app shared secret unless explicitly constrained. + +4. Add local consumer deadbolt. + Access JWTs are stateless, so consumers need a revocation feed/cache or introspection for sensitive actions. + Add redacted feed: `GET /v1/revocations/since?cursor=...` or equivalent CultMesh/CultCache witness. + Extend `src/verifier.ts` to accept an optional revocation index callback checking `sid`, `jti`, `account_id`, `app.slug`, and `access_revision`. + +5. Wire managed credential deadbolt. + `managed-credentials/resolve`, patron support sync, and entitlement refresh must reject revoked linked credentials before decrypting or refreshing provider tokens. + OAuth relink should create a new active credential generation and audit the replacement, not silently un-revoke the old one. + +6. Publish receipt/provenance. + Emit audit events: `session_revoked`, `account_sessions_revoked`, `grant_revoked`, `managed_credential_revoked`, `refresh_denied_revoked`, `managed_credential_denied_revoked`. + Extend `verse-witness.ts` with `heimdall.revocation.v0`. + Advertise revocation command shapes in `verse-state.ts`, but keep mutation behind Heimdall APIs. + Bifrost later consumes revocation feed for `HeimdallCapabilityReference`; it does not write revocation. + +### Tests And Smokes + +Store tests in `E:/Projects/Heimdall/tests/store.test.ts`: + +- revoked session cannot be updated back to active by claim issuance; +- revoked grants disappear from active grants; +- revoked credentials do not resolve. + +App tests in `E:/Projects/Heimdall/tests/app.test.ts`: + +- refresh works before revocation; +- refresh fails after revocation; +- account-wide revocation kills all sessions; +- completion redemption fails after session/completion revocation; +- managed credential resolve returns `410` or `403` after credential revocation. + +Verifier tests: + +- a valid signed JWT is rejected when `sid` or `jti` appears in local revocation index. + +Witness/runtime tests: + +- revocation schema is advertised; +- no tokens/secrets are exported; +- command boundary lists Heimdall-owned revocation commands. + +### Risks + +TTL alone is not a deadbolt. Already-issued stateless access claims cannot be instantly killed unless consumers adopt a revocation feed/cache or introspection path. + +The session upsert path can accidentally resurrect revoked sessions if not cut first. + +Bifrost currently checks capability-reference shape, not live validity. External crossings need either fresh Heimdall verification plus revocation index, or a Heimdall-issued capability receipt with revocation status. + +Upstream OAuth provider revocation may be best-effort. Local deadbolt must land first. + +## 4. Mimir Live Frame Proof + +Owner agent: Mimir, with Odin/Muninn and Fensalir dependencies. + +Grounded repos: + +- `E:/Projects/Mimir` +- `E:/Projects/Odin` +- `E:/Projects/Fensalir` + +Grounded source: + +- `E:/Projects/Odin/crates/muninn-daemon/src/main.rs` +- `E:/Projects/Odin/crates/muninn-move-tracker/src/lib.rs` +- `E:/Projects/Mimir/src/Mimir.Runtime/Synchronization/MimirMuninnMoveEvidence.cs` +- `E:/Projects/Mimir/src/Mimir.Runtime/Synchronization/MimirNativeReservoirRuntime.cs` +- `E:/Projects/Mimir/native/reservoir/src/lib.rs` +- `E:/Projects/Mimir/src/Mimir.Runtime/Synchronization/MimirMoveFusion.cs` +- `E:/Projects/Mimir/src/Mimir.Runtime/MimirRuntime.cs` + +### Named Proof Target + +Use one explicit frame id as the proof spine: + +`muninn:nightwing:move-evidence:` -> `mimir:starfire:move-evidence:` -> `mimir:starfire:move-pose:` -> Fensalir-visible frame/probe. + +Do not claim "pose solved" unless optical candidates and calibrated camera witnesses are present. + +### Current Mechanism + +Mimir already has most of the receiving path: + +- Muninn-shaped docs are mirrored by `MimirMuninnMoveEvidence.cs`. +- CultMesh evidence frames deserialize and admit latest shared-memory frame via `TryAdmitLatestCultMeshFrame`. +- Native `move_evidence` samples are pinned/admitted through `MimirNativeReservoirRuntime.cs`. +- Native reservoir has fixed `LocalcastMoveEvidenceSample` / descriptor ABI. +- Mimir fusion consumes native samples and emits `mimir.move_controller_pose.v1`. + +The first hard blocker is upstream: Muninn daemon currently serializes `marker_candidates` as an empty slice around `crates/muninn-daemon/src/main.rs:3220`. The marker extractor exists in `crates/muninn-move-tracker/src/lib.rs`, but is not wired into the daemon evidence frame. + +The second gap is presentation: Mimir runtime UI shows tracking buffer summaries, but the active Fensalir visual path is still audio spectrum `AquariumBufferFieldFrame`, not Move evidence/pose proof. + +### Authority Map + +Owner: Mimir owns the proof verdict. Muninn owns local observation and marker/controller extraction. Fensalir renders proof; it does not decide proof. + +Inputs: one Muninn evidence stream frame with at least one `muninn.move_marker_candidate.v1` and one `muninn.move_controller_state.v1`, plus calibration ids, tracking-space id, and camera calibration. + +Outputs: native reservoir `move_evidence` admission handle, Mimir fused pose frame, Fensalir-visible proof surface, and typed proof receipt with clock history. + +Invariant: every stage preserves source timestamp, producer publish time, Mimir arrival/admission time, fusion estimate time, and Fensalir presentation/probe time. No final proof may be created by a late repair loop. + +Forbidden writers: OBS, dashboard broker state, Muninn marker/controller streams, and Odin discovery must not decide pose/fusion/proof success. + +### Implementation Plan + +1. Wire real Muninn marker candidates. + Touch `E:/Projects/Odin/crates/muninn-daemon/src/main.rs` near `publish_move_evidence_stream_frame`. Bridge `muninn-move-tracker` output into `MuninnMoveMarkerCandidateWire`. The empty marker slice is the cut line. + +2. Add Mimir clock-history record. + Add typed record near `MimirMuninnMoveEvidence` or `MimirTrackingObservation`: source timestamps, `ObservedAt`, `PublishedAtNs`, ring handle sequence/time, Mimir read/admit time, reservoir edge/window, fusion estimated time, Fensalir frame time. Keep it typed MessagePack/CultCache-compatible, not load-bearing JSON. + +3. Extend Mimir admission metadata. + `TryAdmitLatestCultMeshFrame` should expose decoded `FrameId`, producer peer, published ns, sample min/max source time, sample counts by kind, reservoir handle, reservoir status edge/window, and Mimir read timestamp. + +4. Prove reservoir retention. + Assert the admitted handle points at the descriptor, count increments once, edge equals source max, arrival equals latest Mimir/Muninn arrival, and expiry behaves under the five-second window. + +5. Fuse only with calibrated optical evidence. + `MimirMoveFusion` already refuses uncalibrated optical evidence and marks orientation `orientation:imu-unresolved`. The live proof should require at least two calibrated marker rays for triangulated proof, or explicitly label single-ray fallback as not full fusion. + +6. Lower to Fensalir as a dev proof surface. + Add a Move proof frame/overlay separate from the audio spectrum field. It should show/probe frame id, pose confidence, latency, evidence kind, and reservoir edge. + +7. Publish observer-only Eve/dashboard surface. + Surface fields: selected frame id, producer, source/publish/arrival/admit/fusion/present timestamps, reservoir counts, pose confidence, failure reason. No commands and no private HTTP/WebSocket truth. + +### Tests And Smokes + +Existing useful smokes: + +```powershell +dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --muninn-move-cultmesh-stream-smoke +dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --move-native-reservoir-smoke +dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --move-fusion-smoke +``` + +Add: + +- Odin unit: evidence frame contains non-empty marker candidates when supplied a bright Y8 frame. +- Mimir unit: decoded frame preserves `FrameId`, `PublishedAtNs`, marker/controller timestamps, and sample ordering. +- Mimir native smoke: one named frame enters `move_evidence` and emits a clock-history receipt. +- Fusion smoke: no calibrated markers means no pose; two calibrated markers means triangulated pose with expected latency. +- Fensalir smoke: render/probe confirms the named frame id and proof overlay are visible in the GPU-presented frame. + +### Risks + +Current daemon path publishes no optical markers. A real fusion proof is blocked until Odin/Muninn wires marker candidates. + +Marker `ObservedAt` currently risks being conflated with source/arrival time. The proof must separate source, publish, read, admit, fusion, and present times. + +Single-ray fallback can look like success. The proof must label fallback explicitly. + +Fensalir currently visualizes audio spectrum fields, not Move proof frames. + +## 5. Ymir Unity Runtime Proof + +Owner agent: Ymir + Aetheria. + +Grounded repos: + +- `E:/Projects/Ymir` +- `E:/Projects/Aetheria` + +Grounded source: + +- `E:/Projects/Ymir/tools/verify-aetheria-ymir-cutover.ps1` +- `E:/Projects/Ymir/src/Ymir.Core/YmirSimulator.cs` +- `E:/Projects/Ymir/src/Ymir.Core/YmirSoAWorld.cs` +- `E:/Projects/Ymir/tests/Ymir.Tests/YmirSimulatorTests.cs` +- `E:/Projects/Aetheria/Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs` +- `E:/Projects/Aetheria/Assets/Scripts/Gameplay/Weapons/Projectile.cs` +- `E:/Projects/Aetheria/Assets/Scripts/Gameplay/HullCollider.cs` +- `E:/Projects/Aetheria/Assets/Scripts/Gameplay/ShieldManager.cs` +- `E:/Projects/Aetheria/Assets/Scripts/ServerShared/YmirPhysicsContracts.cs` +- `E:/Projects/Aetheria/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeYmirProjectilePhysics.cs` + +### Current Mechanism + +The static verifier now proves the obvious source-level cut: old gameplay collision authority paths are absent, projectile travel/direct-hit discovery routes through `AetheriaYmirPhysicsBridge.TryStepProjectile`, and projectile failure closes instead of falling back to Unity physics. + +Ymir core steps cloned SoA worlds, applies CultMath radial acceleration, integrates bodies, resolves circle contacts, and emits `ContactEvent`. DTO worlds are canonicalized by sorting bodies/fields by id before lowering to SoA. + +Aetheria Unity path builds a Ymir request from projectile plus daemon-render-native-view bodies, calls `YmirPhysicsQueries.Step(request)`, resolves contact body ids back to `HullCollider`, and applies visual hit effects from Ymir output. `HullCollider` and `ShieldManager` are presentation only and have no gameplay `OnCollisionEnter`. + +The remaining proof must watch runtime GameObjects, disabled colliders, callback traces, Ymir step/contact output, and visible projectile snapshots in Unity PlayMode. + +### Authority Map + +Owner: Ymir step/query semantics own projectile kinematics and contact discovery for this proof. + +Inputs: projectile pose/velocity/radius/mass/world time; daemon SoA entity ids, positions, velocities, radii, inverse mass; fixed or captured `deltaTime`; Unity presentation objects only as witnesses/effect targets. + +Outputs: next projectile position/velocity, ordered Ymir contacts, resolved target hull for visual effect, diagnostic trace containing frame/tick, Ymir request/result, Unity pose, collider/callback observations. + +Invariants: + +- disabling Unity projectile/target colliders must not prevent Ymir travel or hit detection; +- `OnCollisionEnter` observer may record a callback, but gameplay state cannot change because of it; +- replaying the same Ymir world/request sequence produces same contact order; +- mid-flight projectile transform matches Ymir snapshot within named tolerance; +- Ymir step failure fails closed, not fallback to Unity physics; +- manual scene setup, programmatic spawn, and replay share the same Ymir step/commit primitive. + +Forbidden writers: + +- production `Projectile.OnCollisionEnter`, `HullCollider.OnCollisionEnter`, `ShieldManager.OnCollisionEnter`; +- any `Physics.*` query deciding projectile hit truth; +- transform repair loops overriding Ymir snapshots; +- damage/stat mutation from Unity collision callbacks. + +### Implementation Plan + +1. Add PlayMode test assembly. + Likely file: `E:/Projects/Aetheria/Assets/Scripts/Tests/PlayMode/YmirUnityRuntimeProofTests.cs`. + Existing `Assets/Scripts/Tests/Tests.asmdef` is Editor-only; add a PlayMode-compatible asmdef or separate test assembly. + +2. Add test-only runtime harness. + Helpers inside the PlayMode test: + - `YmirRuntimeProbe`: per-frame projectile transform, expected Ymir position, callback counts, hit events, kill state. + - `CollisionObserverOnly`: `OnCollisionEnter` counter only, no gameplay mutation. + - minimal body-source fixture for `AetheriaYmirPhysicsBridge`. + + If `AetheriaYmirPhysicsBridge.TryBuildDaemonWorld` cannot accept a clean fixture because it is hard-coupled to `AetheriaDaemonObserver.LastRenderNativeView`, introduce a tiny body-source seam. Do not fake the Ymir step. + +3. Disabled-collider proof. + Spawn projectile and target presentation object, disable projectile/target colliders, feed bridge daemon SoA body for target, advance N frames. + Assert projectile position changes by Ymir, Ymir hit occurs, disabled colliders do not affect hit frame beyond tolerance, and removing Ymir bodies kills closed. + +4. Callback observer-only proof. + Attach `CollisionObserverOnly` with colliders enabled. Assert callback count may be zero or positive, but gameplay hit/damage/effect path is driven by Ymir result, and adding/removing observer does not change Ymir sequence. + +5. Deterministic replay/contact order proof. + At contract layer, extend Aetheria or Ymir tests with multi-contact shuffled input order. Prefer proving adapter/request canonicalization. + At PlayMode layer, capture runtime Ymir request sequence and replay twice through `YmirPhysicsQueries.Step`, asserting identical contact body id pairs and first-hit target. + +6. Mid-flight snapshot tolerance proof. + Sample projectile transform after each frame and compare to Ymir result. Use a named tolerance, such as `0.02 Unity units` or `max(0.02, speed * deltaTime * 0.01)`. Assert no later frame repairs an earlier mismatch. + +### Commands + +Static/current: + +```powershell +pwsh -File E:\Projects\Ymir\tools\verify-aetheria-ymir-cutover.ps1 -AetheriaRoot E:\Projects\Aetheria +dotnet test E:\Projects\Ymir\Ymir.slnx +dotnet run --project E:\Projects\Aetheria\Aetheria.State.Verify -- E:\Projects\Aetheria +``` + +Unity target shape: + +```powershell +Unity.exe -batchmode -projectPath E:\Projects\Aetheria -runTests -testPlatform PlayMode -testResults E:\Projects\Aetheria\scratch\ymir-playmode-results.xml -logFile E:\Projects\Aetheria\scratch\ymir-playmode.log +Unity.exe -batchmode -projectPath E:\Projects\Aetheria -runTests -testPlatform EditMode -testResults E:\Projects\Aetheria\scratch\ymir-editmode-results.xml -logFile E:\Projects\Aetheria\scratch\ymir-editmode.log +``` + +Unity version: `6000.4.2f1` from `E:/Projects/Aetheria/ProjectSettings/ProjectVersion.txt`. + +### Risks + +The bridge may be too tightly coupled to `AetheriaDaemonObserver.LastRenderNativeView`. If so, add a tiny body-source seam, not a fake parallel bridge. + +`Projectile.Update()` applies gravity/drag before Ymir step. The proof should account for that as input preparation and watch for split authority if Ymir is supposed to own those forces later. + +Unity collision callbacks are timing-unstable; tests should prove callbacks are irrelevant, not require them to fire. + +Contact order is only as deterministic as body request ordering. Canonicalize before Ymir step or sort contacts explicitly at the boundary. + +## 6. Weksa Language-Project State + +Owner agent: Weksa, with Zyphos/Druzkai and AquaSynth as consumers. + +Grounded repos: + +- `E:/Projects/weksa` +- `E:/Projects/Zyphos` +- `E:/Projects/AquaSynth` +- `E:/Projects/AetheriaLore` + +Grounded source: + +- `E:/Projects/weksa/schemas/language-project-state.md` +- `E:/Projects/weksa/state/map.yaml` +- `E:/Projects/weksa/settings/eusocial-interbeing/language-design.md` +- `E:/Projects/weksa/settings/eusocial-interbeing/airawa-first-contact-slice.md` +- `E:/Projects/weksa/scripts/weksa-daemon.mjs` + +### Current Mechanism + +Weksa currently has a human-facing schema receipt and setting pressure notes for Zyphos/Airawa. It has a standalone daemon script and existing CultMesh/CultCache provider store publication at `.weksa/provider-advertisement-store.cc`. + +Docs already claim `.weksa/projects/{projectId}.cc` and `weksa.language_project`, but the live daemon script does not publish a real `weksa.language_project` nested Verse or witness family. The first job is to make the advertised body real. + +The first proof should be a hand-authored Airawa/Druzkai seed, not a generic schema cathedral. Provisional forms like `atar`, `tazek`, `kazhar`, and `Druzkai` must be structurally provisional until phonology/morphology/usage validate them. + +### Authority Map + +Owner: `weksa.language_project_state.v0`. + +Inputs: `weksa.interlingua_packet.v0` meaning packets, authored language-project state, setting canon refs from Zyphos/Aetheria, provisional seed evidence from notes/chat/examples/model critique, typology/phonology references, consumer constraints from AquaSynth, Zyphos/Druzkai, Nibu/Aetheria, Odin/Eve. + +Outputs: + +- `.weksa/projects/{projectId}.cc` +- `weksa.language_project_state.v0` +- `weksa.language_project_catalog.v0` +- `weksa.language_project_trace.v0` +- optional compiled runtime bundle later +- Eve/CultUI language-project inspection surface +- render/phonetic/provenance traces consumed by Zyphos and AquaSynth + +Invariants: + +- ontology owns concept identity, splits, collapses, salience, worldview pressure; +- grammar owns required expression, syntax, agreement, evidentiality, clause shape; +- phonology owns phonemes, syllables, prosody, phonotactics, romanization; +- morphology owns roots, affixes, derivation, inflection, compounding; +- diachrony owns proto-forms, sound changes, drift, strata, irregularity; +- lexicon entries are receipts from those owners, not independent truth; +- corpus examples cite project state and provenance; +- canon/provisional status is field-level and evidence-backed; +- renderer never invents meaning; +- setting docs supply canon/source pressure, but do not own language state; +- AquaSynth owns articulation/audio realization, while Weksa owns phonological/phonetic intent. + +### State Shape + +Use an executable schema family shaped like: + +```yaml +schema: weksa.language_project_state.v0 +project_id: weksa.language.airawa.pre_imperial.seed.v0 +status: draft | provisional | reviewed | canonical | deprecated +metadata: {} +canon_policy: {} +provenance: {} +ontology: {} +phonology: {} +grammar: {} +morphology: {} +diachrony: {} +lexicon: {} +corpus: {} +validation: {} +trace: {} +``` + +Every project and entry needs status/confidence/provenance. A lexicon entry marked `canonical` without evidence must fail validation. + +### Implementation Plan + +1. Update the schema receipt. + Define `weksa.language_project_state.v0`, `weksa.language_project_catalog.v0`, and `weksa.language_project_trace.v0` in `schemas/language-project-state.md` and related schema docs. Include field-level `status`, `confidence`, `evidence_refs`, `canon_refs`, `reviewer`, `promotion_blockers`, and `supersedes`. + +2. Define the Airawa seed project. + Scope it to pre-imperial/non-imperial Airawa first-contact social mechanics, not all Airawa. Include Druzkai as related Face/name pressure, not language owner. + +3. Normalize provisional forms. + Convert `settings/eusocial-interbeing/airawa-first-contact-slice.md` into lexicon/morphology/grammar seed records with `status: provisional_seed`, stating what is known, unknown, and required before promotion. + +4. Add deterministic validation. + New tool candidate: `E:/Projects/weksa/tools/validate_language_project_state.mjs`. + Validate required sections, stable ids, referential integrity, status enums, provenance refs, lexicon roots pointing to morphology/phonology, corpus examples pointing to entries, and no canon entries lacking evidence. + +5. Add seed emitter. + New tool candidate: `E:/Projects/weksa/tools/emit_airawa_language_project_seed.mjs`. + Emit `.weksa/projects/airawa-pre-imperial-seed.cc` and `.weksa/projects/catalog.cc`. + +6. Publish through daemon. + Extend `scripts/weksa-daemon.mjs` document definitions with language project schemas. Extend provider advertisement with `weksa.language_project` nested Verse and witness family `.weksa/projects/{projectId}.cc`. Extend `cultMeshPublications` with catalog/project entries. + +7. Add operator/Eve visibility. + Show language-project count, reviewed/provisional counts, latest project id, schema drift, and validation status. Later add `weksa.eve.language_project_review.v0`. + +8. Add consumer examples. + Create a Zyphos naming brief consuming Airawa state without claiming canon output. Create an AquaSynth phonetic-intent handoff from one provisional form as phonetic evidence, not tract/audio truth. + +### Tests And Smokes + +Minimum: + +- `validate_language_project_state` passes Airawa seed; +- canonical lexicon entry without evidence fails; +- corpus example referencing missing lexeme/root fails; +- phonology segment not declared in inventory fails unless marked loan/foreign; +- daemon snapshot includes `weksa.language_project`; +- CultMesh store writes project/catalog documents when runtime is available; +- debug JSON disabled still leaves `.cc` authority; +- AquaSynth consumer smoke treats Weksa phonology as intent, not audio truth. + +### Dependencies And Risks + +Zyphos owns setting canon and ecological/political truth. Weksa owns language-project interpretation. Druzkai consumes language surfaces as Persona/context pressure; it does not own state. + +AquaSynth consumes Weksa phonetic/IPA/PanPhon-style intent, but owns realization embeddings, articulatory constraints, synth control, and audio. + +The live daemon docs and code currently disagree. Do not build another lane; make `weksa.language_project` real. + +CultMesh runtime availability may be fragile; keep first implementation to schema receipt, validator, seed emitter, and daemon advertisement update. + +Human IPA/PanPhon evidence can accidentally colonize alien phonology. Keep phonology and audio realization separate. + +Model critique remains evidence, not accepted state.