orbit-agent-runtime 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +750 -0
  2. package/LICENSE +201 -0
  3. package/README.md +492 -0
  4. package/README.zh-CN.md +389 -0
  5. package/benchmarks/README.md +24 -0
  6. package/benchmarks/gateway.mjs +68 -0
  7. package/benchmarks/pae.mjs +90 -0
  8. package/benchmarks/replay.mjs +51 -0
  9. package/benchmarks/run-all.mjs +24 -0
  10. package/benchmarks/wal.mjs +50 -0
  11. package/bin/orbit.mjs +523 -0
  12. package/dist/.tsbuildinfo +1 -0
  13. package/dist/demo-deepseek.d.ts +2 -0
  14. package/dist/demo-deepseek.d.ts.map +1 -0
  15. package/dist/demo-deepseek.js +66 -0
  16. package/dist/demo-deepseek.js.map +1 -0
  17. package/dist/demo-host.d.ts +2 -0
  18. package/dist/demo-host.d.ts.map +1 -0
  19. package/dist/demo-host.js +98 -0
  20. package/dist/demo-host.js.map +1 -0
  21. package/dist/demo-replay.d.ts +2 -0
  22. package/dist/demo-replay.d.ts.map +1 -0
  23. package/dist/demo-replay.js +68 -0
  24. package/dist/demo-replay.js.map +1 -0
  25. package/dist/src/core/orbitRuntimeHost.d.ts +349 -0
  26. package/dist/src/core/orbitRuntimeHost.d.ts.map +1 -0
  27. package/dist/src/core/orbitRuntimeHost.js +706 -0
  28. package/dist/src/core/orbitRuntimeHost.js.map +1 -0
  29. package/dist/src/index.d.ts +92 -0
  30. package/dist/src/index.d.ts.map +1 -0
  31. package/dist/src/index.js +226 -0
  32. package/dist/src/index.js.map +1 -0
  33. package/dist/test/audit_chain.test.d.ts +2 -0
  34. package/dist/test/audit_chain.test.d.ts.map +1 -0
  35. package/dist/test/audit_chain.test.js +283 -0
  36. package/dist/test/audit_chain.test.js.map +1 -0
  37. package/dist/test/channel_hub.test.d.ts +2 -0
  38. package/dist/test/channel_hub.test.d.ts.map +1 -0
  39. package/dist/test/channel_hub.test.js +66 -0
  40. package/dist/test/channel_hub.test.js.map +1 -0
  41. package/dist/test/cordis_adapter.test.d.ts +2 -0
  42. package/dist/test/cordis_adapter.test.d.ts.map +1 -0
  43. package/dist/test/cordis_adapter.test.js +280 -0
  44. package/dist/test/cordis_adapter.test.js.map +1 -0
  45. package/dist/test/cost_routing.test.d.ts +2 -0
  46. package/dist/test/cost_routing.test.d.ts.map +1 -0
  47. package/dist/test/cost_routing.test.js +31 -0
  48. package/dist/test/cost_routing.test.js.map +1 -0
  49. package/dist/test/domain_transaction.test.d.ts +2 -0
  50. package/dist/test/domain_transaction.test.d.ts.map +1 -0
  51. package/dist/test/domain_transaction.test.js +351 -0
  52. package/dist/test/domain_transaction.test.js.map +1 -0
  53. package/dist/test/file_channel.test.d.ts +2 -0
  54. package/dist/test/file_channel.test.d.ts.map +1 -0
  55. package/dist/test/file_channel.test.js +179 -0
  56. package/dist/test/file_channel.test.js.map +1 -0
  57. package/dist/test/gateway.test.d.ts +2 -0
  58. package/dist/test/gateway.test.d.ts.map +1 -0
  59. package/dist/test/gateway.test.js +548 -0
  60. package/dist/test/gateway.test.js.map +1 -0
  61. package/dist/test/governance_profile.test.d.ts +2 -0
  62. package/dist/test/governance_profile.test.d.ts.map +1 -0
  63. package/dist/test/governance_profile.test.js +214 -0
  64. package/dist/test/governance_profile.test.js.map +1 -0
  65. package/dist/test/governance_schema.test.d.ts +2 -0
  66. package/dist/test/governance_schema.test.d.ts.map +1 -0
  67. package/dist/test/governance_schema.test.js +200 -0
  68. package/dist/test/governance_schema.test.js.map +1 -0
  69. package/dist/test/host_integration.test.d.ts +2 -0
  70. package/dist/test/host_integration.test.d.ts.map +1 -0
  71. package/dist/test/host_integration.test.js +104 -0
  72. package/dist/test/host_integration.test.js.map +1 -0
  73. package/dist/test/host_journal_persistence.test.d.ts +2 -0
  74. package/dist/test/host_journal_persistence.test.d.ts.map +1 -0
  75. package/dist/test/host_journal_persistence.test.js +367 -0
  76. package/dist/test/host_journal_persistence.test.js.map +1 -0
  77. package/dist/test/impact_domain.test.d.ts +2 -0
  78. package/dist/test/impact_domain.test.d.ts.map +1 -0
  79. package/dist/test/impact_domain.test.js +48 -0
  80. package/dist/test/impact_domain.test.js.map +1 -0
  81. package/dist/test/injected_clock.test.d.ts +13 -0
  82. package/dist/test/injected_clock.test.d.ts.map +1 -0
  83. package/dist/test/injected_clock.test.js +200 -0
  84. package/dist/test/injected_clock.test.js.map +1 -0
  85. package/dist/test/isolation_domain.test.d.ts +2 -0
  86. package/dist/test/isolation_domain.test.d.ts.map +1 -0
  87. package/dist/test/isolation_domain.test.js +302 -0
  88. package/dist/test/isolation_domain.test.js.map +1 -0
  89. package/dist/test/journal_wal.test.d.ts +2 -0
  90. package/dist/test/journal_wal.test.d.ts.map +1 -0
  91. package/dist/test/journal_wal.test.js +622 -0
  92. package/dist/test/journal_wal.test.js.map +1 -0
  93. package/dist/test/mcp_adapter.test.d.ts +2 -0
  94. package/dist/test/mcp_adapter.test.d.ts.map +1 -0
  95. package/dist/test/mcp_adapter.test.js +440 -0
  96. package/dist/test/mcp_adapter.test.js.map +1 -0
  97. package/dist/test/openai_compat_channel.test.d.ts +2 -0
  98. package/dist/test/openai_compat_channel.test.d.ts.map +1 -0
  99. package/dist/test/openai_compat_channel.test.js +325 -0
  100. package/dist/test/openai_compat_channel.test.js.map +1 -0
  101. package/dist/test/openapi_adapter.test.d.ts +2 -0
  102. package/dist/test/openapi_adapter.test.d.ts.map +1 -0
  103. package/dist/test/openapi_adapter.test.js +270 -0
  104. package/dist/test/openapi_adapter.test.js.map +1 -0
  105. package/dist/test/orbit_cli.test.d.ts +2 -0
  106. package/dist/test/orbit_cli.test.d.ts.map +1 -0
  107. package/dist/test/orbit_cli.test.js +137 -0
  108. package/dist/test/orbit_cli.test.js.map +1 -0
  109. package/dist/test/pact_verifier.test.d.ts +2 -0
  110. package/dist/test/pact_verifier.test.d.ts.map +1 -0
  111. package/dist/test/pact_verifier.test.js +41 -0
  112. package/dist/test/pact_verifier.test.js.map +1 -0
  113. package/dist/test/pae_adapter.test.d.ts +2 -0
  114. package/dist/test/pae_adapter.test.d.ts.map +1 -0
  115. package/dist/test/pae_adapter.test.js +469 -0
  116. package/dist/test/pae_adapter.test.js.map +1 -0
  117. package/dist/test/plugin_sandbox_guard.test.d.ts +2 -0
  118. package/dist/test/plugin_sandbox_guard.test.d.ts.map +1 -0
  119. package/dist/test/plugin_sandbox_guard.test.js +57 -0
  120. package/dist/test/plugin_sandbox_guard.test.js.map +1 -0
  121. package/dist/test/rate_limit_behavior.test.d.ts +2 -0
  122. package/dist/test/rate_limit_behavior.test.d.ts.map +1 -0
  123. package/dist/test/rate_limit_behavior.test.js +85 -0
  124. package/dist/test/rate_limit_behavior.test.js.map +1 -0
  125. package/dist/test/replay_compat.test.d.ts +2 -0
  126. package/dist/test/replay_compat.test.d.ts.map +1 -0
  127. package/dist/test/replay_compat.test.js +1122 -0
  128. package/dist/test/replay_compat.test.js.map +1 -0
  129. package/dist/test/replay_engine.test.d.ts +2 -0
  130. package/dist/test/replay_engine.test.d.ts.map +1 -0
  131. package/dist/test/replay_engine.test.js +85 -0
  132. package/dist/test/replay_engine.test.js.map +1 -0
  133. package/dist/test/shell_channel.test.d.ts +2 -0
  134. package/dist/test/shell_channel.test.d.ts.map +1 -0
  135. package/dist/test/shell_channel.test.js +173 -0
  136. package/dist/test/shell_channel.test.js.map +1 -0
  137. package/dist/test/teardown_isolation.test.d.ts +10 -0
  138. package/dist/test/teardown_isolation.test.d.ts.map +1 -0
  139. package/dist/test/teardown_isolation.test.js +279 -0
  140. package/dist/test/teardown_isolation.test.js.map +1 -0
  141. package/dist/test/token_budget.test.d.ts +2 -0
  142. package/dist/test/token_budget.test.d.ts.map +1 -0
  143. package/dist/test/token_budget.test.js +113 -0
  144. package/dist/test/token_budget.test.js.map +1 -0
  145. package/dist/test/trace_journal.test.d.ts +2 -0
  146. package/dist/test/trace_journal.test.d.ts.map +1 -0
  147. package/dist/test/trace_journal.test.js +43 -0
  148. package/dist/test/trace_journal.test.js.map +1 -0
  149. package/dist/test/trace_journal_persistence.test.d.ts +2 -0
  150. package/dist/test/trace_journal_persistence.test.d.ts.map +1 -0
  151. package/dist/test/trace_journal_persistence.test.js +175 -0
  152. package/dist/test/trace_journal_persistence.test.js.map +1 -0
  153. package/dist/test/trace_persistence.test.d.ts +2 -0
  154. package/dist/test/trace_persistence.test.d.ts.map +1 -0
  155. package/dist/test/trace_persistence.test.js +216 -0
  156. package/dist/test/trace_persistence.test.js.map +1 -0
  157. package/dist/test/trip_protector.test.d.ts +2 -0
  158. package/dist/test/trip_protector.test.d.ts.map +1 -0
  159. package/dist/test/trip_protector.test.js +42 -0
  160. package/dist/test/trip_protector.test.js.map +1 -0
  161. package/examples/README.md +34 -0
  162. package/examples/cli-record-replay.mjs +74 -0
  163. package/examples/custom-channel.mjs +102 -0
  164. package/examples/js-pae-plugin.mjs +128 -0
  165. package/examples/mcp-adapter.mjs +102 -0
  166. package/package.json +60 -0
  167. package/src/core/orbitRuntimeHost.ts +853 -0
  168. package/src/index.ts +256 -0
@@ -0,0 +1,1122 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const node_test_1 = require("node:test");
40
+ const strict_1 = __importDefault(require("node:assert/strict"));
41
+ const node_fs_1 = require("node:fs");
42
+ const os = __importStar(require("node:os"));
43
+ const path = __importStar(require("node:path"));
44
+ const core_hub_1 = require("@orbit/core-hub");
45
+ const core_hub_2 = require("@orbit/core-hub");
46
+ const core_hub_3 = require("@orbit/core-hub");
47
+ const core_hub_4 = require("@orbit/core-hub");
48
+ const core_hub_5 = require("@orbit/core-hub");
49
+ const core_hub_6 = require("@orbit/core-hub");
50
+ const core_hub_7 = require("@orbit/core-hub");
51
+ const core_hub_8 = require("@orbit/core-hub");
52
+ const core_hub_9 = require("@orbit/core-hub");
53
+ const pae_engine_1 = require("@orbit/pae-engine");
54
+ const pae_engine_2 = require("@orbit/pae-engine");
55
+ const pae_engine_3 = require("@orbit/pae-engine");
56
+ const pae_engine_4 = require("@orbit/pae-engine");
57
+ const pae_engine_5 = require("@orbit/pae-engine");
58
+ const pae_engine_6 = require("@orbit/pae-engine");
59
+ const pae_engine_7 = require("@orbit/pae-engine");
60
+ const pae_engine_8 = require("@orbit/pae-engine");
61
+ const pae_engine_9 = require("@orbit/pae-engine");
62
+ const pae_engine_10 = require("@orbit/pae-engine");
63
+ const pae_engine_11 = require("@orbit/pae-engine");
64
+ const sandbox_runtime_1 = require("@orbit/sandbox-runtime");
65
+ const sandbox_runtime_2 = require("@orbit/sandbox-runtime");
66
+ const sandbox_runtime_3 = require("@orbit/sandbox-runtime");
67
+ const sandbox_runtime_4 = require("@orbit/sandbox-runtime");
68
+ const sandbox_runtime_5 = require("@orbit/sandbox-runtime");
69
+ const orbitRuntimeHost_1 = require("../src/core/orbitRuntimeHost");
70
+ const infra_common_1 = require("@orbit/infra-common");
71
+ const infra_common_2 = require("@orbit/infra-common");
72
+ /**
73
+ * W2 gate: the replay-compatibility suite. Every new channel mechanism MUST
74
+ * pass "record → replay, byte-identical, zero side effects, zero external
75
+ * calls" before it is allowed into the kernel (kernel charter, axiom A1).
76
+ * CI treats this file as a merge gate.
77
+ *
78
+ * Every script is executed twice: once with replayMode "record" (real
79
+ * channels, outputs journaled) and once with "replay" (journal injection,
80
+ * channels never run). Byte-identity and reconciliation are then asserted.
81
+ */
82
+ const NODE = process.execPath;
83
+ function makeCtx(mode, overrides = {}) {
84
+ return { traceMarkId: "t-compat", maxWaitMs: 30_000, replayMode: mode, ...overrides };
85
+ }
86
+ /**
87
+ * Run one script twice against the same hub: first in record mode (real
88
+ * channels), then in replay mode (journal injection). Returns both outputs
89
+ * plus the reconciliation report over the two journals.
90
+ */
91
+ async function recordThenReplay(hub, script) {
92
+ const recordJournal = new core_hub_5.RecordJournal();
93
+ hub.attachRecordJournal(recordJournal);
94
+ const live = await script("record");
95
+ const replayJournal = new core_hub_5.RecordJournal();
96
+ hub.attachRecordJournal(replayJournal);
97
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal));
98
+ const replayed = await script("replay");
99
+ const report = new core_hub_6.ReplayEngine(recordJournal).reconcile(recordJournal.snapshot(), replayJournal.snapshot());
100
+ return { live, replayed, recordJournal, reconcileDigestChain: report.digestChainConsistent };
101
+ }
102
+ // ---------------------------------------------------------------------------
103
+ // FileChannel
104
+ // ---------------------------------------------------------------------------
105
+ (0, node_test_1.test)("replay_compat FileChannel: record → delete disk → replay is byte-identical with zero IO", async () => {
106
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-file-"));
107
+ const hub = new core_hub_1.ChannelHub();
108
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: root }));
109
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
110
+ const script = async (mode) => {
111
+ const bytes = await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "writeTextFile", "data/a.txt", "orbit replay proof");
112
+ const content = await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "readTextFile", "data/a.txt");
113
+ const listing = await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "listDir", "data");
114
+ const stat = await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "statPath", "data/a.txt");
115
+ return { bytes, content, listing, stat };
116
+ };
117
+ const { live, replayed, recordJournal, reconcileDigestChain } = await recordThenReplay(hub, script);
118
+ strict_1.default.equal(live.content, "orbit replay proof");
119
+ strict_1.default.deepEqual(replayed, live);
120
+ strict_1.default.equal(reconcileDigestChain, true);
121
+ // The proof of "zero IO on replay": wipe the directory, replay again, and
122
+ // the outputs still come back identical from the journal alone.
123
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
124
+ const replayJournal2 = new core_hub_5.RecordJournal();
125
+ hub.attachRecordJournal(replayJournal2);
126
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal)); // start a new replay session
127
+ const replayed2 = await script("replay");
128
+ strict_1.default.deepEqual(replayed2, live);
129
+ strict_1.default.equal(new core_hub_6.ReplayEngine(recordJournal).reconcile(recordJournal.snapshot(), replayJournal2.snapshot()).digestChainConsistent, true);
130
+ await hub.teardown();
131
+ });
132
+ (0, node_test_1.test)("replay_compat FileChannel: a read of a missing file records null and replays null", async () => {
133
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-miss-"));
134
+ const hub = new core_hub_1.ChannelHub();
135
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: root }));
136
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
137
+ const { live, replayed, reconcileDigestChain } = await recordThenReplay(hub, (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "readTextFile", "ghost.txt"));
138
+ strict_1.default.equal(live, null);
139
+ strict_1.default.equal(replayed, null);
140
+ strict_1.default.equal(reconcileDigestChain, true);
141
+ await hub.teardown();
142
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
143
+ });
144
+ // ---------------------------------------------------------------------------
145
+ // ShellChannel
146
+ // ---------------------------------------------------------------------------
147
+ (0, node_test_1.test)("replay_compat ShellChannel: replay reproduces the result without executing anything", async () => {
148
+ const workDir = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-shell-"));
149
+ const sideEffect = path.join(workDir, "side-effect.txt");
150
+ const hub = new core_hub_1.ChannelHub();
151
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.SHELL_EXEC, new core_hub_3.ShellChannel({ allowedCommands: [NODE], workDir, timeoutMs: 15_000 }));
152
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
153
+ // The command has an observable side effect: it writes a file.
154
+ const script = async (mode) => {
155
+ return hub.fireChannelCall(infra_common_2.ChannelKind.SHELL_EXEC, makeCtx(mode), "execCommand", NODE, ["-e", `require("fs").writeFileSync(${JSON.stringify(sideEffect)}, "executed"); process.stdout.write("cmd-output-42")`]);
156
+ };
157
+ const { live, replayed, recordJournal, reconcileDigestChain } = await recordThenReplay(hub, script);
158
+ strict_1.default.equal(live.stdout, "cmd-output-42");
159
+ strict_1.default.equal(live.exitCode, 0);
160
+ strict_1.default.deepEqual(replayed, live);
161
+ strict_1.default.equal(reconcileDigestChain, true);
162
+ // Zero-execution proof: delete the side-effect file, replay again — the
163
+ // result is still served from the journal and the file is NOT recreated.
164
+ await node_fs_1.promises.rm(sideEffect, { force: true });
165
+ const replayJournal2 = new core_hub_5.RecordJournal();
166
+ hub.attachRecordJournal(replayJournal2);
167
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal)); // new replay session
168
+ const replayed2 = await script("replay");
169
+ strict_1.default.deepEqual(replayed2, live);
170
+ strict_1.default.equal(await node_fs_1.promises.stat(sideEffect).then(() => true, () => false), false, "replay must not re-execute commands");
171
+ await hub.teardown();
172
+ await node_fs_1.promises.rm(workDir, { recursive: true, force: true });
173
+ });
174
+ (0, node_test_1.test)("replay_compat ShellChannel: whitelist denials are deterministic pre-execution failures", async () => {
175
+ const workDir = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-deny-"));
176
+ const hub = new core_hub_1.ChannelHub();
177
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.SHELL_EXEC, new core_hub_3.ShellChannel({ allowedCommands: [NODE], workDir }));
178
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
179
+ await strict_1.default.rejects(hub.fireChannelCall(infra_common_2.ChannelKind.SHELL_EXEC, makeCtx("record"), "execCommand", "rm", ["-rf", "/"]), /not whitelisted/);
180
+ await hub.teardown();
181
+ await node_fs_1.promises.rm(workDir, { recursive: true, force: true });
182
+ });
183
+ // ---------------------------------------------------------------------------
184
+ // OpenAICompatChannel
185
+ // ---------------------------------------------------------------------------
186
+ (0, node_test_1.test)("replay_compat OpenAICompat: replay makes zero HTTP calls", async () => {
187
+ let fetchCalls = 0;
188
+ const hub = new core_hub_1.ChannelHub();
189
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.LLM_ACCESS, new core_hub_4.OpenAICompatChannel({
190
+ apiKey: "sk-test",
191
+ baseUrl: "https://replay.test",
192
+ fetchImpl: (async () => {
193
+ fetchCalls += 1;
194
+ return {
195
+ ok: true,
196
+ status: 200,
197
+ statusText: "OK",
198
+ json: async () => ({ choices: [{ message: { role: "assistant", content: "the recorded answer" } }] })
199
+ };
200
+ })
201
+ }));
202
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
203
+ const { live, replayed, reconcileDigestChain } = await recordThenReplay(hub, (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.LLM_ACCESS, makeCtx(mode), "chatRound", "what is determinism?"));
204
+ strict_1.default.equal(live, "the recorded answer");
205
+ strict_1.default.equal(replayed, live);
206
+ strict_1.default.equal(reconcileDigestChain, true);
207
+ strict_1.default.equal(fetchCalls, 1, "replay must not touch the network");
208
+ await hub.teardown();
209
+ });
210
+ (0, node_test_1.test)("replay_compat OpenAICompat: retry-then-success records only the final output", async () => {
211
+ let attempt = 0;
212
+ const hub = new core_hub_1.ChannelHub();
213
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.LLM_ACCESS, new core_hub_4.OpenAICompatChannel({
214
+ apiKey: "sk-test",
215
+ baseUrl: "https://retry.test",
216
+ maxRetries: 2,
217
+ initialRetryDelayMs: 1,
218
+ fetchImpl: (async () => {
219
+ attempt += 1;
220
+ if (attempt <= 2) {
221
+ return {
222
+ ok: false,
223
+ status: 429,
224
+ statusText: "Too Many Requests",
225
+ json: async () => ({ error: { message: "slow down" } })
226
+ };
227
+ }
228
+ return {
229
+ ok: true,
230
+ status: 200,
231
+ statusText: "OK",
232
+ json: async () => ({ choices: [{ message: { role: "assistant", content: "after retries" } }] })
233
+ };
234
+ })
235
+ }));
236
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
237
+ const { live, replayed, recordJournal, reconcileDigestChain } = await recordThenReplay(hub, (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.LLM_ACCESS, makeCtx(mode), "chatRound", "hi"));
238
+ strict_1.default.equal(live, "after retries");
239
+ strict_1.default.equal(replayed, live);
240
+ strict_1.default.equal(recordJournal.size(), 1, "internal retries must not leak into the journal");
241
+ strict_1.default.equal(attempt, 3, "3 HTTP attempts happened during record (2 failures + 1 success)");
242
+ strict_1.default.equal(reconcileDigestChain, true);
243
+ await hub.teardown();
244
+ });
245
+ // ---------------------------------------------------------------------------
246
+ // Cross-channel interleaving + persistence
247
+ // ---------------------------------------------------------------------------
248
+ (0, node_test_1.test)("replay_compat multi-channel: interleaved LLM/File/Shell calls replay in order", async () => {
249
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-multi-"));
250
+ const hub = new core_hub_1.ChannelHub();
251
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: root }));
252
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.SHELL_EXEC, new core_hub_3.ShellChannel({ allowedCommands: [NODE], workDir: root }));
253
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.LLM_ACCESS, new core_hub_4.OpenAICompatChannel({
254
+ apiKey: "sk-test",
255
+ baseUrl: "https://multi.test",
256
+ fetchImpl: (async () => ({
257
+ ok: true,
258
+ status: 200,
259
+ statusText: "OK",
260
+ json: async () => ({ choices: [{ message: { role: "assistant", content: "llm says 7" } }] })
261
+ }))
262
+ }));
263
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
264
+ const script = async (mode) => {
265
+ await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "writeTextFile", "in.txt", "3+4=");
266
+ const llm = await hub.fireChannelCall(infra_common_2.ChannelKind.LLM_ACCESS, makeCtx(mode), "chatRound", "3+4=?");
267
+ const calc = await hub.fireChannelCall(infra_common_2.ChannelKind.SHELL_EXEC, makeCtx(mode), "execCommand", NODE, ["-e", "process.stdout.write(String(3+4))"]);
268
+ await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "writeTextFile", "out.txt", `${llm} ${calc.stdout}`);
269
+ return hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "readTextFile", "out.txt");
270
+ };
271
+ const { live, replayed, recordJournal, reconcileDigestChain } = await recordThenReplay(hub, script);
272
+ strict_1.default.equal(live, "llm says 7 7");
273
+ strict_1.default.equal(replayed, live);
274
+ strict_1.default.equal(recordJournal.size(), 5);
275
+ // Channel kinds interleave in the exact recorded order.
276
+ strict_1.default.deepEqual(recordJournal.snapshot().map((r) => r.channelKind), [infra_common_2.ChannelKind.FILE_SYSTEM, infra_common_2.ChannelKind.LLM_ACCESS, infra_common_2.ChannelKind.SHELL_EXEC, infra_common_2.ChannelKind.FILE_SYSTEM, infra_common_2.ChannelKind.FILE_SYSTEM]);
277
+ strict_1.default.equal(reconcileDigestChain, true);
278
+ await hub.teardown();
279
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
280
+ });
281
+ (0, node_test_1.test)("replay_compat persistence: save → load → replay across engine instances", async () => {
282
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-persist-"));
283
+ const traceFile = path.join(root, "trace.jsonl");
284
+ // "Process 1": record and persist.
285
+ const hub1 = new core_hub_1.ChannelHub();
286
+ hub1.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: path.join(root, "fs") }));
287
+ await hub1.setupAllBuiltInChannels(makeCtx("record"));
288
+ const recordJournal = new core_hub_5.RecordJournal();
289
+ hub1.attachRecordJournal(recordJournal);
290
+ const live = await hub1.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("record"), "writeTextFile", "a.txt", "persisted");
291
+ const liveRead = await hub1.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("record"), "readTextFile", "a.txt");
292
+ await (0, core_hub_7.saveRecordJournal)(recordJournal, traceFile);
293
+ await hub1.teardown();
294
+ // "Process 2": fresh hub, no FileChannel registered at all; pure replay.
295
+ const loaded = await (0, core_hub_7.loadRecordJournal)(traceFile);
296
+ const hub2 = new core_hub_1.ChannelHub();
297
+ const replayJournal = new core_hub_5.RecordJournal();
298
+ hub2.attachRecordJournal(replayJournal);
299
+ hub2.attachReplayEngine(new core_hub_6.ReplayEngine(loaded));
300
+ const replayedWrite = await hub2.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("replay"), "writeTextFile", "a.txt", "persisted");
301
+ const replayedRead = await hub2.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("replay"), "readTextFile", "a.txt");
302
+ strict_1.default.equal(replayedWrite, live);
303
+ strict_1.default.equal(replayedRead, liveRead);
304
+ const report = new core_hub_6.ReplayEngine(loaded).reconcile(recordJournal.snapshot(), replayJournal.snapshot());
305
+ strict_1.default.equal(report.digestChainConsistent, true);
306
+ await hub2.teardown();
307
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
308
+ });
309
+ // ---------------------------------------------------------------------------
310
+ // PAE adaptation surface (W15) — merge gate for foreign runtimes
311
+ // ---------------------------------------------------------------------------
312
+ (0, node_test_1.test)("replay_compat PAE: replay serves foreign tool output without entering the adapter", async () => {
313
+ let foreignCalls = 0;
314
+ const registry = new pae_engine_1.PaeAdapterRegistry();
315
+ registry.register(new pae_engine_3.JsPaeAdapter({
316
+ adapterId: "compat-tools",
317
+ sourceEdition: "1.0.0",
318
+ tools: [
319
+ {
320
+ name: "renderCard",
321
+ capability: "channel:read",
322
+ handler: (args) => {
323
+ foreignCalls += 1;
324
+ return { title: String(args[0]), items: [1, 2, 3] };
325
+ }
326
+ }
327
+ ]
328
+ }));
329
+ const hub = new core_hub_1.ChannelHub();
330
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, new pae_engine_2.PaeChannel(registry));
331
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
332
+ const { live, replayed, reconcileDigestChain } = await recordThenReplay(hub, async (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx(mode), "renderCard", "orbit"));
333
+ strict_1.default.deepEqual(replayed, live);
334
+ strict_1.default.equal(JSON.stringify(replayed), JSON.stringify(live), "byte-identical");
335
+ strict_1.default.equal(foreignCalls, 1, "the foreign runtime runs exactly once, during recording");
336
+ strict_1.default.ok(reconcileDigestChain);
337
+ await hub.teardown();
338
+ });
339
+ (0, node_test_1.test)("replay_compat PAE: an unregistered adapter is not needed to replay its trace", async () => {
340
+ const registry = new pae_engine_1.PaeAdapterRegistry();
341
+ registry.register(new pae_engine_3.JsPaeAdapter({
342
+ adapterId: "ephemeral",
343
+ tools: [{ name: "onceOnly", handler: () => "recorded-value" }]
344
+ }));
345
+ const channel = new pae_engine_2.PaeChannel(registry);
346
+ const hub = new core_hub_1.ChannelHub();
347
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, channel);
348
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
349
+ const recordJournal = new core_hub_5.RecordJournal();
350
+ hub.attachRecordJournal(recordJournal);
351
+ const live = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("record"), "onceOnly");
352
+ strict_1.default.equal(live, "recorded-value");
353
+ // The adapter goes away entirely — as it would on a machine that lacks the
354
+ // foreign runtime, its credentials, or the network to reach it.
355
+ registry.unregister("ephemeral");
356
+ channel.syncTools();
357
+ strict_1.default.deepEqual(channel.installedTools(), []);
358
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal));
359
+ const replayed = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("replay"), "onceOnly");
360
+ strict_1.default.equal(replayed, live, "replay depends on the journal, never on the foreign runtime");
361
+ await hub.teardown();
362
+ });
363
+ (0, node_test_1.test)("replay_compat PAE: adapters run with Math.random poisoned", async () => {
364
+ const registry = new pae_engine_1.PaeAdapterRegistry();
365
+ registry.register(new pae_engine_3.JsPaeAdapter({
366
+ adapterId: "seeded-tools",
367
+ tools: [
368
+ {
369
+ name: "seededPick",
370
+ // Determinism arrives through the injected context; an adapter that
371
+ // minted its own randomness would break replay, so the charter test
372
+ // poisons the global source while the call runs.
373
+ handler: (_args, ctx) => Math.floor((ctx.rng?.next() ?? 0) * 100)
374
+ }
375
+ ]
376
+ }));
377
+ const hub = new core_hub_1.ChannelHub();
378
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, new pae_engine_2.PaeChannel(registry));
379
+ await hub.setupAllBuiltInChannels(makeCtx("record", { rng: new core_hub_9.SeededRng(7) }));
380
+ const originalRandom = Math.random;
381
+ Math.random = () => {
382
+ throw new Error("bare Math.random inside a pae adapter (charter violation)");
383
+ };
384
+ try {
385
+ const first = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("record"), "seededPick");
386
+ strict_1.default.equal(typeof first, "number");
387
+ }
388
+ finally {
389
+ Math.random = originalRandom;
390
+ }
391
+ await hub.teardown();
392
+ });
393
+ // ---------------------------------------------------------------------------
394
+ // Charter guard: no bare randomness anywhere in the new channel paths
395
+ // ---------------------------------------------------------------------------
396
+ (0, node_test_1.test)("replay_compat charter: channels run with Math.random poisoned (no bare randomness)", async () => {
397
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-random-"));
398
+ let poisonedAttempts = 0;
399
+ const hub = new core_hub_1.ChannelHub();
400
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: root }));
401
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.SHELL_EXEC, new core_hub_3.ShellChannel({ allowedCommands: [NODE], workDir: root }));
402
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.LLM_ACCESS, new core_hub_4.OpenAICompatChannel({
403
+ apiKey: "sk-test",
404
+ baseUrl: "https://random.test",
405
+ maxRetries: 1,
406
+ initialRetryDelayMs: 1,
407
+ fetchImpl: (async () => {
408
+ // Alternate failures so the retry path also runs under the poison.
409
+ poisonedAttempts += 1;
410
+ if (poisonedAttempts % 2 === 1) {
411
+ return { ok: false, status: 503, statusText: "Service Unavailable", json: async () => ({ error: { message: "transient" } }) };
412
+ }
413
+ return {
414
+ ok: true,
415
+ status: 200,
416
+ statusText: "OK",
417
+ json: async () => ({ choices: [{ message: { role: "assistant", content: "poison-safe" } }] })
418
+ };
419
+ })
420
+ }));
421
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
422
+ const originalRandom = Math.random;
423
+ Math.random = () => {
424
+ throw new Error("bare Math.random inside a channel (charter violation)");
425
+ };
426
+ try {
427
+ const file = await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("record"), "readTextFile", "missing.txt");
428
+ strict_1.default.equal(file, null);
429
+ const shell = await hub.fireChannelCall(infra_common_2.ChannelKind.SHELL_EXEC, makeCtx("record"), "execCommand", NODE, ["-e", "process.stdout.write('ok')"]);
430
+ strict_1.default.equal(shell.exitCode, 0);
431
+ const llm = await hub.fireChannelCall(infra_common_2.ChannelKind.LLM_ACCESS, makeCtx("record"), "chatRound", "hi");
432
+ strict_1.default.equal(llm, "poison-safe");
433
+ }
434
+ finally {
435
+ Math.random = originalRandom;
436
+ }
437
+ await hub.teardown();
438
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
439
+ });
440
+ // ---------------------------------------------------------------------------
441
+ // Fault-classification interplay with record mode
442
+ // ---------------------------------------------------------------------------
443
+ (0, node_test_1.test)("replay_compat: a recorded LLM fault is not swallowed by record mode", async () => {
444
+ const hub = new core_hub_1.ChannelHub();
445
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.LLM_ACCESS, new core_hub_4.OpenAICompatChannel({
446
+ apiKey: "sk-test",
447
+ baseUrl: "https://fault.test",
448
+ maxRetries: 0,
449
+ fetchImpl: (async () => ({
450
+ ok: false,
451
+ status: 401,
452
+ statusText: "Unauthorized",
453
+ json: async () => ({ error: { message: "bad key" } })
454
+ }))
455
+ }));
456
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
457
+ const recordJournal = new core_hub_5.RecordJournal();
458
+ hub.attachRecordJournal(recordJournal);
459
+ await strict_1.default.rejects(hub.fireChannelCall(infra_common_2.ChannelKind.LLM_ACCESS, makeCtx("record"), "chatRound", "hi"), (err) => {
460
+ strict_1.default.ok(err instanceof core_hub_4.LlmChannelFaultError);
461
+ strict_1.default.equal(err.faultKind, "auth");
462
+ return true;
463
+ });
464
+ strict_1.default.equal(recordJournal.size(), 0, "failed calls are never journaled");
465
+ await hub.teardown();
466
+ });
467
+ (0, node_test_1.test)("replay_compat: digest of identical inputs matches across channels and runs", () => {
468
+ const a = (0, infra_common_1.digestInputs)("x", 1, { k: "v" });
469
+ const b = (0, infra_common_1.digestInputs)("x", 1, { k: "v" });
470
+ const c = (0, infra_common_1.digestInputs)("x", 2, { k: "v" });
471
+ strict_1.default.equal(a, b);
472
+ strict_1.default.notEqual(a, c);
473
+ });
474
+ // ---------------------------------------------------------------------------
475
+ // MCP adaptation surface (W16) — merge gate for cross-process foreign runtimes
476
+ // ---------------------------------------------------------------------------
477
+ (0, node_test_1.test)("replay_compat MCP: replay serves the peer's output without re-entering the process", async () => {
478
+ let peerCalls = 0;
479
+ const transport = new pae_engine_5.InMemoryMcpTransport((method, params) => {
480
+ if (method === "initialize") {
481
+ return { protocolVersion: pae_engine_6.MCP_PROTOCOL_VERSION, serverInfo: { name: "compat-mcp", version: "1.0.0" } };
482
+ }
483
+ if (method === "notifications/initialized")
484
+ return {};
485
+ if (method === "tools/list")
486
+ return { tools: [{ name: "renderCard", description: "render a card" }] };
487
+ if (method === "tools/call") {
488
+ peerCalls += 1;
489
+ const p = params;
490
+ return { content: [{ type: "text", text: `card:${String(p.arguments.title)}` }] };
491
+ }
492
+ throw new Error(`unexpected method ${method}`);
493
+ });
494
+ const registry = new pae_engine_1.PaeAdapterRegistry();
495
+ const adapter = new pae_engine_4.McpPaeAdapter({
496
+ adapterId: "mcp-compat",
497
+ sourceEdition: "1.0.0",
498
+ transport
499
+ });
500
+ await adapter.setup(makeCtx("record"));
501
+ registry.register(adapter);
502
+ const hub = new core_hub_1.ChannelHub();
503
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, new pae_engine_2.PaeChannel(registry));
504
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
505
+ const { live, replayed, reconcileDigestChain } = await recordThenReplay(hub, async (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx(mode), "renderCard", { title: "orbit" }));
506
+ strict_1.default.equal(live, "card:orbit");
507
+ strict_1.default.equal(replayed, live);
508
+ strict_1.default.equal(JSON.stringify(replayed), JSON.stringify(live), "byte-identical across a process boundary");
509
+ strict_1.default.equal(peerCalls, 1, "the peer is entered exactly once, during recording");
510
+ strict_1.default.ok(reconcileDigestChain);
511
+ await hub.teardown();
512
+ });
513
+ (0, node_test_1.test)("replay_compat MCP: a trace replays after its peer is gone", async () => {
514
+ const transport = new pae_engine_5.InMemoryMcpTransport((method) => {
515
+ if (method === "initialize") {
516
+ return { protocolVersion: pae_engine_6.MCP_PROTOCOL_VERSION, serverInfo: { name: "mcp", version: "1.0.0" } };
517
+ }
518
+ if (method === "notifications/initialized")
519
+ return {};
520
+ if (method === "tools/list")
521
+ return { tools: [{ name: "remoteLookup" }] };
522
+ return { content: [{ type: "text", text: "remote-value" }] };
523
+ });
524
+ const registry = new pae_engine_1.PaeAdapterRegistry();
525
+ const adapter = new pae_engine_4.McpPaeAdapter({
526
+ adapterId: "mcp-ephemeral",
527
+ sourceEdition: "1.0.0",
528
+ transport
529
+ });
530
+ await adapter.setup(makeCtx("record"));
531
+ registry.register(adapter);
532
+ const channel = new pae_engine_2.PaeChannel(registry);
533
+ const hub = new core_hub_1.ChannelHub();
534
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, channel);
535
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
536
+ const recordJournal = new core_hub_5.RecordJournal();
537
+ hub.attachRecordJournal(recordJournal);
538
+ const live = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("record"), "remoteLookup", {});
539
+ strict_1.default.equal(live, "remote-value");
540
+ /*
541
+ * The MCP server is shut down and its adapter removed — as it would be on a
542
+ * machine that lacks the peer, its credentials, or the network to reach it.
543
+ * Replaying must depend on the journal alone.
544
+ */
545
+ registry.unregister("mcp-ephemeral");
546
+ channel.syncTools();
547
+ await registry.drainReleases();
548
+ strict_1.default.equal(transport.closed, true, "the peer is released when its adapter goes away");
549
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal));
550
+ const replayed = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("replay"), "remoteLookup", {});
551
+ strict_1.default.equal(replayed, live, "replay needs the journal, never the foreign process");
552
+ await hub.teardown();
553
+ });
554
+ // ---------------------------------------------------------------------------
555
+ // OpenAPI adaptation surface (W17) — merge gate for foreign REST runtimes
556
+ // ---------------------------------------------------------------------------
557
+ const OPENAPI_DOC = {
558
+ openapi: "3.0.1",
559
+ info: { title: "Compat Pet", version: "2.0.0" },
560
+ servers: [{ url: "https://api.petstore.test/v1" }],
561
+ paths: {
562
+ "/echo": {
563
+ get: {
564
+ operationId: "echoTool",
565
+ parameters: [{ name: "msg", in: "query", required: true }]
566
+ }
567
+ }
568
+ }
569
+ };
570
+ (0, node_test_1.test)("replay_compat OpenAPI: replay serves the server's output without re-entering the network", async () => {
571
+ let peerCalls = 0;
572
+ const transport = new pae_engine_8.InMemoryHttpTransport((req) => {
573
+ peerCalls += 1;
574
+ return { status: 200, body: JSON.stringify({ url: req.url }) };
575
+ });
576
+ const registry = new pae_engine_1.PaeAdapterRegistry();
577
+ const adapter = new pae_engine_7.OpenApiPaeAdapter({
578
+ adapterId: "openapi-compat",
579
+ document: OPENAPI_DOC,
580
+ transport
581
+ });
582
+ await adapter.setup(makeCtx("record"));
583
+ registry.register(adapter);
584
+ const hub = new core_hub_1.ChannelHub();
585
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, new pae_engine_2.PaeChannel(registry));
586
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
587
+ const { live, replayed, reconcileDigestChain } = await recordThenReplay(hub, async (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx(mode), "echoTool", { msg: "orbit" }));
588
+ strict_1.default.deepEqual(live, { url: "https://api.petstore.test/v1/echo?msg=orbit" });
589
+ strict_1.default.deepEqual(replayed, live);
590
+ strict_1.default.equal(peerCalls, 1, "the server is entered exactly once, during recording");
591
+ strict_1.default.ok(reconcileDigestChain);
592
+ await hub.teardown();
593
+ });
594
+ (0, node_test_1.test)("replay_compat OpenAPI: a trace replays after the server is gone", async () => {
595
+ const transport = new pae_engine_8.InMemoryHttpTransport(() => ({
596
+ status: 200,
597
+ body: JSON.stringify({ url: "https://api.petstore.test/v1/echo?msg=gone" })
598
+ }));
599
+ const registry = new pae_engine_1.PaeAdapterRegistry();
600
+ const adapter = new pae_engine_7.OpenApiPaeAdapter({
601
+ adapterId: "openapi-ephemeral",
602
+ document: OPENAPI_DOC,
603
+ transport
604
+ });
605
+ await adapter.setup(makeCtx("record"));
606
+ registry.register(adapter);
607
+ const channel = new pae_engine_2.PaeChannel(registry);
608
+ const hub = new core_hub_1.ChannelHub();
609
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, channel);
610
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
611
+ const recordJournal = new core_hub_5.RecordJournal();
612
+ hub.attachRecordJournal(recordJournal);
613
+ const live = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("record"), "echoTool", { msg: "gone" });
614
+ strict_1.default.deepEqual(live, { url: "https://api.petstore.test/v1/echo?msg=gone" });
615
+ /*
616
+ * The API server is shut down and its adapter removed — as it would be on a
617
+ * machine that lacks the endpoint, its credentials, or the network to reach
618
+ * it. Replaying must depend on the journal alone.
619
+ */
620
+ registry.unregister("openapi-ephemeral");
621
+ channel.syncTools();
622
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal));
623
+ const replayed = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("replay"), "echoTool", { msg: "gone" });
624
+ strict_1.default.deepEqual(replayed, live, "replay needs the journal, never the foreign server");
625
+ await hub.teardown();
626
+ });
627
+ // ---------------------------------------------------------------------------
628
+ // Cordis adaptation surface (W18) — merge gate for isolated plugin hosts
629
+ // ---------------------------------------------------------------------------
630
+ (0, node_test_1.test)("replay_compat Cordis: replay serves the host's output without re-entering the process", async () => {
631
+ let hostCalls = 0;
632
+ const transport = new pae_engine_10.InMemoryCordisTransport((method, params) => {
633
+ if (method === "initialize") {
634
+ return { protocolVersion: pae_engine_11.CORDIS_PROTOCOL_VERSION, hostInfo: { name: "compat-cordis", version: "1.0.0" } };
635
+ }
636
+ if (method === "tools/list")
637
+ return { tools: [{ name: "renderCard", description: "render a card" }] };
638
+ if (method === "tools/call") {
639
+ hostCalls += 1;
640
+ const p = params;
641
+ return { card: `card:${String(p.arguments.title)}` };
642
+ }
643
+ throw new Error(`unexpected method ${method}`);
644
+ });
645
+ const registry = new pae_engine_1.PaeAdapterRegistry();
646
+ const adapter = new pae_engine_9.CordisPaeAdapter({
647
+ adapterId: "cordis-compat",
648
+ sourceEdition: "1.0.0",
649
+ transport
650
+ });
651
+ await adapter.setup(makeCtx("record"));
652
+ registry.register(adapter);
653
+ const hub = new core_hub_1.ChannelHub();
654
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, new pae_engine_2.PaeChannel(registry));
655
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
656
+ const { live, replayed, reconcileDigestChain } = await recordThenReplay(hub, async (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx(mode), "renderCard", { title: "orbit" }));
657
+ strict_1.default.deepEqual(live, { card: "card:orbit" });
658
+ strict_1.default.deepEqual(replayed, live);
659
+ strict_1.default.equal(JSON.stringify(replayed), JSON.stringify(live), "byte-identical across a process boundary");
660
+ strict_1.default.equal(hostCalls, 1, "the host is entered exactly once, during recording");
661
+ strict_1.default.ok(reconcileDigestChain);
662
+ await hub.teardown();
663
+ });
664
+ (0, node_test_1.test)("replay_compat Cordis: a trace replays after its host is gone", async () => {
665
+ const transport = new pae_engine_10.InMemoryCordisTransport((method) => {
666
+ if (method === "initialize") {
667
+ return { protocolVersion: pae_engine_11.CORDIS_PROTOCOL_VERSION, hostInfo: { name: "cordis", version: "1.0.0" } };
668
+ }
669
+ if (method === "tools/list")
670
+ return { tools: [{ name: "hostLookup" }] };
671
+ return { value: "host-value" };
672
+ });
673
+ const registry = new pae_engine_1.PaeAdapterRegistry();
674
+ const adapter = new pae_engine_9.CordisPaeAdapter({
675
+ adapterId: "cordis-ephemeral",
676
+ sourceEdition: "1.0.0",
677
+ transport
678
+ });
679
+ await adapter.setup(makeCtx("record"));
680
+ registry.register(adapter);
681
+ const channel = new pae_engine_2.PaeChannel(registry);
682
+ const hub = new core_hub_1.ChannelHub();
683
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.PAE_TOOL, channel);
684
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
685
+ const recordJournal = new core_hub_5.RecordJournal();
686
+ hub.attachRecordJournal(recordJournal);
687
+ const live = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("record"), "hostLookup", {});
688
+ strict_1.default.deepEqual(live, { value: "host-value" });
689
+ /*
690
+ * The Cordis host is shut down and its adapter removed — as it would be on a
691
+ * machine without the plugin instance, its credentials, or its runtime.
692
+ * Replaying must depend on the journal alone.
693
+ */
694
+ registry.unregister("cordis-ephemeral");
695
+ channel.syncTools();
696
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal));
697
+ const replayed = await hub.fireChannelCall(infra_common_2.ChannelKind.PAE_TOOL, makeCtx("replay"), "hostLookup", {});
698
+ strict_1.default.deepEqual(replayed, live, "replay needs the journal, never the foreign host");
699
+ await hub.teardown();
700
+ });
701
+ // ---------------------------------------------------------------------------
702
+ // Isolation domain surface (W19) — merge gate for graph-driven L2 domains
703
+ // ---------------------------------------------------------------------------
704
+ (0, node_test_1.test)("replay_compat domain: replay serves the host's output without re-entering the process", async () => {
705
+ let hostCalls = 0;
706
+ const graph = new sandbox_runtime_5.ImpactDomainGraph();
707
+ graph.addNode("echo");
708
+ const manager = new sandbox_runtime_2.IsolationDomainManager({
709
+ transportFactory: () => new sandbox_runtime_3.InMemoryDomainTransport((method, params) => {
710
+ if (method === "initialize") {
711
+ return { protocolVersion: sandbox_runtime_4.DOMAIN_PROTOCOL_VERSION, hostInfo: { name: "compat-domain", version: "1.0.0" } };
712
+ }
713
+ if (method === "units/list") {
714
+ return { units: [{ id: "echo", tools: [{ name: "sum" }] }] };
715
+ }
716
+ if (method === "units/call") {
717
+ hostCalls += 1;
718
+ const p = params;
719
+ return p.arguments.numbers.reduce((a, b) => a + b, 0);
720
+ }
721
+ throw new Error(`unexpected method ${method}`);
722
+ }),
723
+ defaultTimeoutMs: 10_000
724
+ });
725
+ await manager.syncDomains(graph, makeCtx("record"));
726
+ const hub = new core_hub_1.ChannelHub();
727
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.DOMAIN_TOOL, new sandbox_runtime_1.DomainChannel(manager));
728
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
729
+ const { live, replayed, reconcileDigestChain } = await recordThenReplay(hub, async (mode) => hub.fireChannelCall(infra_common_2.ChannelKind.DOMAIN_TOOL, makeCtx(mode), "echo:sum", { numbers: [1, 2, 3] }));
730
+ strict_1.default.equal(live, 6);
731
+ strict_1.default.equal(replayed, live);
732
+ strict_1.default.equal(JSON.stringify(replayed), JSON.stringify(live), "byte-identical across a process boundary");
733
+ strict_1.default.equal(hostCalls, 1, "the host is entered exactly once, during recording");
734
+ strict_1.default.ok(reconcileDigestChain);
735
+ await hub.teardown();
736
+ await manager.teardownAll();
737
+ });
738
+ (0, node_test_1.test)("replay_compat domain: a trace replays after the domain is gone", async () => {
739
+ const graph = new sandbox_runtime_5.ImpactDomainGraph();
740
+ graph.addNode("echo");
741
+ const manager = new sandbox_runtime_2.IsolationDomainManager({
742
+ transportFactory: () => new sandbox_runtime_3.InMemoryDomainTransport((method) => {
743
+ if (method === "initialize") {
744
+ return { protocolVersion: sandbox_runtime_4.DOMAIN_PROTOCOL_VERSION, hostInfo: { name: "domain", version: "1.0.0" } };
745
+ }
746
+ if (method === "units/list") {
747
+ return { units: [{ id: "echo", tools: [{ name: "sum" }] }] };
748
+ }
749
+ return 42;
750
+ }),
751
+ defaultTimeoutMs: 10_000
752
+ });
753
+ await manager.syncDomains(graph, makeCtx("record"));
754
+ const channel = new sandbox_runtime_1.DomainChannel(manager);
755
+ const hub = new core_hub_1.ChannelHub();
756
+ hub.registerBuiltInChannel(infra_common_2.ChannelKind.DOMAIN_TOOL, channel);
757
+ await hub.setupAllBuiltInChannels(makeCtx("record"));
758
+ const recordJournal = new core_hub_5.RecordJournal();
759
+ hub.attachRecordJournal(recordJournal);
760
+ const live = await hub.fireChannelCall(infra_common_2.ChannelKind.DOMAIN_TOOL, makeCtx("record"), "echo:sum", { numbers: [1, 2] });
761
+ strict_1.default.equal(live, 42);
762
+ /*
763
+ * The domain host is shut down and every domain torn down — as it would be on
764
+ * a machine without the process. Replaying must depend on the journal alone.
765
+ */
766
+ await manager.teardownAll();
767
+ channel.syncTools();
768
+ hub.attachReplayEngine(new core_hub_6.ReplayEngine(recordJournal));
769
+ const replayed = await hub.fireChannelCall(infra_common_2.ChannelKind.DOMAIN_TOOL, makeCtx("replay"), "echo:sum", { numbers: [1, 2] });
770
+ strict_1.default.equal(replayed, live, "replay needs the journal, never the domain host");
771
+ await hub.teardown();
772
+ });
773
+ // ---------------------------------------------------------------------------
774
+ // Domain transactions (W20) — a cross-domain hop as a gateway transaction
775
+ // ---------------------------------------------------------------------------
776
+ /** A host with one plugin unit whose domain host answers `ping` with its args. */
777
+ async function hostWithDomainUnit() {
778
+ const host = new orbitRuntimeHost_1.OrbitRuntimeHost();
779
+ await host.bootHost();
780
+ host.registerPlugin({
781
+ id: "p.txn",
782
+ displayName: "p.txn",
783
+ edition: "1.0.0",
784
+ requireHostMinEdition: "0.2.0",
785
+ allowCapabilities: ["channel:read", "channel:write"],
786
+ declareChannelDeps: [infra_common_2.ChannelKind.LLM_ACCESS]
787
+ });
788
+ await host.allocateIsolationDomains({
789
+ transportFactory: () => new sandbox_runtime_3.InMemoryDomainTransport((method, params) => {
790
+ if (method === "initialize") {
791
+ return { protocolVersion: sandbox_runtime_4.DOMAIN_PROTOCOL_VERSION, hostInfo: { name: "txn-host", version: "1.0.0" } };
792
+ }
793
+ if (method === "units/list") {
794
+ return { units: [{ id: "p.txn", tools: [{ name: "ping" }] }] };
795
+ }
796
+ const p = params;
797
+ return { pong: p.arguments };
798
+ }),
799
+ maxImpactClosure: 1
800
+ });
801
+ return host;
802
+ }
803
+ (0, node_test_1.test)("replay_compat domain txn: a cross-domain hop records and replays byte-identically", async () => {
804
+ const host = await hostWithDomainUnit();
805
+ try {
806
+ const recordJournal = host.beginRecording();
807
+ const live = await host.invokeDomainUnit("p.txn", "ping", [{ hello: "world" }], { pluginUnitId: "p.txn" });
808
+ strict_1.default.deepEqual(live.pong, { hello: "world" });
809
+ // One transaction, settled — the ledger balances before replay even starts.
810
+ const ledger = host.domainLedger();
811
+ strict_1.default.equal(ledger.length, 1);
812
+ strict_1.default.equal(ledger[0].state, "settled");
813
+ strict_1.default.equal(host.reconcileDomainTransactions().balanced, true);
814
+ host.attachReplayEngine(recordJournal);
815
+ const replayed = await host.invokeDomainUnit("p.txn", "ping", [{ hello: "world" }], { pluginUnitId: "p.txn", mode: "replay" });
816
+ strict_1.default.deepEqual(replayed, live);
817
+ strict_1.default.equal(JSON.stringify(replayed), JSON.stringify(live), "byte-identical across the domain boundary");
818
+ /*
819
+ * Replay never re-enters the domain: the frozen output is injected at the
820
+ * gateway, so the physical layer is not touched and no new transaction is
821
+ * opened. That is axiom A1 expressed on the ledger.
822
+ */
823
+ strict_1.default.equal(host.domainLedger().length, 1, "replay injects the output, it does not re-enter the domain");
824
+ strict_1.default.equal(host.reconcileDomainTransactions().balanced, true);
825
+ /*
826
+ * Replay is not a free pass. A fresh replay session (attaching the engine
827
+ * resets the cursor) replaying a *different* input must surface as call
828
+ * drift, not silently serve the frozen output.
829
+ */
830
+ host.attachReplayEngine(recordJournal);
831
+ await strict_1.default.rejects(() => host.invokeDomainUnit("p.txn", "ping", [{ hello: "changed" }], {
832
+ pluginUnitId: "p.txn",
833
+ mode: "replay"
834
+ }), /signature mismatch/, "a changed input is call drift, not a replay hit");
835
+ }
836
+ finally {
837
+ await host.shutdownHost();
838
+ }
839
+ });
840
+ (0, node_test_1.test)("replay_compat domain txn: a trace replays after every domain is released", async () => {
841
+ const host = await hostWithDomainUnit();
842
+ const recordJournal = host.beginRecording();
843
+ const live = await host.invokeDomainUnit("p.txn", "ping", [{ n: 1 }], { pluginUnitId: "p.txn" });
844
+ strict_1.default.deepEqual(live.pong, { n: 1 });
845
+ /*
846
+ * Release the physical layer entirely — every domain host is torn down, as it
847
+ * would be on a machine that never had the child processes. Replaying must
848
+ * depend on the journal alone.
849
+ */
850
+ await host.releaseIsolationDomains();
851
+ strict_1.default.deepEqual(host.domains(), []);
852
+ host.attachReplayEngine(recordJournal);
853
+ const replayed = await host.invokeDomainUnit("p.txn", "ping", [{ n: 1 }], { pluginUnitId: "p.txn", mode: "replay" });
854
+ strict_1.default.deepEqual(replayed, live, "replay needs the journal, never the domain host");
855
+ await host.shutdownHost();
856
+ });
857
+ // ---------------------------------------------------------------------------
858
+ // Journal durability (W27) — merge gate for the write-ahead log
859
+ // ---------------------------------------------------------------------------
860
+ (0, node_test_1.test)("replay_compat wal: a run mirrored to the WAL replays byte-identically in a fresh process", async () => {
861
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-wal-"));
862
+ const walFile = path.join(root, "record.wal.jsonl");
863
+ // "Process 1": record live, mirroring every call to the WAL as it happens.
864
+ const hub1 = new core_hub_1.ChannelHub();
865
+ hub1.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: path.join(root, "fs") }));
866
+ await hub1.setupAllBuiltInChannels(makeCtx("record"));
867
+ const durable = new core_hub_8.PersistedRecordJournal(walFile, { truncate: true });
868
+ hub1.attachRecordJournal(durable);
869
+ const liveWrite = await hub1.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("record"), "writeTextFile", "a.txt", "wal-durable");
870
+ const liveRead = await hub1.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("record"), "readTextFile", "a.txt");
871
+ await durable.flush();
872
+ await hub1.teardown();
873
+ // "Process 2": recover from the WAL alone. No FileChannel is registered, so
874
+ // a replay hit is the only way the calls can resolve.
875
+ const recovered = await core_hub_8.PersistedRecordJournal.recover(walFile);
876
+ strict_1.default.deepEqual(recovered.snapshot(), durable.snapshot(), "WAL recovery is byte-identical");
877
+ const hub2 = new core_hub_1.ChannelHub();
878
+ const replayJournal = new core_hub_5.RecordJournal();
879
+ hub2.attachRecordJournal(replayJournal);
880
+ hub2.attachReplayEngine(new core_hub_6.ReplayEngine(recovered));
881
+ const replayedWrite = await hub2.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("replay"), "writeTextFile", "a.txt", "wal-durable");
882
+ const replayedRead = await hub2.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("replay"), "readTextFile", "a.txt");
883
+ strict_1.default.equal(replayedWrite, liveWrite);
884
+ strict_1.default.equal(replayedRead, liveRead);
885
+ const report = new core_hub_6.ReplayEngine(recovered).reconcile(durable.snapshot(), replayJournal.snapshot());
886
+ strict_1.default.equal(report.digestChainConsistent, true);
887
+ await hub2.teardown();
888
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
889
+ });
890
+ (0, node_test_1.test)("replay_compat wal: a crash-truncated WAL replays its surviving prefix byte-identically", async () => {
891
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-wal-crash-"));
892
+ const walFile = path.join(root, "record.wal.jsonl");
893
+ const hub1 = new core_hub_1.ChannelHub();
894
+ hub1.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: path.join(root, "fs") }));
895
+ await hub1.setupAllBuiltInChannels(makeCtx("record"));
896
+ const durable = new core_hub_8.PersistedRecordJournal(walFile, { truncate: true });
897
+ hub1.attachRecordJournal(durable);
898
+ const liveWrite = await hub1.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("record"), "writeTextFile", "a.txt", "prefix");
899
+ await durable.flush();
900
+ await hub1.teardown();
901
+ // A crash lands a partial line at the tail; recovery must drop exactly that.
902
+ await node_fs_1.promises.appendFile(walFile, '{"entryUid":"partial","orderIndex":1,"chan', "utf8");
903
+ const recovered = await core_hub_8.PersistedRecordJournal.recover(walFile);
904
+ strict_1.default.equal(recovered.size(), 1);
905
+ strict_1.default.deepEqual(recovered.snapshot(), durable.snapshot());
906
+ const hub2 = new core_hub_1.ChannelHub();
907
+ hub2.attachReplayEngine(new core_hub_6.ReplayEngine(recovered));
908
+ const replayedWrite = await hub2.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx("replay"), "writeTextFile", "a.txt", "prefix");
909
+ strict_1.default.equal(replayedWrite, liveWrite, "the surviving prefix still replays verbatim");
910
+ await hub2.teardown();
911
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
912
+ });
913
+ (0, node_test_1.test)("replay_compat wal: durability does not perturb the recorded bytes", async () => {
914
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-wal-parity-"));
915
+ const walFile = path.join(root, "record.wal.jsonl");
916
+ const script = async (hub, mode) => {
917
+ await hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "writeTextFile", "p.txt", "parity");
918
+ return hub.fireChannelCall(infra_common_2.ChannelKind.FILE_SYSTEM, makeCtx(mode), "readTextFile", "p.txt");
919
+ };
920
+ // In-memory journal (the historical path).
921
+ const plainHub = new core_hub_1.ChannelHub();
922
+ plainHub.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: path.join(root, "plain") }));
923
+ await plainHub.setupAllBuiltInChannels(makeCtx("record"));
924
+ const plain = new core_hub_5.RecordJournal();
925
+ plainHub.attachRecordJournal(plain);
926
+ await script(plainHub, "record");
927
+ await plainHub.teardown();
928
+ // Durable journal over the same script.
929
+ const durableHub = new core_hub_1.ChannelHub();
930
+ durableHub.registerBuiltInChannel(infra_common_2.ChannelKind.FILE_SYSTEM, new core_hub_2.FileChannel({ rootDir: path.join(root, "durable") }));
931
+ await durableHub.setupAllBuiltInChannels(makeCtx("record"));
932
+ const durable = new core_hub_8.PersistedRecordJournal(walFile, { truncate: true });
933
+ durableHub.attachRecordJournal(durable);
934
+ await script(durableHub, "record");
935
+ await durable.flush();
936
+ await durableHub.teardown();
937
+ // Everything except the per-entry uid must be identical: durability is a
938
+ // mirror, never a mutation of what gets recorded.
939
+ const strip = (journal) => journal.snapshot().map(({ entryUid: _uid, durationMs: _ms, ...rest }) => rest);
940
+ strict_1.default.deepEqual(strip(durable), strip(plain));
941
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
942
+ });
943
+ // ---------------------------------------------------------------------------
944
+ // Governance tiers (W29) — merge gate for the four-tier model
945
+ // ---------------------------------------------------------------------------
946
+ (0, node_test_1.test)("replay_compat governance: a sandbox-tier recording refuses to replay as standard (config drift)", async () => {
947
+ const { OrbitRuntimeHost, PersistedRecordJournal, ChannelKind } = await Promise.resolve().then(() => __importStar(require("../src/index")));
948
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-gov-"));
949
+ const walFile = path.join(root, "record.wal.jsonl");
950
+ const tracePath = path.join(root, "trace.wal.jsonl");
951
+ // "Process 1": record one governed call under the sandbox tier.
952
+ const sandboxHost = new OrbitRuntimeHost({
953
+ governanceProfile: "sandbox",
954
+ recordJournalPath: walFile,
955
+ traceJournalPath: tracePath
956
+ });
957
+ await sandboxHost.bootHost();
958
+ sandboxHost.registerPlugin({
959
+ id: "gov.compat",
960
+ displayName: "Gov",
961
+ edition: "1.0.0",
962
+ requireHostMinEdition: "1.0.0",
963
+ allowCapabilities: ["channel:read"]
964
+ });
965
+ const ctx = { traceMarkId: "t-gov", maxWaitMs: 5000, pluginUnitId: "gov.compat" };
966
+ await sandboxHost.capabilityInvoke({
967
+ kind: ChannelKind.MEM_KV_STORE,
968
+ pluginId: "gov.compat",
969
+ funcName: "readEntry",
970
+ args: ["k"],
971
+ mode: "record",
972
+ ctx
973
+ });
974
+ await sandboxHost.shutdownHost();
975
+ const recorded = await PersistedRecordJournal.recover(walFile);
976
+ strict_1.default.ok(recorded.get(0).runFingerprint.governanceProfileHash, "sandbox-tier recordings carry the tier hash");
977
+ // "Process 2": a standard-tier host tries to replay the same window.
978
+ const standardHost = new OrbitRuntimeHost({
979
+ recordJournalPath: walFile,
980
+ traceJournalPath: path.join(root, "trace2.wal.jsonl")
981
+ });
982
+ await standardHost.bootHost();
983
+ standardHost.registerPlugin({
984
+ id: "gov.compat",
985
+ displayName: "Gov",
986
+ edition: "1.0.0",
987
+ requireHostMinEdition: "1.0.0",
988
+ allowCapabilities: ["channel:read"]
989
+ });
990
+ const resumed = await PersistedRecordJournal.recover(walFile);
991
+ standardHost.attachReplayEngine(resumed);
992
+ await strict_1.default.rejects(standardHost.capabilityInvoke({
993
+ kind: ChannelKind.MEM_KV_STORE,
994
+ pluginId: "gov.compat",
995
+ funcName: "readEntry",
996
+ args: ["k"],
997
+ mode: "replay",
998
+ ctx
999
+ }), (err) => {
1000
+ strict_1.default.equal(err.errorToken, "RUN_FINGERPRINT_DRIFT", "cross-tier replay must surface as config drift, not digest drift");
1001
+ return true;
1002
+ });
1003
+ await standardHost.shutdownHost();
1004
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
1005
+ });
1006
+ (0, node_test_1.test)("replay_compat governance: same-tier replay stays byte-identical across hosts", async () => {
1007
+ const { OrbitRuntimeHost, PersistedRecordJournal, ChannelKind } = await Promise.resolve().then(() => __importStar(require("../src/index")));
1008
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-gov-ok-"));
1009
+ const walFile = path.join(root, "record.wal.jsonl");
1010
+ const run = async (mode) => {
1011
+ const host = new OrbitRuntimeHost({
1012
+ governanceProfile: "strict",
1013
+ auditSigningKey: "compat-gov-key",
1014
+ recordJournalPath: walFile,
1015
+ traceJournalPath: path.join(root, `trace-${mode}.wal.jsonl`)
1016
+ });
1017
+ await host.bootHost();
1018
+ host.registerPlugin({
1019
+ id: "gov.ok",
1020
+ displayName: "Gov",
1021
+ edition: "1.0.0",
1022
+ requireHostMinEdition: "1.0.0",
1023
+ allowCapabilities: ["channel:read"],
1024
+ schema: { type: "object", properties: {} }
1025
+ });
1026
+ const ctx = { traceMarkId: `t-${mode}`, maxWaitMs: 5000, pluginUnitId: "gov.ok" };
1027
+ if (mode === "record") {
1028
+ const journal = host.beginRecording();
1029
+ await host.capabilityInvoke({
1030
+ kind: ChannelKind.MEM_KV_STORE,
1031
+ pluginId: "gov.ok",
1032
+ funcName: "readEntry",
1033
+ args: ["k"],
1034
+ mode: "record",
1035
+ ctx
1036
+ });
1037
+ await journal.flush();
1038
+ return { host, journal };
1039
+ }
1040
+ const resumed = await PersistedRecordJournal.recover(walFile);
1041
+ // The gateway replays from ITS attached journal: attach the recovered
1042
+ // snapshot through beginRecording, then switch the injection source.
1043
+ const replayed = host.beginRecording();
1044
+ replayed.restoreSnapshot(resumed.snapshot());
1045
+ host.attachReplayEngine(replayed);
1046
+ const out = await host.capabilityInvoke({
1047
+ kind: ChannelKind.MEM_KV_STORE,
1048
+ pluginId: "gov.ok",
1049
+ funcName: "readEntry",
1050
+ args: ["k"],
1051
+ mode: "replay",
1052
+ ctx
1053
+ });
1054
+ return { host, out };
1055
+ };
1056
+ const recordPhase = await run("record");
1057
+ const replayPhase = await run("replay");
1058
+ strict_1.default.equal(replayPhase.out, recordPhase.journal.get(0).outputSnapshot);
1059
+ await recordPhase.host.shutdownHost();
1060
+ await replayPhase.host.shutdownHost();
1061
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
1062
+ });
1063
+ // ---------------------------------------------------------------------------
1064
+ // Audit hash chain (W30) — merge gate: signing must not perturb replay
1065
+ // ---------------------------------------------------------------------------
1066
+ (0, node_test_1.test)("replay_compat audit: a signing-key host records and replays byte-identically", async () => {
1067
+ const { OrbitRuntimeHost, PersistedRecordJournal, ChannelKind } = await Promise.resolve().then(() => __importStar(require("../src/index")));
1068
+ const root = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), "orbit-compat-audit-"));
1069
+ const walFile = path.join(root, "record.wal.jsonl");
1070
+ const tracePath = path.join(root, "trace.wal.jsonl");
1071
+ // "Process 1": record one governed call under a signing-key host. The audit
1072
+ // chain lives on the OBSERVATION layer; the recorded decision values must be
1073
+ // untouched by it.
1074
+ const host = new OrbitRuntimeHost({ auditSigningKey: "compat-key", recordJournalPath: walFile, traceJournalPath: tracePath });
1075
+ await host.bootHost();
1076
+ host.registerPlugin({
1077
+ id: "audit.compat",
1078
+ displayName: "Audit",
1079
+ edition: "1.0.0",
1080
+ requireHostMinEdition: "1.0.0",
1081
+ allowCapabilities: ["channel:read"]
1082
+ });
1083
+ const ctx = { traceMarkId: "t-audit", maxWaitMs: 5000, pluginUnitId: "audit.compat" };
1084
+ const live = await host.capabilityInvoke({
1085
+ kind: ChannelKind.MEM_KV_STORE,
1086
+ pluginId: "audit.compat",
1087
+ funcName: "readEntry",
1088
+ args: ["k"],
1089
+ mode: "record",
1090
+ ctx
1091
+ });
1092
+ await host.shutdownHost();
1093
+ // "Process 2": replay the same window with the same signing key — the
1094
+ // injected output must equal the live one, and the audit chain verifies.
1095
+ const host2 = new OrbitRuntimeHost({ auditSigningKey: "compat-key", recordJournalPath: walFile, traceJournalPath: tracePath });
1096
+ await host2.bootHost();
1097
+ host2.registerPlugin({
1098
+ id: "audit.compat",
1099
+ displayName: "Audit",
1100
+ edition: "1.0.0",
1101
+ requireHostMinEdition: "1.0.0",
1102
+ allowCapabilities: ["channel:read"]
1103
+ });
1104
+ const recovered = await PersistedRecordJournal.recover(walFile);
1105
+ const replayJournal = host2.beginRecording();
1106
+ replayJournal.restoreSnapshot(recovered.snapshot());
1107
+ host2.attachReplayEngine(replayJournal);
1108
+ const replayed = await host2.capabilityInvoke({
1109
+ kind: ChannelKind.MEM_KV_STORE,
1110
+ pluginId: "audit.compat",
1111
+ funcName: "readEntry",
1112
+ args: ["k"],
1113
+ mode: "replay",
1114
+ ctx
1115
+ });
1116
+ strict_1.default.equal(replayed, live, "signing the audit trail does not perturb recorded decisions");
1117
+ const chain = host2.verifyAuditChain();
1118
+ strict_1.default.equal(chain.consistent, true, "the audit chain stays provable across the replay");
1119
+ await host2.shutdownHost();
1120
+ await node_fs_1.promises.rm(root, { recursive: true, force: true });
1121
+ });
1122
+ //# sourceMappingURL=replay_compat.test.js.map