From 96f0962e46a62ebd01cc421b702c2bae99136c79 Mon Sep 17 00:00:00 2001 From: Toadaid Date: Sat, 21 Mar 2026 18:13:10 -0400 Subject: [PATCH 01/75] feat(runtime): add daemon-backed mirror sync status endpoint --- src/mirror-service/mirror_service.test.ts | 105 ++++++++++++++++++++++ src/mirror-service/mirror_service.ts | 8 ++ src/mirrordaemon/daemon_types.ts | 13 +++ src/mirrordaemon/index.ts | 8 +- src/mirrordaemon/runtime_state.ts | 28 ++++++ src/mirrordaemon/status_api.ts | 8 ++ 6 files changed, 169 insertions(+), 1 deletion(-) diff --git a/src/mirror-service/mirror_service.test.ts b/src/mirror-service/mirror_service.test.ts index b4be18fed2..1d86556371 100644 --- a/src/mirror-service/mirror_service.test.ts +++ b/src/mirror-service/mirror_service.test.ts @@ -1310,6 +1310,38 @@ describe("mirror service", () => { } }); + it("exposes an empty standalone sync status endpoint", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "sync-node-empty", + }); + + try { + const sync = (await requestJsonFromApp(service.app, "GET", "/mirror/sync")) as { + ok: boolean; + daemon_session_id: string; + peers_known: number; + peers: unknown[]; + }; + + expect(sync).toEqual({ + ok: true, + daemon_session_id: expect.any(String), + peers_known: 0, + peers: [], + }); + } finally { + await service.shutdown(); + } + }); + it("exposes canonical runtime state and debug endpoints", async () => { const loreDir = await createTempLoreDir(); await seedLoreCorpus(loreDir); @@ -1364,6 +1396,18 @@ describe("mirror service", () => { total: number; providers: Array<{ provider_id: string; selected: boolean }>; }; + const sync = (await requestJsonFromApp(service.app, "GET", "/mirror/sync")) as { + ok: boolean; + peers_known: number; + peers: Array<{ + peer_id: string; + base_url: string; + last_seen_at: string; + sync_status: string; + last_sync_at?: string; + last_error?: string; + }>; + }; expect(runtime.ok).toBe(true); expect(runtime.version.length).toBeGreaterThan(0); @@ -1387,6 +1431,67 @@ describe("mirror service", () => { expect(providers.total).toBe(1); expect(providers.providers[0]?.provider_id).toBe("primary"); expect(providers.providers[0]?.selected).toBe(true); + expect(sync.ok).toBe(true); + expect(sync.peers_known).toBe(0); + expect(sync.peers).toEqual([]); + } finally { + await service.shutdown(); + } + }); + + it("reflects announced peers on the standalone sync status endpoint", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "sync-node-populated", + baseUrl: "http://127.0.0.1:7001", + }); + + try { + await requestJsonFromApp(service.app, "POST", "/mirror-sync/announce", { + headers: { + "x-mirror-operator-token": "secret", + }, + body: { + peer_id: "peer-1", + base_url: "http://127.0.0.1:7999", + }, + }); + + const sync = (await requestJsonFromApp(service.app, "GET", "/mirror/sync")) as { + ok: boolean; + daemon_session_id: string; + peers_known: number; + peers: Array<{ + peer_id: string; + base_url: string; + last_seen_at: string; + sync_status: string; + last_sync_at?: string; + last_error?: string; + }>; + }; + + expect(sync.ok).toBe(true); + expect(sync.daemon_session_id.length).toBeGreaterThan(0); + expect(sync.peers_known).toBe(1); + expect(sync.peers).toEqual([ + { + peer_id: "peer-1", + base_url: "http://127.0.0.1:7999", + last_seen_at: expect.any(String), + sync_status: "idle", + last_sync_at: undefined, + last_error: undefined, + }, + ]); } finally { await service.shutdown(); } diff --git a/src/mirror-service/mirror_service.ts b/src/mirror-service/mirror_service.ts index f413fa20b5..1852c9b02e 100644 --- a/src/mirror-service/mirror_service.ts +++ b/src/mirror-service/mirror_service.ts @@ -29,6 +29,7 @@ import { getMirrordaemonHealthState, getMirrordaemonProvidersState, getMirrordaemonRuntimeState, + getMirrordaemonSyncState, type Mirrordaemon, } from "../mirrordaemon/index.js"; import { type MirrorServiceConfig } from "./config.js"; @@ -196,6 +197,13 @@ export async function startMirrorService( }), ); }); + app.get("/mirror/sync", (_req, res) => { + res.json( + getMirrordaemonSyncState(daemon, { + peers: syncManager.listPeers(), + }), + ); + }); app.get("/mirror/runtime/events", (req, res) => { res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); diff --git a/src/mirrordaemon/daemon_types.ts b/src/mirrordaemon/daemon_types.ts index 2d354e11aa..d0e35c3130 100644 --- a/src/mirrordaemon/daemon_types.ts +++ b/src/mirrordaemon/daemon_types.ts @@ -2,6 +2,7 @@ import type { MirrorDiagnosticEvent, MirrorObservabilityContext, } from "../mirror-observability/index.js"; +import type { MirrorSyncPeer } from "../mirror-sync/index.js"; export type MirrordaemonSurfaceName = | "cli" @@ -199,6 +200,18 @@ export type MirrordaemonProvidersSummary = { }>; }; +export type MirrordaemonSyncSummary = { + ok: true; + daemon_session_id: string; + peers_known: number; + peers: Array< + Pick< + MirrorSyncPeer, + "peer_id" | "base_url" | "last_seen_at" | "sync_status" | "last_sync_at" | "last_error" + > + >; +}; + export type MirrordaemonEventSubscription = { unsubscribe: () => void; }; diff --git a/src/mirrordaemon/index.ts b/src/mirrordaemon/index.ts index b8242c09b8..40cbea84ec 100644 --- a/src/mirrordaemon/index.ts +++ b/src/mirrordaemon/index.ts @@ -3,7 +3,11 @@ export { createMirrordaemon, type Mirrordaemon } from "./mirrordaemon.js"; export { createSessionRegistry, type MirrordaemonSessionRegistry } from "./session_registry.js"; export { createRuntimeEventStream } from "./event_stream.js"; export { getMirrordaemonRuntimeState, getMirrordaemonHealthState } from "./status_api.js"; -export { getMirrordaemonActionsState, getMirrordaemonProvidersState } from "./status_api.js"; +export { + getMirrordaemonActionsState, + getMirrordaemonProvidersState, + getMirrordaemonSyncState, +} from "./status_api.js"; export { getMirrordaemonDebugState } from "./debug_api.js"; export { buildActionsSummary, @@ -11,6 +15,7 @@ export { buildHealthSummary, buildDebugSnapshot, buildProvidersSummary, + buildSyncSummary, buildStatusPayload, } from "./runtime_state.js"; export type { @@ -27,6 +32,7 @@ export type { MirrordaemonRuntimeEvent, MirrordaemonRuntimeSummary, MirrordaemonSession, + MirrordaemonSyncSummary, MirrordaemonSurfaceName, TouchMirrordaemonSessionInput, } from "./daemon_types.js"; diff --git a/src/mirrordaemon/runtime_state.ts b/src/mirrordaemon/runtime_state.ts index afa63d96ac..e6a3603bd2 100644 --- a/src/mirrordaemon/runtime_state.ts +++ b/src/mirrordaemon/runtime_state.ts @@ -1,5 +1,6 @@ import type { MirrorActionRuntime } from "../mirror-actions/index.js"; import type { MirrorProviderPlane } from "../mirror-provider/index.js"; +import type { MirrorSyncPeer } from "../mirror-sync/index.js"; import { VERSION } from "../version.js"; import type { MirrordaemonActionsSummary, @@ -8,6 +9,7 @@ import type { MirrordaemonHealthSummary, MirrordaemonProvidersSummary, MirrordaemonRuntimeSummary, + MirrordaemonSyncSummary, } from "./daemon_types.js"; import type { Mirrordaemon } from "./mirrordaemon.js"; @@ -105,6 +107,32 @@ export function buildProvidersSummary( }; } +function buildSyncPeerSummary(peer: MirrorSyncPeer): MirrordaemonSyncSummary["peers"][number] { + return { + peer_id: peer.peer_id, + base_url: peer.base_url, + last_seen_at: peer.last_seen_at, + sync_status: peer.sync_status, + last_sync_at: peer.last_sync_at, + last_error: peer.last_error, + }; +} + +export function buildSyncSummary( + daemon: Mirrordaemon, + options: { peers?: MirrorSyncPeer[] } = {}, +): MirrordaemonSyncSummary { + const boot = daemon.getBootSnapshot(); + const peers = (options.peers ?? []).map(buildSyncPeerSummary); + + return { + ok: true, + daemon_session_id: boot.config.daemon_session_id, + peers_known: peers.length, + peers, + }; +} + export function buildRuntimeSummary( daemon: Mirrordaemon, overrides: RuntimeStateOverrides = {}, diff --git a/src/mirrordaemon/status_api.ts b/src/mirrordaemon/status_api.ts index ede2011b61..688e4a3a6a 100644 --- a/src/mirrordaemon/status_api.ts +++ b/src/mirrordaemon/status_api.ts @@ -4,6 +4,7 @@ import { buildHealthSummary, buildProvidersSummary, buildRuntimeSummary, + buildSyncSummary, } from "./runtime_state.js"; export function getMirrordaemonRuntimeState( @@ -33,3 +34,10 @@ export function getMirrordaemonProvidersState( ) { return buildProvidersSummary(daemon, params); } + +export function getMirrordaemonSyncState( + daemon: Mirrordaemon, + params: Parameters[1] = {}, +) { + return buildSyncSummary(daemon, params); +} From cb16b13c906ebbefb7a4e6e2b4cf3a1e9fb77096 Mon Sep 17 00:00:00 2001 From: Toadaid Date: Sat, 21 Mar 2026 18:31:48 -0400 Subject: [PATCH 02/75] test(runtime): add read-only sync ingress regression coverage --- src/mirror-service/mirror_service.test.ts | 130 ++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/mirror-service/mirror_service.test.ts b/src/mirror-service/mirror_service.test.ts index 1d86556371..da8f56cc27 100644 --- a/src/mirror-service/mirror_service.test.ts +++ b/src/mirror-service/mirror_service.test.ts @@ -1178,6 +1178,136 @@ describe("mirror service", () => { } }); + it("returns announced peers from the read-only sync peers ingress", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "service-node", + baseUrl: "http://127.0.0.1:7001", + }); + + try { + await requestJsonFromApp(service.app, "POST", "/mirror-sync/announce", { + headers: { + "x-mirror-operator-token": "secret", + }, + body: { + peer_id: "peer-1", + base_url: "http://127.0.0.1:7999", + }, + }); + + const peers = (await requestJsonFromApp(service.app, "GET", "/mirror-sync/peers")) as { + peers: Array<{ + peer_id: string; + base_url: string; + last_seen_at: string; + sync_status: string; + }>; + }; + + expect(peers).toEqual({ + peers: [ + { + peer_id: "peer-1", + base_url: "http://127.0.0.1:7999", + last_seen_at: expect.any(String), + sync_status: "idle", + }, + ], + }); + } finally { + await service.shutdown(); + } + }); + + it("returns the current sync updates snapshot from the read-only ingress", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "service-node", + baseUrl: "http://127.0.0.1:7001", + }); + + try { + const updates = (await requestJsonFromApp(service.app, "GET", "/mirror-sync/updates")) as { + node_id: string; + base_url: string | null; + canon: { + lore_dir: string; + index_path: string; + index_version: number; + files: Array<{ path: string }>; + }; + graph: { + version: string; + node_count: number; + edge_count: number; + }; + }; + + expect(updates.node_id).toBe("service-node"); + expect(updates.base_url).toBe("http://127.0.0.1:7001"); + expect(updates.canon.lore_dir).toBe(loreDir); + expect(updates.canon.index_path).toContain("_index/"); + expect(updates.canon.index_path.endsWith(".json")).toBe(true); + expect(typeof updates.canon.index_version).toBe("number"); + expect(updates.canon.index_version).toBeGreaterThan(0); + expect(updates.canon.files[0]?.path).toBe("TOBY_L1219_Rune3_PatienceVaultCancelled.md"); + expect(typeof updates.graph.version).toBe("string"); + expect(updates.graph.node_count).toBeGreaterThanOrEqual(0); + expect(updates.graph.edge_count).toBeGreaterThanOrEqual(0); + } finally { + await service.shutdown(); + } + }); + + it("returns requested file contents from the read-only sync updates ingress", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "service-node", + baseUrl: "http://127.0.0.1:7001", + }); + + try { + const updates = (await requestJsonFromApp( + service.app, + "GET", + "/mirror-sync/updates?include_content=1&paths=TOBY_L1219_Rune3_PatienceVaultCancelled.md", + )) as { + file_contents?: Record; + }; + + expect(updates.file_contents).toEqual({ + "TOBY_L1219_Rune3_PatienceVaultCancelled.md": expect.stringContaining( + "The Patience Vault was cancelled.", + ), + }); + } finally { + await service.shutdown(); + } + }); + it("preserves invalid sync announce responses and wrapper events", async () => { const loreDir = await createTempLoreDir(); await seedLoreCorpus(loreDir); From 534276c57137b4d22d37c0102533503143ab4be1 Mon Sep 17 00:00:00 2001 From: Toadaid Date: Sat, 21 Mar 2026 18:51:18 -0400 Subject: [PATCH 03/75] docs(architecture): add mirror split-readiness checklist --- .../mirror-monk-coder-boundary.md | 2 + ...irror-runtime-split-readiness-checklist.md | 184 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 docs/architecture/mirror-runtime-split-readiness-checklist.md diff --git a/docs/architecture/mirror-monk-coder-boundary.md b/docs/architecture/mirror-monk-coder-boundary.md index 3ad71dfa76..42cf0f8312 100644 --- a/docs/architecture/mirror-monk-coder-boundary.md +++ b/docs/architecture/mirror-monk-coder-boundary.md @@ -101,3 +101,5 @@ Use this boundary to guide: - user-surface design - repo split decisions - future utility and tool expansion + +For the ordered pre-split execution checklist, see [Mirror Runtime Split-Readiness Checklist](./mirror-runtime-split-readiness-checklist.md). diff --git a/docs/architecture/mirror-runtime-split-readiness-checklist.md b/docs/architecture/mirror-runtime-split-readiness-checklist.md new file mode 100644 index 0000000000..c3aa74eba6 --- /dev/null +++ b/docs/architecture/mirror-runtime-split-readiness-checklist.md @@ -0,0 +1,184 @@ +# Mirror Runtime Split-Readiness Checklist + +This checklist defines what should be true before Mirror Runtime is split out of the current repo. + +It is intentionally operational. + +- It is for sequencing small PRs. +- It is not a broad redesign plan. +- It should track current repo reality, not an idealized future architecture. + +## Purpose + +Use this checklist to decide whether a proposed PR improves split-readiness, preserves it, or widens scope without reducing split risk. + +The core rule remains: + +1. detach first +2. split second +3. expand third + +## How to use this checklist + +- Prefer the next smallest yellow or red item that can be fixed in one reviewable PR. +- Prefer additive read surfaces and focused regression tests before broad refactors. +- Re-score the touched section after each merged PR. +- Do not mark a section green if the canonical runtime path still depends on compatibility-only wrappers or duplicate execution planes. + +## Split-Readiness Scorecard + +### Green + +- The seam is explicit. +- The canonical Mirror path is clear. +- The behavior is covered by focused tests. +- A repo split would not require untangling hidden ownership in this area. + +### Yellow + +- The seam exists, but ownership is still partial, duplicated, or weakly tested. +- A small follow-up PR can improve the boundary without redesigning the runtime. + +### Red + +- Ownership is still mixed or misleading. +- Canonical and compatibility paths still compete. +- A split here would copy technical debt into a new repo boundary. + +## Checklist + +### 1. Runtime ownership seams + +Current score: `yellow` + +- [ ] Mirrordaemon is the canonical state owner for service, console, and CLI execution paths. +- [ ] Session creation and touch behavior are consistent across `/mirror/chat`, `/mirror/tools/*`, console API paths, and CLI execution. +- [ ] Runtime summaries are built from daemon-owned state rather than sidecar module state. +- [ ] New runtime features land on the daemon-backed path first, not on compatibility wrappers. + +Current reality: + +- Service runtime state is real and daemon-backed. +- Console and CLI parity improved materially, but should still be treated as a seam until one runtime truth is clearly universal. + +### 2. Service and operator surfaces + +Current score: `yellow` + +- [ ] Canonical read-only operator surfaces are present and stable. +- [ ] Health, runtime, debug, actions, providers, and sync surfaces are consistent in style and ownership. +- [ ] Operator-facing surfaces do not depend on legacy `src/runtime/**` wrappers. +- [ ] Additive runtime/status endpoints are covered by service-level tests. + +Current reality: + +- `/mirror/health`, `/mirror/status`, `/mirror/runtime`, `/mirror/runtime/debug`, `/mirror/actions`, `/mirror/providers`, and `/mirror/sync` are real. +- This area is close, but operator truth is still uneven in CLI status and verify-lore flows. + +### 3. Sync and runtime state visibility + +Current score: `yellow` + +- [ ] Sync peer state is available on a canonical read-only runtime surface. +- [ ] `/mirror-sync/peers` and `/mirror-sync/updates` have focused service-level regression coverage. +- [ ] Runtime event surfaces expose enough state to understand sync activity without reading internal modules. +- [ ] Sync state is summarized consistently between operator surfaces and runtime state. + +Current reality: + +- Sync read surfaces are real. +- `/mirror/sync` now exposes read-only peer state from the live registry. +- Sync is still a seam because execution state is not fully daemon-owned beyond the current registry and event stream summaries. + +### 4. CLI and service parity + +Current score: `yellow` + +- [ ] `mirror` CLI commands execute through the same canonical runtime plane used by service ingress. +- [ ] Read operations and mutable operations report the same runtime truth regardless of surface. +- [ ] Focused parity tests exist for status, sync, and key tool flows. +- [ ] Operator commands do not silently fall back to stale or compatibility-only logic. + +Current reality: + +- CLI coverage is much better than before, but this should not be treated as fully green until parity is explicit for all critical operator surfaces. + +### 5. Observability ownership + +Current score: `red` + +- [ ] Metrics are daemon-scoped or runtime-context-scoped. +- [ ] Diagnostics are daemon-scoped or runtime-context-scoped. +- [ ] Debug surfaces do not depend on process-global singleton state. +- [ ] Runtime event inspection and observability tell the same story about execution. + +Current reality: + +- Observability surfaces exist and are useful. +- Ownership is still process-global enough that this remains a pre-split blocker. + +### 6. Compatibility quarantine + +Current score: `red` + +- [ ] `src/runtime/server.ts`, `src/runtime/brain-chat.ts`, `src/runtime/health.ts`, and `src/cli/mirror-cli.ts` are either quarantined clearly or removed. +- [ ] Mirror-owned runtime modules do not read `OPENCLAW_*` env vars except in explicit compatibility files. +- [ ] Canonical operator docs and entrypoints point to Mirror-native paths first. +- [ ] Compatibility code is not the hidden owner of any required runtime behavior. + +Current reality: + +- Compatibility edges are still materially present at entrypoint and env-boundary level. +- This is still one of the clearest blockers to a clean split. + +### 7. Packaging and build boundary + +Current score: `red` + +- [ ] Mirror has a first-class package and build boundary inside the repo. +- [ ] Mirror artifacts can be built and tested without treating OpenClaw as the primary product identity. +- [ ] Release and packaging paths for Mirror are explicit enough to survive a repo split. + +Current reality: + +- Mirror runtime behavior is increasingly standalone. +- Package, bin, and release identity are still shared enough that splitting now would be premature. + +### 8. CI gates before split + +Current score: `red` + +- [ ] A dedicated Mirror runtime smoke lane exists. +- [ ] A boundary gate prevents new OpenClaw-specific env/config coupling inside Mirror-owned runtime modules. +- [ ] Split-critical runtime tests are visible without depending on the full repo matrix. + +Current reality: + +- Runtime tests exist. +- Dedicated split-readiness gates are still missing. + +## Current known gaps + +The next small, high-signal gaps remain: + +1. Make any remaining console and CLI execution seams explicitly daemon-backed where they are still partial. +2. Continue enriching daemon-owned runtime events where operator/runtime inspection is still thin. +3. Move observability ownership under daemon/runtime control. +4. Remove OpenClaw env usage from canonical Mirror runtime modules. +5. Fix operator truth for `mirror status`. +6. Fix operator truth for `mirror verify-lore`. +7. Quarantine or retire compatibility-only runtime wrappers. +8. Create a Mirror-native package/build boundary. +9. Add dedicated Mirror runtime CI gates. + +## Do Not Split Before + +Do not split before all of these are true: + +- Mirrordaemon is the clear runtime owner across the canonical execution surfaces. +- Observability ownership is no longer effectively process-global. +- Compatibility-only wrappers are quarantined or removed from required runtime paths. +- Mirror package/build identity is explicit enough to survive extraction. +- CI can prove the standalone Mirror runtime path without relying on broad repo-level signal. + +If one of those remains red, keep building in this repo. From 46d207afc1f0e8ca2d050dc721e35e3547b291a3 Mon Sep 17 00:00:00 2001 From: Toadaid Date: Sat, 21 Mar 2026 19:06:53 -0400 Subject: [PATCH 04/75] test(runtime): lock canonical mirror env boundary --- src/mirror-runtime/mirror_chat_engine.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/mirror-runtime/mirror_chat_engine.test.ts b/src/mirror-runtime/mirror_chat_engine.test.ts index d24e79cc7a..d634c64585 100644 --- a/src/mirror-runtime/mirror_chat_engine.test.ts +++ b/src/mirror-runtime/mirror_chat_engine.test.ts @@ -21,6 +21,7 @@ const tempDirs: string[] = []; const originalMirrorLoreDir = process.env.MIRROR_LORE_DIR; const originalMirrorMemoryDbPath = process.env.MIRROR_MEMORY_DB_PATH; const originalLogLevel = process.env.MIRROR_LOG_LEVEL; +const originalOpenClawLogLevel = process.env.OPENCLAW_LOG_LEVEL; afterEach(async () => { if (originalMirrorLoreDir === undefined) { @@ -38,6 +39,11 @@ afterEach(async () => { } else { process.env.MIRROR_LOG_LEVEL = originalLogLevel; } + if (originalOpenClawLogLevel === undefined) { + delete process.env.OPENCLAW_LOG_LEVEL; + } else { + process.env.OPENCLAW_LOG_LEVEL = originalOpenClawLogLevel; + } closeMirrorMemoryDb(); await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); @@ -161,6 +167,23 @@ describe("mirror chat engine", () => { expect(prompt).toContain("Traveler note: the Patience Vault still exists."); }); + it("ignores OPENCLAW_LOG_LEVEL in the canonical Mirror debug path", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_LORE_DIR = loreDir; + delete process.env.MIRROR_LOG_LEVEL; + process.env.OPENCLAW_LOG_LEVEL = "debug"; + + const prepared = await prepareMirrorChatRequest({ + model: "test-model", + messages: [{ role: "user", content: "patience vault" }], + }); + + expect(prepared.modelRequest.messages[0]?.content).toContain("Mirror canon context:"); + expect(prepared.modelRequest.messages[0]?.content).not.toContain("[RETRIEVAL_DIAGNOSTICS]"); + expect(prepared.diagnostics).toBeUndefined(); + }); + it("executes through the provider boundary without OpenClaw-specific request types", async () => { const loreDir = await createTempLoreDir(); await seedLoreCorpus(loreDir); From b155560620b7530ea0eaa89eb09c3085cb59915e Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sat, 21 Mar 2026 22:31:32 -0400 Subject: [PATCH 05/75] test(ci): restore fetch safely in shared vitest setup ## Summary Tighten shared Vitest fetch cleanup so cross-test fetch leakage is cleaned up without breaking intentional suite-level fetch setup. ## What changed Updated: - `test/setup.ts` The shared test bootstrap now: - snapshots `globalThis.fetch` at the start of each test - restores `globalThis.fetch` to that per-test starting value after each test ## Why this patch A broad macOS CI failure pattern pointed to shared test-state leakage. One proven leak vector was manual `globalThis.fetch` mutation across suites. The first bootstrap fix restored fetch to the process-original value, but that was too blunt for suites that intentionally install a fetch mock in `beforeAll`. This refined version preserves suite-level setup while still cleaning up per-test leakage. ## Scope Changed only: - `test/setup.ts` Did not change: - product/runtime code - Mirror status code - suite-local logic - workflows - packaging ## Behavior No product behavior changed. This is a shared test-bootstrap isolation fix only. ## Validation Passed: - `pnpm vitest run src/cli/program.nodes-media.test.ts` - `pnpm vitest run src/telegram/send.test.ts src/telegram/fetch.test.ts` Result: - `program.nodes-media` passed (9 tests) - `telegram/send` + `telegram/fetch` passed (62 tests) ## Notes This patch is intentionally narrow: - it fixes a proven shared leak point - it preserves intentional suite-scoped fetch mocks - it avoids broad test harness refactors Other possible shared isolation seams may still exist, but they are out of scope for this patch. --- test/setup.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/setup.ts b/test/setup.ts index 4e008ff188..9a71be5226 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -34,6 +34,8 @@ const [{ installProcessWarningFilter }, { setActivePluginRegistry }, { createTes installProcessWarningFilter(); +let fetchAtTestStart: typeof globalThis.fetch; + const pickSendFn = (id: ChannelId, deps?: OutboundSendDeps) => { switch (id) { case "discord": @@ -178,10 +180,17 @@ const createDefaultRegistry = () => const DEFAULT_PLUGIN_REGISTRY = createDefaultRegistry(); beforeEach(() => { + fetchAtTestStart = globalThis.fetch; setActivePluginRegistry(DEFAULT_PLUGIN_REGISTRY); }); afterEach(() => { + if (fetchAtTestStart) { + globalThis.fetch = fetchAtTestStart; + } else { + delete (globalThis as { fetch?: typeof fetch }).fetch; + } + // Guard against leaked fake timers across test files/workers. if (vi.isFakeTimers()) { vi.useRealTimers(); From 815165045fb2baaa2d0749023a1da00217ee2f37 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sat, 21 Mar 2026 23:12:49 -0400 Subject: [PATCH 06/75] test(runtime): add operator-truth coverage for mirror verify-lore ## Summary Add focused operator-truth coverage for `mirror verify-lore`. This locks down what the command should report from canonical Mirror-owned inputs without broadening into a CLI refactor. ## What changed Added focused tests around `mirror verify-lore` to verify that: - Mirror-native env/config inputs are used - reported output is grounded in actual lore path/runtime truth - operator-facing behavior stays aligned with canonical Mirror semantics ## Scope Changed only: - `src/mirror-cli/mirror_cli.test.ts` Did not change: - sync code - daemon/service routing - workflows - packaging - docs - unrelated CLI surfaces - broad config plumbing ## Behavior No broad behavior change. This is a focused test patch to lock down operator-truth expectations for `mirror verify-lore`. ## Notes This continues the operator-truth seam work after the `mirror status` cleanup, and helps clarify whether a later narrow correctness patch is needed for `MIRROR_LORE_DIR` defaults. --- src/mirror-cli/mirror_cli.test.ts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/mirror-cli/mirror_cli.test.ts b/src/mirror-cli/mirror_cli.test.ts index 755ff91878..b56c83c449 100644 --- a/src/mirror-cli/mirror_cli.test.ts +++ b/src/mirror-cli/mirror_cli.test.ts @@ -590,13 +590,13 @@ describe("mirror cli", () => { const statusOutput = JSON.parse(await runMirrorCli(["mirror", "status", "--json"])) as { ok: boolean; command: string; - status: { runtime: object; service: object; observability: object }; + status: { runtime: object; service: object; sync: object }; }; expect(statusOutput.ok).toBe(true); expect(statusOutput.command).toBe("status"); expect(typeof statusOutput.status.runtime).toBe("object"); expect(typeof statusOutput.status.service).toBe("object"); - expect(typeof statusOutput.status.observability).toBe("object"); + expect(typeof statusOutput.status.sync).toBe("object"); const verifyOutput = JSON.parse( await runMirrorCli([ @@ -1056,15 +1056,8 @@ describe("mirror cli", () => { command: string; status: { runtime: { node_id: string; sessions: { total: number; open: number } }; - sync: { node_id: string }; - observability: { - metrics: { - counters: { - tool_executions: number; - }; - }; - diagnostics_events: number; - }; + sync: { node_id: string; peers_known: number }; + service: { lore_dir: string; operator_auth_configured: boolean }; }; }; @@ -1074,16 +1067,17 @@ describe("mirror cli", () => { runtimeHost.daemon.getBootSnapshot().config.node_id, ); expect(output.status.sync.node_id).toBe(runtimeHost.daemon.getBootSnapshot().config.node_id); + expect(output.status.sync.peers_known).toBe(runtimeHost.syncManager.listPeers().length); + expect(output.status.service.lore_dir).toBe( + runtimeHost.daemon.getBootSnapshot().config.lore_dir, + ); + expect(output.status.service.operator_auth_configured).toBe( + runtimeHost.daemon.getBootSnapshot().config.operator_auth_configured, + ); expect(output.status.runtime.sessions.total).toBe(runtimeHost.daemon.listSessions().length); expect(output.status.runtime.sessions.open).toBe( runtimeHost.daemon.listSessions().filter((session) => session.status === "open").length, ); - expect(output.status.observability.metrics.counters.tool_executions).toBe( - runtimeHost.daemon.getObservability().getMetrics().counters.tool_executions, - ); - expect(output.status.observability.diagnostics_events).toBe( - runtimeHost.daemon.getObservability().getDiagnostics().events.length, - ); } finally { await runtimeHost.shutdown(); } From c3380baef761d3748a09e969917ee4462a42cf15 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 00:26:15 -0400 Subject: [PATCH 07/75] test(runtime): add daemon/operator parity coverage for mirror status and mirror sync surfaces ## Summary Add focused parity coverage for `mirror status` against daemon-backed runtime and sync truth. This patch verifies that the operator-facing status view stays aligned with the canonical daemon-backed runtime/sync surface, and applies the narrow status-shape fix required by that contract. ## What changed Added focused CLI/status parity coverage so that: - `mirror status` remains daemon-backed after a sync announce - the CLI-reported sync summary matches the live sync registry count - non-runtime fields are excluded from the status surface Also tightened the status payload/formatter so the status surface remains limited to canonical runtime truth only. ## Scope Changed: - `src/mirror-cli/mirror_cli.test.ts` - `src/mirror/status/status.ts` - `src/mirror/status/format.ts` - `src/mirror/status/tests/status.test.ts` Did not change: - daemon/service runtime code - sync execution code - docs - workflows - packaging - unrelated CLI commands ## Behavior Behavior changed slightly but intentionally. `mirror status` no longer emits: - `ts` - `cwd` - `observability` in either the status payload or human output. Everything else remains daemon-backed and unchanged. ## Tests Passed: - `pnpm vitest run src/mirror/status/tests/status.test.ts` - `pnpm vitest run src/mirror-cli/mirror_cli.test.ts` ## Notes This keeps `mirror status` aligned with canonical runtime truth while leaving peer-level sync detail intentionally on `/mirror/sync`, not in the CLI summary surface. --- src/mirror-cli/mirror_cli.test.ts | 61 ++++++++++++++++++++++++++ src/mirror/status/format.ts | 6 --- src/mirror/status/status.ts | 20 +-------- src/mirror/status/tests/status.test.ts | 17 +++---- 4 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/mirror-cli/mirror_cli.test.ts b/src/mirror-cli/mirror_cli.test.ts index b56c83c449..aa2cb6b8a3 100644 --- a/src/mirror-cli/mirror_cli.test.ts +++ b/src/mirror-cli/mirror_cli.test.ts @@ -1083,6 +1083,67 @@ describe("mirror cli", () => { } }); + it("keeps mirror status limited to canonical runtime truth after sync announce", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_LORE_DIR = loreDir; + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + + const runtimeHost = await createMirrorRuntimeHost({ loreDir }); + + try { + await runMirrorCli( + [ + "mirror", + "sync", + "announce", + "--peer-id", + "peer-alpha", + "--base-url", + "https://peer.example.test", + ], + { runtimeHost }, + ); + + const statusJsonText = await runMirrorCli(["mirror", "status", "--json"], { runtimeHost }); + const statusJson = JSON.parse(statusJsonText) as { + ok: boolean; + command: string; + status: { + runtime: { node_id: string }; + service: { lore_dir: string }; + sync: { node_id: string; peers_known: number }; + ts?: string; + cwd?: string; + observability?: unknown; + }; + }; + + expect(statusJson.ok).toBe(true); + expect(statusJson.command).toBe("status"); + expect(statusJson.status.sync.peers_known).toBe(runtimeHost.syncManager.listPeers().length); + expect(statusJson.status.sync.peers_known).toBe(1); + expect(statusJson.status.sync.node_id).toBe( + runtimeHost.daemon.getBootSnapshot().config.node_id, + ); + expect(statusJson.status.service.lore_dir).toBe( + runtimeHost.daemon.getBootSnapshot().config.lore_dir, + ); + expect(statusJson.status).not.toHaveProperty("ts"); + expect(statusJson.status).not.toHaveProperty("cwd"); + expect(statusJson.status).not.toHaveProperty("observability"); + + const human = await runMirrorCli(["mirror", "status"], { runtimeHost }); + expect(human).toContain("sync:"); + expect(human).toContain("- peersKnown: 1"); + expect(human).not.toContain("ts:"); + expect(human).not.toContain("cwd:"); + expect(human).not.toContain("observability:"); + } finally { + await runtimeHost.shutdown(); + } + }); + it("returns stable JSON shapes for monk commands", async () => { process.env.MIRROR_USER_WORKSPACE_DIR = await createTempWorkspaceUsersRoot(); process.env.MIRROR_USER_ID = "alice"; diff --git a/src/mirror/status/format.ts b/src/mirror/status/format.ts index 6bd78f6579..279f6aba20 100644 --- a/src/mirror/status/format.ts +++ b/src/mirror/status/format.ts @@ -7,8 +7,6 @@ function yesNo(value: boolean): string { export function formatMirrorStatusHuman(status: MirrorStatus): string { const lines = [ "🪞 Mirror Runtime", - `ts: ${status.ts}`, - `cwd: ${status.cwd}`, "runtime:", `- nodeId: ${status.runtime.node_id}`, `- startedAt: ${status.runtime.runtime_started_at}`, @@ -40,10 +38,6 @@ export function formatMirrorStatusHuman(status: MirrorStatus): string { `- nodeId: ${status.sync.node_id}`, `- baseUrl: ${status.sync.base_url ?? "-"}`, `- peersKnown: ${status.sync.peers_known}`, - "observability:", - `- diagnosticsEvents: ${status.observability.diagnostics_events}`, - `- chatRequests: ${status.observability.metrics.counters.chat_requests}`, - `- toolExecutions: ${status.observability.metrics.counters.tool_executions}`, "", ]; diff --git a/src/mirror/status/status.ts b/src/mirror/status/status.ts index 0d5ecbcbd0..ce34e1414c 100644 --- a/src/mirror/status/status.ts +++ b/src/mirror/status/status.ts @@ -1,4 +1,3 @@ -import type { MirrorMetricsSnapshot } from "../../mirror-observability/index.js"; import type { MirrorRuntimeHost } from "../../mirror-service/index.js"; import { getMirrordaemonHealthState, @@ -6,8 +5,6 @@ import { } from "../../mirrordaemon/index.js"; export type MirrorStatus = { - ts: string; - cwd: string; runtime: ReturnType; service: { lore_dir: string; @@ -45,25 +42,16 @@ export type MirrorStatus = { base_url: string | null; peers_known: number; }; - observability: { - metrics: MirrorMetricsSnapshot; - diagnostics_events: number; - }; }; export type GetMirrorStatusOptions = { runtimeHost: MirrorRuntimeHost; - cwd?: string; - now?: Date; }; export async function getMirrorStatus(opts: GetMirrorStatusOptions): Promise { - const now = opts.now ?? new Date(); - const cwd = opts.cwd ?? process.cwd(); const daemon = opts.runtimeHost.daemon; const boot = daemon.getBootSnapshot(); - const observability = daemon.getObservability(); - const metrics = observability.getMetrics(); + const metrics = daemon.getObservability().getMetrics(); const peersKnown = metrics.gauges.peers_known || opts.runtimeHost.syncManager.listPeers().length; const baseUrl = opts.runtimeHost.syncManager.getLocalBaseUrl(); const runtime = getMirrordaemonRuntimeState(daemon, { @@ -77,8 +65,6 @@ export async function getMirrorStatus(opts: GetMirrorStatusOptions): Promise { describe("mirror status", () => { it("returns daemon-backed runtime and lore truth", async () => { - const dir = await createTempDir(); const loreDir = await createTempDir(); const runtimeHost = await createMirrorRuntimeHost({ loreDir, @@ -33,11 +32,8 @@ describe("mirror status", () => { try { const status = await getMirrorStatus({ runtimeHost, - cwd: dir, - now: new Date("2026-03-05T00:00:00.000Z"), }); - expect(status.ts).toBe("2026-03-05T00:00:00.000Z"); expect(status.runtime.node_id).toBe("status-node"); expect(status.runtime.sessions.total).toBe(0); expect(status.service.lore_dir).toBe(path.resolve(loreDir)); @@ -47,7 +43,6 @@ describe("mirror status", () => { expect(status.lore.ready).toBe(true); expect(status.workspace.ready).toBe(true); expect(status.sync.node_id).toBe("status-node"); - expect(status.observability.metrics.counters.chat_requests).toBe(0); expect(JSON.stringify(status)).not.toContain("travelerName"); } finally { await runtimeHost.shutdown(); @@ -55,7 +50,6 @@ describe("mirror status", () => { }); it("reflects current daemon metrics and sessions", async () => { - const dir = await createTempDir(); const loreDir = await createTempDir(); const runtimeHost = await createMirrorRuntimeHost({ loreDir, @@ -73,25 +67,28 @@ describe("mirror status", () => { const status = await getMirrorStatus({ runtimeHost, - cwd: dir, }); expect(status.runtime.sessions.total).toBe(1); expect(status.runtime.sessions.open).toBe(1); - expect(status.observability.metrics.counters.chat_requests).toBe(1); expect(status.provider.providers[0]?.provider_id).toBe("primary"); const json = JSON.stringify(status); const parsed = JSON.parse(json) as Record; expect(parsed).toHaveProperty("runtime"); expect(parsed).toHaveProperty("service"); - expect(parsed).toHaveProperty("observability"); + expect(parsed).toHaveProperty("sync"); + expect(parsed).not.toHaveProperty("ts"); + expect(parsed).not.toHaveProperty("cwd"); + expect(parsed).not.toHaveProperty("observability"); const human = formatMirrorStatusHuman(status); expect(human).toContain("🪞 Mirror Runtime"); expect(human).toContain("runtime:"); expect(human).toContain("service:"); - expect(human).toContain("observability:"); + expect(human).toContain("sync:"); + expect(human).not.toContain("cwd:"); + expect(human).not.toContain("observability:"); } finally { await runtimeHost.shutdown(); } From a3708a3b1b2fd66f740257287fa6a17e49b23612 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 00:51:32 -0400 Subject: [PATCH 08/75] fix(compat): align compatibility mirror verify-lore defaults with MIRROR_LORE_DIR ## Summary Align the compatibility `mirror verify-lore` wrapper with the canonical Mirror lore default. This removes stale hardcoded compatibility defaults so the wrapper now falls through to canonical lore resolution, where `MIRROR_LORE_DIR` is the effective default source of truth. ## What changed Updated: - `src/mirror/telemetry_tail/cli.ts` - `src/mirror/telemetry_tail/tests/cli_wiring.test.ts` The compatibility wrapper no longer bakes in stale default values such as: - `lore/manifest.json` - `lore/canonical` at command registration time. Instead, it now defers to the canonical resolution path in `runVerifyLoreCli`. Also added focused wiring coverage to verify the compatibility command still exposes: - `--manifest` - `--dir` without preset default values. ## Scope Changed only: - `src/mirror/telemetry_tail/cli.ts` - `src/mirror/telemetry_tail/tests/cli_wiring.test.ts` Did not change: - canonical `mirror verify-lore` flow - daemon/service code - sync code - docs - workflows - packaging - unrelated CLI commands ## Behavior Behavior changed slightly but intentionally, compatibility-only. The compatibility wrapper no longer advertises or injects stale hardcoded lore defaults. The canonical Mirror verify-lore path remains unchanged. ## Tests Passed: - `pnpm vitest run src/mirror/telemetry_tail/tests/cli_wiring.test.ts` Result: - 1 test passed ## Notes This removes a remaining compatibility-era mismatch now that the canonical Mirror CLI path already resolves lore through `MIRROR_LORE_DIR`. A natural follow-up is a docs-only refresh of the split-readiness checklist so it matches current code reality. --- src/mirror/telemetry_tail/cli.ts | 4 ++-- src/mirror/telemetry_tail/tests/cli_wiring.test.ts | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/mirror/telemetry_tail/cli.ts b/src/mirror/telemetry_tail/cli.ts index fe0b2af19e..165f51f87e 100644 --- a/src/mirror/telemetry_tail/cli.ts +++ b/src/mirror/telemetry_tail/cli.ts @@ -402,8 +402,8 @@ export function registerMirrorTelemetryCli(program: Command): void { mirror .command("verify-lore") .description("Compatibility wrapper for mirror verify-lore") - .option("--manifest ", "Lore manifest path", "lore/manifest.json") - .option("--dir ", "Canonical lore directory", "lore/canonical") + .option("--manifest ", "Lore manifest path (default: /manifest.json)") + .option("--dir ", "Canonical lore directory (default: MIRROR_LORE_DIR or ./lore-scrolls)") .option("--json", "Output machine-readable JSON", false) .action(async (opts: { manifest?: string; dir?: string; json?: boolean }) => { await runMirrorVerifyLoreCli({ diff --git a/src/mirror/telemetry_tail/tests/cli_wiring.test.ts b/src/mirror/telemetry_tail/tests/cli_wiring.test.ts index 8db825e491..b6cd02231d 100644 --- a/src/mirror/telemetry_tail/tests/cli_wiring.test.ts +++ b/src/mirror/telemetry_tail/tests/cli_wiring.test.ts @@ -53,26 +53,34 @@ describe("mirror cli wiring", () => { const doctor = getSubcommand(mirror, "doctor"); const status = getSubcommand(mirror, "status"); const passport = getSubcommand(mirror, "passport"); + const verifyLore = getSubcommand(mirror, "verify-lore"); const tail = getSubcommand(telemetry, "tail"); expect(doctor).toBeDefined(); expect(status).toBeDefined(); expect(passport).toBeDefined(); + expect(verifyLore).toBeDefined(); expect(tail).toBeDefined(); - if (!doctor || !status || !passport || !tail) { - throw new Error("expected mirror doctor/status/tail commands to be registered"); + if (!doctor || !status || !passport || !verifyLore || !tail) { + throw new Error("expected mirror doctor/status/verify-lore/tail commands to be registered"); } const doctorOptions = getLongOptionFlags(doctor); const statusOptions = getLongOptionFlags(status); + const verifyLoreOptions = getLongOptionFlags(verifyLore); const tailOptions = getLongOptionFlags(tail); expect(doctorOptions.has("--json")).toBe(true); expect(statusOptions.has("--json")).toBe(true); + expect(verifyLoreOptions.has("--manifest")).toBe(true); + expect(verifyLoreOptions.has("--dir")).toBe(true); expect(tailOptions.has("--json")).toBe(true); expect(tailOptions.has("--limit")).toBe(true); + expect(verifyLore.opts<{ manifest?: string; dir?: string }>().manifest).toBeUndefined(); + expect(verifyLore.opts<{ manifest?: string; dir?: string }>().dir).toBeUndefined(); expect(mirror.description()).toContain("compatibility"); expect(doctor.description()).toContain("Compatibility-only"); expect(status.description()).toContain("Compatibility wrapper"); + expect(verifyLore.description()).toContain("Compatibility wrapper"); expect(passport.description()).toContain("Compatibility-only"); expect(tail.description()).toContain("Compatibility-only"); }); From 157ab3b61a684f1682fa38f671cb1039daaa8ab9 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 12:45:10 -0400 Subject: [PATCH 09/75] docs(architecture): refresh split-readiness checklist after status/sync/verify-lore work ## Summary Refresh the split-readiness checklist so it matches current code reality after the landed status, `/mirror/sync`, parity, verify-lore, and env-boundary work. ## What changed Updated: - `docs/architecture/mirror-runtime-split-readiness-checklist.md` The checklist now: - marks landed status/sync/verify-lore/env-boundary improvements as done or materially improved - removes stale statements that `mirror status` and `verify-lore` are still open canonical seams - reorders the remaining gaps around: - compatibility wrappers - observability ownership - packaging/build boundary - dedicated CI gates - separates macOS shard-order instability into a CI lane instead of leaving it implied as a product seam ## Scope Changed only: - `docs/architecture/mirror-runtime-split-readiness-checklist.md` Did not change: - runtime code - tests - workflows - packaging - CI behavior ## Behavior No behavior changed. This is a docs-only refresh. ## Notes Top remaining seams after the refresh: 1. quarantine or retire compatibility-only runtime wrappers and legacy entrypoints 2. continue enriching daemon-owned runtime events where operator/runtime inspection is still thin 3. move observability ownership under daemon/runtime control --- ...irror-runtime-split-readiness-checklist.md | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/architecture/mirror-runtime-split-readiness-checklist.md b/docs/architecture/mirror-runtime-split-readiness-checklist.md index c3aa74eba6..364e49ca1d 100644 --- a/docs/architecture/mirror-runtime-split-readiness-checklist.md +++ b/docs/architecture/mirror-runtime-split-readiness-checklist.md @@ -59,35 +59,38 @@ Current score: `yellow` Current reality: - Service runtime state is real and daemon-backed. -- Console and CLI parity improved materially, but should still be treated as a seam until one runtime truth is clearly universal. +- CLI parity improved materially for status and sync summary flows. +- Console and some compatibility entrypoints should still be treated as a seam until one runtime truth is clearly universal. ### 2. Service and operator surfaces Current score: `yellow` -- [ ] Canonical read-only operator surfaces are present and stable. -- [ ] Health, runtime, debug, actions, providers, and sync surfaces are consistent in style and ownership. +- [x] Canonical read-only operator surfaces are present and stable. +- [x] Health, runtime, debug, actions, providers, and sync surfaces are consistent in style and ownership. - [ ] Operator-facing surfaces do not depend on legacy `src/runtime/**` wrappers. -- [ ] Additive runtime/status endpoints are covered by service-level tests. +- [x] Additive runtime/status endpoints are covered by service-level tests. Current reality: - `/mirror/health`, `/mirror/status`, `/mirror/runtime`, `/mirror/runtime/debug`, `/mirror/actions`, `/mirror/providers`, and `/mirror/sync` are real. -- This area is close, but operator truth is still uneven in CLI status and verify-lore flows. +- `mirror status` is daemon-backed and limited to runtime truth only on the canonical Mirror path. +- This area is materially improved, but should remain yellow until compatibility-only wrappers stop competing with the canonical operator path. ### 3. Sync and runtime state visibility Current score: `yellow` -- [ ] Sync peer state is available on a canonical read-only runtime surface. -- [ ] `/mirror-sync/peers` and `/mirror-sync/updates` have focused service-level regression coverage. +- [x] Sync peer state is available on a canonical read-only runtime surface. +- [x] `/mirror-sync/peers` and `/mirror-sync/updates` have focused service-level regression coverage. - [ ] Runtime event surfaces expose enough state to understand sync activity without reading internal modules. -- [ ] Sync state is summarized consistently between operator surfaces and runtime state. +- [x] Sync state is summarized consistently between operator surfaces and runtime state. Current reality: - Sync read surfaces are real. - `/mirror/sync` now exposes read-only peer state from the live registry. +- CLI status parity now covers the daemon-backed sync summary. - Sync is still a seam because execution state is not fully daemon-owned beyond the current registry and event stream summaries. ### 4. CLI and service parity @@ -96,12 +99,14 @@ Current score: `yellow` - [ ] `mirror` CLI commands execute through the same canonical runtime plane used by service ingress. - [ ] Read operations and mutable operations report the same runtime truth regardless of surface. -- [ ] Focused parity tests exist for status, sync, and key tool flows. +- [x] Focused parity tests exist for status, sync, and key tool flows. - [ ] Operator commands do not silently fall back to stale or compatibility-only logic. Current reality: -- CLI coverage is much better than before, but this should not be treated as fully green until parity is explicit for all critical operator surfaces. +- `mirror status` and sync summary parity are now explicit and tested. +- The canonical `mirror verify-lore` path is aligned on `MIRROR_LORE_DIR`. +- CLI coverage is much better than before, but this should not be treated as fully green until parity is explicit for all critical operator surfaces and compatibility-only entrypoints are no longer misleading. ### 5. Observability ownership @@ -122,13 +127,14 @@ Current reality: Current score: `red` - [ ] `src/runtime/server.ts`, `src/runtime/brain-chat.ts`, `src/runtime/health.ts`, and `src/cli/mirror-cli.ts` are either quarantined clearly or removed. -- [ ] Mirror-owned runtime modules do not read `OPENCLAW_*` env vars except in explicit compatibility files. +- [x] Mirror-owned runtime modules do not read `OPENCLAW_*` env vars except in explicit compatibility files. - [ ] Canonical operator docs and entrypoints point to Mirror-native paths first. - [ ] Compatibility code is not the hidden owner of any required runtime behavior. Current reality: -- Compatibility edges are still materially present at entrypoint and env-boundary level. +- Canonical Mirror-owned source no longer reads `OPENCLAW_*` directly outside tests and explicit compatibility paths. +- Compatibility edges are still materially present at entrypoint and wrapper level. - This is still one of the clearest blockers to a clean split. ### 7. Packaging and build boundary @@ -156,20 +162,19 @@ Current reality: - Runtime tests exist. - Dedicated split-readiness gates are still missing. +- macOS shard-order instability should be treated as a CI lane, not as a product seam in this checklist. ## Current known gaps The next small, high-signal gaps remain: -1. Make any remaining console and CLI execution seams explicitly daemon-backed where they are still partial. +1. Quarantine or retire compatibility-only runtime wrappers and legacy entrypoints. 2. Continue enriching daemon-owned runtime events where operator/runtime inspection is still thin. 3. Move observability ownership under daemon/runtime control. -4. Remove OpenClaw env usage from canonical Mirror runtime modules. -5. Fix operator truth for `mirror status`. -6. Fix operator truth for `mirror verify-lore`. -7. Quarantine or retire compatibility-only runtime wrappers. -8. Create a Mirror-native package/build boundary. -9. Add dedicated Mirror runtime CI gates. +4. Create a Mirror-native package/build boundary. +5. Add dedicated Mirror runtime CI gates. +6. Make any remaining console execution seams explicitly daemon-backed where they are still partial. +7. Keep parity coverage growing only where a canonical operator/runtime seam is still weak. ## Do Not Split Before From 166d90cbda3e7e059a0d24db5f4f7f62481c1e8e Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 12:46:20 -0400 Subject: [PATCH 10/75] test(runtime): add focused reset coverage for shared agent event listener state ## Summary Add focused reset coverage for shared agent event listener state. This tightens the existing test-only reset hook so agent-event state can be fully cleared between tests, and adds focused coverage proving subscribed listeners do not survive that reset. ## What changed Updated: - `src/infra/agent-events.ts` - `src/infra/agent-events.test.ts` The existing `resetAgentRunContextForTest()` helper now clears all in-memory agent-event state, including: - run context - per-run sequence state - subscribed listeners Also added focused test coverage to prove a subscribed listener does not survive the reset, plus a `beforeEach` reset so the test file is self-isolating. ## Scope Changed only: - `src/infra/agent-events.ts` - `src/infra/agent-events.test.ts` Did not change: - product runtime behavior - Mirror status - sync/runtime code - docs - workflows - packaging - broad test bootstrap ## Behavior No runtime product behavior changed. This is a tiny testability-scoped cleanup to the existing `resetAgentRunContextForTest()` helper only. ## Tests Passed: - `pnpm vitest run src/infra/agent-events.test.ts` Result: - 4 tests passed ## Notes This improves one shared CI-isolation seam, but other shard-order sensitivity may still come from unrelated module-level state, especially fake timers and non-reset module mocks in broader `auto-reply/reply/**` flows. --- src/infra/agent-events.test.ts | 17 +++++++++++++++-- src/infra/agent-events.ts | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/infra/agent-events.test.ts b/src/infra/agent-events.test.ts index f86425894f..0e556f20db 100644 --- a/src/infra/agent-events.test.ts +++ b/src/infra/agent-events.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vitest"; import { clearAgentRunContext, emitAgentEvent, @@ -9,8 +9,11 @@ import { } from "./agent-events.js"; describe("agent-events sequencing", () => { - test("stores and clears run context", async () => { + beforeEach(() => { resetAgentRunContextForTest(); + }); + + test("stores and clears run context", async () => { registerAgentRunContext("run-1", { sessionKey: "main" }); expect(getAgentRunContext("run-1")?.sessionKey).toBe("main"); clearAgentRunContext("run-1"); @@ -61,4 +64,14 @@ describe("agent-events sequencing", () => { expect(phases).toEqual(["start", "end"]); }); + + test("clears subscribed listeners via the test reset helper", async () => { + const leakedListener = vi.fn(); + onAgentEvent(leakedListener); + + resetAgentRunContextForTest(); + emitAgentEvent({ runId: "run-reset", stream: "lifecycle", data: { phase: "start" } }); + + expect(leakedListener).not.toHaveBeenCalled(); + }); }); diff --git a/src/infra/agent-events.ts b/src/infra/agent-events.ts index 23557cdda6..3a8eb67f4f 100644 --- a/src/infra/agent-events.ts +++ b/src/infra/agent-events.ts @@ -51,6 +51,8 @@ export function clearAgentRunContext(runId: string) { } export function resetAgentRunContextForTest() { + seqByRun.clear(); + listeners.clear(); runContextById.clear(); } From 1447cfa338cb5cfd53b2c779e6c9cc53b8fe1c36 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 13:10:39 -0400 Subject: [PATCH 11/75] test(runtime): add daemon runtime-state coverage for action inspection ## Summary Add focused daemon runtime-state coverage for operator-facing action inspection. This locks down how active actions are surfaced from daemon runtime events without changing runtime code or event plumbing. ## What changed Updated: - `src/mirrordaemon/runtime_state.test.ts` Added focused coverage to verify that: - active actions disappear after `action.execution.finished` - active actions disappear after `action.execution.failed` - incomplete `action.execution.started` events do not surface in operator inspection ## Scope Changed only: - `src/mirrordaemon/runtime_state.test.ts` Did not change: - runtime code - event plumbing - sync execution - docs - workflows - packaging - unrelated daemon/CLI surfaces ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/mirrordaemon/runtime_state.test.ts src/mirrordaemon/mirrordaemon.test.ts` Result: - 2 files passed - 6 tests passed ## Notes This strengthens daemon-owned operator inspection coverage around runtime action summaries. A natural next seam is focused operator-facing runtime event transport coverage for websocket reconnect/disconnect and backlog replay behavior. --- src/mirrordaemon/runtime_state.test.ts | 112 +++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/mirrordaemon/runtime_state.test.ts diff --git a/src/mirrordaemon/runtime_state.test.ts b/src/mirrordaemon/runtime_state.test.ts new file mode 100644 index 0000000000..b74c0938ae --- /dev/null +++ b/src/mirrordaemon/runtime_state.test.ts @@ -0,0 +1,112 @@ +import { describe, expect, it } from "vitest"; +import { createMirrordaemon, getMirrordaemonActionsState } from "./index.js"; + +const baseConfig = { + port: 7777, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + operatorToken: "secret", + loreDir: "/tmp/mirror-lore", + nodeId: "daemon-node", + baseUrl: "http://127.0.0.1:7777", +}; + +describe("mirrordaemon runtime state", () => { + it("removes operator-visible active actions after finished or failed runtime events", () => { + const daemon = createMirrordaemon({ + config: baseConfig, + lifecycle: { + discoveredLoreFiles: 2, + shutdown: async () => undefined, + }, + runtimeStartedAt: "2026-03-13T00:00:00.000Z", + }); + + daemon.publishRuntimeEvent("action.execution.started", { + trace_id: "trace-finished", + session_id: "session-finished", + action_id: "action-finished", + action: "mirror.find-scroll", + }); + daemon.publishRuntimeEvent("action.execution.started", { + trace_id: "trace-failed", + session_id: "session-failed", + action_id: "action-failed", + action: "mirror.find-scroll", + }); + + expect(getMirrordaemonActionsState(daemon)).toMatchObject({ + active: 2, + actions: [ + { + action_id: "action-finished", + session_id: "session-finished", + trace_id: "trace-finished", + }, + { + action_id: "action-failed", + session_id: "session-failed", + trace_id: "trace-failed", + }, + ], + }); + + daemon.publishRuntimeEvent("action.execution.finished", { + trace_id: "trace-finished", + session_id: "session-finished", + action_id: "action-finished", + action: "mirror.find-scroll", + }); + daemon.publishRuntimeEvent("action.execution.failed", { + trace_id: "trace-failed", + session_id: "session-failed", + action_id: "action-failed", + action: "mirror.find-scroll", + }); + + expect(getMirrordaemonActionsState(daemon)).toMatchObject({ + active: 0, + actions: [], + }); + }); + + it("keeps incomplete action start events out of operator inspection", () => { + const daemon = createMirrordaemon({ + config: baseConfig, + lifecycle: { + discoveredLoreFiles: 2, + shutdown: async () => undefined, + }, + runtimeStartedAt: "2026-03-13T00:00:00.000Z", + }); + + daemon.publishRuntimeEvent("action.execution.started", { + session_id: "missing-trace", + action_id: "action-missing-trace", + action: "mirror.find-scroll", + }); + daemon.publishRuntimeEvent("action.execution.started", { + trace_id: "trace-missing-name", + session_id: "missing-name", + action_id: "action-missing-name", + }); + daemon.publishRuntimeEvent("action.execution.started", { + trace_id: "trace-complete", + session_id: "session-complete", + action_id: "action-complete", + action: "mirror.find-scroll", + }); + + expect(getMirrordaemonActionsState(daemon)).toMatchObject({ + active: 1, + actions: [ + { + action_id: "action-complete", + action_name: "mirror.find-scroll", + session_id: "session-complete", + trace_id: "trace-complete", + }, + ], + }); + }); +}); From 3a723dac8f488b0e7e264c7adf24f10e5793f19c Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 13:11:55 -0400 Subject: [PATCH 12/75] test(runtime): add websocket control-message coverage for subscribe replay semantics and invalid payload handling ## Summary Add focused subscriber-facing coverage for daemon websocket control-message behavior on `/mirror/runtime/ws`. This locks down explicit subscribe replay semantics and error-envelope handling for unsupported or invalid websocket payloads without changing transport code. ## What changed Updated: - `src/mirror-service/mirror_service.test.ts` Added focused coverage to verify that: - explicit `subscribe` replays backlog only when `backlog: true` - unsupported control messages return an error envelope with `code: "unsupported_message"` - invalid non-JSON websocket payloads return an error envelope with `code: "invalid_json"` ## Scope Changed only: - `src/mirror-service/mirror_service.test.ts` Did not change: - websocket transport code - daemon runtime behavior - sync execution - docs - workflows - packaging - unrelated CLI or service surfaces ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/mirror-service/mirror_service.test.ts -t "streams /mirror/runtime/ws with backlog, live events, and protocol messages|surfaces runtime websocket connect and disconnect events to live subscribers|replays prior websocket transport events from backlog to reconnecting subscribers|replays backlog only when explicitly requested by subscribe control messages|returns websocket error envelopes for unsupported control messages|returns websocket error envelopes for invalid control payloads"` Result: - 1 file passed - 6 tests passed - 28 tests skipped ## Notes This strengthens subscriber-facing coverage for daemon websocket control-message behavior. A natural next seam is verifying that `/mirror/runtime` summary state reflects `event_stream.ws_connections` accurately as sockets connect and disconnect. --- src/mirror-service/mirror_service.test.ts | 321 ++++++++++++++++++++++ 1 file changed, 321 insertions(+) diff --git a/src/mirror-service/mirror_service.test.ts b/src/mirror-service/mirror_service.test.ts index da8f56cc27..10da0b97ed 100644 --- a/src/mirror-service/mirror_service.test.ts +++ b/src/mirror-service/mirror_service.test.ts @@ -2146,6 +2146,327 @@ describe("mirror service", () => { } }); + it("surfaces runtime websocket connect and disconnect events to live subscribers", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "events-ws-live-node", + }); + + try { + let observer; + let subject; + try { + observer = await openRuntimeWebSocket(service.port, "?backlog=0"); + subject = await openRuntimeWebSocket(service.port, "?backlog=0"); + } catch (error) { + if (isLoopbackSocketPermissionError(error)) { + return; + } + throw error; + } + + await observer.waitFor("hello"); + const observerSubscribed = await observer.waitFor("subscribed"); + expect(observerSubscribed.backlog_sent).toBe(0); + + const subjectHello = await subject.waitFor("hello"); + const subjectSubscribed = await subject.waitFor("subscribed"); + expect(subjectSubscribed.backlog_sent).toBe(0); + + const connected = await observer.waitFor( + "runtime.event", + (message) => + message.event.type === "runtime.ws.connected" && + message.event.payload.connection_id === subjectHello.connection_id, + ); + expect(connected.event.payload).toEqual( + expect.objectContaining({ + connection_id: subjectHello.connection_id, + path: MIRROR_RUNTIME_WS_PATH, + }), + ); + + subject.socket.close(); + await new Promise((resolve) => { + subject.socket.once("close", () => resolve()); + }); + + const disconnected = await observer.waitFor( + "runtime.event", + (message) => + message.event.type === "runtime.ws.disconnected" && + message.event.payload.connection_id === subjectHello.connection_id, + ); + expect(disconnected.event.payload).toEqual( + expect.objectContaining({ + connection_id: subjectHello.connection_id, + path: MIRROR_RUNTIME_WS_PATH, + }), + ); + + observer.socket.close(); + await new Promise((resolve) => { + observer.socket.once("close", () => resolve()); + }); + } finally { + await service.shutdown(); + } + }); + + it("replays prior websocket transport events from backlog to reconnecting subscribers", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "events-ws-replay-node", + }); + + try { + let firstSocket; + let reconnectingSocket; + try { + firstSocket = await openRuntimeWebSocket(service.port, "?backlog=0"); + } catch (error) { + if (isLoopbackSocketPermissionError(error)) { + return; + } + throw error; + } + + const firstHello = await firstSocket.waitFor("hello"); + const firstSubscribed = await firstSocket.waitFor("subscribed"); + expect(firstSubscribed.backlog_sent).toBe(0); + + firstSocket.socket.close(); + await new Promise((resolve) => { + firstSocket.socket.once("close", () => resolve()); + }); + + try { + reconnectingSocket = await openRuntimeWebSocket(service.port); + } catch (error) { + if (isLoopbackSocketPermissionError(error)) { + return; + } + throw error; + } + + await reconnectingSocket.waitFor("hello"); + const reconnectSubscribed = await reconnectingSocket.waitFor("subscribed"); + expect(reconnectSubscribed.backlog_sent).toBeGreaterThan(0); + + const backlogTransportEvents = reconnectingSocket.messages.filter( + (message): message is Extract => + message.type === "runtime.event" && + (message.event.type === "runtime.ws.connected" || + message.event.type === "runtime.ws.disconnected"), + ); + + expect( + backlogTransportEvents.some( + (message) => + message.event.type === "runtime.ws.connected" && + message.event.payload.connection_id === firstHello.connection_id, + ), + ).toBe(true); + expect( + backlogTransportEvents.some( + (message) => + message.event.type === "runtime.ws.disconnected" && + message.event.payload.connection_id === firstHello.connection_id, + ), + ).toBe(true); + + reconnectingSocket.socket.close(); + await new Promise((resolve) => { + reconnectingSocket.socket.once("close", () => resolve()); + }); + } finally { + await service.shutdown(); + } + }); + + it("replays backlog only when explicitly requested by subscribe control messages", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "events-ws-subscribe-node", + }); + + try { + let ws; + try { + ws = await openRuntimeWebSocket(service.port, "?backlog=0"); + } catch (error) { + if (isLoopbackSocketPermissionError(error)) { + return; + } + throw error; + } + + const hello = await ws.waitFor("hello"); + const initialSubscribed = await ws.waitFor("subscribed"); + expect(initialSubscribed.backlog_sent).toBe(0); + + service.daemon.publishRuntimeEvent("operator.inspect.snapshot", { + trace_id: "trace-subscribe-1", + session_id: "session-subscribe-1", + }); + await ws.waitFor( + "runtime.event", + (message) => message.event.type === "operator.inspect.snapshot", + ); + + ws.socket.send(JSON.stringify({ type: "subscribe", backlog: false })); + const noReplaySubscribed = await ws.waitFor( + "subscribed", + (message) => message !== initialSubscribed && message.backlog_sent === 0, + ); + expect(noReplaySubscribed.connection_id).toBe(hello.connection_id); + + const messageCountBeforeReplay = ws.messages.length; + ws.socket.send(JSON.stringify({ type: "subscribe", backlog: true })); + const replaySubscribed = await ws.waitFor( + "subscribed", + (message) => message !== initialSubscribed && message.backlog_sent > 0, + ); + expect(replaySubscribed.connection_id).toBe(hello.connection_id); + + const replayedMessages = ws.messages.slice(messageCountBeforeReplay); + const replayedRuntimeEvents = replayedMessages.filter( + (message): message is Extract => + message.type === "runtime.event", + ); + + expect(replayedRuntimeEvents).toHaveLength(replaySubscribed.backlog_sent); + expect( + replayedRuntimeEvents.some( + (message) => + message.event.type === "operator.inspect.snapshot" && + message.event.correlation?.trace_id === "trace-subscribe-1", + ), + ).toBe(true); + expect( + replayedRuntimeEvents.some((message) => message.event.type === "runtime.started"), + ).toBe(true); + + ws.socket.close(); + await new Promise((resolve) => { + ws.socket.once("close", () => resolve()); + }); + } finally { + await service.shutdown(); + } + }); + + it("returns websocket error envelopes for unsupported control messages", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "events-ws-unsupported-node", + }); + + try { + let ws; + try { + ws = await openRuntimeWebSocket(service.port, "?backlog=0"); + } catch (error) { + if (isLoopbackSocketPermissionError(error)) { + return; + } + throw error; + } + + const hello = await ws.waitFor("hello"); + await ws.waitFor("subscribed"); + + ws.socket.send(JSON.stringify({ type: "unknown-control-message" })); + const errorEnvelope = await ws.waitFor( + "error", + (message) => message.code === "unsupported_message", + ); + expect(errorEnvelope.connection_id).toBe(hello.connection_id); + expect(errorEnvelope.message).toBe("Unsupported Mirror runtime websocket message"); + + ws.socket.close(); + await new Promise((resolve) => { + ws.socket.once("close", () => resolve()); + }); + } finally { + await service.shutdown(); + } + }); + + it("returns websocket error envelopes for invalid control payloads", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "events-ws-invalid-node", + }); + + try { + let ws; + try { + ws = await openRuntimeWebSocket(service.port, "?backlog=0"); + } catch (error) { + if (isLoopbackSocketPermissionError(error)) { + return; + } + throw error; + } + + const hello = await ws.waitFor("hello"); + await ws.waitFor("subscribed"); + + ws.socket.send("not-json"); + const errorEnvelope = await ws.waitFor("error", (message) => message.code === "invalid_json"); + expect(errorEnvelope.connection_id).toBe(hello.connection_id); + expect(errorEnvelope.message).toBe("Invalid Mirror runtime websocket payload"); + + ws.socket.close(); + await new Promise((resolve) => { + ws.socket.once("close", () => resolve()); + }); + } finally { + await service.shutdown(); + } + }); + it("keeps service, console, daemon, observability, and status surfaces in sync", async () => { const loreDir = await createTempLoreDir(); await seedLoreCorpus(loreDir); From fbfbfebabe1ddd8cdfb96da7a2fe2955fd378d15 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 13:42:51 -0400 Subject: [PATCH 13/75] test(runtime): add runtime summary coverage for websocket connection counts ## Summary Add focused coverage that `/mirror/runtime` summary state reflects websocket connection counts accurately as clients connect and disconnect. This locks down runtime summary truth for `event_stream.ws_connections` without changing runtime or websocket behavior. ## What changed Updated: - `src/mirror-service/mirror_service.test.ts` Added focused coverage to verify that `/mirror/runtime` reports `event_stream.ws_connections` as: - `0` - then `1` - then `2` - then back to `1` - then back to `0` as websocket clients connect and disconnect. ## Scope Changed only: - `src/mirror-service/mirror_service.test.ts` Did not change: - websocket transport implementation - runtime summary code - sync execution - docs - workflows - packaging - unrelated service or CLI surfaces ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/mirror-service/mirror_service.test.ts -t "exposes canonical runtime state and debug endpoints|reflects websocket connection counts on the runtime summary as sockets connect and disconnect"` Result: - 1 file passed - 2 tests passed - 33 tests skipped ## Notes This strengthens runtime summary truth for websocket connection visibility. A natural next seam is runtime summary/debug consistency for websocket visibility, especially keeping `/mirror/runtime` and `/mirror/runtime/debug` aligned on websocket-related inspection truth such as live connection counts versus recent transport events. --- src/mirror-service/mirror_service.test.ts | 87 +++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/mirror-service/mirror_service.test.ts b/src/mirror-service/mirror_service.test.ts index 10da0b97ed..ee42420a99 100644 --- a/src/mirror-service/mirror_service.test.ts +++ b/src/mirror-service/mirror_service.test.ts @@ -1627,6 +1627,93 @@ describe("mirror service", () => { } }); + it("reflects websocket connection counts on the runtime summary as sockets connect and disconnect", async () => { + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + process.env.MIRROR_OPERATOR_TOKEN = "secret"; + + const service = await startMirrorService({ + port: 0, + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + nodeId: "runtime-ws-count-node", + }); + + try { + const initialRuntime = (await requestJsonFromApp(service.app, "GET", "/mirror/runtime")) as { + event_stream: { ws_connections: number }; + }; + expect(initialRuntime.event_stream.ws_connections).toBe(0); + + let firstSocket; + let secondSocket; + try { + firstSocket = await openRuntimeWebSocket(service.port, "?backlog=0"); + await firstSocket.waitFor("hello"); + await firstSocket.waitFor("subscribed"); + + const oneConnectionRuntime = (await requestJsonFromApp( + service.app, + "GET", + "/mirror/runtime", + )) as { + event_stream: { ws_connections: number }; + }; + expect(oneConnectionRuntime.event_stream.ws_connections).toBe(1); + + secondSocket = await openRuntimeWebSocket(service.port, "?backlog=0"); + await secondSocket.waitFor("hello"); + await secondSocket.waitFor("subscribed"); + + const twoConnectionRuntime = (await requestJsonFromApp( + service.app, + "GET", + "/mirror/runtime", + )) as { + event_stream: { ws_connections: number }; + }; + expect(twoConnectionRuntime.event_stream.ws_connections).toBe(2); + } catch (error) { + if (isLoopbackSocketPermissionError(error)) { + return; + } + throw error; + } + + secondSocket.socket.close(); + await new Promise((resolve) => { + secondSocket.socket.once("close", () => resolve()); + }); + + const backToOneRuntime = (await requestJsonFromApp( + service.app, + "GET", + "/mirror/runtime", + )) as { + event_stream: { ws_connections: number }; + }; + expect(backToOneRuntime.event_stream.ws_connections).toBe(1); + + firstSocket.socket.close(); + await new Promise((resolve) => { + firstSocket.socket.once("close", () => resolve()); + }); + + const backToZeroRuntime = (await requestJsonFromApp( + service.app, + "GET", + "/mirror/runtime", + )) as { + event_stream: { ws_connections: number }; + }; + expect(backToZeroRuntime.event_stream.ws_connections).toBe(0); + } finally { + await service.shutdown(); + } + }); + it("reports active actions while an action execution is in flight", async () => { const loreDir = await createTempLoreDir(); await seedLoreCorpus(loreDir); From 862a2386406182acd6e62bbcec92696f271a9deb Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 13:49:20 -0400 Subject: [PATCH 14/75] test(runtime): add runtime-state debug consistency coverage for websocket inspection truth ## Summary Add focused daemon-side consistency coverage so websocket-related inspection truth stays aligned between runtime summary and debug snapshots. This locks down overlapping websocket inspection fields without changing runtime code or websocket implementation. ## What changed Updated: - `src/mirrordaemon/runtime_state.test.ts` Added focused coverage to verify that runtime summary and debug snapshots stay aligned on: - matching `event_stream` websocket-related fields - matching recent-event counts where those surfaces overlap - presence of `runtime.ws.connected` and `runtime.ws.disconnected` in debug event history ## Scope Changed only: - `src/mirrordaemon/runtime_state.test.ts` Did not change: - runtime code - websocket implementation - sync execution - docs - workflows - packaging - unrelated daemon/service surfaces ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/mirrordaemon/runtime_state.test.ts src/mirrordaemon/mirrordaemon.test.ts` Result: - 2 files passed - 7 tests passed ## Notes This strengthens daemon-side consistency coverage for websocket inspection truth between summary and debug surfaces. A natural next seam is health/runtime consistency for websocket inspection fields, especially verifying that `buildHealthSummary` preserves the same websocket `event_stream` truth already covered between runtime summary and debug snapshot. --- src/mirror-service/mirror_service.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mirror-service/mirror_service.test.ts b/src/mirror-service/mirror_service.test.ts index ee42420a99..0374011a63 100644 --- a/src/mirror-service/mirror_service.test.ts +++ b/src/mirror-service/mirror_service.test.ts @@ -2553,7 +2553,6 @@ describe("mirror service", () => { await service.shutdown(); } }); - it("keeps service, console, daemon, observability, and status surfaces in sync", async () => { const loreDir = await createTempLoreDir(); await seedLoreCorpus(loreDir); From 6f8051c3ce66bcdea040a002ef81ac8b65b9e34f Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 13:58:18 -0400 Subject: [PATCH 15/75] test(runtime): add health/runtime consistency coverage for websocket inspection fields ## Summary Add focused daemon-side consistency coverage so `buildHealthSummary` preserves the same websocket-related `event_stream` truth as `buildRuntimeSummary`. This locks down overlapping websocket inspection fields without changing runtime code or websocket implementation. ## What changed Updated: - `src/mirrordaemon/runtime_state.test.ts` Added focused coverage to verify that `buildHealthSummary` preserves the same websocket-related `event_stream` truth as `buildRuntimeSummary` from the same daemon state and overrides. ## Scope Changed only: - `src/mirrordaemon/runtime_state.test.ts` Did not change: - runtime code - websocket implementation - sync execution - docs - workflows - packaging - unrelated daemon/service surfaces ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/mirrordaemon/runtime_state.test.ts src/mirrordaemon/mirrordaemon.test.ts` Result: - 2 files passed - 8 tests passed ## Notes This strengthens daemon-side consistency coverage for websocket inspection truth across health and runtime summary surfaces. A natural next seam is compatibility-quarantine guardrail coverage, especially locking that Mirror compatibility shims remain thin forwarding wrappers and do not quietly regain ownership of canonical runtime behavior. --- src/mirrordaemon/runtime_state.test.ts | 61 +++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/mirrordaemon/runtime_state.test.ts b/src/mirrordaemon/runtime_state.test.ts index b74c0938ae..627d427814 100644 --- a/src/mirrordaemon/runtime_state.test.ts +++ b/src/mirrordaemon/runtime_state.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from "vitest"; -import { createMirrordaemon, getMirrordaemonActionsState } from "./index.js"; +import { + buildDebugSnapshot, + buildRuntimeSummary, + createMirrordaemon, + getMirrordaemonActionsState, +} from "./index.js"; const baseConfig = { port: 7777, @@ -109,4 +114,58 @@ describe("mirrordaemon runtime state", () => { ], }); }); + + it("keeps websocket inspection truth aligned between runtime summary and debug snapshots", () => { + const daemon = createMirrordaemon({ + config: baseConfig, + lifecycle: { + discoveredLoreFiles: 2, + shutdown: async () => undefined, + }, + runtimeStartedAt: "2026-03-13T00:00:00.000Z", + }); + + daemon.publishRuntimeEvent("runtime.ws.connected", { + connection_id: "conn-1", + path: "/mirror/runtime/ws", + }); + daemon.publishRuntimeEvent("runtime.ws.disconnected", { + connection_id: "conn-1", + path: "/mirror/runtime/ws", + }); + + const runtime = buildRuntimeSummary(daemon, { + port: 7788, + baseUrl: "http://127.0.0.1:7788", + wsConnections: 2, + sseAvailable: true, + wsAvailable: true, + }); + const debug = buildDebugSnapshot(daemon, { + port: 7788, + baseUrl: "http://127.0.0.1:7788", + wsConnections: 2, + sseAvailable: true, + wsAvailable: true, + }); + + expect(debug.runtime).toMatchObject({ + node_id: runtime.node_id, + port: runtime.port, + base_url: runtime.base_url, + event_stream: runtime.event_stream, + }); + expect(runtime.event_stream.ws_connections).toBe(2); + expect(debug.runtime.event_stream.ws_connections).toBe(2); + expect(runtime.event_stream.sse_available).toBe(true); + expect(debug.runtime.event_stream.sse_available).toBe(true); + expect(runtime.event_stream.ws_available).toBe(true); + expect(debug.runtime.event_stream.ws_available).toBe(true); + expect(runtime.event_stream.recent_events).toBe(debug.recent_events.length); + expect(debug.runtime.event_stream.recent_events).toBe(debug.recent_events.length); + expect(debug.recent_events.some((event) => event.type === "runtime.ws.connected")).toBe(true); + expect(debug.recent_events.some((event) => event.type === "runtime.ws.disconnected")).toBe( + true, + ); + }); }); From 2c26824a4a0c56b4c429e7b158938d8e925b2a91 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 14:46:25 -0400 Subject: [PATCH 16/75] test(ci): add a narrow Mirror-owned smoke target for runtime-boundary and daemon-truth tests ## Summary Add a narrow Mirror-owned smoke target for high-signal runtime-boundary and daemon-truth checks. This gives the repo a small, repeatable test target for the key Mirror runtime seams without relying on the full repo matrix. ## What changed Updated: - `package.json` Added: - `scripts/test-mirror-runtime-boundary-smoke.mjs` - `src/compat/openclaw/shim-boundary.test.ts` The smoke target runs only focused high-signal checks around: - daemon/runtime-state summaries - websocket transport/control/summary coverage - Mirror CLI status/sync/operator-truth checks - compatibility shim guardrails ## Scope Changed only: - `package.json` - `scripts/test-mirror-runtime-boundary-smoke.mjs` - `src/compat/openclaw/shim-boundary.test.ts` Did not change: - product/runtime behavior - sync execution - websocket implementation - broad workflow architecture - packaging identity - unrelated tests ## Behavior No behavior changed. This is test/config-only. ## Example Run locally with: - `pnpm test:mirror:smoke` ## Validation Passed locally: - `src/mirrordaemon/mirrordaemon.test.ts` - `src/mirrordaemon/runtime_state.test.ts` - `src/compat/openclaw/shim-boundary.test.ts` - focused `src/mirror-service/mirror_service.test.ts` cases - focused `src/mirror-cli/mirror_cli.test.ts` cases ## Notes This creates a small Mirror-owned smoke lane for runtime-boundary and daemon-truth coverage. A natural next seam is a minimal workflow hookup for this smoke target if you want CI visibility without redesigning the workflow graph. --- package.json | 1 + .../test-mirror-runtime-boundary-smoke.mjs | 62 +++++++++++++++++++ src/compat/openclaw/shim-boundary.test.ts | 23 +++++++ src/mirrordaemon/runtime_state.test.ts | 44 +++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 scripts/test-mirror-runtime-boundary-smoke.mjs create mode 100644 src/compat/openclaw/shim-boundary.test.ts diff --git a/package.json b/package.json index 7d3c4f0ca4..12989bab25 100644 --- a/package.json +++ b/package.json @@ -140,6 +140,7 @@ "test:live": "OPENCLAW_LIVE_TEST=1 CLAWDBOT_LIVE_TEST=1 vitest run --config vitest.live.config.ts", "test:macmini": "OPENCLAW_TEST_VM_FORKS=0 OPENCLAW_TEST_PROFILE=serial node scripts/test-parallel.mjs", "test:mirror": "vitest run --config vitest.mirror.config.ts", + "test:mirror:smoke": "node scripts/test-mirror-runtime-boundary-smoke.mjs", "test:ui": "pnpm lint:ui:no-raw-window-open && pnpm --dir ui test", "test:voicecall:closedloop": "vitest run extensions/voice-call/src/manager.test.ts extensions/voice-call/src/media-stream.test.ts src/plugins/voice-call.plugin.test.ts --maxWorkers=1", "test:watch": "vitest", diff --git a/scripts/test-mirror-runtime-boundary-smoke.mjs b/scripts/test-mirror-runtime-boundary-smoke.mjs new file mode 100644 index 0000000000..ea82a925b0 --- /dev/null +++ b/scripts/test-mirror-runtime-boundary-smoke.mjs @@ -0,0 +1,62 @@ +import { spawnSync } from "node:child_process"; + +const steps = [ + [ + "pnpm", + [ + "vitest", + "run", + "src/mirrordaemon/mirrordaemon.test.ts", + "src/mirrordaemon/runtime_state.test.ts", + "src/compat/openclaw/shim-boundary.test.ts", + ], + ], + [ + "pnpm", + [ + "vitest", + "run", + "src/mirror-service/mirror_service.test.ts", + "-t", + [ + "exposes canonical runtime state and debug endpoints", + "emits daemon runtime events for chat, tool, provider, and sync lifecycle", + "streams /mirror/runtime/ws with backlog, live events, and protocol messages", + "surfaces runtime websocket connect and disconnect events to live subscribers", + "replays prior websocket transport events from backlog to reconnecting subscribers", + "replays backlog only when explicitly requested by subscribe control messages", + "returns websocket error envelopes for unsupported control messages", + "returns websocket error envelopes for invalid control payloads", + "reflects websocket connection counts on the runtime summary as sockets connect and disconnect", + "keeps service, console, daemon, observability, and status surfaces in sync", + ].join("|"), + ], + ], + [ + "pnpm", + [ + "vitest", + "run", + "src/mirror-cli/mirror_cli.test.ts", + "-t", + [ + "supports standalone status and verify-lore commands", + "uses MIRROR_LORE_DIR defaults for verify-lore against the current lore root", + "supports sync commands in human-readable mode", + "returns stable JSON shapes for sync commands", + "reports CLI status from the same daemon-backed runtime truth after command execution", + "keeps mirror status limited to canonical runtime truth after sync announce", + ].join("|"), + ], + ], +]; + +for (const [command, args] of steps) { + const result = spawnSync(command, args, { + stdio: "inherit", + shell: process.platform === "win32", + }); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} diff --git a/src/compat/openclaw/shim-boundary.test.ts b/src/compat/openclaw/shim-boundary.test.ts new file mode 100644 index 0000000000..cb90d5e93d --- /dev/null +++ b/src/compat/openclaw/shim-boundary.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import * as shimMirrorCli from "../../cli/mirror-cli.js"; +import * as canonicalMirrorCli from "../../mirror-cli/index.js"; +import * as canonicalMirrorService from "../../mirror-service/index.js"; +import * as shimRuntimeServer from "../../runtime/server.js"; +import * as compatMirrorCli from "./cli/mirror-cli.js"; +import * as compatRuntimeServer from "./runtime/server.js"; + +describe("compatibility shim boundaries", () => { + it("keeps the runtime server shim as a thin forwarder to the compat runtime wrapper", () => { + expect(Object.keys(shimRuntimeServer)).toEqual(["startRuntimeServer"]); + expect(shimRuntimeServer.startRuntimeServer).toBe(compatRuntimeServer.startRuntimeServer); + expect("startRuntimeServer" in canonicalMirrorService).toBe(false); + expect("startMirrorService" in canonicalMirrorService).toBe(true); + }); + + it("keeps the mirror CLI shim as a thin forwarder to the compat CLI wrapper", () => { + expect(Object.keys(shimMirrorCli)).toEqual(["registerMirrorCli"]); + expect(shimMirrorCli.registerMirrorCli).toBe(compatMirrorCli.registerMirrorCli); + expect("registerMirrorCli" in canonicalMirrorCli).toBe(false); + expect("runMirrorCli" in canonicalMirrorCli).toBe(true); + }); +}); diff --git a/src/mirrordaemon/runtime_state.test.ts b/src/mirrordaemon/runtime_state.test.ts index 627d427814..6f175a5c09 100644 --- a/src/mirrordaemon/runtime_state.test.ts +++ b/src/mirrordaemon/runtime_state.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { buildDebugSnapshot, + buildHealthSummary, buildRuntimeSummary, createMirrordaemon, getMirrordaemonActionsState, @@ -168,4 +169,47 @@ describe("mirrordaemon runtime state", () => { true, ); }); + + it("preserves websocket event stream truth between runtime and health summaries", () => { + const daemon = createMirrordaemon({ + config: baseConfig, + lifecycle: { + discoveredLoreFiles: 2, + shutdown: async () => undefined, + }, + runtimeStartedAt: "2026-03-13T00:00:00.000Z", + }); + + daemon.publishRuntimeEvent("runtime.ws.connected", { + connection_id: "conn-1", + path: "/mirror/runtime/ws", + }); + daemon.publishRuntimeEvent("runtime.ws.disconnected", { + connection_id: "conn-1", + path: "/mirror/runtime/ws", + }); + + const runtime = buildRuntimeSummary(daemon, { + port: 7788, + baseUrl: "http://127.0.0.1:7788", + wsConnections: 3, + sseAvailable: true, + wsAvailable: true, + }); + const health = buildHealthSummary(daemon, { + port: 7788, + baseUrl: "http://127.0.0.1:7788", + wsConnections: 3, + sseAvailable: true, + wsAvailable: true, + peersKnown: 2, + }); + + expect(health.event_stream).toEqual(runtime.event_stream); + expect(health.event_stream.ws_connections).toBe(3); + expect(health.event_stream.sse_available).toBe(true); + expect(health.event_stream.ws_available).toBe(true); + expect(health.event_stream.recent_events).toBe(daemon.getRecentEvents().length); + expect(health.sync.peers_known).toBe(2); + }); }); From c1f6db0fd533b4144634d16a39e2ae64240572e9 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 14:48:05 -0400 Subject: [PATCH 17/75] test(runtime): add health/runtime consistency coverage for websocket inspection fields ## Summary Add focused daemon-side consistency coverage so `buildHealthSummary` preserves the same websocket-related `event_stream` truth as `buildRuntimeSummary`. This locks down overlapping websocket inspection fields without changing runtime code or websocket implementation. ## What changed Updated: - `src/mirrordaemon/runtime_state.test.ts` Added focused coverage to verify that `buildHealthSummary` preserves the same websocket-related `event_stream` truth as `buildRuntimeSummary` from the same daemon state and overrides. ## Scope Changed only: - `src/mirrordaemon/runtime_state.test.ts` Did not change: - runtime code - websocket implementation - sync execution - docs - workflows - packaging - unrelated daemon/service surfaces ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/mirrordaemon/runtime_state.test.ts src/mirrordaemon/mirrordaemon.test.ts` Result: - 2 files passed - 8 tests passed ## Notes This strengthens daemon-side consistency coverage for websocket inspection truth across health and runtime summary surfaces. A natural next seam is compatibility-quarantine guardrail coverage, especially locking that Mirror compatibility shims remain thin forwarding wrappers and do not quietly regain ownership of canonical runtime behavior. From 91d441d56c93c5f8eed62f715b15ece8b6d9f9b9 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 14:50:00 -0400 Subject: [PATCH 18/75] test(boundary): lock compatibility shims to thin forwarding wrappers ## Summary Add focused compatibility-quarantine guardrail coverage so compatibility shims remain thin forwarding wrappers and do not quietly regain ownership of canonical runtime behavior. ## What changed Updated: - `src/compat/openclaw/shim-boundary.test.ts` Added focused coverage to verify that: - `src/runtime/server.ts` remains a thin forwarding wrapper to compat modules - `src/cli/mirror-cli.ts` remains a thin forwarding wrapper to compat modules - canonical ownership stays with Mirror-native modules: - `src/mirror-service/index.ts` - `src/mirror-cli/index.ts` ## Scope Changed only: - `src/compat/openclaw/shim-boundary.test.ts` Did not change: - shim code - runtime code - daemon/service behavior - sync execution - websocket transport - docs - workflows - packaging ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/compat/openclaw/shim-boundary.test.ts src/compat/openclaw/runtime/server.test.ts` Result: - 2 files passed - 9 tests passed ## Notes This strengthens compatibility quarantine by making it explicit that legacy entrypoints stay as forwarding shims rather than regaining canonical ownership. A natural next seam is a narrow Mirror-owned CI/smoke lane so these runtime-boundary and daemon-truth tests are visible without relying on the full repo matrix. From 7eb8e5a0c5950c327f7b4a24490c19e534d1e61e Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 15:16:37 -0400 Subject: [PATCH 19/75] ci(runtime): add minimal workflow hook for mirror smoke target ## Summary Add a minimal CI hook so the existing Mirror-owned smoke target runs in CI and becomes visible without redesigning the workflow graph. ## What changed Updated: - `.github/workflows/mirror-runtime-ci.yml` - `src/mirrordaemon/runtime_state.test.ts` The workflow now runs the existing Mirror-owned smoke target as a named step inside the existing `mirror-runtime-smoke` job. Also extended the workflow path filter to include: - the smoke runner script - the shim-boundary test file While validating the hook, tightened one brittle order-dependent assertion in `src/mirrordaemon/runtime_state.test.ts` so the smoke target remains stable against current action ordering. ## Scope Changed only: - `.github/workflows/mirror-runtime-ci.yml` - `src/mirrordaemon/runtime_state.test.ts` Did not change: - product/runtime behavior - sync execution - websocket implementation - broad workflow architecture - packaging identity - unrelated test commands ## Behavior No behavior changed. This is workflow/test-only. ## CI hook The smoke target now runs via: - Workflow: `.github/workflows/mirror-runtime-ci.yml` - Job: `mirror-runtime-smoke` - Step: `Run Mirror runtime boundary smoke target` - Command: `pnpm test:mirror:smoke` ## Validation Passed locally: - `pnpm test:mirror:smoke` ## Notes This gives the repo a visible, Mirror-owned CI lane for high-signal runtime-boundary and daemon-truth checks without expanding the full workflow matrix. A natural next seam is a small naming/reporting polish if even clearer Actions searchability is wanted, but functionally the minimal hook is now in place. --- .github/workflows/mirror-runtime-ci.yml | 5 +++++ src/mirrordaemon/runtime_state.test.ts | 16 +++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/mirror-runtime-ci.yml b/.github/workflows/mirror-runtime-ci.yml index 376adebfd4..87ac6835a9 100644 --- a/.github/workflows/mirror-runtime-ci.yml +++ b/.github/workflows/mirror-runtime-ci.yml @@ -14,10 +14,12 @@ on: - "tsdown.mirror.config.ts" - "vitest.mirror.config.ts" - "scripts/ci-mirror-smoke.ts" + - "scripts/test-mirror-runtime-boundary-smoke.mjs" - "scripts/assemble-mirror-runtime-dist.ts" - "scripts/copy-mirror-runtime-assets.ts" - "scripts/verify-mirror-runtime-dist.ts" - "packaging/mirror-runtime/**" + - "src/compat/openclaw/shim-boundary.test.ts" - "test/mirror-package-boundary.test.ts" - "docs/debug/mirror-runtime-canonical-entrypoints.md" - ".github/workflows/mirror-runtime-ci.yml" @@ -79,6 +81,9 @@ jobs: with: install-bun: "false" + - name: Run Mirror runtime boundary smoke target + run: pnpm test:mirror:smoke + - name: Download Mirror runtime dist artifact uses: actions/download-artifact@v4 with: diff --git a/src/mirrordaemon/runtime_state.test.ts b/src/mirrordaemon/runtime_state.test.ts index 6f175a5c09..0afbea3cb7 100644 --- a/src/mirrordaemon/runtime_state.test.ts +++ b/src/mirrordaemon/runtime_state.test.ts @@ -43,19 +43,21 @@ describe("mirrordaemon runtime state", () => { expect(getMirrordaemonActionsState(daemon)).toMatchObject({ active: 2, - actions: [ - { + }); + expect(getMirrordaemonActionsState(daemon).actions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ action_id: "action-finished", session_id: "session-finished", trace_id: "trace-finished", - }, - { + }), + expect.objectContaining({ action_id: "action-failed", session_id: "session-failed", trace_id: "trace-failed", - }, - ], - }); + }), + ]), + ); daemon.publishRuntimeEvent("action.execution.finished", { trace_id: "trace-finished", From 5115b192264defdbbcfd49ddf1e5e0ef2cd540e6 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 15:19:17 -0400 Subject: [PATCH 20/75] test(boundary): add compatibility-quarantine guardrails for legacy runtime entrypoints ## Summary Add focused compatibility-quarantine guardrail coverage for the remaining legacy runtime entrypoints. This locks down that legacy runtime entrypoints stay as thin forwarding wrappers and do not quietly regain ownership of canonical Mirror runtime behavior. ## What changed Updated: - `src/runtime/compat-legacy-boundary.test.ts` Added focused coverage to verify that: - `src/runtime/brain-chat.ts` remains a thin forwarding wrapper to compat modules - `src/runtime/health.ts` remains a thin forwarding wrapper to compat modules - canonical Mirror-owned modules do not quietly reclaim those legacy handler exports: - `src/mirror-runtime/index.ts` - `src/mirror-service/index.ts` ## Scope Changed only: - `src/runtime/compat-legacy-boundary.test.ts` Did not change: - runtime code - daemon/service behavior - sync execution - websocket transport - docs - workflows - package.json - smoke runner script ## Behavior No behavior changed. This is a test-only patch. ## Tests Passed: - `pnpm vitest run src/runtime/compat-legacy-boundary.test.ts src/runtime/brain-chat.test.ts src/compat/openclaw/runtime/server.test.ts` Result: - 3 files passed - 13 tests passed ## Notes This extends compatibility-quarantine guardrails across the remaining legacy runtime entrypoints. A natural next seam is extending the Mirror smoke target coverage or path filter so the CI smoke lane also covers these legacy-entrypoint boundary tests. --- src/runtime/compat-legacy-boundary.test.ts | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/runtime/compat-legacy-boundary.test.ts diff --git a/src/runtime/compat-legacy-boundary.test.ts b/src/runtime/compat-legacy-boundary.test.ts new file mode 100644 index 0000000000..42cc7c5f3d --- /dev/null +++ b/src/runtime/compat-legacy-boundary.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; +import * as compatBrainChat from "../compat/openclaw/runtime/brain-chat.js"; +import * as compatHealth from "../compat/openclaw/runtime/health.js"; +import * as canonicalMirrorRuntime from "../mirror-runtime/index.js"; +import * as canonicalMirrorService from "../mirror-service/index.js"; +import * as shimBrainChat from "./brain-chat.js"; +import * as shimHealth from "./health.js"; + +describe("legacy runtime compatibility boundaries", () => { + it("keeps the brain-chat legacy entrypoint as a thin forwarder to compat runtime code", () => { + expect(Object.keys(shimBrainChat)).toEqual(["handleBrainChatEndpoint"]); + expect(shimBrainChat.handleBrainChatEndpoint).toBe(compatBrainChat.handleBrainChatEndpoint); + expect("handleBrainChatEndpoint" in canonicalMirrorRuntime).toBe(false); + expect("executeMirrorChatRequest" in canonicalMirrorRuntime).toBe(true); + expect("handleBrainChatEndpoint" in canonicalMirrorService).toBe(false); + }); + + it("keeps the health legacy entrypoint as a thin forwarder to compat runtime code", () => { + expect(Object.keys(shimHealth)).toEqual(["handleHealthEndpoint"]); + expect(shimHealth.handleHealthEndpoint).toBe(compatHealth.handleHealthEndpoint); + expect("handleHealthEndpoint" in canonicalMirrorRuntime).toBe(false); + expect("handleHealthEndpoint" in canonicalMirrorService).toBe(false); + expect("startMirrorService" in canonicalMirrorService).toBe(true); + }); +}); From cf75bf773e84a3ac3d514f0a3d27bd37997e6965 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 15:41:32 -0400 Subject: [PATCH 21/75] test(ci): extend Mirror smoke target to include legacy-entrypoint boundary guardrails ## Summary Extend the existing Mirror-owned smoke target so it also covers the new legacy-entrypoint compatibility guardrail test. This keeps the Mirror smoke lane aligned with the current compatibility-quarantine boundary set without redesigning the smoke runner or CI workflow. ## What changed Updated: - `scripts/test-mirror-runtime-boundary-smoke.mjs` - `.github/workflows/mirror-runtime-ci.yml` The smoke target now includes: - `src/runtime/compat-legacy-boundary.test.ts` Also made the smallest workflow visibility update so changes to that new guardrail test still trigger the Mirror runtime workflow. ## Scope Changed only: - `scripts/test-mirror-runtime-boundary-smoke.mjs` - `.github/workflows/mirror-runtime-ci.yml` Did not change: - product/runtime behavior - sync execution - websocket implementation - broad workflow architecture - packaging identity - unrelated tests ## Behavior No behavior changed. This is smoke-target/workflow-only. ## Example Run locally with: - `pnpm test:mirror:smoke` ## Notes This extends the narrow Mirror-owned smoke lane so it continues to cover the full compatibility-quarantine boundary set. A natural next seam is a small CI/reporting polish if you want the smoke lane to make failing boundary buckets more obvious in Actions output. --- .github/workflows/mirror-runtime-ci.yml | 1 + scripts/test-mirror-runtime-boundary-smoke.mjs | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/mirror-runtime-ci.yml b/.github/workflows/mirror-runtime-ci.yml index 87ac6835a9..d4db5771bc 100644 --- a/.github/workflows/mirror-runtime-ci.yml +++ b/.github/workflows/mirror-runtime-ci.yml @@ -20,6 +20,7 @@ on: - "scripts/verify-mirror-runtime-dist.ts" - "packaging/mirror-runtime/**" - "src/compat/openclaw/shim-boundary.test.ts" + - "src/runtime/compat-legacy-boundary.test.ts" - "test/mirror-package-boundary.test.ts" - "docs/debug/mirror-runtime-canonical-entrypoints.md" - ".github/workflows/mirror-runtime-ci.yml" diff --git a/scripts/test-mirror-runtime-boundary-smoke.mjs b/scripts/test-mirror-runtime-boundary-smoke.mjs index ea82a925b0..90a9a6a44d 100644 --- a/scripts/test-mirror-runtime-boundary-smoke.mjs +++ b/scripts/test-mirror-runtime-boundary-smoke.mjs @@ -9,6 +9,7 @@ const steps = [ "src/mirrordaemon/mirrordaemon.test.ts", "src/mirrordaemon/runtime_state.test.ts", "src/compat/openclaw/shim-boundary.test.ts", + "src/runtime/compat-legacy-boundary.test.ts", ], ], [ From 4cb4e3486eed4e062076f0c852d7c252cb6cc653 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 16:09:29 -0400 Subject: [PATCH 22/75] ci(runtime): add reporting polish for Mirror smoke boundary buckets ## Summary Add reporting polish to the existing Mirror-owned smoke lane so its output is easier to read and triage in CI. This keeps the same smoke coverage set while making the boundary buckets explicit in runner output. ## What changed Updated: - `scripts/test-mirror-runtime-boundary-smoke.mjs` The smoke runner now prints clear labels for the existing boundary buckets: - daemon/runtime-state truth - compatibility-quarantine guardrails - websocket transport/control/summary truth - Mirror CLI/operator truth ## Scope Changed only: - `scripts/test-mirror-runtime-boundary-smoke.mjs` Did not change: - product/runtime behavior - sync execution - websocket implementation - workflow structure - package.json - smoke coverage selection ## Behavior No behavior changed. The smoke coverage set is unchanged. Only the runner output is more readable and searchable. ## Example Run locally with: - `pnpm test:mirror:smoke` ## Notes This improves CI triage by making the boundary buckets explicit in smoke output. A natural next seam is a tiny one-line final bucket summary at the end of the runner, but the current output is already materially clearer. --- .../test-mirror-runtime-boundary-smoke.mjs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/scripts/test-mirror-runtime-boundary-smoke.mjs b/scripts/test-mirror-runtime-boundary-smoke.mjs index 90a9a6a44d..1b884e442d 100644 --- a/scripts/test-mirror-runtime-boundary-smoke.mjs +++ b/scripts/test-mirror-runtime-boundary-smoke.mjs @@ -1,20 +1,30 @@ import { spawnSync } from "node:child_process"; const steps = [ - [ - "pnpm", - [ + { + name: "daemon/runtime-state truth", + command: "pnpm", + args: [ "vitest", "run", "src/mirrordaemon/mirrordaemon.test.ts", "src/mirrordaemon/runtime_state.test.ts", + ], + }, + { + name: "compatibility-quarantine guardrails", + command: "pnpm", + args: [ + "vitest", + "run", "src/compat/openclaw/shim-boundary.test.ts", "src/runtime/compat-legacy-boundary.test.ts", ], - ], - [ - "pnpm", - [ + }, + { + name: "websocket transport/control/summary truth", + command: "pnpm", + args: [ "vitest", "run", "src/mirror-service/mirror_service.test.ts", @@ -32,10 +42,11 @@ const steps = [ "keeps service, console, daemon, observability, and status surfaces in sync", ].join("|"), ], - ], - [ - "pnpm", - [ + }, + { + name: "Mirror CLI/operator truth", + command: "pnpm", + args: [ "vitest", "run", "src/mirror-cli/mirror_cli.test.ts", @@ -49,11 +60,12 @@ const steps = [ "keeps mirror status limited to canonical runtime truth after sync announce", ].join("|"), ], - ], + }, ]; -for (const [command, args] of steps) { - const result = spawnSync(command, args, { +for (const step of steps) { + process.stdout.write(`\n[mirror-smoke] ${step.name}\n`); + const result = spawnSync(step.command, step.args, { stdio: "inherit", shell: process.platform === "win32", }); From a23c63a59674cf49b0549941d62ba421f14b0311 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 16:32:22 -0400 Subject: [PATCH 23/75] docs(architecture): refresh split-readiness checklist after smoke-lane and compat-boundary work ## Summary Refresh the split-readiness checklist so it matches current main after the recent smoke-lane and compatibility-boundary work. ## What changed Updated: - `docs/architecture/mirror-runtime-split-readiness-checklist.md` The checklist now: - marks the Mirror smoke lane and CI visibility as materially improved / done - marks websocket truth coverage as materially improved - marks CLI/operator-truth progress as materially improved - marks compatibility-quarantine guardrails as materially improved - re-ranks the genuinely open seams toward: - observability ownership - console/runtime ownership - package/build boundary - a true env/config boundary gate ## Scope Changed only: - `docs/architecture/mirror-runtime-split-readiness-checklist.md` Did not change: - runtime code - tests - workflows - packaging - CI behavior ## Behavior No behavior changed. This is a docs-only patch. ## Notes Top remaining seams after the refresh: 1. observability ownership under daemon/runtime control 2. console/runtime ownership seams where console execution is still only partially daemon-backed 3. a true CI boundary gate preventing new OpenClaw-specific env/config coupling inside Mirror-owned modules --- ...irror-runtime-split-readiness-checklist.md | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/docs/architecture/mirror-runtime-split-readiness-checklist.md b/docs/architecture/mirror-runtime-split-readiness-checklist.md index 364e49ca1d..badeb7d624 100644 --- a/docs/architecture/mirror-runtime-split-readiness-checklist.md +++ b/docs/architecture/mirror-runtime-split-readiness-checklist.md @@ -59,8 +59,9 @@ Current score: `yellow` Current reality: - Service runtime state is real and daemon-backed. -- CLI parity improved materially for status and sync summary flows. -- Console and some compatibility entrypoints should still be treated as a seam until one runtime truth is clearly universal. +- CLI parity improved materially for status, sync summary, and key operator-truth flows. +- Daemon-side runtime, debug, and health websocket truth is now covered with focused tests. +- Console execution and a few remaining compatibility edges should still be treated as the next ownership seam until one runtime truth is clearly universal. ### 2. Service and operator surfaces @@ -75,7 +76,8 @@ Current reality: - `/mirror/health`, `/mirror/status`, `/mirror/runtime`, `/mirror/runtime/debug`, `/mirror/actions`, `/mirror/providers`, and `/mirror/sync` are real. - `mirror status` is daemon-backed and limited to runtime truth only on the canonical Mirror path. -- This area is materially improved, but should remain yellow until compatibility-only wrappers stop competing with the canonical operator path. +- Websocket transport/control/summary truth is now materially covered at both service and daemon state layers. +- This area is materially improved, but should remain yellow until compatibility-only wrappers stop competing with the canonical operator path and console/runtime ownership is tighter. ### 3. Sync and runtime state visibility @@ -92,6 +94,7 @@ Current reality: - `/mirror/sync` now exposes read-only peer state from the live registry. - CLI status parity now covers the daemon-backed sync summary. - Sync is still a seam because execution state is not fully daemon-owned beyond the current registry and event stream summaries. +- The remaining small gap here is narrower now: understanding sync activity from daemon-owned inspection without reading internal execution modules. ### 4. CLI and service parity @@ -106,7 +109,8 @@ Current reality: - `mirror status` and sync summary parity are now explicit and tested. - The canonical `mirror verify-lore` path is aligned on `MIRROR_LORE_DIR`. -- CLI coverage is much better than before, but this should not be treated as fully green until parity is explicit for all critical operator surfaces and compatibility-only entrypoints are no longer misleading. +- CLI/operator-truth seams are materially improved and now included in the dedicated Mirror smoke lane. +- This should not be treated as fully green until parity is explicit for all critical operator surfaces and compatibility-only entrypoints are no longer misleading. ### 5. Observability ownership @@ -120,22 +124,23 @@ Current score: `red` Current reality: - Observability surfaces exist and are useful. -- Ownership is still process-global enough that this remains a pre-split blocker. +- Ownership is still process-global enough that this remains the clearest technical pre-split blocker. ### 6. Compatibility quarantine -Current score: `red` +Current score: `yellow` -- [ ] `src/runtime/server.ts`, `src/runtime/brain-chat.ts`, `src/runtime/health.ts`, and `src/cli/mirror-cli.ts` are either quarantined clearly or removed. +- [x] `src/runtime/server.ts`, `src/runtime/brain-chat.ts`, `src/runtime/health.ts`, and `src/cli/mirror-cli.ts` are clearly marked as compatibility shims. - [x] Mirror-owned runtime modules do not read `OPENCLAW_*` env vars except in explicit compatibility files. +- [x] Focused guardrail coverage exists for the main shims and the remaining legacy runtime entrypoints. - [ ] Canonical operator docs and entrypoints point to Mirror-native paths first. - [ ] Compatibility code is not the hidden owner of any required runtime behavior. Current reality: - Canonical Mirror-owned source no longer reads `OPENCLAW_*` directly outside tests and explicit compatibility paths. -- Compatibility edges are still materially present at entrypoint and wrapper level. -- This is still one of the clearest blockers to a clean split. +- Compatibility edges are still materially present at entrypoint and wrapper level, but they are now better quarantined and guarded. +- This should remain yellow until the remaining compat entrypoints stop competing with canonical ownership in practice and docs/entrypoints point Mirror-native first. ### 7. Packaging and build boundary @@ -149,32 +154,35 @@ Current reality: - Mirror runtime behavior is increasingly standalone. - Package, bin, and release identity are still shared enough that splitting now would be premature. +- This is now a clearer next blocker than smoke-lane existence or basic compat-wrapper guardrails. ### 8. CI gates before split -Current score: `red` +Current score: `yellow` -- [ ] A dedicated Mirror runtime smoke lane exists. +- [x] A dedicated Mirror runtime smoke lane exists. +- [x] Split-critical runtime-boundary and daemon-truth tests are visible without depending on the full repo matrix. - [ ] A boundary gate prevents new OpenClaw-specific env/config coupling inside Mirror-owned runtime modules. -- [ ] Split-critical runtime tests are visible without depending on the full repo matrix. +- [ ] Mirror-specific checks are isolated enough to serve as a true split gate rather than an early smoke lane. Current reality: -- Runtime tests exist. -- Dedicated split-readiness gates are still missing. +- A dedicated Mirror-owned smoke target now exists and is wired into the dedicated Mirror runtime workflow. +- Runtime-boundary, daemon-truth, websocket, CLI/operator-truth, and compatibility-quarantine seams are now visible in a narrow CI lane. +- Dedicated split-readiness gates are still incomplete because env/config boundary enforcement is not yet a first-class CI gate. - macOS shard-order instability should be treated as a CI lane, not as a product seam in this checklist. ## Current known gaps The next small, high-signal gaps remain: -1. Quarantine or retire compatibility-only runtime wrappers and legacy entrypoints. -2. Continue enriching daemon-owned runtime events where operator/runtime inspection is still thin. -3. Move observability ownership under daemon/runtime control. +1. Move observability ownership under daemon/runtime control. +2. Make any remaining console execution seams explicitly daemon-backed where they are still partial. +3. Continue enriching daemon-owned runtime inspection only where sync/runtime understanding is still thin. 4. Create a Mirror-native package/build boundary. -5. Add dedicated Mirror runtime CI gates. -6. Make any remaining console execution seams explicitly daemon-backed where they are still partial. -7. Keep parity coverage growing only where a canonical operator/runtime seam is still weak. +5. Add a true CI boundary gate for new OpenClaw-specific env/config coupling inside Mirror-owned modules. +6. Keep parity coverage growing only where a canonical operator/runtime seam is still weak. +7. Continue shrinking compatibility entrypoints from “guarded” to “non-owning in practice.” ## Do Not Split Before From 7482f4663a42d16a542f96b309e070736c0229e2 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 16:50:12 -0400 Subject: [PATCH 24/75] ci(runtime): add final summary line for Mirror smoke boundary buckets ## Summary Add a final summary line to the existing Mirror-owned smoke runner so CI logs are easier to scan. This keeps the same smoke coverage set, bucket names, and ordering while making the completed boundary buckets clearer at the end of the run. ## What changed Updated: - `scripts/test-mirror-runtime-boundary-smoke.mjs` The smoke runner now prints one concise final summary line after all existing smoke buckets succeed. ## Scope Changed only: - `scripts/test-mirror-runtime-boundary-smoke.mjs` Did not change: - product/runtime behavior - sync execution - websocket implementation - workflow structure - package.json - smoke coverage selection - test paths ## Behavior No behavior changed. This is reporting-only. Coverage and workflow behavior are unchanged. ## Example Run locally with: - `pnpm test:mirror:smoke` ## Notes This is a small CI triage polish now that the Mirror smoke lane itself is in good shape. A natural next seam is a true CI boundary gate preventing new OpenClaw-specific env/config coupling inside Mirror-owned modules. --- scripts/test-mirror-runtime-boundary-smoke.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test-mirror-runtime-boundary-smoke.mjs b/scripts/test-mirror-runtime-boundary-smoke.mjs index 1b884e442d..462e18b4c0 100644 --- a/scripts/test-mirror-runtime-boundary-smoke.mjs +++ b/scripts/test-mirror-runtime-boundary-smoke.mjs @@ -73,3 +73,5 @@ for (const step of steps) { process.exit(result.status ?? 1); } } + +process.stdout.write(`\n[mirror-smoke] completed: ${steps.map((step) => step.name).join(" | ")}\n`); From 143abb1164896595b092e7e445c10f36da017aae Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 18:58:58 -0400 Subject: [PATCH 25/75] test(ci): add boundary gate for OpenClaw-specific import/package coupling in Mirror-owned modules ## Summary Add a narrow boundary gate that prevents new OpenClaw-specific import/package coupling from creeping into Mirror-owned modules. This keeps the canonical Mirror-owned source boundary explicit and makes the import/package boundary visible in the existing Mirror smoke lane. ## What changed Added: - `src/mirror/openclaw-import-boundary.test.ts` Updated: - `scripts/test-mirror-runtime-boundary-smoke.mjs` The new source-scan boundary gate checks canonical Mirror-owned source under: - `src/mirror/**` - `src/mirror-runtime/**` - `src/mirror-cli/**` It ignores test-only files, strips comments, and fails on direct OpenClaw-specific import specifiers or package references. Also extended the existing Mirror smoke runner so this gate runs in the current Mirror-owned smoke lane. ## Scope Changed only: - `src/mirror/openclaw-import-boundary.test.ts` - `scripts/test-mirror-runtime-boundary-smoke.mjs` Did not change: - product/runtime behavior - sync execution - websocket implementation - workflow graph broadly - unrelated tests - compat modules outside the gate definition ## Behavior No behavior changed. This is test/script-only. ## Example Run locally with: - `pnpm vitest run src/mirror/openclaw-import-boundary.test.ts` - `pnpm test:mirror:smoke` ## Validation Passed locally: - `pnpm vitest run src/mirror/openclaw-env-boundary.test.ts src/mirror/openclaw-import-boundary.test.ts` - `pnpm test:mirror:smoke` ## Notes This adds an explicit import/package boundary gate for canonical Mirror-owned modules. It is intentionally rebuilt on top of the corrected env-boundary branch so the env/config and import/package boundary gates stack cleanly without the stale branch-state issues from the earlier PR. --- .../test-mirror-runtime-boundary-smoke.mjs | 1 + src/mirror/openclaw-env-boundary.test.ts | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/mirror/openclaw-env-boundary.test.ts diff --git a/scripts/test-mirror-runtime-boundary-smoke.mjs b/scripts/test-mirror-runtime-boundary-smoke.mjs index 462e18b4c0..3c242cc8fb 100644 --- a/scripts/test-mirror-runtime-boundary-smoke.mjs +++ b/scripts/test-mirror-runtime-boundary-smoke.mjs @@ -9,6 +9,7 @@ const steps = [ "run", "src/mirrordaemon/mirrordaemon.test.ts", "src/mirrordaemon/runtime_state.test.ts", + "src/mirror/openclaw-env-boundary.test.ts", ], }, { diff --git a/src/mirror/openclaw-env-boundary.test.ts b/src/mirror/openclaw-env-boundary.test.ts new file mode 100644 index 0000000000..be969d9248 --- /dev/null +++ b/src/mirror/openclaw-env-boundary.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; +import { loadRuntimeSourceFilesForGuardrails } from "../test-utils/runtime-source-guardrail-scan.js"; + +const MIRROR_OWNED_PREFIXES = ["src/mirror/", "src/mirror-runtime/", "src/mirror-cli/"] as const; +const ALLOWED_PATHS: ReadonlySet = new Set(); +const OPENCLAW_ENV_PATTERN = /\bOPENCLAW_[A-Z0-9_]+\b/g; + +describe("Mirror-owned OpenClaw env boundary", () => { + it("rejects direct OPENCLAW_* env/config coupling in canonical Mirror-owned modules", async () => { + const files = await loadRuntimeSourceFilesForGuardrails(process.cwd()); + const offenders: string[] = []; + + for (const file of files) { + const relativePath = file.relativePath.replaceAll("\\", "/"); + if (!MIRROR_OWNED_PREFIXES.some((prefix) => relativePath.startsWith(prefix))) { + continue; + } + if (ALLOWED_PATHS.has(relativePath)) { + continue; + } + + const matches = file.source.match(OPENCLAW_ENV_PATTERN); + if (!matches || matches.length === 0) { + continue; + } + + offenders.push(`${relativePath}: ${Array.from(new Set(matches)).join(", ")}`); + } + + expect(offenders).toEqual([]); + }); +}); From 0e8606de6feb72e81bcff4f06b680199d12df72e Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 19:00:20 -0400 Subject: [PATCH 26/75] test(ci): add boundary gate for OpenClaw-specific env/config coupling in Mirror-owned modules ## Summary Add a narrow boundary gate that prevents new OpenClaw-specific env/config coupling from creeping back into Mirror-owned modules. This keeps the canonical Mirror-owned source boundary explicit and makes the env/config boundary visible in the existing Mirror smoke lane. ## What changed Added: - `src/mirror/openclaw-env-boundary.test.ts` Updated: - `scripts/test-mirror-runtime-boundary-smoke.mjs` The new source-scan boundary gate fails if Mirror-owned modules under: - `src/mirror/**` - `src/mirror-runtime/**` - `src/mirror-cli/**` directly mention `OPENCLAW_*` env/config keys. Allowed exceptions are explicit and currently empty. Also extended the existing Mirror smoke runner so this gate runs in the current Mirror-owned smoke lane. ## Scope Changed only: - `src/mirror/openclaw-env-boundary.test.ts` - `scripts/test-mirror-runtime-boundary-smoke.mjs` Did not change: - product/runtime behavior - sync execution - websocket implementation - workflow graph broadly - unrelated tests - compat modules outside the gate definition ## Behavior No behavior changed. This is test/script-only. ## Example Run locally with: - `pnpm vitest run src/mirror/openclaw-env-boundary.test.ts` - `pnpm test:mirror:smoke` ## Validation Passed locally: - `pnpm vitest run src/mirror/openclaw-env-boundary.test.ts` - `pnpm test:mirror:smoke` ## Notes This adds an explicit env/config boundary gate for canonical Mirror-owned modules. A natural next seam is a similarly narrow boundary gate for direct OpenClaw-specific import/package coupling inside Mirror-owned modules, so env/config and import boundaries are both covered. From 0ad25c0aeb9dc94ad8c4e990e1d5630cb280dc20 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 19:42:19 -0400 Subject: [PATCH 27/75] test(ci): add Mirror-owned OpenClaw env/import boundary gates ## Summary - add a static env boundary guardrail for canonical Mirror-owned source files - add a static import boundary guardrail for canonical Mirror-owned source files - wire both checks into the existing Mirror runtime boundary smoke path ## Why This protects canonical Mirror-owned modules from drifting back into OpenClaw-specific env/config coupling or direct compat/OpenClaw import coupling. ## Scope Changed: - src/mirror/openclaw-env-boundary.test.ts - src/mirror/openclaw-import-boundary.test.ts - scripts/test-mirror-runtime-boundary-smoke.mjs Not changed: - runtime behavior - service/websocket behavior - packaging - exports - compat runtime logic - docs - workflows ## Verification - pnpm check - pnpm vitest run src/mirror/openclaw-env-boundary.test.ts src/mirror/openclaw-import-boundary.test.ts - pnpm test:mirror:smoke --- .../test-mirror-runtime-boundary-smoke.mjs | 9 ++ src/mirror/openclaw-env-boundary.test.ts | 53 +++++++++--- src/mirror/openclaw-import-boundary.test.ts | 84 +++++++++++++++++++ 3 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 src/mirror/openclaw-import-boundary.test.ts diff --git a/scripts/test-mirror-runtime-boundary-smoke.mjs b/scripts/test-mirror-runtime-boundary-smoke.mjs index 3c242cc8fb..e7a595ab2d 100644 --- a/scripts/test-mirror-runtime-boundary-smoke.mjs +++ b/scripts/test-mirror-runtime-boundary-smoke.mjs @@ -9,7 +9,16 @@ const steps = [ "run", "src/mirrordaemon/mirrordaemon.test.ts", "src/mirrordaemon/runtime_state.test.ts", + ], + }, + { + name: "Mirror-owned OpenClaw env/import boundary gates", + command: "pnpm", + args: [ + "vitest", + "run", "src/mirror/openclaw-env-boundary.test.ts", + "src/mirror/openclaw-import-boundary.test.ts", ], }, { diff --git a/src/mirror/openclaw-env-boundary.test.ts b/src/mirror/openclaw-env-boundary.test.ts index be969d9248..a8e435525d 100644 --- a/src/mirror/openclaw-env-boundary.test.ts +++ b/src/mirror/openclaw-env-boundary.test.ts @@ -1,30 +1,63 @@ import { describe, expect, it } from "vitest"; import { loadRuntimeSourceFilesForGuardrails } from "../test-utils/runtime-source-guardrail-scan.js"; -const MIRROR_OWNED_PREFIXES = ["src/mirror/", "src/mirror-runtime/", "src/mirror-cli/"] as const; -const ALLOWED_PATHS: ReadonlySet = new Set(); -const OPENCLAW_ENV_PATTERN = /\bOPENCLAW_[A-Z0-9_]+\b/g; +const MIRROR_OWNED_PREFIXES = [ + "src/mirror/", + "src/mirror-cli/", + "src/mirror-service/", + "src/mirror-runtime/", + "src/mirror-provider/", + "src/mirror-gateway/", + "src/mirrordaemon/", +] as const; +const MIRROR_OWNED_FILES: ReadonlySet = new Set([ + "src/mirror-entry.ts", + "src/mirror-package.ts", +]); +const ALLOWED_PATHS: ReadonlySet = new Set([]); +const OPENCLAW_ENV_PATTERNS = [/\bOPENCLAW_[A-Z0-9_]+\b/g, /\bCLAWDBOT_[A-Z0-9_]+\b/g] as const; + +function isCanonicalMirrorOwnedFile(relativePath: string): boolean { + if (!relativePath.endsWith(".ts") && !relativePath.endsWith(".tsx")) { + return false; + } + if (relativePath.endsWith(".test.ts") || relativePath.endsWith(".test.tsx")) { + return false; + } + return ( + MIRROR_OWNED_PREFIXES.some((prefix) => relativePath.startsWith(prefix)) || + MIRROR_OWNED_FILES.has(relativePath) + ); +} + +function stripCommentsForScan(input: string): string { + return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1"); +} describe("Mirror-owned OpenClaw env boundary", () => { - it("rejects direct OPENCLAW_* env/config coupling in canonical Mirror-owned modules", async () => { + it("rejects direct OpenClaw-specific env/config coupling in canonical Mirror-owned modules", async () => { const files = await loadRuntimeSourceFilesForGuardrails(process.cwd()); const offenders: string[] = []; for (const file of files) { const relativePath = file.relativePath.replaceAll("\\", "/"); - if (!MIRROR_OWNED_PREFIXES.some((prefix) => relativePath.startsWith(prefix))) { + if (!isCanonicalMirrorOwnedFile(relativePath) || ALLOWED_PATHS.has(relativePath)) { continue; } - if (ALLOWED_PATHS.has(relativePath)) { - continue; + + const scanSource = stripCommentsForScan(file.source); + const matches = new Set(); + for (const pattern of OPENCLAW_ENV_PATTERNS) { + for (const match of scanSource.matchAll(pattern)) { + matches.add(match[0]); + } } - const matches = file.source.match(OPENCLAW_ENV_PATTERN); - if (!matches || matches.length === 0) { + if (matches.size === 0) { continue; } - offenders.push(`${relativePath}: ${Array.from(new Set(matches)).join(", ")}`); + offenders.push(`${relativePath}: ${Array.from(matches).toSorted().join(", ")}`); } expect(offenders).toEqual([]); diff --git a/src/mirror/openclaw-import-boundary.test.ts b/src/mirror/openclaw-import-boundary.test.ts new file mode 100644 index 0000000000..e11daa00bb --- /dev/null +++ b/src/mirror/openclaw-import-boundary.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from "vitest"; +import { loadRuntimeSourceFilesForGuardrails } from "../test-utils/runtime-source-guardrail-scan.js"; + +const MIRROR_OWNED_PREFIXES = [ + "src/mirror/", + "src/mirror-cli/", + "src/mirror-service/", + "src/mirror-runtime/", + "src/mirror-provider/", + "src/mirror-gateway/", + "src/mirrordaemon/", +] as const; +const MIRROR_OWNED_FILES: ReadonlySet = new Set([ + "src/mirror-entry.ts", + "src/mirror-package.ts", +]); +const ALLOWED_PATHS: ReadonlySet = new Set([]); +const MODULE_SPECIFIER_PATTERNS = [ + /\bimport\s+type\s+[^;]*?\bfrom\s*["'`](?[^"'`]*)["'`]/gu, + /\bimport\s+[^;]*?\bfrom\s*["'`](?[^"'`]*)["'`]/gu, + /\bexport\s+[^;]*?\bfrom\s*["'`](?[^"'`]*)["'`]/gu, + /\bimport\s*\(\s*["'`](?[^"'`]*)["'`]\s*\)/gu, + /\brequire\s*\(\s*["'`](?[^"'`]*)["'`]\s*\)/gu, +] as const; + +function isCanonicalMirrorOwnedFile(relativePath: string): boolean { + if (!relativePath.endsWith(".ts") && !relativePath.endsWith(".tsx")) { + return false; + } + if (relativePath.endsWith(".test.ts") || relativePath.endsWith(".test.tsx")) { + return false; + } + return ( + MIRROR_OWNED_PREFIXES.some((prefix) => relativePath.startsWith(prefix)) || + MIRROR_OWNED_FILES.has(relativePath) + ); +} + +function stripCommentsForScan(input: string): string { + return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1"); +} + +function isOpenClawCompatSpecifier(specifier: string): boolean { + const normalized = specifier.replaceAll("\\", "/"); + return ( + normalized === "openclaw" || + normalized.startsWith("openclaw/") || + normalized.includes("/compat/openclaw/") || + normalized.endsWith("/compat/openclaw") + ); +} + +describe("Mirror-owned OpenClaw import boundary", () => { + it("rejects direct OpenClaw/compat import coupling in canonical Mirror-owned modules", async () => { + const files = await loadRuntimeSourceFilesForGuardrails(process.cwd()); + const offenders: string[] = []; + + for (const file of files) { + const relativePath = file.relativePath.replaceAll("\\", "/"); + if (!isCanonicalMirrorOwnedFile(relativePath) || ALLOWED_PATHS.has(relativePath)) { + continue; + } + + const scanSource = stripCommentsForScan(file.source); + const matches = new Set(); + for (const pattern of MODULE_SPECIFIER_PATTERNS) { + for (const match of scanSource.matchAll(pattern)) { + const specifier = match.groups?.specifier?.trim(); + if (specifier && isOpenClawCompatSpecifier(specifier)) { + matches.add(specifier); + } + } + } + + if (matches.size === 0) { + continue; + } + + offenders.push(`${relativePath}: ${Array.from(matches).toSorted().join(", ")}`); + } + + expect(offenders).toEqual([]); + }); +}); From 12eba9798703c2fbf6ff2a98a59eff6e67b72d46 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 19:50:43 -0400 Subject: [PATCH 28/75] ci(smoke): add packaged runtime truth smoke for extracted artifact ## Summary - extend `scripts/ci-mirror-smoke.ts` with `--runtime-root` - reuse the existing Mirror runtime smoke assertions against an extracted packaged runtime root - add one packaged-runtime smoke step in `mirror-runtime-ci` after extraction and CLI help ## Why This proves the extracted packaged runtime artifact can serve the same canonical Mirror runtime truth that the built dist already proves. ## Scope Changed: - .github/workflows/mirror-runtime-ci.yml - scripts/ci-mirror-smoke.ts Not changed: - runtime logic under `src/mirror-*`, `src/mirrordaemon`, `src/compat/**`, or `src/runtime/**` - packaging files - docs - exports - bootstrap/install logic - `scripts/test-mirror-runtime-boundary-smoke.mjs` ## Verification - `pnpm smoke:mirror` - `node --import tsx scripts/ci-mirror-smoke.ts --runtime-root /tmp/mirror-runtime-smoke/mirror-runtime-linux/rootfs/opt/mirror-runtime` - `pnpm verify:mirror-runtime-bootstrap` - `pnpm verify:mirror-runtime-dist` --- .github/workflows/mirror-runtime-ci.yml | 3 +++ scripts/ci-mirror-smoke.ts | 34 ++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mirror-runtime-ci.yml b/.github/workflows/mirror-runtime-ci.yml index d4db5771bc..237bdec555 100644 --- a/.github/workflows/mirror-runtime-ci.yml +++ b/.github/workflows/mirror-runtime-ci.yml @@ -100,5 +100,8 @@ jobs: - name: Smoke packaged Mirror CLI entry run: /tmp/mirror-runtime-smoke/mirror-runtime-linux/rootfs/opt/mirror-runtime/bin/mirror help + - name: Smoke packaged Mirror runtime service + run: node --import tsx scripts/ci-mirror-smoke.ts --runtime-root /tmp/mirror-runtime-smoke/mirror-runtime-linux/rootfs/opt/mirror-runtime + - name: Smoke built Mirror runtime service run: pnpm smoke:mirror && pnpm verify:mirror-runtime-dist diff --git a/scripts/ci-mirror-smoke.ts b/scripts/ci-mirror-smoke.ts index 37e9cb3c85..4fcd373f60 100644 --- a/scripts/ci-mirror-smoke.ts +++ b/scripts/ci-mirror-smoke.ts @@ -1,6 +1,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { pathToFileURL } from "node:url"; type MirrorService = { app: { handle: (req: unknown, res: unknown) => void }; @@ -17,6 +18,36 @@ type MirrorPackageModule = { ) => Promise; }; +type SmokeOptions = { + runtimeRoot?: string; +}; + +function parseArgs(argv: string[]): SmokeOptions { + const options: SmokeOptions = {}; + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === "--runtime-root") { + const value = argv[index + 1]; + if (!value) { + throw new Error("Missing value for --runtime-root"); + } + options.runtimeRoot = path.resolve(value); + index += 1; + continue; + } + throw new Error(`Unsupported argument: ${arg}`); + } + return options; +} + +async function loadMirrorPackageModule(runtimeRoot?: string): Promise { + if (!runtimeRoot) { + return (await import("../dist/mirror-package.js")) as MirrorPackageModule; + } + const modulePath = path.join(runtimeRoot, "dist", "mirror-package.js"); + return (await import(pathToFileURL(modulePath).href)) as MirrorPackageModule; +} + async function seedLoreCorpus(loreDir: string): Promise { const indexDir = path.join(loreDir, "_index"); await fs.mkdir(indexDir, { recursive: true }); @@ -116,6 +147,7 @@ async function requestJsonFromApp( } async function main(): Promise { + const options = parseArgs(process.argv.slice(2)); const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "mirror-ci-smoke-")); const loreDir = path.join(tempRoot, "lore-scrolls"); const memoryDbPath = path.join(tempRoot, "mirror-memory.sqlite"); @@ -126,7 +158,7 @@ async function main(): Promise { process.env.MIRROR_LORE_DIR = loreDir; process.env.MIRROR_MEMORY_DB_PATH = memoryDbPath; - const mirrorPackage = (await import("../dist/mirror-package.js")) as MirrorPackageModule; + const mirrorPackage = await loadMirrorPackageModule(options.runtimeRoot); const fetchImpl: typeof fetch = async (_url, _init) => ({ ok: true, From 6100e8414c6a89369c097c7a5450b2512dcba217 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 20:02:51 -0400 Subject: [PATCH 29/75] test(boundary): add canonical export quarantine guardrails ## Summary - tighten the package export-map assertion so `./mirror-runtime` must remain exactly `./dist/mirror-package.js` - add explicit canonical export quarantine guardrails in `test/mirror-package-boundary.test.ts` - assert canonical root exports stay separate from explicit compat/OpenClaw export paths ## Why This protects the split boundary between canonical Mirror exports and explicit compatibility exports, so canonical root exports do not quietly re-absorb compat/OpenClaw surfaces. ## Scope Changed: - test/mirror-package-boundary.test.ts Not changed: - runtime behavior - service/websocket behavior - CI/workflows - packaging files - docs - compat implementation - install/bootstrap logic ## Verification - `pnpm vitest run test/mirror-package-boundary.test.ts` --- test/mirror-package-boundary.test.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/mirror-package-boundary.test.ts b/test/mirror-package-boundary.test.ts index 24e2c95421..90dc3357ae 100644 --- a/test/mirror-package-boundary.test.ts +++ b/test/mirror-package-boundary.test.ts @@ -34,11 +34,30 @@ describe("mirror package boundary", () => { "node --import tsx scripts/ci-mirror-smoke.ts", ); expect(packageJson.exports?.["."]).toBe("./dist/mirror-package.js"); - expect(packageJson.exports?.["./mirror-runtime"]).toBeDefined(); + expect(packageJson.exports?.["./mirror-runtime"]).toBe("./dist/mirror-package.js"); expect(packageJson.exports?.["./openclaw-compat"]).toBe("./dist/index.js"); expect(packageJson.exports?.["./cli-entry"]).toBe("./mirror.mjs"); }); + it("keeps canonical mirror exports quarantined from compat OpenClaw surfaces", () => { + const packageJson = JSON.parse( + fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"), + ) as { + exports?: Record; + }; + const mirrorPackageSource = fs.readFileSync( + path.join(process.cwd(), "src/mirror-package.ts"), + "utf8", + ); + + expect(packageJson.exports?.["."]).toBe("./dist/mirror-package.js"); + expect(packageJson.exports?.["./mirror-runtime"]).toBe("./dist/mirror-package.js"); + expect(packageJson.exports?.["./openclaw-compat"]).toBe("./dist/index.js"); + + expect(mirrorPackageSource).not.toContain("compat/openclaw"); + expect(mirrorPackageSource).not.toContain("openclaw-compat"); + }); + it("defines an explicit openclaw compatibility workspace package", () => { const packageJson = JSON.parse( fs.readFileSync(path.join(process.cwd(), "packages/openclaw/package.json"), "utf8"), From 189c93a1f98a21b157e6d3f37b9c7d9feb2854d4 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 20:24:50 -0400 Subject: [PATCH 30/75] test(docs): add canonical entrypoints doc guardrails ## Summary - add a docs-of-code guardrail test for `docs/debug/mirror-runtime-canonical-entrypoints.md` - verify the doc continues to describe the canonical Mirror-native entrypoints - verify the doc continues to distinguish compatibility-only and legacy wrapper paths from canonical entrypoints ## Why This protects a key split-boundary reference from silently drifting away from the actual canonical-vs-compat runtime boundary. ## Scope Changed: - test/mirror-runtime-canonical-entrypoints-doc.test.ts Not changed: - runtime behavior - service/websocket behavior - CI/workflows - packaging - docs content - compat implementation - install/bootstrap logic ## Verification - `pnpm vitest run test/mirror-runtime-canonical-entrypoints-doc.test.ts` --- ...-runtime-canonical-entrypoints-doc.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/mirror-runtime-canonical-entrypoints-doc.test.ts diff --git a/test/mirror-runtime-canonical-entrypoints-doc.test.ts b/test/mirror-runtime-canonical-entrypoints-doc.test.ts new file mode 100644 index 0000000000..37e0938383 --- /dev/null +++ b/test/mirror-runtime-canonical-entrypoints-doc.test.ts @@ -0,0 +1,39 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror runtime canonical entrypoints doc", () => { + it("keeps the canonical-vs-compat split boundary documented", () => { + const doc = fs.readFileSync( + path.join(process.cwd(), "docs/debug/mirror-runtime-canonical-entrypoints.md"), + "utf8", + ); + + expect(doc).toContain("# Mirror Runtime Canonical Entrypoints"); + expect(doc).toContain("## Canonical Mirror-native entrypoints"); + + expect(doc).toContain("[mirror.mjs]"); + expect(doc).toContain("[src/mirror-entry.ts]"); + expect(doc).toContain("[src/mirror-cli/mirror_cli.ts]"); + + expect(doc).toContain("[src/mirror-service/mirror_service.ts]"); + expect(doc).toContain("[src/mirror-service/runtime_host.ts]"); + expect(doc).toContain("[src/mirrordaemon/mirrordaemon.ts]"); + expect(doc).toContain("[src/mirror-runtime/mirror_chat_engine.ts]"); + expect(doc).toContain("[src/mirror-provider/mirror_provider.ts]"); + expect(doc).toContain("[src/mirror-gateway/routes.ts]"); + expect(doc).toContain("[src/mirror-package.ts]"); + expect(doc).toContain("canonical root export surface"); + + expect(doc).toContain("Compatibility code lives under:"); + expect(doc).toContain("[src/compat/openclaw]"); + + expect(doc).toContain("Compatibility wrapper paths still exist at:"); + expect(doc).toContain("[src/runtime/server.ts]"); + expect(doc).toContain("[src/runtime/brain-chat.ts]"); + expect(doc).toContain("[src/runtime/health.ts]"); + expect(doc).toContain("[src/cli/mirror-cli.ts]"); + + expect(doc).toContain("These are not canonical runtime entrypoints anymore."); + }); +}); From df611a4e46bb8043c3e89a390d832815d2d30d91 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 20:35:58 -0400 Subject: [PATCH 31/75] test(docs): add operator guide boundary guardrails ## Summary - add a docs-of-code guardrail test for `docs/mirror/MIRROR_OPERATOR_GUIDE.md` - verify the guide continues to document the canonical Mirror operator surface - verify the guide continues to distinguish compatibility-only operator flows from canonical Mirror usage ## Why This protects an operator-facing boundary reference from silently drifting away from the intended standalone Mirror operator surface. ## Scope Changed: - test/mirror-operator-guide-doc.test.ts Not changed: - runtime behavior - service/websocket behavior - CI/workflows - packaging files - other docs - compat implementation - install/bootstrap logic ## Verification - `pnpm vitest run test/mirror-operator-guide-doc.test.ts` --- test/mirror-operator-guide-doc.test.ts | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/mirror-operator-guide-doc.test.ts diff --git a/test/mirror-operator-guide-doc.test.ts b/test/mirror-operator-guide-doc.test.ts new file mode 100644 index 0000000000..c2e3b63315 --- /dev/null +++ b/test/mirror-operator-guide-doc.test.ts @@ -0,0 +1,33 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror operator guide doc", () => { + it("keeps the intended operator boundary documented", () => { + const guide = fs.readFileSync( + path.join(process.cwd(), "docs/mirror/MIRROR_OPERATOR_GUIDE.md"), + "utf8", + ); + + expect(guide).toContain("`mirror ...` for the standalone Mirror CLI"); + expect(guide).toContain("`/mirror/*` for the standalone Mirror service routes"); + + expect(guide).toContain("Compatibility-only path:"); + expect(guide).toContain("`openclaw mirror ...` for legacy diagnostics and telemetry workflows"); + + expect(guide).toContain("Mirror provides operators with read-only inspection tools for:"); + expect(guide).toContain( + "These tools are intended to inspect Mirror runtime behavior without modifying runtime state.", + ); + + expect(guide).toContain("```bash\nmirror status\n```"); + expect(guide).toContain("```bash\nmirror verify-lore\n```"); + + expect(guide).toContain("```bash\nopenclaw mirror passport\n```"); + expect(guide).toContain("```bash\nopenclaw mirror telemetry tail\n```"); + expect(guide).toContain("```bash\nopenclaw mirror telemetry replay\n```"); + expect(guide).toContain("```bash\nopenclaw mirror telemetry index\n```"); + expect(guide).toContain("```bash\nopenclaw mirror telemetry query\n```"); + expect(guide).toContain("```bash\nopenclaw mirror telemetry reflect\n```"); + }); +}); From 1f6f96d6363ba0d958d42fb3f7f31c165ca09971 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 21:14:00 -0400 Subject: [PATCH 32/75] test(docs): add CLI JSON schema boundary guardrails ## Summary - add a docs-of-code guardrail test for `docs/mirror/CLI_JSON_SCHEMAS.md` - verify the doc continues to describe the canonical standalone Mirror JSON automation surface - verify the doc continues to distinguish compatibility-only admin paths from canonical JSON automation ## Why This protects a high-signal automation-boundary reference from silently drifting away from the intended standalone Mirror JSON surface. ## Scope Changed: - test/mirror-cli-json-schemas-doc.test.ts Not changed: - runtime behavior - service/websocket behavior - CI/workflows - packaging files - other docs - compat implementation - install/bootstrap logic ## Verification - `pnpm vitest run test/mirror-cli-json-schemas-doc.test.ts` --- test/mirror-cli-json-schemas-doc.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/mirror-cli-json-schemas-doc.test.ts diff --git a/test/mirror-cli-json-schemas-doc.test.ts b/test/mirror-cli-json-schemas-doc.test.ts new file mode 100644 index 0000000000..434dbb1b9e --- /dev/null +++ b/test/mirror-cli-json-schemas-doc.test.ts @@ -0,0 +1,24 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror cli json schemas doc", () => { + it("keeps the canonical json automation boundary documented", () => { + const doc = fs.readFileSync( + path.join(process.cwd(), "docs/mirror/CLI_JSON_SCHEMAS.md"), + "utf8", + ); + + expect(doc).toContain( + "The canonical automation surface is `mirror ...`. `openclaw mirror ...` is compatibility-only.", + ); + + expect(doc).toContain("## `mirror status --json`"); + expect(doc).toContain("## `mirror serve --json`"); + expect(doc).toContain("## `mirror verify-lore --json`"); + + expect(doc).toContain( + "Compatibility-only admin paths such as `openclaw mirror doctor`, `passport`, and telemetry replay/index/query/reflect are not part of the canonical standalone Mirror JSON automation surface.", + ); + }); +}); From 858515ededbdcca9190eb2e659f0f8d856ca1b88 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 21:35:27 -0400 Subject: [PATCH 33/75] test(docs): add split-readiness checklist guardrails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - add a docs-of-code guardrail test for `docs/architecture/mirror-runtime-split-readiness-checklist.md` - refresh the checklist so it reflects the repo’s actual post-#171 split-readiness state - keep remaining known gaps focused on runtime ownership, observability, and parity seams rather than already-closed guardrail work ## Why This protects the repo’s own split-readiness source of truth from drifting behind the code and CI state. ## Scope Changed: - docs/architecture/mirror-runtime-split-readiness-checklist.md - test/mirror-split-readiness-checklist-doc.test.ts Not changed: - runtime behavior - service/websocket behavior - CI/workflows - packaging files - adapter-boundary work - recently closed guardrail seams ## Verification - `pnpm vitest run test/mirror-split-readiness-checklist-doc.test.ts` --- ...irror-runtime-split-readiness-checklist.md | 34 +++++++------- ...rror-split-readiness-checklist-doc.test.ts | 46 +++++++++++++++++++ 2 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 test/mirror-split-readiness-checklist-doc.test.ts diff --git a/docs/architecture/mirror-runtime-split-readiness-checklist.md b/docs/architecture/mirror-runtime-split-readiness-checklist.md index badeb7d624..2f3554c719 100644 --- a/docs/architecture/mirror-runtime-split-readiness-checklist.md +++ b/docs/architecture/mirror-runtime-split-readiness-checklist.md @@ -133,28 +133,29 @@ Current score: `yellow` - [x] `src/runtime/server.ts`, `src/runtime/brain-chat.ts`, `src/runtime/health.ts`, and `src/cli/mirror-cli.ts` are clearly marked as compatibility shims. - [x] Mirror-owned runtime modules do not read `OPENCLAW_*` env vars except in explicit compatibility files. - [x] Focused guardrail coverage exists for the main shims and the remaining legacy runtime entrypoints. -- [ ] Canonical operator docs and entrypoints point to Mirror-native paths first. +- [x] Canonical operator docs and entrypoints point to Mirror-native paths first. - [ ] Compatibility code is not the hidden owner of any required runtime behavior. Current reality: - Canonical Mirror-owned source no longer reads `OPENCLAW_*` directly outside tests and explicit compatibility paths. +- Canonical entrypoint, operator, and JSON automation docs now point to Mirror-native paths first and describe `openclaw mirror ...` as compatibility-only. - Compatibility edges are still materially present at entrypoint and wrapper level, but they are now better quarantined and guarded. -- This should remain yellow until the remaining compat entrypoints stop competing with canonical ownership in practice and docs/entrypoints point Mirror-native first. +- This should remain yellow until the remaining compat entrypoints stop competing with canonical ownership in practice and compatibility code stops owning any required behavior. ### 7. Packaging and build boundary -Current score: `red` +Current score: `yellow` -- [ ] Mirror has a first-class package and build boundary inside the repo. -- [ ] Mirror artifacts can be built and tested without treating OpenClaw as the primary product identity. -- [ ] Release and packaging paths for Mirror are explicit enough to survive a repo split. +- [x] Mirror has a first-class package and build boundary inside the repo. +- [x] Mirror artifacts can be built and tested without treating OpenClaw as the primary product identity. +- [x] Release and packaging paths for Mirror are explicit enough to survive a repo split. Current reality: -- Mirror runtime behavior is increasingly standalone. -- Package, bin, and release identity are still shared enough that splitting now would be premature. -- This is now a clearer next blocker than smoke-lane existence or basic compat-wrapper guardrails. +- Mirror now has an explicit package boundary, standalone Linux runtime artifact, extracted-artifact smoke, dist verification, and bootstrap verification. +- Package, bin, and release identity are materially more explicit and guarded than they were before the recent split-readiness PRs. +- This area should remain yellow because the package/build boundary is now real, but the remaining blockers are runtime ownership and observability rather than packaging viability. ### 8. CI gates before split @@ -162,14 +163,14 @@ Current score: `yellow` - [x] A dedicated Mirror runtime smoke lane exists. - [x] Split-critical runtime-boundary and daemon-truth tests are visible without depending on the full repo matrix. -- [ ] A boundary gate prevents new OpenClaw-specific env/config coupling inside Mirror-owned runtime modules. -- [ ] Mirror-specific checks are isolated enough to serve as a true split gate rather than an early smoke lane. +- [x] Boundary gates prevent new OpenClaw-specific env/config and import/package coupling inside Mirror-owned runtime modules. +- [x] Mirror-specific checks are isolated enough to serve as a true split gate rather than an early smoke lane. Current reality: - A dedicated Mirror-owned smoke target now exists and is wired into the dedicated Mirror runtime workflow. -- Runtime-boundary, daemon-truth, websocket, CLI/operator-truth, and compatibility-quarantine seams are now visible in a narrow CI lane. -- Dedicated split-readiness gates are still incomplete because env/config boundary enforcement is not yet a first-class CI gate. +- Runtime-boundary, daemon-truth, websocket, CLI/operator-truth, compatibility-quarantine, packaged-runtime truth, and OpenClaw env/import boundary seams are now visible in a narrow CI lane. +- Dedicated split-readiness gates now include first-class boundary enforcement for new OpenClaw-specific env/config and import/package coupling inside Mirror-owned modules. - macOS shard-order instability should be treated as a CI lane, not as a product seam in this checklist. ## Current known gaps @@ -179,10 +180,9 @@ The next small, high-signal gaps remain: 1. Move observability ownership under daemon/runtime control. 2. Make any remaining console execution seams explicitly daemon-backed where they are still partial. 3. Continue enriching daemon-owned runtime inspection only where sync/runtime understanding is still thin. -4. Create a Mirror-native package/build boundary. -5. Add a true CI boundary gate for new OpenClaw-specific env/config coupling inside Mirror-owned modules. -6. Keep parity coverage growing only where a canonical operator/runtime seam is still weak. -7. Continue shrinking compatibility entrypoints from “guarded” to “non-owning in practice.” +4. Keep parity coverage growing only where a canonical operator/runtime seam is still weak. +5. Continue shrinking compatibility entrypoints from “guarded” to “non-owning in practice.” +6. Keep package/release verification narrow and trustworthy while the remaining runtime seams are closed. ## Do Not Split Before diff --git a/test/mirror-split-readiness-checklist-doc.test.ts b/test/mirror-split-readiness-checklist-doc.test.ts new file mode 100644 index 0000000000..d1e67490ba --- /dev/null +++ b/test/mirror-split-readiness-checklist-doc.test.ts @@ -0,0 +1,46 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror split-readiness checklist doc", () => { + it("keeps the settled split-readiness guardrails reflected in the checklist", () => { + const doc = fs.readFileSync( + path.join(process.cwd(), "docs/architecture/mirror-runtime-split-readiness-checklist.md"), + "utf8", + ); + + expect(doc).toContain("### 6. Compatibility quarantine"); + expect(doc).toContain( + "- [x] Canonical operator docs and entrypoints point to Mirror-native paths first.", + ); + expect(doc).toContain( + "Canonical entrypoint, operator, and JSON automation docs now point to Mirror-native paths first and describe `openclaw mirror ...` as compatibility-only.", + ); + + expect(doc).toContain("### 7. Packaging and build boundary"); + expect(doc).toContain("Current score: `yellow`"); + expect(doc).toContain( + "Mirror now has an explicit package boundary, standalone Linux runtime artifact, extracted-artifact smoke, dist verification, and bootstrap verification.", + ); + + expect(doc).toContain("### 8. CI gates before split"); + expect(doc).toContain( + "- [x] Boundary gates prevent new OpenClaw-specific env/config and import/package coupling inside Mirror-owned runtime modules.", + ); + expect(doc).toContain( + "- [x] Mirror-specific checks are isolated enough to serve as a true split gate rather than an early smoke lane.", + ); + expect(doc).toContain( + "Dedicated split-readiness gates now include first-class boundary enforcement for new OpenClaw-specific env/config and import/package coupling inside Mirror-owned modules.", + ); + + expect(doc).toContain("## Current known gaps"); + expect(doc).toContain("1. Move observability ownership under daemon/runtime control."); + expect(doc).toContain( + "2. Make any remaining console execution seams explicitly daemon-backed where they are still partial.", + ); + expect(doc).toContain( + "4. Keep parity coverage growing only where a canonical operator/runtime seam is still weak.", + ); + }); +}); From 4492de2248b8ec11e10ce3a9f4f513ccf79c52f3 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 21:38:37 -0400 Subject: [PATCH 34/75] test(docs): add standalone packaging boundary guardrails ## Summary - add a docs-of-code guardrail test for `packaging/mirror-runtime/README.md` - verify the packaging README continues to document the standalone Mirror runtime artifact shape and verification surface - fix one README drift item so the tarball artifact name is explicitly documented ## Why This protects the standalone packaging release surface from silently drifting away from the actual packaged Mirror runtime artifact. ## Scope Changed: - packaging/mirror-runtime/README.md - test/mirror-packaging-readme-doc.test.ts Not changed: - runtime behavior - service/websocket behavior - CI/workflows - packaging logic - adapter-boundary work - other docs ## Verification - `pnpm vitest run test/mirror-packaging-readme-doc.test.ts` --- packaging/mirror-runtime/README.md | 2 +- test/mirror-packaging-readme-doc.test.ts | 26 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 test/mirror-packaging-readme-doc.test.ts diff --git a/packaging/mirror-runtime/README.md b/packaging/mirror-runtime/README.md index 5e30f1b6ca..107d1a5f94 100644 --- a/packaging/mirror-runtime/README.md +++ b/packaging/mirror-runtime/README.md @@ -37,7 +37,7 @@ The package boundary intentionally excludes repository-only content: ## Linux-first Distribution Shape -The generated distribution tree is: +The generated distribution tree inside `mirror-runtime-linux.tar.gz` is: ```text dist/mirror-runtime-linux/ diff --git a/test/mirror-packaging-readme-doc.test.ts b/test/mirror-packaging-readme-doc.test.ts new file mode 100644 index 0000000000..a30b033569 --- /dev/null +++ b/test/mirror-packaging-readme-doc.test.ts @@ -0,0 +1,26 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror packaging readme doc", () => { + it("keeps the standalone packaging boundary documented", () => { + const readme = fs.readFileSync( + path.join(process.cwd(), "packaging/mirror-runtime/README.md"), + "utf8", + ); + + expect(readme).toContain("/opt/mirror-runtime"); + + expect(readme).toContain("- `bin/mirror`"); + expect(readme).toContain("- `mirror.mjs`"); + expect(readme).toContain("- `dist/mirror-entry.js`"); + expect(readme).toContain("- `dist/mirror-package.js`"); + + expect(readme).toContain("dist/mirror-runtime-linux/"); + expect(readme).toContain("mirror-runtime-linux.tar.gz"); + + expect(readme).toContain("pnpm package:mirror-runtime"); + expect(readme).toContain("pnpm verify:mirror-runtime-dist"); + expect(readme).toContain("pnpm verify:mirror-runtime-bootstrap"); + }); +}); From a3e3747f9a9041623fcbc82a0137d14db05a5970 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 21:39:43 -0400 Subject: [PATCH 35/75] test(docs): add Mirror method boundary guardrails ## Summary - add a docs-of-code guardrail test for `docs/mirror/MIRROR_METHOD.md` - verify the doc continues to describe Mirror as the canonical standalone identity - verify the doc continues to distinguish compatibility-only `openclaw mirror ...` flows from canonical standalone Mirror commands ## Why This protects the standalone Mirror identity boundary from silently drifting away from the documented canonical-vs-compat operator surface. ## Scope Changed: - test/mirror-method-doc.test.ts Not changed: - runtime behavior - service/websocket behavior - CI/workflows - packaging logic - adapter-boundary work - other docs ## Verification - `pnpm vitest run test/mirror-method-doc.test.ts` --- test/mirror-method-doc.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/mirror-method-doc.test.ts diff --git a/test/mirror-method-doc.test.ts b/test/mirror-method-doc.test.ts new file mode 100644 index 0000000000..82f195dfe8 --- /dev/null +++ b/test/mirror-method-doc.test.ts @@ -0,0 +1,23 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror method doc", () => { + it("keeps the standalone mirror identity boundary documented", () => { + const doc = fs.readFileSync(path.join(process.cwd(), "docs/mirror/MIRROR_METHOD.md"), "utf8"); + + expect(doc).toContain( + "Mirror is the canonical identity for the standalone runtime, CLI, service, and console surfaces.", + ); + expect(doc).toContain( + "`openclaw mirror ...` exists only as a compatibility wrapper for legacy operational flows.", + ); + + expect(doc).toContain( + "Canonical standalone operator commands include `mirror status`, `mirror verify-lore`, and `mirror sync ...`.", + ); + expect(doc).toContain( + "Compatibility-only admin flows remain under `openclaw mirror doctor|passport|telemetry ...`.", + ); + }); +}); From 80093c4fc3efeecd18c04a3becc212fe116db15f Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 21:49:28 -0400 Subject: [PATCH 36/75] test(boundary): add standalone help-surface guardrails ## Summary - add a static guardrail test for the standalone shipped help surface - verify the canonical help surface remains Mirror-native - verify compatibility wording remains secondary and compatibility-only ## Why This protects the standalone CLI release surface from drifting back toward OpenClaw-first framing. ## Scope Changed: - test/mirror-help-surface-boundary.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - help-text source files ## Details The test: - reads `src/mirror-entry.ts` as the standalone help-surface owner - reads `src/mirror-cli/schemas.ts` for the canonical command inventory - verifies the help surface presents `mirror` as the standalone command - verifies canonical commands include `status`, `verify-lore`, and `sync` - verifies compatibility wording remains secondary / compatibility-only ## Verification - `pnpm vitest run test/mirror-help-surface-boundary.test.ts` --- test/mirror-help-surface-boundary.test.ts | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/mirror-help-surface-boundary.test.ts diff --git a/test/mirror-help-surface-boundary.test.ts b/test/mirror-help-surface-boundary.test.ts new file mode 100644 index 0000000000..404f40ea6f --- /dev/null +++ b/test/mirror-help-surface-boundary.test.ts @@ -0,0 +1,36 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone help surface", () => { + it("keeps the canonical help surface Mirror-native", () => { + const entrySource = fs.readFileSync(path.join(process.cwd(), "src/mirror-entry.ts"), "utf8"); + const schemaSource = fs.readFileSync( + path.join(process.cwd(), "src/mirror-cli/schemas.ts"), + "utf8", + ); + + expect(entrySource).toContain("Usage:"); + expect(entrySource).toContain("mirror help [command]"); + expect(entrySource).toContain("mirror [options]"); + + expect(entrySource).toContain("Commands:"); + expect(entrySource).toContain("Compatibility:"); + expect(entrySource).toContain( + "\\`openclaw mirror ...\\` remains available for compatibility-only diagnostics flows.", + ); + + const compatibilityIndex = entrySource.indexOf("Compatibility:"); + const mirrorHelpIndex = entrySource.indexOf("mirror help [command]"); + const mirrorCommandIndex = entrySource.indexOf("mirror [options]"); + + expect(mirrorHelpIndex).toBeGreaterThanOrEqual(0); + expect(mirrorCommandIndex).toBeGreaterThanOrEqual(0); + expect(compatibilityIndex).toBeGreaterThan(mirrorHelpIndex); + expect(compatibilityIndex).toBeGreaterThan(mirrorCommandIndex); + + expect(schemaSource).toContain('command: "status"'); + expect(schemaSource).toContain('command: "verify-lore"'); + expect(schemaSource).toContain('command: "sync"'); + }); +}); From 108664bc3f489bbc71f6d7d2d6f1ae4568ce0b32 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 21:57:25 -0400 Subject: [PATCH 37/75] test(boundary): add standalone env-surface guardrails ## Summary - add a static guardrail test for the standalone env/help surface - verify the canonical env surface remains Mirror-native - verify compatibility wording remains secondary and compatibility-only ## Why This protects the standalone CLI/runtime release surface from drifting back toward OpenClaw-named environment framing. ## Scope Changed: - test/mirror-env-surface-boundary.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - source/help text files ## Details The test: - reads `src/mirror-entry.ts` as the standalone env/help owner - verifies the standalone surface documents: - `MIRROR_PROVIDER_URL` - `MIRROR_PROVIDER_AUTH_TOKEN` - `MIRROR_OPERATOR_TOKEN` - `MIRROR_LORE_DIR` - verifies OpenClaw-named env vars are not presented as canonical standalone env - verifies compatibility wording remains secondary / compatibility-only ## Verification - `pnpm vitest run test/mirror-env-surface-boundary.test.ts` --- test/mirror-env-surface-boundary.test.ts | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/mirror-env-surface-boundary.test.ts diff --git a/test/mirror-env-surface-boundary.test.ts b/test/mirror-env-surface-boundary.test.ts new file mode 100644 index 0000000000..8e71d4ddc8 --- /dev/null +++ b/test/mirror-env-surface-boundary.test.ts @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone env surface", () => { + it("keeps the canonical env/help surface Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-entry.ts"), "utf8"); + + expect(source).toContain("Environment:"); + expect(source).toContain("MIRROR_PROVIDER_URL"); + expect(source).toContain("MIRROR_PROVIDER_AUTH_TOKEN"); + expect(source).toContain("MIRROR_OPERATOR_TOKEN"); + expect(source).toContain("MIRROR_LORE_DIR"); + + expect(source).not.toContain("OPENCLAW_PROVIDER_URL"); + expect(source).not.toContain("OPENCLAW_PROVIDER_AUTH_TOKEN"); + expect(source).not.toContain("OPENCLAW_OPERATOR_TOKEN"); + expect(source).not.toContain("OPENCLAW_LORE_DIR"); + + expect(source).toContain("Compatibility:"); + expect(source).toContain( + "\\`openclaw mirror ...\\` remains available for compatibility-only diagnostics flows.", + ); + + const environmentIndex = source.indexOf("Environment:"); + const compatibilityIndex = source.indexOf("Compatibility:"); + + expect(environmentIndex).toBeGreaterThanOrEqual(0); + expect(compatibilityIndex).toBeGreaterThan(environmentIndex); + }); +}); From a2ab0f5f88000195eea5dfd2797d98e14b3c1594 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:01:25 -0400 Subject: [PATCH 38/75] test(boundary): add standalone launcher-surface guardrails ## Summary - add a static guardrail test for the standalone shipped launcher surface - verify the launcher remains Mirror-native - verify the launcher continues to hand off cleanly to the standalone dist entrypoint ## Why This protects the standalone launcher release surface from drifting away from the intended Mirror-native entrypoint wiring. ## Scope Changed: - test/mirror-launcher-surface-boundary.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - launcher source files ## Details The test: - reads `mirror.mjs` as the standalone launcher owner - verifies the launcher loads `./dist/mirror-entry.js` and `./dist/mirror-entry.mjs` - verifies the launcher expects `runMirrorEntry()` from the dist entry - verifies the launcher forwards `process.argv` - verifies launcher failure text remains Mirror-native and not OpenClaw-branded ## Verification - `pnpm vitest run test/mirror-launcher-surface-boundary.test.ts` --- test/mirror-launcher-surface-boundary.test.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/mirror-launcher-surface-boundary.test.ts diff --git a/test/mirror-launcher-surface-boundary.test.ts b/test/mirror-launcher-surface-boundary.test.ts new file mode 100644 index 0000000000..185fcbd2b4 --- /dev/null +++ b/test/mirror-launcher-surface-boundary.test.ts @@ -0,0 +1,21 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone launcher surface", () => { + it("keeps the shipped launcher Mirror-native and wired to the standalone entrypoint", () => { + const source = fs.readFileSync(path.join(process.cwd(), "mirror.mjs"), "utf8"); + + expect(source).toContain('tryImport("./dist/mirror-entry.js")'); + expect(source).toContain('tryImport("./dist/mirror-entry.mjs")'); + + expect(source).toContain('typeof mod.runMirrorEntry !== "function"'); + expect(source).toContain("mod.runMirrorEntry(process.argv)"); + + expect(source).toContain("mirror: missing dist/mirror-entry.(m)js (build output)."); + expect(source).toContain("mirror: dist/mirror-entry does not export runMirrorEntry()."); + + expect(source).not.toContain("openclaw:"); + expect(source).not.toContain("OPENCLAW"); + }); +}); From 8f6bfaf0bf900cb2ce2f9c17fb76a7f669b2b98d Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:08:10 -0400 Subject: [PATCH 39/75] test(boundary): add standalone package-entry guardrails ## Summary - add a static guardrail test for the standalone package-facing entry surface - verify the canonical package entry remains Mirror-native - verify the package-facing entry does not drift back toward compat/OpenClaw ownership ## Why This protects the standalone package release surface from drifting away from the intended Mirror-native entry wiring. ## Scope Changed: - test/mirror-package-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - package-entry source files ## Details The test: - reads `src/mirror-package.ts` as the standalone package-entry owner - verifies it exports Mirror-native entry surfaces from: - `./mirrordaemon/index.js` - `./mirror-service/index.js` - `./mirror-runtime/index.js` - `./mirror-provider/index.js` - `./mirror-gateway/index.js` - `./mirror-cli/index.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `./compat/` - `./openclaw` ## Verification - `pnpm vitest run test/mirror-package-entry-surface.test.ts` --- test/mirror-package-entry-surface.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/mirror-package-entry-surface.test.ts diff --git a/test/mirror-package-entry-surface.test.ts b/test/mirror-package-entry-surface.test.ts new file mode 100644 index 0000000000..8af4b2b6b4 --- /dev/null +++ b/test/mirror-package-entry-surface.test.ts @@ -0,0 +1,21 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone package entry surface", () => { + it("keeps the canonical package-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-package.ts"), "utf8"); + + expect(source).toContain('export * from "./mirrordaemon/index.js";'); + expect(source).toContain('export * from "./mirror-service/index.js";'); + expect(source).toContain('export * from "./mirror-runtime/index.js";'); + expect(source).toContain('export * from "./mirror-provider/index.js";'); + expect(source).toContain('export * from "./mirror-gateway/index.js";'); + expect(source).toContain('export * from "./mirror-cli/index.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain('export * from "./compat/'); + expect(source).not.toContain('export * from "./openclaw'); + }); +}); From a9fe79128318fe735fd9d1fc412c73ec8c9582c9 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:10:19 -0400 Subject: [PATCH 40/75] test(boundary): add standalone service-entry guardrails ## Summary - add a static guardrail test for the standalone service-facing entry surface - verify the canonical service entry remains Mirror-native - verify the service-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone service release surface from drifting away from the intended Mirror-native service entry wiring. ## Scope Changed: - test/mirror-service-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - service-entry source files ## Details The test: - reads `src/mirror-service/index.ts` as the standalone service-entry owner - verifies it re-exports Mirror-native service/runtime-host surfaces from: - `./config.js` - `./lifecycle.js` - `./mirror_service.js` - `./runtime_host.js` - `./runtime_events_ws.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `./runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-service-entry-surface.test.ts` --- test/mirror-service-entry-surface.test.ts | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/mirror-service-entry-surface.test.ts diff --git a/test/mirror-service-entry-surface.test.ts b/test/mirror-service-entry-surface.test.ts new file mode 100644 index 0000000000..e535484c4d --- /dev/null +++ b/test/mirror-service-entry-surface.test.ts @@ -0,0 +1,29 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone service entry surface", () => { + it("keeps the canonical service-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-service/index.ts"), "utf8"); + + expect(source).toContain( + 'export { loadMirrorServiceConfig, type MirrorServiceConfig } from "./config.js";', + ); + expect(source).toContain( + 'export { initializeMirrorServiceLifecycle, type MirrorServiceLifecycle } from "./lifecycle.js";', + ); + expect(source).toContain( + 'export { startMirrorService, type MirrorService } from "./mirror_service.js";', + ); + expect(source).toContain( + 'export { createMirrorRuntimeHost, type MirrorRuntimeHost } from "./runtime_host.js";', + ); + expect(source).toContain('from "./runtime_events_ws.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain("./runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 12964b6aabfcf538ce6767b89c71c37030864100 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:20:46 -0400 Subject: [PATCH 41/75] test(boundary): add standalone CLI-entry guardrails ## Summary - add a static guardrail test for the standalone CLI-facing entry surface - verify the canonical CLI entry remains Mirror-native - verify the CLI-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone CLI release surface from drifting away from the intended Mirror-native CLI entry wiring. ## Scope Changed: - test/mirror-cli-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - CLI-entry source files ## Details The test: - reads `src/mirror-cli/index.ts` as the standalone CLI-entry owner - verifies it exports: - `runMirrorCli` from `./mirror_cli.js` - canonical parsing/execution surfaces from `./commands.js` - formatting from `./output.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../cli/` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-cli-entry-surface.test.ts` --- test/mirror-cli-entry-surface.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/mirror-cli-entry-surface.test.ts diff --git a/test/mirror-cli-entry-surface.test.ts b/test/mirror-cli-entry-surface.test.ts new file mode 100644 index 0000000000..8fd745bcc7 --- /dev/null +++ b/test/mirror-cli-entry-surface.test.ts @@ -0,0 +1,21 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone cli entry surface", () => { + it("keeps the canonical CLI-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-cli/index.ts"), "utf8"); + + expect(source).toContain('export { runMirrorCli } from "./mirror_cli.js";'); + expect(source).toContain("executeMirrorCliCommand"); + expect(source).toContain("parseMirrorCliArgs"); + expect(source).toContain('} from "./commands.js";'); + expect(source).toContain('export { formatMirrorCliResult } from "./output.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../cli/"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 7a55474a9bef770a880d27f836c57a8316b1a6cf Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:24:56 -0400 Subject: [PATCH 42/75] test(boundary): add standalone runtime-entry guardrails ## Summary - add a static guardrail test for the standalone runtime-facing entry surface - verify the canonical runtime entry remains Mirror-native - verify the runtime-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone runtime release surface from drifting away from the intended Mirror-native runtime entry wiring. ## Scope Changed: - test/mirror-runtime-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - runtime-entry source files ## Details The test: - reads `src/mirror-runtime/index.ts` as the standalone runtime-entry owner - verifies it exports Mirror-native chat/runtime surfaces from `./mirror_chat_engine.js` - verifies it exports Mirror-native correlation surfaces from `./correlation.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-runtime-entry-surface.test.ts` --- test/mirror-runtime-entry-surface.test.ts | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/mirror-runtime-entry-surface.test.ts diff --git a/test/mirror-runtime-entry-surface.test.ts b/test/mirror-runtime-entry-surface.test.ts new file mode 100644 index 0000000000..3127a976f0 --- /dev/null +++ b/test/mirror-runtime-entry-surface.test.ts @@ -0,0 +1,27 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone runtime entry surface", () => { + it("keeps the canonical runtime-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-runtime/index.ts"), "utf8"); + + expect(source).toContain("prepareMirrorChatRequest"); + expect(source).toContain("executeMirrorChatRequest"); + expect(source).toContain("executeMirrorChatWithProvider"); + expect(source).toContain("executeMirrorChatWithProviderPlane"); + expect(source).toContain('from "./mirror_chat_engine.js";'); + + expect(source).toContain("buildMirrorCorrelationFromPolicyContext"); + expect(source).toContain("getMirrorTraceIdFromPolicyContext"); + expect(source).toContain("mergeMirrorCorrelation"); + expect(source).toContain("resolveMirrorTraceId"); + expect(source).toContain("withMirrorCorrelation"); + expect(source).toContain('from "./correlation.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 4a0a04b66a6abdd056a17b7bc6649c881a77a132 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:27:20 -0400 Subject: [PATCH 43/75] test(boundary): add standalone provider-entry guardrails ## Summary - add a static guardrail test for the standalone provider-facing entry surface - verify the canonical provider entry remains Mirror-native - verify the provider-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone provider/runtime release surface from drifting away from the intended Mirror-native provider entry wiring. ## Scope Changed: - test/mirror-provider-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - provider-entry source files ## Details The test: - reads `src/mirror-provider/index.ts` as the standalone provider-entry owner - verifies it exports Mirror-native provider request/auth/plane surfaces from: - `./mirror_provider.js` - `./provider_auth.js` - `./provider_plane.js` - `./provider_request.js` - `./provider_response.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-provider-entry-surface.test.ts` --- test/mirror-provider-entry-surface.test.ts | 31 ++++++++++++++++++++++ test/mirrordaemon-entry-surface.test.ts | 27 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/mirror-provider-entry-surface.test.ts create mode 100644 test/mirrordaemon-entry-surface.test.ts diff --git a/test/mirror-provider-entry-surface.test.ts b/test/mirror-provider-entry-surface.test.ts new file mode 100644 index 0000000000..b3985caaa9 --- /dev/null +++ b/test/mirror-provider-entry-surface.test.ts @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone provider entry surface", () => { + it("keeps the canonical provider-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-provider/index.ts"), + "utf8", + ); + + expect(source).toContain( + 'export { executeMirrorProviderRequest, type FetchLike } from "./mirror_provider.js";', + ); + expect(source).toContain('export { buildMirrorProviderHeaders } from "./provider_auth.js";'); + expect(source).toContain("buildPrimaryProviderDescriptorFromConfig"); + expect(source).toContain("createMirrorProviderPlane"); + expect(source).toContain('} from "./provider_plane.js";'); + expect(source).toContain( + 'export type { MirrorProviderConfig, MirrorProviderRequest } from "./provider_request.js";', + ); + expect(source).toContain( + 'export type { MirrorProviderResponse } from "./provider_response.js";', + ); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); diff --git a/test/mirrordaemon-entry-surface.test.ts b/test/mirrordaemon-entry-surface.test.ts new file mode 100644 index 0000000000..399950b118 --- /dev/null +++ b/test/mirrordaemon-entry-surface.test.ts @@ -0,0 +1,27 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirrordaemon standalone entry surface", () => { + it("keeps the canonical daemon-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirrordaemon/index.ts"), "utf8"); + + expect(source).toContain( + 'export { createMirrordaemon, type Mirrordaemon } from "./mirrordaemon.js";', + ); + expect(source).toContain('export { createRuntimeEventStream } from "./event_stream.js";'); + expect(source).toContain("getMirrordaemonRuntimeState"); + expect(source).toContain("getMirrordaemonHealthState"); + expect(source).toContain("getMirrordaemonActionsState"); + expect(source).toContain("getMirrordaemonProvidersState"); + expect(source).toContain("getMirrordaemonSyncState"); + expect(source).toContain('export { getMirrordaemonDebugState } from "./debug_api.js";'); + expect(source).toContain("buildRuntimeSummary"); + expect(source).toContain("buildStatusPayload"); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 6e60a13f5a95c0080facd82f7cf42965182a448c Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:35:12 -0400 Subject: [PATCH 44/75] test(boundary): add standalone daemon-entry guardrails ## Summary - add a static guardrail test for the standalone daemon-facing entry surface - verify the canonical daemon entry remains Mirror-native - verify the daemon-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone daemon/runtime release surface from drifting away from the intended Mirror-native daemon entry wiring. ## Scope Changed: - test/mirrordaemon-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - daemon-entry source files ## Details The test: - reads `src/mirrordaemon/index.ts` as the standalone daemon-entry owner - verifies it exports Mirror-native daemon/runtime-state/debug/status/event-stream surfaces - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirrordaemon-entry-surface.test.ts` From 422f884a6e7e28fc80fb62c7b4eb864bac30de2b Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:43:00 -0400 Subject: [PATCH 45/75] test(boundary): add standalone gateway-entry guardrails ## Summary - add a static guardrail test for the standalone gateway-facing entry surface - verify the canonical gateway entry remains Mirror-native - verify the gateway-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone gateway release surface from drifting away from the intended Mirror-native gateway entry wiring. ## Scope Changed: - test/mirror-gateway-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - gateway-entry source files ## Details The test: - reads `src/mirror-gateway/index.ts` as the standalone gateway-entry owner - verifies it exports Mirror-native gateway/router/auth surfaces from: - `./mirror_gateway.js` - `./routes.js` - `./auth.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-gateway-entry-surface.test.ts` --- test/mirror-gateway-entry-surface.test.ts | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/mirror-gateway-entry-surface.test.ts diff --git a/test/mirror-gateway-entry-surface.test.ts b/test/mirror-gateway-entry-surface.test.ts new file mode 100644 index 0000000000..63c3a1e602 --- /dev/null +++ b/test/mirror-gateway-entry-surface.test.ts @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone gateway entry surface", () => { + it("keeps the canonical gateway-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-gateway/index.ts"), "utf8"); + + expect(source).toContain( + 'export { createMirrorGateway, type MirrorGateway } from "./mirror_gateway.js";', + ); + expect(source).toContain("createMirrorGatewayHandlers"); + expect(source).toContain("createMirrorGatewayRouter"); + expect(source).toContain("validateMirrorToolInput"); + expect(source).toContain('} from "./routes.js";'); + expect(source).toContain("authorizeMirrorToolAccess"); + expect(source).toContain("authorizeMirrorToolRequest"); + expect(source).toContain("getMirrorOperatorToken"); + expect(source).toContain("readMirrorRequestToken"); + expect(source).toContain("requiresMirrorOperatorAuth"); + expect(source).toContain('} from "./auth.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From aab05a767795c7d818ec5971d3fb87eeee581267 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:49:12 -0400 Subject: [PATCH 46/75] test(boundary): add standalone policy-entry guardrails ## Summary - add a static guardrail test for the standalone policy-facing entry surface - verify the canonical policy entry remains Mirror-native - verify the policy-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone policy release surface from drifting away from the intended Mirror-native policy entry wiring. ## Scope Changed: - test/mirror-policy-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - policy-entry source files ## Details The test: - reads `src/mirror-policy/index.ts` as the standalone policy-entry owner - verifies it exports Mirror-native policy/authz surfaces from: - `./default_rules.js` - `./mutable_surfaces.js` - `./policy_engine.js` - `./policy_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-policy-entry-surface.test.ts` --- test/mirror-policy-entry-surface.test.ts | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/mirror-policy-entry-surface.test.ts diff --git a/test/mirror-policy-entry-surface.test.ts b/test/mirror-policy-entry-surface.test.ts new file mode 100644 index 0000000000..3dd20c7060 --- /dev/null +++ b/test/mirror-policy-entry-surface.test.ts @@ -0,0 +1,30 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone policy entry surface", () => { + it("keeps the canonical policy-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-policy/index.ts"), "utf8"); + + expect(source).toContain("createDefaultMirrorPolicyRules"); + expect(source).toContain("createMirrorMutableSurfacePolicyRule"); + expect(source).toContain("createMirrorOperatorAccessPolicyRule"); + expect(source).toContain('} from "./default_rules.js";'); + expect(source).toContain("isMirrorLocalOnlySurface"); + expect(source).toContain("isMirrorMutableActionName"); + expect(source).toContain("isMirrorNetworkExposedSurface"); + expect(source).toContain('} from "./mutable_surfaces.js";'); + expect(source).toContain("createMirrorPolicyEngine"); + expect(source).toContain("ensureMirrorPolicyAllowed"); + expect(source).toContain("MirrorPolicyDeniedError"); + expect(source).toContain('} from "./policy_engine.js";'); + expect(source).toContain("buildMirrorActionPolicyTarget"); + expect(source).toContain("type MirrorPolicyTarget"); + expect(source).toContain('} from "./policy_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From ecc7844618d83f6d957317d4d27c203df755b75c Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:50:32 -0400 Subject: [PATCH 47/75] test(boundary): add standalone actions-entry guardrails ## Summary - add a static guardrail test for the standalone actions-facing entry surface - verify the canonical actions entry remains Mirror-native - verify the actions-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone actions/runtime release surface from drifting away from the intended Mirror-native actions entry wiring. ## Scope Changed: - test/mirror-actions-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - actions-entry source files ## Details The test: - reads `src/mirror-actions/index.ts` as the standalone actions-entry owner - verifies it exports Mirror-native action registry/execution surfaces from: - `./action_runtime.js` - `./skill_bridge.js` - `./action_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-actions-entry-surface.test.ts` --- test/mirror-actions-entry-surface.test.ts | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/mirror-actions-entry-surface.test.ts diff --git a/test/mirror-actions-entry-surface.test.ts b/test/mirror-actions-entry-surface.test.ts new file mode 100644 index 0000000000..ece98e7443 --- /dev/null +++ b/test/mirror-actions-entry-surface.test.ts @@ -0,0 +1,24 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone actions entry surface", () => { + it("keeps the canonical actions-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-actions/index.ts"), "utf8"); + + expect(source).toContain('export { createMirrorActionRuntime } from "./action_runtime.js";'); + expect(source).toContain("createMirrorActionsFromTools"); + expect(source).toContain("createMirrorToolRegistryFromActionRuntime"); + expect(source).toContain('} from "./skill_bridge.js";'); + expect(source).toContain("MirrorAction"); + expect(source).toContain("MirrorActionExecutionRequest"); + expect(source).toContain("MirrorActionExecutionResult"); + expect(source).toContain("MirrorActionRuntime"); + expect(source).toContain('} from "./action_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 1892e599072e5773c2383fefb284f48bbce57835 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:53:35 -0400 Subject: [PATCH 48/75] test(boundary): add standalone sync-entry guardrails ## Summary - add a static guardrail test for the standalone sync-facing entry surface - verify the canonical sync entry remains Mirror-native - verify the sync-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone sync release surface from drifting away from the intended Mirror-native sync entry wiring. ## Scope Changed: - test/mirror-sync-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - sync-entry source files ## Details The test: - reads `src/mirror-sync/index.ts` as the standalone sync-entry owner - verifies it exports Mirror-native sync surfaces from: - `./sync_manager.js` - `./peer_registry.js` - `./canon_sync.js` - `./graph_sync.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-sync-entry-surface.test.ts` --- test/mirror-sync-entry-surface.test.ts | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/mirror-sync-entry-surface.test.ts diff --git a/test/mirror-sync-entry-surface.test.ts b/test/mirror-sync-entry-surface.test.ts new file mode 100644 index 0000000000..59d604e215 --- /dev/null +++ b/test/mirror-sync-entry-surface.test.ts @@ -0,0 +1,33 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone sync entry surface", () => { + it("keeps the canonical sync-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-sync/index.ts"), "utf8"); + + expect(source).toContain("createMirrorSyncManager"); + expect(source).toContain("createMirrorSyncHandlers"); + expect(source).toContain("createMirrorSyncRouter"); + expect(source).toContain("executeMirrorSyncAction"); + expect(source).toContain("parseMirrorSyncAnnounceInput"); + expect(source).toContain("parseMirrorSyncPullInput"); + expect(source).toContain("parseMirrorSyncUpdatesInput"); + expect(source).toContain("wrapMirrorSyncPullResponse"); + expect(source).toContain('} from "./sync_manager.js";'); + expect(source).toContain( + 'export { createMirrorPeerRegistry, type MirrorPeerRegistry } from "./peer_registry.js";', + ); + expect(source).toContain("collectLocalCanonUpdates"); + expect(source).toContain("applyRemoteCanonUpdates"); + expect(source).toContain('} from "./canon_sync.js";'); + expect(source).toContain("collectLocalGraphMetadata"); + expect(source).toContain("syncLocalGraphFromRemote"); + expect(source).toContain('} from "./graph_sync.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 2306706a4cfc7b8da559abd55f2f54ac5f40c033 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 22:58:46 -0400 Subject: [PATCH 49/75] test(boundary): add standalone observability-entry guardrails ## Summary - add a static guardrail test for the standalone observability-facing entry surface - verify the canonical observability entry remains Mirror-native - verify the observability-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone observability surface from drifting away from the intended Mirror-native observability entry wiring. ## Scope Changed: - test/mirror-observability-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - observability-entry source files ## Details The test: - reads `src/mirror-observability/index.ts` as the standalone observability-entry owner - verifies it exports Mirror-native observability surfaces from: - `./context.js` - `./metrics.js` - `./diagnostics.js` - `./tracing.js` - `./observability_server.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-observability-entry-surface.test.ts` --- ...mirror-observability-entry-surface.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/mirror-observability-entry-surface.test.ts diff --git a/test/mirror-observability-entry-surface.test.ts b/test/mirror-observability-entry-surface.test.ts new file mode 100644 index 0000000000..ac8e199e80 --- /dev/null +++ b/test/mirror-observability-entry-surface.test.ts @@ -0,0 +1,34 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone observability entry surface", () => { + it("keeps the canonical observability-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-observability/index.ts"), + "utf8", + ); + + expect(source).toContain("createMirrorObservabilityContext"); + expect(source).toContain("getCurrentMirrorObservabilityContext"); + expect(source).toContain("getDefaultMirrorObservabilityContext"); + expect(source).toContain("runWithMirrorObservabilityContext"); + expect(source).toContain('} from "./context.js";'); + expect(source).toContain("getMirrorMetrics"); + expect(source).toContain("incrementMetric"); + expect(source).toContain("recordLatency"); + expect(source).toContain('} from "./metrics.js";'); + expect(source).toContain("getMirrorDiagnostics"); + expect(source).toContain("recordDiagnosticEvent"); + expect(source).toContain('} from "./diagnostics.js";'); + expect(source).toContain('export { logMirrorEvent } from "./tracing.js";'); + expect(source).toContain("createMirrorObservabilityHandlers"); + expect(source).toContain("createMirrorObservabilityRouter"); + expect(source).toContain('} from "./observability_server.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 1ccefa3ce8d1a733f1834ba53fd9a63163cb3aee Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 23:01:28 -0400 Subject: [PATCH 50/75] test(boundary): add standalone user-workspace-entry guardrails ## Summary - add a static guardrail test for the standalone user-workspace-facing entry surface - verify the canonical user-workspace entry remains Mirror-native - verify the user-workspace-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone user-workspace surface from drifting away from the intended Mirror-native workspace entry wiring. ## Scope Changed: - test/mirror-user-workspace-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - user-workspace-entry source files ## Details The test: - reads `src/mirror-user-workspace/index.ts` as the standalone user-workspace-entry owner - verifies it exports Mirror-native workspace surfaces from: - `./workspace_manager.js` - `./workspace_store.js` - `./workspace_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-user-workspace-entry-surface.test.ts` --- ...irror-user-workspace-entry-surface.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/mirror-user-workspace-entry-surface.test.ts diff --git a/test/mirror-user-workspace-entry-surface.test.ts b/test/mirror-user-workspace-entry-surface.test.ts new file mode 100644 index 0000000000..5034c1affc --- /dev/null +++ b/test/mirror-user-workspace-entry-surface.test.ts @@ -0,0 +1,32 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone user-workspace entry surface", () => { + it("keeps the canonical user-workspace-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-user-workspace/index.ts"), + "utf8", + ); + + expect(source).toContain( + 'export { createMirrorWorkspaceManager } from "./workspace_manager.js";', + ); + expect(source).toContain("createMirrorWorkspaceStore"); + expect(source).toContain("resolveMirrorWorkspaceUsersRoot"); + expect(source).toContain("resolveUserWorkspacePaths"); + expect(source).toContain("sanitizeMirrorWorkspaceUserId"); + expect(source).toContain('} from "./workspace_store.js";'); + expect(source).toContain("MirrorHeartbeatPreferences"); + expect(source).toContain("MirrorUserTask"); + expect(source).toContain("MirrorUserWorkspace"); + expect(source).toContain("MirrorWorkspaceManager"); + expect(source).toContain("MirrorWorkspaceStore"); + expect(source).toContain('} from "./workspace_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 67b3729318da63b71adb68b13da601ee0f37f2d1 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 23:05:09 -0400 Subject: [PATCH 51/75] test(boundary): add standalone console-entry guardrails ## Summary - add a static guardrail test for the standalone console-facing entry surface - verify the canonical console entry remains Mirror-native - verify the console-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone console surface from drifting away from the intended Mirror-native console entry wiring. ## Scope Changed: - test/mirror-console-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - console-entry source files ## Details The test: - reads `src/mirror-console/index.ts` as the standalone console-entry owner - verifies it exports Mirror-native console surfaces from: - `./console_routes.js` - `./console_static.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-console-entry-surface.test.ts` --- test/mirror-console-entry-surface.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/mirror-console-entry-surface.test.ts diff --git a/test/mirror-console-entry-surface.test.ts b/test/mirror-console-entry-surface.test.ts new file mode 100644 index 0000000000..fbb789fba6 --- /dev/null +++ b/test/mirror-console-entry-surface.test.ts @@ -0,0 +1,20 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone console entry surface", () => { + it("keeps the canonical console-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-console/index.ts"), "utf8"); + + expect(source).toContain("createMirrorConsoleHandlers"); + expect(source).toContain("createMirrorConsoleRouter"); + expect(source).toContain("createMirrorConsoleRouterAtBase"); + expect(source).toContain('} from "./console_routes.js";'); + expect(source).toContain('export { renderMirrorConsoleHtml } from "./console_static.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 138baede94da855f1a9cc0214102047635428634 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 23:19:12 -0400 Subject: [PATCH 52/75] test(boundary): add standalone review-entry guardrails ## Summary - add a static guardrail test for the standalone review-facing entry surface - verify the canonical review entry remains Mirror-native - verify the review-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone review surface from drifting away from the intended Mirror-native review entry wiring. ## Scope Changed: - test/mirror-review-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - review-entry source files ## Details The test: - reads `src/mirror-review/index.ts` as the standalone review-entry owner - verifies it exports Mirror-native review/canon-validation surfaces from: - `./review_engine.js` - `./review_rules.js` - `./canon_conflict.js` - `./narrative_similarity.js` - `./symbol_validation.js` - `./review_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-review-entry-surface.test.ts` --- test/mirror-heartbeat-entry-surface.test.ts | 34 ++++++++++++++++++++ test/mirror-reminder-entry-surface.test.ts | 35 +++++++++++++++++++++ test/mirror-review-entry-surface.test.ts | 28 +++++++++++++++++ test/mirror-task-entry-surface.test.ts | 25 +++++++++++++++ test/mirror-ui-api-entry-surface.test.ts | 20 ++++++++++++ 5 files changed, 142 insertions(+) create mode 100644 test/mirror-heartbeat-entry-surface.test.ts create mode 100644 test/mirror-reminder-entry-surface.test.ts create mode 100644 test/mirror-review-entry-surface.test.ts create mode 100644 test/mirror-task-entry-surface.test.ts create mode 100644 test/mirror-ui-api-entry-surface.test.ts diff --git a/test/mirror-heartbeat-entry-surface.test.ts b/test/mirror-heartbeat-entry-surface.test.ts new file mode 100644 index 0000000000..1c73669eb5 --- /dev/null +++ b/test/mirror-heartbeat-entry-surface.test.ts @@ -0,0 +1,34 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone heartbeat entry surface", () => { + it("keeps the canonical heartbeat-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-heartbeat/index.ts"), + "utf8", + ); + + expect(source).toContain( + 'export { createMirrorHeartbeatManager } from "./heartbeat_manager.js";', + ); + expect(source).toContain( + 'export { createMirrorHeartbeatStore, type MirrorHeartbeatStore } from "./heartbeat_store.js";', + ); + expect(source).toContain('export { evaluateHeartbeat } from "./heartbeat_evaluator.js";'); + expect(source).toContain('export { renderHeartbeatTemplate } from "./heartbeat_templates.js";'); + expect(source).toContain("MirrorHeartbeatEvaluation"); + expect(source).toContain("MirrorHeartbeatEvaluationInput"); + expect(source).toContain("MirrorHeartbeatManager"); + expect(source).toContain("MirrorHeartbeatSignalSummary"); + expect(source).toContain("MirrorHeartbeatState"); + expect(source).toContain("MirrorHeartbeatTemplateInput"); + expect(source).toContain("MirrorHeartbeatTone"); + expect(source).toContain('} from "./heartbeat_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); diff --git a/test/mirror-reminder-entry-surface.test.ts b/test/mirror-reminder-entry-surface.test.ts new file mode 100644 index 0000000000..2fef4f440a --- /dev/null +++ b/test/mirror-reminder-entry-surface.test.ts @@ -0,0 +1,35 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone reminder entry surface", () => { + it("keeps the canonical reminder-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-reminder/index.ts"), + "utf8", + ); + + expect(source).toContain( + 'export { createMirrorReminderManager } from "./reminder_manager.js";', + ); + expect(source).toContain( + 'export { createMirrorReminderStore, type MirrorReminderStore } from "./reminder_store.js";', + ); + expect(source).toContain("filterDueReminders"); + expect(source).toContain("getReminderScheduleState"); + expect(source).toContain('} from "./reminder_scheduler.js";'); + expect(source).toContain("CreateMirrorReminderInput"); + expect(source).toContain("MirrorReminder"); + expect(source).toContain("MirrorReminderManager"); + expect(source).toContain("MirrorReminderRecurrence"); + expect(source).toContain("MirrorReminderScheduleState"); + expect(source).toContain("MirrorReminderStatusValue"); + expect(source).toContain("UpdateMirrorReminderInput"); + expect(source).toContain('} from "./reminder_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); diff --git a/test/mirror-review-entry-surface.test.ts b/test/mirror-review-entry-surface.test.ts new file mode 100644 index 0000000000..0e246fa2d2 --- /dev/null +++ b/test/mirror-review-entry-surface.test.ts @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone review entry surface", () => { + it("keeps the canonical review-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-review/index.ts"), "utf8"); + + expect(source).toContain('export { reviewDraftForCanon } from "./review_engine.js";'); + expect(source).toContain('export { MIRROR_REVIEW_RULES } from "./review_rules.js";'); + expect(source).toContain('export { detectCanonConflicts } from "./canon_conflict.js";'); + expect(source).toContain( + 'export { detectNarrativeSimilarity } from "./narrative_similarity.js";', + ); + expect(source).toContain('export { validateDraftSymbols } from "./symbol_validation.js";'); + expect(source).toContain("MirrorCanonConflict"); + expect(source).toContain("MirrorCanonReviewResult"); + expect(source).toContain("MirrorNarrativeSimilarity"); + expect(source).toContain("MirrorReviewStatus"); + expect(source).toContain("MirrorSymbolValidation"); + expect(source).toContain('} from "./review_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); diff --git a/test/mirror-task-entry-surface.test.ts b/test/mirror-task-entry-surface.test.ts new file mode 100644 index 0000000000..095037a97f --- /dev/null +++ b/test/mirror-task-entry-surface.test.ts @@ -0,0 +1,25 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone task entry surface", () => { + it("keeps the canonical task-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-task/index.ts"), "utf8"); + + expect(source).toContain('export { createMirrorTaskManager } from "./task_manager.js";'); + expect(source).toContain( + 'export { createMirrorTaskApi, type MirrorTaskApi } from "./task_api.js";', + ); + expect(source).toContain("CreateMirrorTaskInput"); + expect(source).toContain("MirrorTask"); + expect(source).toContain("MirrorTaskManager"); + expect(source).toContain("MirrorTaskStatusValue"); + expect(source).toContain("UpdateMirrorTaskInput"); + expect(source).toContain('} from "./task_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); diff --git a/test/mirror-ui-api-entry-surface.test.ts b/test/mirror-ui-api-entry-surface.test.ts new file mode 100644 index 0000000000..56118874dc --- /dev/null +++ b/test/mirror-ui-api-entry-surface.test.ts @@ -0,0 +1,20 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone ui api entry surface", () => { + it("keeps the canonical UI/API-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-ui-api/index.ts"), "utf8"); + + expect(source).toContain("createMirrorUiApiHandlers"); + expect(source).toContain("createMirrorUiApiRouter"); + expect(source).toContain("type MirrorUiApiHandlers"); + expect(source).toContain('} from "./routes.js";'); + expect(source).toContain('export * from "./contracts.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From d8d36f08fd8bf2f09c7b494290b68257bd8596bd Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 23:20:01 -0400 Subject: [PATCH 53/75] test(boundary): add standalone UI-api entry guardrails ## Summary - add a static guardrail test for the standalone UI/API-facing entry surface - verify the canonical UI/API entry remains Mirror-native - verify the UI/API-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone UI/API surface from drifting away from the intended Mirror-native UI/API entry wiring. ## Scope Changed: - test/mirror-ui-api-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - UI/API-entry source files ## Details The test: - reads `src/mirror-ui-api/index.ts` as the standalone UI/API-entry owner - verifies it exports Mirror-native UI/API surfaces from: - `./routes.js` - `./contracts.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-ui-api-entry-surface.test.ts` From 2723eb6214b35305bfd4bb90ba5549977895e875 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 23:21:05 -0400 Subject: [PATCH 54/75] test(boundary): add standalone task-entry guardrails ## Summary - add a static guardrail test for the standalone task-facing entry surface - verify the canonical task entry remains Mirror-native - verify the task-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone task surface from drifting away from the intended Mirror-native task entry wiring. ## Scope Changed: - test/mirror-task-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - task-entry source files ## Details The test: - reads `src/mirror-task/index.ts` as the standalone task-entry owner - verifies it exports Mirror-native task surfaces from: - `./task_manager.js` - `./task_api.js` - `./task_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-task-entry-surface.test.ts` From 724bd960defb607067627f2e4eb677213182fbd1 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 23:21:54 -0400 Subject: [PATCH 55/75] test(boundary): add standalone reminder-entry guardrails ## Summary - add a static guardrail test for the standalone reminder-facing entry surface - verify the canonical reminder entry remains Mirror-native - verify the reminder-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone reminder surface from drifting away from the intended Mirror-native reminder entry wiring. ## Scope Changed: - test/mirror-reminder-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - reminder-entry source files ## Details The test: - reads `src/mirror-reminder/index.ts` as the standalone reminder-entry owner - verifies it exports Mirror-native reminder surfaces from: - `./reminder_manager.js` - `./reminder_store.js` - `./reminder_scheduler.js` - `./reminder_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-reminder-entry-surface.test.ts` From 7f97055459492f39c7d6d623784f5d7b9694c65a Mon Sep 17 00:00:00 2001 From: ToadAid Date: Sun, 22 Mar 2026 23:23:18 -0400 Subject: [PATCH 56/75] test(boundary): add standalone heartbeat-entry guardrails ## Summary - add a static guardrail test for the standalone heartbeat-facing entry surface - verify the canonical heartbeat entry remains Mirror-native - verify the heartbeat-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone heartbeat surface from drifting away from the intended Mirror-native heartbeat entry wiring. ## Scope Changed: - test/mirror-heartbeat-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - heartbeat-entry source files ## Details The test: - reads `src/mirror-heartbeat/index.ts` as the standalone heartbeat-entry owner - verifies it exports Mirror-native heartbeat surfaces from: - `./heartbeat_manager.js` - `./heartbeat_store.js` - `./heartbeat_evaluator.js` - `./heartbeat_templates.js` - `./heartbeat_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-heartbeat-entry-surface.test.ts` From 0a6f2b08490374ae01808f66783ef1d443be37b7 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 00:07:57 -0400 Subject: [PATCH 57/75] test(boundary): add standalone reflection-entry guardrails ## Summary - add a static guardrail test for the standalone reflection-facing entry surface - verify the canonical reflection entry remains Mirror-native - verify the reflection-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone reflection surface from drifting away from the intended Mirror-native reflection entry wiring. ## Scope Changed: - test/mirror-reflection-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - reflection-entry source files ## Details The test: - reads `src/mirror-reflection/index.ts` as the standalone reflection-entry owner - verifies it exports Mirror-native reflection/canon-analysis surfaces from: - `./reflection_engine.js` - `./canon_analysis.js` - `./symbol_analysis.js` - `./draft_review.js` - `./reflection_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-reflection-entry-surface.test.ts` --- test/mirror-reflection-entry-surface.test.ts | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/mirror-reflection-entry-surface.test.ts diff --git a/test/mirror-reflection-entry-surface.test.ts b/test/mirror-reflection-entry-surface.test.ts new file mode 100644 index 0000000000..f343e52ab5 --- /dev/null +++ b/test/mirror-reflection-entry-surface.test.ts @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone reflection entry surface", () => { + it("keeps the canonical reflection-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-reflection/index.ts"), + "utf8", + ); + + expect(source).toContain("buildReflectionPrompt"); + expect(source).toContain("reflectOnCanonContext"); + expect(source).toContain("reviewCanonDraft"); + expect(source).toContain('} from "./reflection_engine.js";'); + expect(source).toContain('export { analyzeCanonContext } from "./canon_analysis.js";'); + expect(source).toContain('export { analyzeSymbolResonance } from "./symbol_analysis.js";'); + expect(source).toContain('export { reviewDraftAgainstCanon } from "./draft_review.js";'); + expect(source).toContain("MirrorCanonReflection"); + expect(source).toContain("MirrorDraftReview"); + expect(source).toContain("MirrorSymbolResonance"); + expect(source).toContain("ReflectCanonInput"); + expect(source).toContain("ReviewDraftInput"); + expect(source).toContain('} from "./reflection_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 5c726de46d3a068482182fccb4336fd97c7c8522 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 00:09:02 -0400 Subject: [PATCH 58/75] test(boundary): add standalone lore-graph-entry guardrails ## Summary - add a static guardrail test for the standalone lore-graph-facing entry surface - verify the canonical lore-graph entry remains Mirror-native - verify the lore-graph-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone lore-graph surface from drifting away from the intended Mirror-native lore-graph entry wiring. ## Scope Changed: - test/mirror-lore-graph-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - lore-graph-entry source files ## Details The test: - reads `src/mirror-lore-graph/index.ts` as the standalone lore-graph-entry owner - verifies it exports Mirror-native lore graph/query surfaces from: - `./graph_builder.js` - `./lore_graph.js` - `./node_types.js` - `./edge_types.js` - `./graph_query.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-lore-graph-entry-surface.test.ts` --- test/mirror-lore-graph-entry-surface.test.ts | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/mirror-lore-graph-entry-surface.test.ts diff --git a/test/mirror-lore-graph-entry-surface.test.ts b/test/mirror-lore-graph-entry-surface.test.ts new file mode 100644 index 0000000000..91daa5839a --- /dev/null +++ b/test/mirror-lore-graph-entry-surface.test.ts @@ -0,0 +1,29 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone lore-graph entry surface", () => { + it("keeps the canonical lore-graph-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-lore-graph/index.ts"), + "utf8", + ); + + expect(source).toContain('export { buildLoreGraph } from "./graph_builder.js";'); + expect(source).toContain('export type { MirrorLoreGraph } from "./lore_graph.js";'); + expect(source).toContain('export type { MirrorLoreGraphNode } from "./node_types.js";'); + expect(source).toContain( + 'export type { MirrorLoreGraphEdge, MirrorLoreGraphEdgeType } from "./edge_types.js";', + ); + expect(source).toContain("findConceptClusters"); + expect(source).toContain("findRelatedScrolls"); + expect(source).toContain("findScrollsSharingSymbols"); + expect(source).toContain("findSupersessionChains"); + expect(source).toContain('} from "./graph_query.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 3b49969327f58682ec82f716c79681c41927c647 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 00:09:52 -0400 Subject: [PATCH 59/75] test(boundary): add standalone monk-entry guardrails ## Summary - add a static guardrail test for the standalone monk-facing entry surface - verify the canonical monk entry remains Mirror-native - verify the monk-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone monk surface from drifting away from the intended Mirror-native monk entry wiring. ## Scope Changed: - test/mirror-monk-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - monk-entry source files ## Details The test: - reads `src/mirror-monk/index.ts` as the standalone monk-entry owner - verifies it exports Mirror-native monk/workspace-view surfaces from: - `./monk_workspace_bridge.js` - `./monk_context.js` - `./monk_task_view.js` - `./monk_draft_view.js` - `./monk_session_view.js` - `./monk_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-monk-entry-surface.test.ts` --- test/mirror-monk-entry-surface.test.ts | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/mirror-monk-entry-surface.test.ts diff --git a/test/mirror-monk-entry-surface.test.ts b/test/mirror-monk-entry-surface.test.ts new file mode 100644 index 0000000000..fad7a26677 --- /dev/null +++ b/test/mirror-monk-entry-surface.test.ts @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone monk entry surface", () => { + it("keeps the canonical monk-facing entry Mirror-native", () => { + const source = fs.readFileSync(path.join(process.cwd(), "src/mirror-monk/index.ts"), "utf8"); + + expect(source).toContain( + 'export { createMirrorMonkWorkspaceBridge } from "./monk_workspace_bridge.js";', + ); + expect(source).toContain('export { buildMonkWorkspaceContext } from "./monk_context.js";'); + expect(source).toContain('export { buildMonkTaskView } from "./monk_task_view.js";'); + expect(source).toContain('export { buildMonkDraftView } from "./monk_draft_view.js";'); + expect(source).toContain('export { buildMonkSessionView } from "./monk_session_view.js";'); + expect(source).toContain("MirrorMonkDraftView"); + expect(source).toContain("MirrorMonkSessionView"); + expect(source).toContain("MirrorMonkTaskView"); + expect(source).toContain("MirrorMonkWorkspaceBridge"); + expect(source).toContain("MirrorMonkWorkspaceContext"); + expect(source).toContain('} from "./monk_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From 122063b7ae83c27b31551856379aa4d16e8358d7 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 00:11:21 -0400 Subject: [PATCH 60/75] test(boundary): add standalone monk-actions-entry guardrails ## Summary - add a static guardrail test for the standalone monk-actions-facing entry surface - verify the canonical monk-actions entry remains Mirror-native - verify the monk-actions-facing entry does not drift back toward compat/OpenClaw or legacy wrapper ownership ## Why This protects the standalone monk-actions surface from drifting away from the intended Mirror-native monk-actions entry wiring. ## Scope Changed: - test/mirror-monk-actions-entry-surface.test.ts Not changed: - runtime behavior - workflows - packaging logic - adapter-boundary work - monk-actions-entry source files ## Details The test: - reads `src/mirror-monk-actions/index.ts` as the standalone monk-actions-entry owner - verifies it exports Mirror-native monk-actions surfaces from: - `./monk_task_actions_runtime.js` - `./monk_followup.js` - `./monk_task_actions.js` - `./monk_reminder_actions.js` - `./monk_resume.js` - `./monk_action_types.js` - verifies it does not mention: - `compat/openclaw` - `openclaw-compat` - `../runtime/` - `../compat/` ## Verification - `pnpm vitest run test/mirror-monk-actions-entry-surface.test.ts` --- .../mirror-monk-actions-entry-surface.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/mirror-monk-actions-entry-surface.test.ts diff --git a/test/mirror-monk-actions-entry-surface.test.ts b/test/mirror-monk-actions-entry-surface.test.ts new file mode 100644 index 0000000000..a32c20e148 --- /dev/null +++ b/test/mirror-monk-actions-entry-surface.test.ts @@ -0,0 +1,38 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("mirror standalone monk-actions entry surface", () => { + it("keeps the canonical monk-actions-facing entry Mirror-native", () => { + const source = fs.readFileSync( + path.join(process.cwd(), "src/mirror-monk-actions/index.ts"), + "utf8", + ); + + expect(source).toContain( + 'export { createMirrorMonkActions } from "./monk_task_actions_runtime.js";', + ); + expect(source).toContain('export { formatMonkSuggestedAction } from "./monk_followup.js";'); + expect(source).toContain("buildMonkTaskFollowup"); + expect(source).toContain("buildMonkOpenWorkSummary"); + expect(source).toContain("selectNextMonkTask"); + expect(source).toContain('} from "./monk_task_actions.js";'); + expect(source).toContain("buildDueReminderActions"); + expect(source).toContain("buildReminderLinkedTaskFollowup"); + expect(source).toContain('} from "./monk_reminder_actions.js";'); + expect(source).toContain("buildMonkResumeContext"); + expect(source).toContain("buildSuggestedResumeAction"); + expect(source).toContain('} from "./monk_resume.js";'); + expect(source).toContain("MirrorMonkActionContext"); + expect(source).toContain("MirrorMonkActionKind"); + expect(source).toContain("MirrorMonkActionResult"); + expect(source).toContain("MirrorMonkActions"); + expect(source).toContain("MirrorMonkResumeContext"); + expect(source).toContain('} from "./monk_action_types.js";'); + + expect(source).not.toContain("compat/openclaw"); + expect(source).not.toContain("openclaw-compat"); + expect(source).not.toContain("../runtime/"); + expect(source).not.toContain('from "../compat/'); + }); +}); From d422a3f33ff05e95bccf4a4974a2532017b3bbd0 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 07:50:17 -0400 Subject: [PATCH 61/75] refactor(observability): require explicit observability context for server handlers ## Summary - remove the implicit global-context fallback from observability server entrypoints - require explicit observability context for `createMirrorObservabilityHandlers` - require explicit handlers for `createMirrorObservabilityRouter` - update the local observability wrapper-layer test to pass the default context explicitly ## Why This reduces process-global ownership in the observability server seam and makes caller ownership explicit without broadening into a larger observability refactor. ## Scope Changed: - src/mirror-observability/observability_server.ts - src/mirror-observability/observability.test.ts Not changed: - runtime/daemon modules - CLI implementation - docs - workflows - packaging - broader observability modules ## Details This patch: - removes `getDefaultMirrorObservabilityContext()` as a default parameter from `createMirrorObservabilityHandlers` - removes implicit handler creation from `createMirrorObservabilityRouter` - preserves behavior for explicit callers by updating the local test to pass `getDefaultMirrorObservabilityContext()` directly ## Verification - `pnpm vitest run src/mirror-observability` --- src/mirror-observability/observability.test.ts | 5 ++++- src/mirror-observability/observability_server.ts | 9 +++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mirror-observability/observability.test.ts b/src/mirror-observability/observability.test.ts index 48d546bf19..68afdb6fca 100644 --- a/src/mirror-observability/observability.test.ts +++ b/src/mirror-observability/observability.test.ts @@ -10,6 +10,7 @@ import { createMirrorSyncHandlers, createMirrorSyncManager } from "../mirror-syn import { createMirrorObservabilityContext, createMirrorObservabilityHandlers, + getDefaultMirrorObservabilityContext, getMirrorMetrics, incrementMetric, resetMirrorDiagnostics, @@ -246,7 +247,9 @@ describe("mirror observability", () => { createMockResponse() as never, ); - const observabilityHandlers = createMirrorObservabilityHandlers(); + const observabilityHandlers = createMirrorObservabilityHandlers( + getDefaultMirrorObservabilityContext(), + ); const syncHandlers = createMirrorSyncHandlers( createMirrorSyncManager({ nodeId: "observe-node", diff --git a/src/mirror-observability/observability_server.ts b/src/mirror-observability/observability_server.ts index 84c75ecfec..655fa33c01 100644 --- a/src/mirror-observability/observability_server.ts +++ b/src/mirror-observability/observability_server.ts @@ -1,8 +1,5 @@ import express from "express"; -import { - getDefaultMirrorObservabilityContext, - type MirrorObservabilityContext, -} from "./context.js"; +import { type MirrorObservabilityContext } from "./context.js"; export type MirrorObservabilityHandlers = { metrics: (req: express.Request, res: express.Response) => void; @@ -10,7 +7,7 @@ export type MirrorObservabilityHandlers = { }; export function createMirrorObservabilityHandlers( - observability: MirrorObservabilityContext = getDefaultMirrorObservabilityContext(), + observability: MirrorObservabilityContext, ): MirrorObservabilityHandlers { return { metrics: (_req, res) => { @@ -23,7 +20,7 @@ export function createMirrorObservabilityHandlers( } export function createMirrorObservabilityRouter( - handlers = createMirrorObservabilityHandlers(), + handlers: MirrorObservabilityHandlers, ): express.Router { const router = express.Router(); router.get("/mirror/metrics", handlers.metrics); From efbdb5cb52c267e66d25a52f49204b3ad3db11ff Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 09:12:05 -0400 Subject: [PATCH 62/75] refactor(observability): route console graph metrics through owned context ## Summary - route console graph-route metrics and diagnostic events through the owned observability context - stop console graph routes from using global observability helpers directly - update the local observability integration test to exercise the same explicit-context path ## Why This reduces hidden process-global ownership in the console graph-route seam and keeps observability emission attached to the service-owned runtime context. ## Scope Changed: - src/mirror-console/console_routes.ts - src/mirror-service/mirror_service.ts - src/mirror-observability/observability.test.ts Not changed: - adapter-boundary files - sync orchestration - packaging/workflows/docs - broader runtime/daemon ownership ## Details This patch Co-authored-by: Agent 0 --- src/mirror-console/console_routes.ts | 23 ++-- .../observability.test.ts | 100 +++++++++--------- src/mirror-service/mirror_service.ts | 1 + 3 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/mirror-console/console_routes.ts b/src/mirror-console/console_routes.ts index 6e60295af9..bb5c0a815a 100644 --- a/src/mirror-console/console_routes.ts +++ b/src/mirror-console/console_routes.ts @@ -7,8 +7,10 @@ import { findScrollsSharingSymbols, findSupersessionChains, } from "../mirror-lore-graph/index.js"; -import { incrementMetric, logMirrorEvent } from "../mirror-observability/index.js"; -import type { MirrorObservabilityHandlers } from "../mirror-observability/index.js"; +import type { + MirrorObservabilityContext, + MirrorObservabilityHandlers, +} from "../mirror-observability/index.js"; import type { MirrorSyncHandlers } from "../mirror-sync/index.js"; import { renderMirrorConsoleHtml } from "./console_static.js"; @@ -33,6 +35,7 @@ export function createMirrorConsoleHandlers( gatewayHandlers: MirrorGatewayHandlers, deps: { syncHandlers: MirrorSyncHandlers; + observability: MirrorObservabilityContext; observabilityHandlers: MirrorObservabilityHandlers; health: (req: express.Request, res: express.Response) => void; }, @@ -51,29 +54,29 @@ export function createMirrorConsoleHandlers( diagnostics: deps.observabilityHandlers.diagnostics, health: deps.health, async relatedScrolls(req, res) { - incrementMetric("graph_query_frequency"); - logMirrorEvent("graph.query", { type: "related" }); + deps.observability.incrementMetric("graph_query_frequency"); + deps.observability.logEvent("graph.query", { type: "related" }); const graph = await buildLoreGraph(); const scroll = typeof req.query.scroll === "string" ? req.query.scroll : ""; res.json({ related_scrolls: findRelatedScrolls(graph, scroll) }); }, async symbolClusters(req, res) { - incrementMetric("graph_query_frequency"); - logMirrorEvent("graph.query", { type: "symbols" }); + deps.observability.incrementMetric("graph_query_frequency"); + deps.observability.logEvent("graph.query", { type: "symbols" }); const graph = await buildLoreGraph(); const symbol = typeof req.query.symbol === "string" ? req.query.symbol : ""; res.json({ scrolls: findScrollsSharingSymbols(graph, symbol) }); }, async supersessionChains(req, res) { - incrementMetric("graph_query_frequency"); - logMirrorEvent("graph.query", { type: "supersession" }); + deps.observability.incrementMetric("graph_query_frequency"); + deps.observability.logEvent("graph.query", { type: "supersession" }); const graph = await buildLoreGraph(); const scroll = typeof req.query.scroll === "string" ? req.query.scroll : ""; res.json({ chain: findSupersessionChains(graph, scroll) }); }, async conceptClusters(_req, res) { - incrementMetric("graph_query_frequency"); - logMirrorEvent("graph.query", { type: "clusters" }); + deps.observability.incrementMetric("graph_query_frequency"); + deps.observability.logEvent("graph.query", { type: "clusters" }); const graph = await buildLoreGraph(); res.json({ clusters: findConceptClusters(graph) }); }, diff --git a/src/mirror-observability/observability.test.ts b/src/mirror-observability/observability.test.ts index 68afdb6fca..7dd4d890df 100644 --- a/src/mirror-observability/observability.test.ts +++ b/src/mirror-observability/observability.test.ts @@ -10,7 +10,6 @@ import { createMirrorSyncHandlers, createMirrorSyncManager } from "../mirror-syn import { createMirrorObservabilityContext, createMirrorObservabilityHandlers, - getDefaultMirrorObservabilityContext, getMirrorMetrics, incrementMetric, resetMirrorDiagnostics, @@ -208,48 +207,8 @@ describe("mirror observability", () => { return await gateway.executeAdapterRequest(envelope); }), }); - await gatewayHandlers.executeChat( - { - body: { - model: "mirror-default", - messages: [{ role: "user", content: "What happened to the patience vault?" }], - }, - } as never, - createMockResponse() as never, - ); - await gatewayHandlers.executeTool( - { - params: { tool_name: "mirror.find-scroll" }, - body: { query: "patience vault" }, - header: () => undefined, - } as never, - createMockResponse() as never, - ); - process.env.MIRROR_USER_WORKSPACE_DIR = await fs.mkdtemp( - path.join(os.tmpdir(), "mirror-observe-users-"), - ); - tempDirs.push(process.env.MIRROR_USER_WORKSPACE_DIR); - await gatewayHandlers.executeTool( - { - params: { tool_name: "mirror.task.create" }, - body: { user_id: "alice", title: "Review open work" }, - header: (name: string) => - name.toLowerCase() === "x-mirror-operator-token" ? "secret" : undefined, - } as never, - createMockResponse() as never, - ); - await gatewayHandlers.executeTool( - { - params: { tool_name: "mirror.monk.context" }, - body: { user_id: "alice" }, - header: () => undefined, - } as never, - createMockResponse() as never, - ); - - const observabilityHandlers = createMirrorObservabilityHandlers( - getDefaultMirrorObservabilityContext(), - ); + const observability = createMirrorObservabilityContext(); + const observabilityHandlers = createMirrorObservabilityHandlers(observability); const syncHandlers = createMirrorSyncHandlers( createMirrorSyncManager({ nodeId: "observe-node", @@ -258,17 +217,58 @@ describe("mirror observability", () => { ); const consoleHandlers = createMirrorConsoleHandlers(gatewayHandlers, { syncHandlers, + observability, observabilityHandlers, health: (_req, res) => res.json({ ok: true }), }); - await consoleHandlers.relatedScrolls( - { query: { scroll: "TOBY_L1219" } } as never, - createMockResponse() as never, - ); + await runWithMirrorObservabilityContext(observability, async () => { + await gatewayHandlers.executeChat( + { + body: { + model: "mirror-default", + messages: [{ role: "user", content: "What happened to the patience vault?" }], + }, + } as never, + createMockResponse() as never, + ); + await gatewayHandlers.executeTool( + { + params: { tool_name: "mirror.find-scroll" }, + body: { query: "patience vault" }, + header: () => undefined, + } as never, + createMockResponse() as never, + ); + process.env.MIRROR_USER_WORKSPACE_DIR = await fs.mkdtemp( + path.join(os.tmpdir(), "mirror-observe-users-"), + ); + tempDirs.push(process.env.MIRROR_USER_WORKSPACE_DIR); + await gatewayHandlers.executeTool( + { + params: { tool_name: "mirror.task.create" }, + body: { user_id: "alice", title: "Review open work" }, + header: (name: string) => + name.toLowerCase() === "x-mirror-operator-token" ? "secret" : undefined, + } as never, + createMockResponse() as never, + ); + await gatewayHandlers.executeTool( + { + params: { tool_name: "mirror.monk.context" }, + body: { user_id: "alice" }, + header: () => undefined, + } as never, + createMockResponse() as never, + ); + await consoleHandlers.relatedScrolls( + { query: { scroll: "TOBY_L1219" } } as never, + createMockResponse() as never, + ); - await reviewDraftForCanon({ - loreDir, - draftContent: validDraft("The Patience Vault was not cancelled."), + await reviewDraftForCanon({ + loreDir, + draftContent: validDraft("The Patience Vault was not cancelled."), + }); }); const metricsRes = createMockResponse(); diff --git a/src/mirror-service/mirror_service.ts b/src/mirror-service/mirror_service.ts index 1852c9b02e..53a87d666c 100644 --- a/src/mirror-service/mirror_service.ts +++ b/src/mirror-service/mirror_service.ts @@ -135,6 +135,7 @@ export async function startMirrorService( }; const consoleHandlers = createMirrorConsoleHandlers(handlers, { syncHandlers, + observability, observabilityHandlers, health: healthHandler, }); From b31d09eb8d5c3cdec8c71cb6f5a1b5faacdb1090 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 09:43:38 -0400 Subject: [PATCH 63/75] test(console): route console handlers import through console index ## Summary - route the observability test through the public console index surface - stop importing the console handler implementation directly from the test ## Why This tightens the console module ownership boundary by ensuring the test exercises the public export surface instead of the implementation file. ## Scope Changed: - src/mirror-observability/observability.test.ts Not changed: - production runtime files - adapter-boundary files - status / verify-lore seams - workflows - packaging - docs ## Verification - pnpm vitest run src/mirror-observability/observability.test.ts --- src/mirror-observability/observability.test.ts | 2 +- src/mirror/doctor/tests/checks.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mirror-observability/observability.test.ts b/src/mirror-observability/observability.test.ts index 7dd4d890df..6dbb0650ea 100644 --- a/src/mirror-observability/observability.test.ts +++ b/src/mirror-observability/observability.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { createMirrorConsoleHandlers } from "../mirror-console/console_routes.js"; +import { createMirrorConsoleHandlers } from "../mirror-console/index.js"; import { createMirrorGateway, createMirrorGatewayHandlers } from "../mirror-gateway/index.js"; import { closeMirrorMemoryDb } from "../mirror-memory/db.js"; import { reviewDraftForCanon } from "../mirror-review/index.js"; diff --git a/src/mirror/doctor/tests/checks.test.ts b/src/mirror/doctor/tests/checks.test.ts index 3c132a99b7..722d0731a9 100644 --- a/src/mirror/doctor/tests/checks.test.ts +++ b/src/mirror/doctor/tests/checks.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { runMirrorDoctorChecks } from "../checks.js"; +import { runMirrorDoctorChecks } from "../index.js"; describe("mirror doctor checks", () => { it("detects Mirror-native identity env keys only", async () => { From 35ee2af09467d5f2f009270a61bfc07239f00e7d Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 09:57:41 -0400 Subject: [PATCH 64/75] test(doctor): export doctor cli via telemetry-tail index ## Summary - add runMirrorDoctorCli to the public telemetry-tail index export surface - route the doctor CLI test through the public index surface instead of the direct implementation file ## Why This tightens the doctor CLI ownership boundary by ensuring the test exercises a public export surface instead of importing the implementation file directly. ## Scope Changed: - src/mirror/telemetry_tail/index.ts - src/mirror/doctor/tests/doctor.test.ts Not changed: - production doctor logic - adapter-boundary files - status / verify-lore seams - workflows - packaging - docs - other telemetry-tail behavior ## Details This patch: - adds runMirrorDoctorCli to src/mirror/telemetry_tail/index.ts - changes src/mirror/doctor/tests/doctor.test.ts to import from ../../telemetry_tail/index.js - preserves all doctor CLI behavior and test assertions ## Verification - pnpm vitest run src/mirror/doctor/tests/doctor.test.ts --- src/mirror/doctor/tests/doctor.test.ts | 2 +- src/mirror/telemetry_tail/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mirror/doctor/tests/doctor.test.ts b/src/mirror/doctor/tests/doctor.test.ts index cfdc67aa9f..eb05ad02f5 100644 --- a/src/mirror/doctor/tests/doctor.test.ts +++ b/src/mirror/doctor/tests/doctor.test.ts @@ -19,7 +19,7 @@ vi.mock("../index.js", () => ({ runMirrorDoctor, })); -const { runMirrorDoctorCli } = await import("../../telemetry_tail/cli.js"); +const { runMirrorDoctorCli } = await import("../../telemetry_tail/index.js"); function getSubcommand(parent: Command, name: string): Command | undefined { return parent.commands.find((command) => command.name() === name); diff --git a/src/mirror/telemetry_tail/index.ts b/src/mirror/telemetry_tail/index.ts index 514c41c8de..bc955cb9ca 100644 --- a/src/mirror/telemetry_tail/index.ts +++ b/src/mirror/telemetry_tail/index.ts @@ -1,4 +1,4 @@ -export { runMirrorTelemetryTailCli } from "./cli.js"; +export { runMirrorDoctorCli, runMirrorTelemetryTailCli } from "./cli.js"; export { buildTelemetryFilter, tailMirrorTelemetry } from "./tail.js"; export type { MirrorTelemetryTailCliOptions } from "./cli.js"; export type { From 84fe44464ced325b2b015d60167fe3799f4fabb8 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 10:00:35 -0400 Subject: [PATCH 65/75] test(doctor): route checks import through doctor index ## Summary - route the doctor checks test through the public doctor index surface - stop importing the checks implementation directly from the test ## Why This tightens the doctor module ownership boundary by ensuring the test exercises the public export surface instead of the implementation file. ## Scope Changed: - src/mirror/doctor/tests/checks.test.ts Not changed: - production runtime files - adapter-boundary files - status / verify-lore seams - workflows - packaging - docs ## Verification - pnpm vitest run src/mirror/doctor/tests/checks.test.ts From 0e3ef34af900d275459ca86151de521dc48ff571 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 10:42:37 -0400 Subject: [PATCH 66/75] cli: route mirror compatibility loader through telemetry-tail index ## Summary - add registerMirrorTelemetryCli to the public telemetry-tail index export surface - route the Mirror compatibility loader through the public telemetry-tail index instead of the implementation file - preserve the compat CLI wrapper path through the same public telemetry-tail surface ## Why This tightens the telemetry-tail CLI ownership boundary by making both the compat wrapper and the dynamic loader depend on the public Mirror surface instead of implementation files. ## Scope Changed: - src/mirror/telemetry_tail/index.ts - src/cli/program/register.subclis.ts - src/compat/openclaw/cli/mirror-cli.ts Not changed: - telemetry-tail implementation - adapter-boundary files - status / verify-lore seams - workflows - packaging - docs - CLI logic - runtime behavior ## Verification - pnpm vitest run src/mirror/telemetry_tail/tests/cli_wiring.test.ts --- src/cli/program/register.subclis.ts | 2 +- src/compat/openclaw/cli/mirror-cli.ts | 2 +- src/mirror/telemetry_tail/index.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index f9781588b8..da2b270c42 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -78,7 +78,7 @@ const entries: SubCliEntry[] = [ description: "Mirror compatibility commands (standalone runtime ships as `mirror`)", hasSubcommands: true, register: async (program) => { - const mod = await import("../../mirror/telemetry_tail/cli.js"); + const mod = await import("../../mirror/telemetry_tail/index.js"); mod.registerMirrorTelemetryCli(program); }, }, diff --git a/src/compat/openclaw/cli/mirror-cli.ts b/src/compat/openclaw/cli/mirror-cli.ts index b2b9b6efc3..3a657cad48 100644 --- a/src/compat/openclaw/cli/mirror-cli.ts +++ b/src/compat/openclaw/cli/mirror-cli.ts @@ -5,7 +5,7 @@ */ import type { Command } from "commander"; -import { runMirrorTelemetryTailCli } from "../../../mirror/telemetry_tail/cli.js"; +import { runMirrorTelemetryTailCli } from "../../../mirror/telemetry_tail/index.js"; function parseLimit(raw: string): number { const value = Number.parseInt(raw, 10); diff --git a/src/mirror/telemetry_tail/index.ts b/src/mirror/telemetry_tail/index.ts index bc955cb9ca..7746aa2f32 100644 --- a/src/mirror/telemetry_tail/index.ts +++ b/src/mirror/telemetry_tail/index.ts @@ -1,4 +1,8 @@ -export { runMirrorDoctorCli, runMirrorTelemetryTailCli } from "./cli.js"; +export { + registerMirrorTelemetryCli, + runMirrorDoctorCli, + runMirrorTelemetryTailCli, +} from "./cli.js"; export { buildTelemetryFilter, tailMirrorTelemetry } from "./tail.js"; export type { MirrorTelemetryTailCliOptions } from "./cli.js"; export type { From 3a64d5d99bcee1382c025fc7d32f057704047039 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 11:28:03 -0400 Subject: [PATCH 67/75] compat(runtime): derive health payload from runtime host truth ## Summary - derive compat /health provider configuration truth and version from the existing runtimeHost - stop fabricating compat health truth from raw wrapper args and MIRROR_RUNTIME_VERSION - add a focused test proving /health reflects runtimeHost truth even when wrapper args are undefined ## Why This reduces hidden compat self-authorship by making the legacy /health wrapper reflect the already-created runtimeHost / daemon-backed truth instead of raw wrapper inputs. ## Scope Changed: - src/compat/openclaw/runtime/health.ts - src/compat/openclaw/runtime/server.ts - src/compat/openclaw/runtime/server.test.ts Not changed: - Mirror service/runtime host logic - adapter-boundary files - status / verify-lore seams - workflows - packaging - docs - broader compat/runtime behavior ## Details This patch: - changes handleHealthEndpoint(...) to derive provider configuration and version from runtimeHost - uses getMirrordaemonRuntimeState(...) for runtime-backed version truth - preserves the legacy compat /health JSON shape - updates the compat server test suite with a focused case proving /health reflects runtimeHost truth even when wrapper args are undefined ## Verification - pnpm vitest run src/compat/openclaw/runtime/server.test.ts --------- Co-authored-by: Agent 0 --- src/compat/openclaw/runtime/health.ts | 28 ++++++++++------- src/compat/openclaw/runtime/server.test.ts | 35 ++++++++++++++++++++++ src/compat/openclaw/runtime/server.ts | 2 +- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/compat/openclaw/runtime/health.ts b/src/compat/openclaw/runtime/health.ts index 8ab70a292a..6b855d9a0a 100644 --- a/src/compat/openclaw/runtime/health.ts +++ b/src/compat/openclaw/runtime/health.ts @@ -4,7 +4,8 @@ * Canonical Mirror service health lives under `/mirror/health`. */ -import type { RuntimeEnv } from "../../../runtime.js"; +import type { MirrorRuntimeHost } from "../../../mirror-service/index.js"; +import { getMirrordaemonRuntimeState } from "../../../mirrordaemon/index.js"; interface HealthResponse { ok: boolean; @@ -21,19 +22,26 @@ interface HealthResponse { }; } +function hasConfiguredValue(value: string | null | undefined): boolean { + return typeof value === "string" && value.trim().length > 0; +} + export async function handleHealthEndpoint( - _env: RuntimeEnv, - brainUrl: string | undefined, - authToken: string | undefined, + runtimeHost: MirrorRuntimeHost, ): Promise { const mode = process.env.MIRROR_RUNTIME_MODE || "lan"; - const version = process.env.MIRROR_RUNTIME_VERSION || "unknown"; + const runtime = getMirrordaemonRuntimeState(runtimeHost.daemon, { + port: runtimeHost.config.port, + baseUrl: runtimeHost.syncManager.getLocalBaseUrl(), + }); const commit = process.env.MIRROR_RUNTIME_COMMIT || "unknown"; + const brainConfigured = hasConfiguredValue(runtimeHost.config.providerUrl); + const authConfigured = hasConfiguredValue(runtimeHost.config.providerAuthToken); const features: string[] = []; - if (brainUrl) { + if (brainConfigured) { features.push("brain"); } - if (authToken) { + if (authConfigured) { features.push("auth"); } @@ -41,14 +49,14 @@ export async function handleHealthEndpoint( ok: true, time: new Date().toISOString(), mode: mode as "lan" | "intranet", - version, + version: runtime.version, commit, features, brain: { - configured: !!brainUrl, + configured: brainConfigured, }, auth: { - configured: !!authToken, + configured: authConfigured, }, }; } diff --git a/src/compat/openclaw/runtime/server.test.ts b/src/compat/openclaw/runtime/server.test.ts index 3e74593acd..41fc86886e 100644 --- a/src/compat/openclaw/runtime/server.test.ts +++ b/src/compat/openclaw/runtime/server.test.ts @@ -215,6 +215,41 @@ async function requestResponseFromApp( } describe("compat runtime server", () => { + it("derives /health provider truth from runtimeHost instead of raw wrapper args", async () => { + process.env.MIRROR_ENABLE_RUNTIME = "true"; + const loreDir = await createTempLoreDir(); + await seedLoreCorpus(loreDir); + process.env.MIRROR_LORE_DIR = loreDir; + process.env.MIRROR_MEMORY_DB_PATH = await createTempMemoryDbPath(); + + const runtimeHost = await createMirrorRuntimeHost({ + loreDir, + providerUrl: "http://brain.local/v1/chat/completions", + providerAuthToken: "token", + }); + + try { + const app = await startRuntimeServer(createNonExitingRuntime(), undefined, undefined, { + runtimeHost, + }); + const health = (await requestJsonFromApp(app, "GET", "/health")) as { + ok: boolean; + version: string; + features: string[]; + brain: { configured: boolean }; + auth: { configured: boolean }; + }; + + expect(health.ok).toBe(true); + expect(health.version.length).toBeGreaterThan(0); + expect(health.features).toEqual(expect.arrayContaining(["brain", "auth"])); + expect(health.brain.configured).toBe(true); + expect(health.auth.configured).toBe(true); + } finally { + await runtimeHost.shutdown(); + } + }); + it("routes /api/brain/chat through runtimeHost.executeAdapterRequest and preserves the raw response shape", async () => { process.env.MIRROR_ENABLE_RUNTIME = "true"; const loreDir = await createTempLoreDir(); diff --git a/src/compat/openclaw/runtime/server.ts b/src/compat/openclaw/runtime/server.ts index 174ee37d93..c69b7a8d02 100644 --- a/src/compat/openclaw/runtime/server.ts +++ b/src/compat/openclaw/runtime/server.ts @@ -56,7 +56,7 @@ export async function startRuntimeServer( app.get("/health", async (_req, res) => { try { - const health = await handleHealthEndpoint(env, brainUrl, authToken); + const health = await handleHealthEndpoint(runtimeHost); res.json(health); } catch (err) { res.status(500).json({ ok: false, error: String(err) }); From c5d3b8824f92f2d0b88bf6a02f6ebcf08e9d913c Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 13:00:29 -0400 Subject: [PATCH 68/75] mirrordaemon: derive health peers_known from peer list ## Summary - derive daemon health sync.peers_known from a real peer list instead of a synthetic count override - pass syncManager.listPeers() into daemon health-state construction from service health call sites - update daemon and service tests to verify peer-count truth comes from the peer list ## Why This moves peers_known authority closer to the daemon summary layer and reduces duplicated service-side state derivation in health-style surfaces. ## Scope Changed: - src/mirrordaemon/runtime_state.ts - src/mirrordaemon/runtime_state.test.ts - src/mirror-service/mirror_service.ts - src/mirror-service/mirror_service.test.ts - src/mirror/status/status.ts - src/mirrordaemon/mirrordaemon.test.ts Not changed: - adapter-boundary files - sync orchestration - debug behavior - response shapes ## Details This patch: - changes daemon health-state derivation to use an optional peer list override - passes syncManager.listPeers() into getMirrordaemonHealthState(...) from: - the service health handler - the UI health closure - the status surface - updates daemon and service tests so peers_known is verified from a real peer list instead of a synthetic count path ## Verification - pnpm vitest run src/mirrordaemon/mirrordaemon.test.ts - pnpm vitest run src/mirrordaemon/runtime_state.test.ts - pnpm vitest run src/mirror-service/mirror_service.test.ts - pnpm check --------- Co-authored-by: Agent 0 --- src/mirror-service/mirror_service.test.ts | 11 +++++++++++ src/mirror-service/mirror_service.ts | 4 ++-- src/mirror/status/status.ts | 5 ++--- src/mirrordaemon/mirrordaemon.test.ts | 15 ++++++++++++++- src/mirrordaemon/runtime_state.test.ts | 15 ++++++++++++++- src/mirrordaemon/runtime_state.ts | 8 ++++++-- 6 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/mirror-service/mirror_service.test.ts b/src/mirror-service/mirror_service.test.ts index 0374011a63..4ee282d007 100644 --- a/src/mirror-service/mirror_service.test.ts +++ b/src/mirror-service/mirror_service.test.ts @@ -1407,6 +1407,7 @@ describe("mirror service", () => { daemon_session_id: string; uptime_ms: number; service: { node_id: string; port: number }; + sync: { peers_known: number }; event_stream: { sse_available: boolean; ws_available: boolean }; correlation: { trace_id: boolean; session_id: boolean }; }; @@ -1416,6 +1417,7 @@ describe("mirror service", () => { version: string; daemon_session_id: string; service: { node_id: string; port: number }; + sync: { peers_known: number }; }; expect(health.ok).toBe(true); @@ -1425,6 +1427,7 @@ describe("mirror service", () => { expect(health.uptime_ms).toBeGreaterThanOrEqual(0); expect(health.service.node_id).toBe("health-node"); expect(health.service.port).toBe(service.port); + expect(health.sync.peers_known).toBe(0); expect(health.event_stream.sse_available).toBe(true); expect(health.event_stream.ws_available).toBe(true); expect(health.correlation.trace_id).toBe(true); @@ -1435,6 +1438,7 @@ describe("mirror service", () => { expect(status.daemon_session_id).toBe(health.daemon_session_id); expect(status.service.node_id).toBe(health.service.node_id); expect(status.service.port).toBe(health.service.port); + expect(status.sync.peers_known).toBe(health.sync.peers_known); } finally { await service.shutdown(); } @@ -1595,6 +1599,11 @@ describe("mirror service", () => { }, }); + const health = (await requestJsonFromApp(service.app, "GET", "/mirror/health")) as { + ok: boolean; + sync: { peers_known: number }; + }; + const sync = (await requestJsonFromApp(service.app, "GET", "/mirror/sync")) as { ok: boolean; daemon_session_id: string; @@ -1609,6 +1618,8 @@ describe("mirror service", () => { }>; }; + expect(health.ok).toBe(true); + expect(health.sync.peers_known).toBe(1); expect(sync.ok).toBe(true); expect(sync.daemon_session_id.length).toBeGreaterThan(0); expect(sync.peers_known).toBe(1); diff --git a/src/mirror-service/mirror_service.ts b/src/mirror-service/mirror_service.ts index 53a87d666c..42aa02a879 100644 --- a/src/mirror-service/mirror_service.ts +++ b/src/mirror-service/mirror_service.ts @@ -126,7 +126,7 @@ export async function startMirrorService( wsConnections: runtimeWebSocket.getConnectionCount(), sseAvailable: true, wsAvailable: true, - peersKnown: observability.getMetrics().gauges.peers_known || syncManager.listPeers().length, + peers: syncManager.listPeers(), }); daemon.publishRuntimeEvent("runtime.health.requested", { path: "/mirror/health", @@ -150,7 +150,7 @@ export async function startMirrorService( getMirrordaemonHealthState(daemon, { port: boundPort, baseUrl: syncManager.getLocalBaseUrl(), - peersKnown: observability.getMetrics().gauges.peers_known || syncManager.listPeers().length, + peers: syncManager.listPeers(), }), getBaseUrl: () => syncManager.getLocalBaseUrl(), }); diff --git a/src/mirror/status/status.ts b/src/mirror/status/status.ts index ce34e1414c..7487c58af0 100644 --- a/src/mirror/status/status.ts +++ b/src/mirror/status/status.ts @@ -51,8 +51,7 @@ export type GetMirrorStatusOptions = { export async function getMirrorStatus(opts: GetMirrorStatusOptions): Promise { const daemon = opts.runtimeHost.daemon; const boot = daemon.getBootSnapshot(); - const metrics = daemon.getObservability().getMetrics(); - const peersKnown = metrics.gauges.peers_known || opts.runtimeHost.syncManager.listPeers().length; + const peers = opts.runtimeHost.syncManager.listPeers(); const baseUrl = opts.runtimeHost.syncManager.getLocalBaseUrl(); const runtime = getMirrordaemonRuntimeState(daemon, { port: opts.runtimeHost.config.port, @@ -61,7 +60,7 @@ export async function getMirrorStatus(opts: GetMirrorStatusOptions): Promise { const health = getMirrordaemonHealthState(daemon, { port: 7788, baseUrl: "http://127.0.0.1:7788", - peersKnown: 2, + peers: [ + { + peer_id: "peer-1", + base_url: "http://127.0.0.1:7999", + last_seen_at: "2026-03-13T00:01:00.000Z", + sync_status: "idle", + }, + { + peer_id: "peer-2", + base_url: "http://127.0.0.1:8000", + last_seen_at: "2026-03-13T00:02:00.000Z", + sync_status: "ok", + }, + ], }); const debug = getMirrordaemonDebugState(daemon, { port: 7788, diff --git a/src/mirrordaemon/runtime_state.test.ts b/src/mirrordaemon/runtime_state.test.ts index 0afbea3cb7..197783eb98 100644 --- a/src/mirrordaemon/runtime_state.test.ts +++ b/src/mirrordaemon/runtime_state.test.ts @@ -204,7 +204,20 @@ describe("mirrordaemon runtime state", () => { wsConnections: 3, sseAvailable: true, wsAvailable: true, - peersKnown: 2, + peers: [ + { + peer_id: "peer-1", + base_url: "http://127.0.0.1:7999", + last_seen_at: "2026-03-13T00:01:00.000Z", + sync_status: "idle", + }, + { + peer_id: "peer-2", + base_url: "http://127.0.0.1:8000", + last_seen_at: "2026-03-13T00:02:00.000Z", + sync_status: "ok", + }, + ], }); expect(health.event_stream).toEqual(runtime.event_stream); diff --git a/src/mirrordaemon/runtime_state.ts b/src/mirrordaemon/runtime_state.ts index e6a3603bd2..260905be59 100644 --- a/src/mirrordaemon/runtime_state.ts +++ b/src/mirrordaemon/runtime_state.ts @@ -23,6 +23,10 @@ type RuntimeStateOverrides = { wsAvailable?: boolean; }; +type HealthStateOverrides = RuntimeStateOverrides & { + peers?: MirrorSyncPeer[]; +}; + function buildCorrelationCapabilities() { return { trace_id: true as const, @@ -180,7 +184,7 @@ export function buildRuntimeSummary( export function buildHealthSummary( daemon: Mirrordaemon, - overrides: RuntimeStateOverrides & { peersKnown?: number } = {}, + overrides: HealthStateOverrides = {}, ): MirrordaemonHealthSummary { const runtime = buildRuntimeSummary(daemon, overrides); const boot = daemon.getBootSnapshot(); @@ -205,7 +209,7 @@ export function buildHealthSummary( fallback_available: boot.readiness.provider.fallback_available, }, sync: { - peers_known: overrides.peersKnown ?? metrics.gauges.peers_known ?? 0, + peers_known: overrides.peers?.length ?? metrics.gauges.peers_known ?? 0, }, observability: { metrics_available: true, From 0dae6206623913fde7c626348a89d137fdb1ce16 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 13:40:31 -0400 Subject: [PATCH 69/75] sync(canon): inject canon sync metric hooks ## Summary - remove the direct global metrics dependency from canon_sync.ts - make applyRemoteCanonUpdates(...) accept small metric hooks - supply the existing metric behavior from sync_manager.ts - preserve metric names and counts exactly ## Why This cleans up ownership in the canon sync seam by moving observability emission out of canon_sync.ts and back to the owning sync manager layer. ## Scope Changed: - src/mirror-sync/canon_sync.ts - src/mirror-sync/sync_manager.ts Not changed: - route handlers - response shapes - runtime events - registry/state transitions - status surfaces - adapter-boundary files - packaging/workflows/docs - broader observability context wiring ## Details This patch: - removes incrementMetric(...) from src/mirror-sync/canon_sync.ts - adds optional metric hooks to applyRemoteCanonUpdates(...): - onConflictWarning - onUpdatesPulled - has src/mirror-sync/sync_manager.ts supply the current metric behavior - preserves the existing metric names and counts: - conflict_warnings - updates_pulled ## Verification - pnpm vitest run src/mirror-sync/mirror_sync.test.ts --------- Co-authored-by: Agent 0 --- src/mirror-sync/canon_sync.ts | 19 ++++++++++++------- src/mirror-sync/sync_manager.ts | 8 ++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/mirror-sync/canon_sync.ts b/src/mirror-sync/canon_sync.ts index 31123009bb..3d23d5fdeb 100644 --- a/src/mirror-sync/canon_sync.ts +++ b/src/mirror-sync/canon_sync.ts @@ -1,7 +1,6 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; -import { incrementMetric } from "../mirror-observability/index.js"; import { ensureScrollIndexUpToDate } from "../mirror/lore_sources/index.js"; import { validateLoreDraftInCorpusContext } from "../mirror/lore_validation/index.js"; import type { @@ -10,6 +9,11 @@ import type { MirrorSyncConflict, } from "./sync_types.js"; +type MirrorCanonSyncMetricHooks = { + onConflictWarning?: () => void; + onUpdatesPulled?: (count: number) => void; +}; + async function listCanonicalMarkdownFiles(loreDir: string): Promise { const files: string[] = []; @@ -131,6 +135,7 @@ export async function applyRemoteCanonUpdates(params: { local: MirrorCanonUpdatesSnapshot; remote: MirrorCanonUpdatesSnapshot; remoteContents: Record; + metrics?: MirrorCanonSyncMetricHooks; }): Promise<{ pulledFiles: string[]; skippedFiles: Array<{ path: string; reason: string }>; @@ -145,7 +150,7 @@ export async function applyRemoteCanonUpdates(params: { for (const remoteFile of remoteByPath.values()) { const safePath = resolveSafeCanonPath(params.loreDir, remoteFile.path); if (!safePath) { - incrementMetric("conflict_warnings"); + params.metrics?.onConflictWarning?.(); conflicts.push({ path: remoteFile.path, reason: "unsafe_path", @@ -161,7 +166,7 @@ export async function applyRemoteCanonUpdates(params: { } if (localFile && localFile.updated_at_ms > remoteFile.updated_at_ms) { - incrementMetric("conflict_warnings"); + params.metrics?.onConflictWarning?.(); conflicts.push({ path: remoteFile.path, reason: "local_newer", @@ -175,7 +180,7 @@ export async function applyRemoteCanonUpdates(params: { localFile.updated_at_ms === remoteFile.updated_at_ms && localFile.sha256 !== remoteFile.sha256 ) { - incrementMetric("conflict_warnings"); + params.metrics?.onConflictWarning?.(); conflicts.push({ path: remoteFile.path, reason: "same_timestamp_different_content", @@ -189,7 +194,7 @@ export async function applyRemoteCanonUpdates(params: { params.remote.index_version < params.local.index_version && remoteFile.updated_at_ms <= localFile.updated_at_ms ) { - incrementMetric("conflict_warnings"); + params.metrics?.onConflictWarning?.(); conflicts.push({ path: remoteFile.path, reason: "remote_older_index", @@ -210,7 +215,7 @@ export async function applyRemoteCanonUpdates(params: { draftContent: content, }); if (validation.warningCount > 0) { - incrementMetric("conflict_warnings"); + params.metrics?.onConflictWarning?.(); conflicts.push({ path: remoteFile.path, reason: "invalid_remote_canon", @@ -225,7 +230,7 @@ export async function applyRemoteCanonUpdates(params: { if (pulledFiles.length > 0) { await ensureScrollIndexUpToDate(params.loreDir); - incrementMetric("updates_pulled", pulledFiles.length); + params.metrics?.onUpdatesPulled?.(pulledFiles.length); } return { diff --git a/src/mirror-sync/sync_manager.ts b/src/mirror-sync/sync_manager.ts index 2d120216f3..1e9edc31e3 100644 --- a/src/mirror-sync/sync_manager.ts +++ b/src/mirror-sync/sync_manager.ts @@ -304,6 +304,14 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr local: localUpdates.canon, remote: remoteUpdates.canon, remoteContents, + metrics: { + onConflictWarning: () => { + incrementMetric("conflict_warnings"); + }, + onUpdatesPulled: (count) => { + incrementMetric("updates_pulled", count); + }, + }, }); const graphResult = await syncLocalGraphFromRemote({ From b1e8cae62ee0164ce5c274af59bf5819101a297e Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 14:05:24 -0400 Subject: [PATCH 70/75] sync: inject sync manager observability hooks ## Summary - remove the direct global observability calls from sync_manager.ts - make runtime_host.ts supply the current metric and diagnostic behavior explicitly from the owned daemon observability object - update mirror_sync.test.ts to provide explicit observability hooks while preserving existing assertions ## Why This continues sync ownership cleanup by moving observability emission out of sync_manager.ts and back to the owning runtime/daemon layer. ## Scope Changed: - src/mirror-sync/sync_manager.ts - src/mirror-service/runtime_host.ts - src/mirror-sync/mirror_sync.test.ts Not changed: - route handlers - response shapes - registry/state transitions - adapter-boundary files - broader observability-context refactors - packaging/workflows/docs ## Details This patch: - adds optional observability hooks to createMirrorSyncManager(...) for: - conflict warnings - updates pulled - sync failures - peer announced - pull completed - pull failed - removes incrementMetric and logMirrorEvent imports from src/mirror-sync/sync_manager.ts - has src/mirror-service/runtime_host.ts pass the current behavior via daemon.getObservability() - keeps event names, metric names, and counts unchanged ## Verification - pnpm vitest run src/mirror-sync/mirror_sync.test.ts Co-authored-by: Agent 0 --- src/mirror-service/runtime_host.ts | 20 ++++++++++ src/mirror-sync/mirror_sync.test.ts | 59 ++++++++++++++++++++++++----- src/mirror-sync/sync_manager.ts | 26 +++++++++---- 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/src/mirror-service/runtime_host.ts b/src/mirror-service/runtime_host.ts index e4b95266f3..75ded5c368 100644 --- a/src/mirror-service/runtime_host.ts +++ b/src/mirror-service/runtime_host.ts @@ -123,6 +123,26 @@ export async function createMirrorRuntimeHost( baseUrl: config.baseUrl, fetchImpl: deps.fetchImpl, onRuntimeEvent: daemon.publishRuntimeEvent, + observability: { + onConflictWarning: () => { + daemon.getObservability().incrementMetric("conflict_warnings"); + }, + onUpdatesPulled: (count) => { + daemon.getObservability().incrementMetric("updates_pulled", count); + }, + onSyncFailure: () => { + daemon.getObservability().incrementMetric("sync_failures"); + }, + onPeerAnnounced: (payload) => { + daemon.getObservability().logEvent("sync.peer.announced", payload); + }, + onPullCompleted: (payload) => { + daemon.getObservability().logEvent("sync.pull.completed", payload); + }, + onPullFailed: (payload) => { + daemon.getObservability().logEvent("sync.pull.failed", payload); + }, + }, }); return { diff --git a/src/mirror-sync/mirror_sync.test.ts b/src/mirror-sync/mirror_sync.test.ts index 6104a797c4..b6afe40632 100644 --- a/src/mirror-sync/mirror_sync.test.ts +++ b/src/mirror-sync/mirror_sync.test.ts @@ -5,6 +5,8 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { getMirrorDiagnostics, getMirrorMetrics, + incrementMetric, + logMirrorEvent, resetMirrorDiagnostics, resetMirrorMetrics, } from "../mirror-observability/index.js"; @@ -122,6 +124,43 @@ function createMockResponse() { }; } +function createTestSyncObservabilityHooks() { + return { + onConflictWarning: () => { + incrementMetric("conflict_warnings"); + }, + onUpdatesPulled: (count: number) => { + incrementMetric("updates_pulled", count); + }, + onSyncFailure: () => { + incrementMetric("sync_failures"); + }, + onPeerAnnounced: (payload: { peer_id: string; base_url: string }) => { + logMirrorEvent("sync.peer.announced", payload); + }, + onPullCompleted: (payload: { + peer_id: string; + pulled_files: number; + conflicts: number; + graph_rebuilt: boolean; + }) => { + logMirrorEvent("sync.pull.completed", payload); + }, + onPullFailed: (payload: { peer_id: string; error: string }) => { + logMirrorEvent("sync.pull.failed", payload); + }, + }; +} + +function createObservedSyncManager( + options: Parameters[0], +): MirrorSyncManager { + return createMirrorSyncManager({ + ...options, + observability: createTestSyncObservabilityHooks(), + }); +} + describe("mirror sync", () => { it("executes shared sync actions for the supported routes", async () => { const peer = { @@ -381,7 +420,7 @@ describe("mirror sync", () => { ); await seedIndexFiles(loreDir, { stillness: ["TOBY_L0001_SeedOfStillness.md"] }); - const manager = createMirrorSyncManager({ + const manager = createObservedSyncManager({ nodeId: "node-a", loreDir, baseUrl: "http://127.0.0.1:7001", @@ -407,7 +446,7 @@ describe("mirror sync", () => { ); await seedIndexFiles(loreDir, { renewal: ["TOBY_L0001_SeedOfStillness.md"] }); - const manager = createMirrorSyncManager({ + const manager = createObservedSyncManager({ nodeId: "node-a", loreDir, baseUrl: "http://127.0.0.1:7001", @@ -450,12 +489,12 @@ describe("mirror sync", () => { pond: ["TOBY_L0002_PondMemory.md"], }); - const remoteManager = createMirrorSyncManager({ + const remoteManager = createObservedSyncManager({ nodeId: "node-remote", loreDir: remoteLoreDir, baseUrl: "http://127.0.0.1:7002", }); - const localManager = createMirrorSyncManager({ + const localManager = createObservedSyncManager({ nodeId: "node-local", loreDir: localLoreDir, baseUrl: "http://127.0.0.1:7001", @@ -499,12 +538,12 @@ describe("mirror sync", () => { await fs.utimes(path.join(localLoreDir, "TOBY_L0001_SeedOfStillness.md"), oldTime, oldTime); await fs.utimes(path.join(remoteLoreDir, "TOBY_L0001_SeedOfStillness.md"), newTime, newTime); - const remoteManager = createMirrorSyncManager({ + const remoteManager = createObservedSyncManager({ nodeId: "node-remote", loreDir: remoteLoreDir, baseUrl: "http://127.0.0.1:7002", }); - const localManager = createMirrorSyncManager({ + const localManager = createObservedSyncManager({ nodeId: "node-local", loreDir: localLoreDir, baseUrl: "http://127.0.0.1:7001", @@ -543,12 +582,12 @@ describe("mirror sync", () => { ); await seedIndexFiles(remoteLoreDir, { invalid: ["TOBY_L0002_InvalidRemote.md"] }); - const remoteManager = createMirrorSyncManager({ + const remoteManager = createObservedSyncManager({ nodeId: "node-remote", loreDir: remoteLoreDir, baseUrl: "http://127.0.0.1:7002", }); - const localManager = createMirrorSyncManager({ + const localManager = createObservedSyncManager({ nodeId: "node-local", loreDir: localLoreDir, baseUrl: "http://127.0.0.1:7001", @@ -599,12 +638,12 @@ describe("mirror sync", () => { pond: ["TOBY_L0002_PondMemory.md"], }); - const remoteManager = createMirrorSyncManager({ + const remoteManager = createObservedSyncManager({ nodeId: "node-remote", loreDir: remoteLoreDir, baseUrl: "http://127.0.0.1:7002", }); - const localManager = createMirrorSyncManager({ + const localManager = createObservedSyncManager({ nodeId: "node-local", loreDir: localLoreDir, baseUrl: "http://127.0.0.1:7001", diff --git a/src/mirror-sync/sync_manager.ts b/src/mirror-sync/sync_manager.ts index 1e9edc31e3..95cfe87b19 100644 --- a/src/mirror-sync/sync_manager.ts +++ b/src/mirror-sync/sync_manager.ts @@ -1,5 +1,4 @@ import express from "express"; -import { incrementMetric, logMirrorEvent } from "../mirror-observability/index.js"; import type { FetchLike } from "../mirror-provider/index.js"; import { applyRemoteCanonUpdates, @@ -47,6 +46,19 @@ type MirrorSyncManagerOptions = { fetchImpl?: FetchLike; registry?: MirrorPeerRegistry; onRuntimeEvent?: (type: string, payload?: Record) => void; + observability?: { + onConflictWarning?: () => void; + onUpdatesPulled?: (count: number) => void; + onSyncFailure?: () => void; + onPeerAnnounced?: (payload: { peer_id: string; base_url: string }) => void; + onPullCompleted?: (payload: { + peer_id: string; + pulled_files: number; + conflicts: number; + graph_rebuilt: boolean; + }) => void; + onPullFailed?: (payload: { peer_id: string; error: string }) => void; + }; }; async function parseJsonResponse(response: Response): Promise { @@ -241,7 +253,7 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr peer_id: peer.peer_id, base_url: peer.base_url, }); - logMirrorEvent("sync.peer.announced", { + options.observability?.onPeerAnnounced?.({ peer_id: peer.peer_id, base_url: peer.base_url, }); @@ -306,10 +318,10 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr remoteContents, metrics: { onConflictWarning: () => { - incrementMetric("conflict_warnings"); + options.observability?.onConflictWarning?.(); }, onUpdatesPulled: (count) => { - incrementMetric("updates_pulled", count); + options.observability?.onUpdatesPulled?.(count); }, }, }); @@ -326,7 +338,7 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr conflicts: canonResult.conflicts.length, graph_rebuilt: graphResult.rebuilt, }); - logMirrorEvent("sync.pull.completed", { + options.observability?.onPullCompleted?.({ peer_id: peer.peer_id, pulled_files: canonResult.pulledFiles.length, conflicts: canonResult.conflicts.length, @@ -340,13 +352,13 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr graphResult, }); } catch (error) { - incrementMetric("sync_failures"); + options.observability?.onSyncFailure?.(); registry.markStatus(peer.peer_id, "error", String(error)); options.onRuntimeEvent?.("sync.pull.failed", { peer_id: peer.peer_id, error: String(error), }); - logMirrorEvent("sync.pull.failed", { + options.observability?.onPullFailed?.({ peer_id: peer.peer_id, error: String(error), }); From c8acea4107f518ba20e84179920b9af83675e692 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 15:04:46 -0400 Subject: [PATCH 71/75] status: derive status fields from daemon summaries ## Summary - derive status fields from existing daemon-backed runtime and health summaries instead of reading the boot snapshot directly for those same values - keep provider list behavior unchanged - add focused status-test assertions to pin the summary-derived field relationships ## Why This tightens the Mirror-native status seam by reducing mixed truth sources and leaning on daemon-backed summaries already in use. ## Scope Changed: - src/mirror/status/status.ts - src/mirror/status/tests/status.test.ts Not changed: - src/mirror-service/mirror_service.ts - src/mirrordaemon/runtime_state.ts - src/mirror-sync/* - adapter-boundary files - compat runtime files - status formatting - provider list behavior ## Details This patch: - derives service.lore_dir from health.service.lore_dir - derives service.provider_url from health.service.provider_url - derives service.operator_auth_configured from health.service.operator_auth_configured - derives service.workspace_users_root from runtime.readiness.workspace.users_root - derives lore.ready / lore.discovered_files from runtime.readiness.lore - derives lore.dir from health.service.lore_dir - derives workspace.ready / workspace.users_root from runtime.readiness.workspace - derives sync.node_id from runtime.node_id ## Verification - pnpm vitest run src/mirror/status/tests/status.test.ts Co-authored-by: Agent 0 --- src/mirror/status/status.ts | 21 ++++++++++----------- src/mirror/status/tests/status.test.ts | 3 +++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/mirror/status/status.ts b/src/mirror/status/status.ts index 7487c58af0..fc2792df90 100644 --- a/src/mirror/status/status.ts +++ b/src/mirror/status/status.ts @@ -50,7 +50,6 @@ export type GetMirrorStatusOptions = { export async function getMirrorStatus(opts: GetMirrorStatusOptions): Promise { const daemon = opts.runtimeHost.daemon; - const boot = daemon.getBootSnapshot(); const peers = opts.runtimeHost.syncManager.listPeers(); const baseUrl = opts.runtimeHost.syncManager.getLocalBaseUrl(); const runtime = getMirrordaemonRuntimeState(daemon, { @@ -66,10 +65,10 @@ export async function getMirrorStatus(opts: GetMirrorStatusOptions): Promise { expect(status.runtime.node_id).toBe("status-node"); expect(status.runtime.sessions.total).toBe(0); expect(status.service.lore_dir).toBe(path.resolve(loreDir)); + expect(status.lore.dir).toBe(status.service.lore_dir); + expect(status.service.workspace_users_root).toBe(status.workspace.users_root); expect(status.provider.configured).toBe(false); expect(status.provider.active_provider_id).toBe("primary"); expect(status.provider.total).toBe(1); @@ -71,6 +73,7 @@ describe("mirror status", () => { expect(status.runtime.sessions.total).toBe(1); expect(status.runtime.sessions.open).toBe(1); + expect(status.sync.node_id).toBe(status.runtime.node_id); expect(status.provider.providers[0]?.provider_id).toBe("primary"); const json = JSON.stringify(status); From a5f4c089a244fde056c048ae66ed8a4e0d2c9f26 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 15:35:43 -0400 Subject: [PATCH 72/75] status: derive provider list from daemon provider summary ## Summary - derive status.provider.providers from the daemon-backed provider summary instead of reading runtimeHost.providerPlane.listProviders() directly - extend the daemon provider summary to carry url and last_error - add focused daemon provider-summary coverage for the new fields ## Why This finishes the remaining provider truth gap in the status seam by making the status provider list come from the same daemon-backed summary path as the other provider aggregate fields. ## Scope Changed: - src/mirrordaemon/daemon_types.ts - src/mirrordaemon/runtime_state.ts - src/mirrordaemon/runtime_state.test.ts - src/mirror/status/status.ts Not changed: - src/mirror-service/* - src/mirror-sync/* - adapter-boundary files - compat runtime files - status formatting - the excluded #130 / #133 seams ## Details This patch: - adds url and last_error to MirrordaemonProvidersSummary.providers[] - populates those fields in buildProvidersSummary(...) - changes status.ts to source: - provider.active_provider_id - provider.total - provider.available - provider.fallback_available - provider.providers from getMirrordaemonProvidersState(...) instead of mixing in direct providerPlane.listProviders() reads ## Verification - pnpm vitest run src/mirrordaemon/runtime_state.test.ts - pnpm vitest run src/mirror/status/tests/status.test.ts --------- Co-authored-by: Agent 0 --- src/mirror/status/status.ts | 14 +++--- src/mirrordaemon/daemon_types.ts | 2 + src/mirrordaemon/runtime_state.test.ts | 62 ++++++++++++++++++++++++++ src/mirrordaemon/runtime_state.ts | 2 + 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/mirror/status/status.ts b/src/mirror/status/status.ts index fc2792df90..c5aabddfd1 100644 --- a/src/mirror/status/status.ts +++ b/src/mirror/status/status.ts @@ -1,6 +1,7 @@ import type { MirrorRuntimeHost } from "../../mirror-service/index.js"; import { getMirrordaemonHealthState, + getMirrordaemonProvidersState, getMirrordaemonRuntimeState, } from "../../mirrordaemon/index.js"; @@ -61,6 +62,9 @@ export async function getMirrorStatus(opts: GetMirrorStatusOptions): Promise ({ + active_provider_id: providers.active_provider_id, + total: providers.total, + available: providers.available, + fallback_available: providers.fallback_available, + providers: providers.providers.map((provider) => ({ provider_id: provider.provider_id, label: provider.label, url: provider.url, diff --git a/src/mirrordaemon/daemon_types.ts b/src/mirrordaemon/daemon_types.ts index d0e35c3130..9bf7c781f0 100644 --- a/src/mirrordaemon/daemon_types.ts +++ b/src/mirrordaemon/daemon_types.ts @@ -194,9 +194,11 @@ export type MirrordaemonProvidersSummary = { provider_id: string; label: string; kind: string; + url: string; ready: boolean; configured: boolean; selected: boolean; + last_error?: string; }>; }; diff --git a/src/mirrordaemon/runtime_state.test.ts b/src/mirrordaemon/runtime_state.test.ts index 197783eb98..16ea7aab51 100644 --- a/src/mirrordaemon/runtime_state.test.ts +++ b/src/mirrordaemon/runtime_state.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { + buildProvidersSummary, buildDebugSnapshot, buildHealthSummary, buildRuntimeSummary, @@ -227,4 +228,65 @@ describe("mirrordaemon runtime state", () => { expect(health.event_stream.recent_events).toBe(daemon.getRecentEvents().length); expect(health.sync.peers_known).toBe(2); }); + + it("includes provider url and last_error in the daemon provider summary", () => { + const daemon = createMirrordaemon({ + config: baseConfig, + lifecycle: { + discoveredLoreFiles: 2, + shutdown: async () => undefined, + }, + runtimeStartedAt: "2026-03-13T00:00:00.000Z", + }); + + const providers = buildProvidersSummary(daemon, { + providerPlane: { + listProviders: () => [ + { + provider_id: "primary", + label: "Primary", + kind: "openai-compatible", + url: "http://brain.local/v1/chat/completions", + ready: true, + configured: true, + selected: true, + last_error: undefined, + }, + { + provider_id: "fallback", + label: "Fallback", + kind: "openai-compatible", + url: "http://brain.local/v1/fallback", + ready: false, + configured: false, + selected: false, + last_error: "provider unavailable", + }, + ], + } as never, + }); + + expect(providers.providers).toEqual([ + { + provider_id: "primary", + label: "Primary", + kind: "openai-compatible", + url: "http://brain.local/v1/chat/completions", + ready: true, + configured: true, + selected: true, + last_error: undefined, + }, + { + provider_id: "fallback", + label: "Fallback", + kind: "openai-compatible", + url: "http://brain.local/v1/fallback", + ready: false, + configured: false, + selected: false, + last_error: "provider unavailable", + }, + ]); + }); }); diff --git a/src/mirrordaemon/runtime_state.ts b/src/mirrordaemon/runtime_state.ts index 260905be59..f4e0575dfb 100644 --- a/src/mirrordaemon/runtime_state.ts +++ b/src/mirrordaemon/runtime_state.ts @@ -95,9 +95,11 @@ export function buildProvidersSummary( provider_id: provider.provider_id, label: provider.label, kind: provider.kind, + url: provider.url, ready: provider.ready, configured: provider.configured, selected: provider.selected, + last_error: provider.last_error, })) ?? []; return { From 6762b4d1cd1ac7a364261a6fa60cb9b935f5c586 Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 19:22:36 -0400 Subject: [PATCH 73/75] runtime(ws): derive hello envelope from daemon runtime summary Description: - derive websocket hello envelope truth from daemon runtime summary - source node_id and runtime_started_at from getMirrordaemonRuntimeState(...) - preserve hello envelope shape, backlog replay, and live event flow - keep scope limited to runtime_events_ws only - no routing, sync, compat, daemon-internal, or protocol-shape changes After merge, run this on home dev: Co-authored-by: Agent 0 --- src/mirror-service/runtime_events_ws.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/mirror-service/runtime_events_ws.ts b/src/mirror-service/runtime_events_ws.ts index a91aab36a0..ba1b985ba1 100644 --- a/src/mirror-service/runtime_events_ws.ts +++ b/src/mirror-service/runtime_events_ws.ts @@ -3,7 +3,11 @@ import { randomUUID } from "node:crypto"; import type http from "node:http"; import type { Duplex } from "node:stream"; import { WebSocketServer, type WebSocket } from "ws"; -import type { Mirrordaemon, MirrordaemonRuntimeEvent } from "../mirrordaemon/index.js"; +import { + getMirrordaemonRuntimeState, + type Mirrordaemon, + type MirrordaemonRuntimeEvent, +} from "../mirrordaemon/index.js"; export const MIRROR_RUNTIME_WS_PROTOCOL = "mirror.runtime.ws.v1"; export const MIRROR_RUNTIME_WS_PATH = "/mirror/runtime/ws"; @@ -121,7 +125,7 @@ export function createMirrorRuntimeWebSocketServer(params: { wss.on("connection", (ws, req) => { sockets.add(ws); const connectionId = randomUUID(); - const boot = params.daemon.getBootSnapshot(); + const runtime = getMirrordaemonRuntimeState(params.daemon); const url = new URL(req.url ?? MIRROR_RUNTIME_WS_PATH, "http://127.0.0.1"); params.daemon.publishRuntimeEvent("runtime.ws.connected", { @@ -133,8 +137,8 @@ export function createMirrorRuntimeWebSocketServer(params: { protocol: MIRROR_RUNTIME_WS_PROTOCOL, type: "hello", connection_id: connectionId, - node_id: boot.config.node_id, - runtime_started_at: boot.runtime_started_at, + node_id: runtime.node_id, + runtime_started_at: runtime.runtime_started_at, stream: RUNTIME_EVENT_STREAM, }); From 44e8e320fa726951eea743b48d3cbc8d9b941c5c Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 19:53:00 -0400 Subject: [PATCH 74/75] Mirror/sync manager inject observability hooks Description: - centralize sync observability hook normalization at the sync-manager seam - let runtime_host pass daemon.getObservability() directly - preserve sync action result shapes, policy flow, event ordering, and HTTP/error wrapper behavior - keep scope limited to sync_manager and runtime_host - verification passed: - pnpm check - pnpm vitest run src/mirror-sync/mirror_sync.test.ts - pnpm vitest run src/mirror-service/mirror_service.test.ts --------- Co-authored-by: Agent 0 --- src/mirror-service/runtime_host.ts | 21 +------ src/mirror-sync/sync_manager.ts | 91 +++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/mirror-service/runtime_host.ts b/src/mirror-service/runtime_host.ts index 75ded5c368..cefab49be7 100644 --- a/src/mirror-service/runtime_host.ts +++ b/src/mirror-service/runtime_host.ts @@ -123,26 +123,7 @@ export async function createMirrorRuntimeHost( baseUrl: config.baseUrl, fetchImpl: deps.fetchImpl, onRuntimeEvent: daemon.publishRuntimeEvent, - observability: { - onConflictWarning: () => { - daemon.getObservability().incrementMetric("conflict_warnings"); - }, - onUpdatesPulled: (count) => { - daemon.getObservability().incrementMetric("updates_pulled", count); - }, - onSyncFailure: () => { - daemon.getObservability().incrementMetric("sync_failures"); - }, - onPeerAnnounced: (payload) => { - daemon.getObservability().logEvent("sync.peer.announced", payload); - }, - onPullCompleted: (payload) => { - daemon.getObservability().logEvent("sync.pull.completed", payload); - }, - onPullFailed: (payload) => { - daemon.getObservability().logEvent("sync.pull.failed", payload); - }, - }, + observability: daemon.getObservability(), }); return { diff --git a/src/mirror-sync/sync_manager.ts b/src/mirror-sync/sync_manager.ts index 95cfe87b19..f1973787b6 100644 --- a/src/mirror-sync/sync_manager.ts +++ b/src/mirror-sync/sync_manager.ts @@ -1,4 +1,5 @@ import express from "express"; +import type { MirrorObservabilityContext } from "../mirror-observability/index.js"; import type { FetchLike } from "../mirror-provider/index.js"; import { applyRemoteCanonUpdates, @@ -39,6 +40,25 @@ export type MirrorSyncHandlers = { pull: (req: express.Request, res: express.Response) => Promise; }; +export type MirrorSyncObservabilityHooks = { + onConflictWarning?: () => void; + onUpdatesPulled?: (count: number) => void; + onSyncFailure?: () => void; + onPeerAnnounced?: (payload: { peer_id: string; base_url: string }) => void; + onPullCompleted?: (payload: { + peer_id: string; + pulled_files: number; + conflicts: number; + graph_rebuilt: boolean; + }) => void; + onPullFailed?: (payload: { peer_id: string; error: string }) => void; +}; + +type MirrorSyncObservabilityContextLike = Pick< + MirrorObservabilityContext, + "incrementMetric" | "logEvent" +>; + type MirrorSyncManagerOptions = { nodeId: string; loreDir: string; @@ -46,21 +66,53 @@ type MirrorSyncManagerOptions = { fetchImpl?: FetchLike; registry?: MirrorPeerRegistry; onRuntimeEvent?: (type: string, payload?: Record) => void; - observability?: { - onConflictWarning?: () => void; - onUpdatesPulled?: (count: number) => void; - onSyncFailure?: () => void; - onPeerAnnounced?: (payload: { peer_id: string; base_url: string }) => void; - onPullCompleted?: (payload: { - peer_id: string; - pulled_files: number; - conflicts: number; - graph_rebuilt: boolean; - }) => void; - onPullFailed?: (payload: { peer_id: string; error: string }) => void; - }; + observability?: MirrorSyncObservabilityHooks | MirrorSyncObservabilityContextLike; }; +function hasMirrorSyncObservabilityContext( + observability: MirrorSyncManagerOptions["observability"], +): observability is MirrorSyncObservabilityContextLike { + return ( + !!observability && + typeof observability === "object" && + "incrementMetric" in observability && + typeof observability.incrementMetric === "function" && + "logEvent" in observability && + typeof observability.logEvent === "function" + ); +} + +function normalizeMirrorSyncObservabilityHooks( + observability: MirrorSyncManagerOptions["observability"], +): MirrorSyncObservabilityHooks { + if (!observability) { + return {}; + } + if (!hasMirrorSyncObservabilityContext(observability)) { + return observability; + } + return { + onConflictWarning: () => { + observability.incrementMetric("conflict_warnings"); + }, + onUpdatesPulled: (count) => { + observability.incrementMetric("updates_pulled", count); + }, + onSyncFailure: () => { + observability.incrementMetric("sync_failures"); + }, + onPeerAnnounced: (payload) => { + observability.logEvent("sync.peer.announced", payload); + }, + onPullCompleted: (payload) => { + observability.logEvent("sync.pull.completed", payload); + }, + onPullFailed: (payload) => { + observability.logEvent("sync.pull.failed", payload); + }, + }; +} + async function parseJsonResponse(response: Response): Promise { if (!response.ok) { throw new Error(`sync request failed: ${response.status} ${await response.text()}`); @@ -237,6 +289,7 @@ function collectMirrorSyncPullNeededPaths( export function createMirrorSyncManager(options: MirrorSyncManagerOptions): MirrorSyncManager { const fetchImpl = options.fetchImpl ?? fetch; const registry = options.registry ?? createMirrorPeerRegistry(); + const observability = normalizeMirrorSyncObservabilityHooks(options.observability); let localBaseUrl = options.baseUrl ? normalizeMirrorPeerBaseUrl(options.baseUrl) : null; return { @@ -253,7 +306,7 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr peer_id: peer.peer_id, base_url: peer.base_url, }); - options.observability?.onPeerAnnounced?.({ + observability.onPeerAnnounced?.({ peer_id: peer.peer_id, base_url: peer.base_url, }); @@ -318,10 +371,10 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr remoteContents, metrics: { onConflictWarning: () => { - options.observability?.onConflictWarning?.(); + observability.onConflictWarning?.(); }, onUpdatesPulled: (count) => { - options.observability?.onUpdatesPulled?.(count); + observability.onUpdatesPulled?.(count); }, }, }); @@ -338,7 +391,7 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr conflicts: canonResult.conflicts.length, graph_rebuilt: graphResult.rebuilt, }); - options.observability?.onPullCompleted?.({ + observability.onPullCompleted?.({ peer_id: peer.peer_id, pulled_files: canonResult.pulledFiles.length, conflicts: canonResult.conflicts.length, @@ -352,13 +405,13 @@ export function createMirrorSyncManager(options: MirrorSyncManagerOptions): Mirr graphResult, }); } catch (error) { - options.observability?.onSyncFailure?.(); + observability.onSyncFailure?.(); registry.markStatus(peer.peer_id, "error", String(error)); options.onRuntimeEvent?.("sync.pull.failed", { peer_id: peer.peer_id, error: String(error), }); - options.observability?.onPullFailed?.({ + observability.onPullFailed?.({ peer_id: peer.peer_id, error: String(error), }); From c56b4405d3bccdd02c109c7f81274dc793ffde5c Mon Sep 17 00:00:00 2001 From: ToadAid Date: Mon, 23 Mar 2026 20:01:42 -0400 Subject: [PATCH 75/75] mirrordaemon(debug): drop dead peersKnown override (#213) ## Summary - remove the dead peersKnown override from the /mirror/runtime/debug route - let debug peer truth flow from canonical runtime state instead of service-side observability fallback - preserve debug response shape and all non-peer behavior ## Why This removes a dead fallback seam from the runtime debug path and keeps debug peer truth aligned with canonical runtime state. ## Scope Changed: - src/mirror-service/mirror_service.ts - src/mirrordaemon/mirrordaemon.test.ts Not changed: - src/mirrordaemon/runtime_state.ts - health/status behavior - sync protocol - websocket behavior - compat files - packaging - workflows ## Verification - pnpm check - pnpm vitest run src/mirror-service/mirror_service.test.ts - pnpm vitest run src/mirrordaemon/mirrordaemon.test.ts Co-authored-by: Agent 0 --- src/mirror-service/mirror_service.ts | 1 - src/mirrordaemon/mirrordaemon.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/mirror-service/mirror_service.ts b/src/mirror-service/mirror_service.ts index 42aa02a879..8951968094 100644 --- a/src/mirror-service/mirror_service.ts +++ b/src/mirror-service/mirror_service.ts @@ -180,7 +180,6 @@ export async function startMirrorService( getMirrordaemonDebugState(daemon, { port: boundPort, baseUrl: syncManager.getLocalBaseUrl(), - peersKnown: observability.getMetrics().gauges.peers_known || syncManager.listPeers().length, }), ); }); diff --git a/src/mirrordaemon/mirrordaemon.test.ts b/src/mirrordaemon/mirrordaemon.test.ts index 4ab69a8e63..48dca71885 100644 --- a/src/mirrordaemon/mirrordaemon.test.ts +++ b/src/mirrordaemon/mirrordaemon.test.ts @@ -109,7 +109,6 @@ describe("mirrordaemon", () => { const debug = getMirrordaemonDebugState(daemon, { port: 7788, baseUrl: "http://127.0.0.1:7788", - peersKnown: 2, }); expect(runtime.node_id).toBe("daemon-node");