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
package/CHANGELOG.md ADDED
@@ -0,0 +1,750 @@
1
+ # Changelog
2
+
3
+ All notable changes to Orbit Agent Runtime are documented here. This project
4
+ follows a pre-alpha versioning scheme: `v0.x.minor` marks a release wave,
5
+ `patch` marks fixes. Until `v1.0` the public API is not yet stability-promised.
6
+
7
+ ## [0.8.0] — 2026-09-01 · Trust assumption & contractification (W31)
8
+
9
+ The last two VISION §3.1 governance dimensions ship, closing the four-tier
10
+ model: every dimension in the table now has a code path.
11
+
12
+ ### Added
13
+ - **Trust assumption → PAE isolation cap** (`GovernanceProfile.maxIsolationLevel`,
14
+ L0 < L1 < L2): `strict` caps foreign adapters at L1 (no out-of-process
15
+ children); `sandbox`/`standard` allow L2. `assertPaeAdmitted` checks kind
16
+ admission AND isolation before any handshake. Honest note (architecture §12):
17
+ with `strict`'s empty kind admission the kind gate fires first today — the
18
+ cap is the defense-in-depth for future tier combinations that admit kinds
19
+ but cap isolation.
20
+ - **Progressive contractification** (`schemaMode` + `validateArgsAgainstSchema`):
21
+ - `PluginUnitPact.schema` and PAE tool `schema` declare an optional parameter
22
+ contract (JSON-Schema subset: object/array/string/number/boolean, required,
23
+ additionalProperties, maxItems).
24
+ - Pure validator with precise first-failure location (`arg.payload.name`).
25
+ - `sandbox` checks nothing; `standard` validates a declared schema before the
26
+ call executes; `strict` REQUIRES a schema on every plugin
27
+ (`registerPlugin` rejects schema-less plugins).
28
+ - Gateway path: `capabilityInvoke` (now async, so the rejection is a promise
29
+ rejection, never a synchronous throw) validates PAE tool arguments in
30
+ record/live; **replay bypasses** — arguments were already checked at record
31
+ time and injection stays a pure replay.
32
+ - Profile hash extended with the two new dimensions (cross-tier replay keeps
33
+ refusing as config drift).
34
+
35
+ ### Verification
36
+ - Clean `tsc -b`, strict, zero errors.
37
+ - Kernel suite: **423 cases** green (413 → +10 `governance_schema`). Console
38
+ 97/97. Examples 4/4.
39
+ - The strict-tier schema requirement surfaced as 2 pre-existing test fixes
40
+ (W29 strict hosts registered schema-less plugins) — caught by full
41
+ regression.
42
+
43
+ ### Migration
44
+ - `schemaMode` is `optional` on sandbox, `declared` on standard: a default
45
+ host only validates PAE tools that declare a schema — no behavioural change
46
+ for existing plugins without schemas.
47
+
48
+ ## [0.7.0] — 2026-09-01 · Audit hash chain (W30)
49
+
50
+ VISION §3.1's "落盘 + 签名" lands: the append-only audit log becomes
51
+ tamper-evident. This is the commercial core of the product story — "prove the
52
+ agent really did what it did" needs more than a file that anyone with write
53
+ access can edit.
54
+
55
+ ### Added
56
+ - **Audit hash chain** (`core-hub/audit/audit_chain.ts`): every audit entry
57
+ carries `prevHash` (previous entry's `chainHash`, genesis seed for the first)
58
+ and `chainHash` = HMAC-SHA256(key, prevHash + canonical entry). Editing ANY
59
+ entry — content, timestamp, or deleting one in the middle — breaks the chain
60
+ at that entry and every entry after it. HMAC is a pure function and the
61
+ canonical form is key-sorted JSON, so the hash is reproducible anywhere.
62
+ - **Host option** — `new OrbitRuntimeHost({ auditSigningKey })` signs the audit
63
+ trail; without a key the journal records NO chain fields (pre-W30 behaviour,
64
+ byte for byte). `host.verifyAuditChain()` proves integrity; `strict` tier now
65
+ REQUIRES the signing key (construction fails without it) and refuses to boot
66
+ on a broken recovered chain — an untrusted audit trail is an untrusted
67
+ environment.
68
+ - **Recovery continuation** — restored entries keep their chain fields and
69
+ `restoreSnapshot` rebuilds the chain tail, so a window continued across
70
+ processes stays one unbroken chain.
71
+ - **`orbit audit <trace.wal.jsonl> [--key]` CLI command** — verifies a chain
72
+ from the genesis seed; unsigned reports need a key, a broken chain exits
73
+ non-zero with the break point and reason.
74
+ - **Console** — audit page "kernel audit chain" card (`GET /api/audit/chain`);
75
+ the bridge signs when `ORBIT_AUDIT_SIGNING_KEY` is set.
76
+
77
+ ### Changed
78
+ - `strict` governance now validates BOTH a durable trace path and a signing
79
+ key at construction, and verifies the recovered chain at boot.
80
+ - `TraceJournal` accepts an optional signing key; `PersistedTraceJournal`
81
+ threads it through (WAL mirrors the chained entries verbatim).
82
+
83
+ ### Verification
84
+ - Clean `tsc -b`, strict, zero errors.
85
+ - Kernel suite: **413 cases** green (397 → +15 `audit_chain` + 1
86
+ `replay_compat` merge gate: signing does not perturb replay — a keyed host
87
+ records and replays byte-identically while the chain stays provable).
88
+ - Console suite: **97/97** green (css-coverage gate caught a missing `.strong`
89
+ style for the new audit card; added). Examples: 4/4 green.
90
+
91
+ ### Migration
92
+ - No API break: `auditSigningKey` is optional; unsigned hosts behave exactly
93
+ as before.
94
+
95
+ ## [0.6.0] — 2026-09-01 · Four-tier governance (W29)
96
+
97
+ VISION §3.1 — the four-tier governance model — stops being a design goal and
98
+ becomes concrete, switchable configuration. The last major "documented but not
99
+ shipped" architectural surface is closed.
100
+
101
+ ### Added
102
+ - **`GovernanceProfile` contract** (`@orbit/infra-common/types/governance`):
103
+ `sandbox` (development) / `standard` (default) / `strict` (compliance),
104
+ resolved by `resolveGovernanceProfile()` and hashed by
105
+ `governanceProfileHash()`. Each profile declares compression strength, rate
106
+ limit, trip threshold/cooldown, PAE admission and trace durability.
107
+ - **Host option** — `new OrbitRuntimeHost({ governanceProfile: "strict" })`
108
+ plus a read-only `host.currentGovernanceProfile` accessor. `strict`
109
+ construction fails without a durable `traceJournalPath` (a compliance tier
110
+ with an ephemeral audit trail is a contradiction).
111
+ - **Mechanism injection** — limiter and trip numbers come from the profile;
112
+ `tokenBudgetConfigForProfile()` maps compression strength onto
113
+ `TokenBudgetEngine` (off / normal / aggressive with halved thresholds);
114
+ `tripThresholdForProfile()` softens the threshold by dependency out-degree
115
+ (strict collapses to a floor of 1, standard/sandbox to 2).
116
+ - **PAE admission gate** — `registerPaeToolAdapter` / `connectPaeToolAdapter`
117
+ check the adapter kind against the profile (sandbox + standard: all kinds;
118
+ strict: none). `connect` gates BEFORE the handshake so a denied kind never
119
+ spawns a child process.
120
+ - **Config-drift surface** — a non-default tier adds `governanceProfileHash`
121
+ to the run fingerprint and `CapabilityGateway.verifyFingerprint` compares it
122
+ (absent-on-both = compatible, same pattern as `paeAdaptersHash`). A trace
123
+ recorded under one tier refuses to replay under another with
124
+ `RunFingerprintDriftError`. The `standard` tier is omitted from the
125
+ fingerprint, so default hosts keep the pre-W29 fingerprint byte for byte.
126
+ - **Console** — the settings panel shows the active tier and its concrete
127
+ numbers (read-only; the tier is a construction-time decision).
128
+
129
+ ### Changed
130
+ - The `standard` profile is the kernel's pre-W29 numbers **verbatim** — a
131
+ default host behaves exactly as before (asserted by test).
132
+ - Engineering note on the VISION table: `standard` keeps the FULL PAE surface
133
+ (`all` kinds) rather than "MCP + JS" — the governance axiom is that tiers
134
+ scale strength, never capability, and the already-shipped OpenAPI/Cordis
135
+ adapters must not silently disappear from the default tier. `strict` still
136
+ closes the foreign-runtime surface as a compliance choice. See VISION §3.1
137
+ "与原始表的偏差".
138
+ - `src/index.ts` three-way duplicate `export * from "@orbit/infra-common"` was
139
+ collapsed to one line.
140
+
141
+ ### Verification
142
+ - Clean `tsc -b`, strict mode, zero errors.
143
+ - Kernel suite: **397 cases** green (381 → +14 `governance_profile` + 2
144
+ `replay_compat` merge gates: cross-tier replay refuses as config drift;
145
+ same-tier replay stays byte-identical across hosts).
146
+ - Console suite: **97 cases** green (css-coverage gate caught a missing `.col`
147
+ style for the new settings panel block; added).
148
+ - Examples unchanged and green.
149
+
150
+ ### Migration
151
+ - No API break: `governanceProfile` is optional and defaults to `standard`,
152
+ which resolves to the previous behaviour verbatim.
153
+
154
+ ## [0.5.0] — 2026-09-01 · Engineering hardening & release prep (M5/M6)
155
+
156
+ The kernel is architecturally complete (VISION Phases 1–5 shipped); this wave
157
+ closes the product-hardening track so the release is not just complete but
158
+ provable and publishable.
159
+
160
+ ### Added
161
+ - **`examples/`** — four runnable, assertion-gated walkthroughs:
162
+ `custom-channel.mjs` (implement a channel, wire it in, prove
163
+ record → replay byte-identical plus drift detection), `js-pae-plugin.mjs`
164
+ (foreign JS tools through a governed channel), `mcp-adapter.mjs` (a real
165
+ stdio child process, handshake-discovered, replayed after the peer is dead),
166
+ and `cli-record-replay.mjs` (the three-command CLI loop). Each exits
167
+ non-zero on failure, so the set doubles as CI smoke checks.
168
+ - **`benchmarks/`** — `gateway` (governed `capabilityInvoke` cost), `replay`
169
+ (journal fast-path throughput), `wal` (durable append + flush), `pae`
170
+ (L0 in-process vs L2 stdio-child latency), plus `run-all.mjs` and
171
+ `npm run benchmark`. Sample numbers on Node 22: gateway ~82k calls/s
172
+ (~12 µs), replay ~261k calls/s (~3.8 µs), WAL ~1.5k durable appends/s,
173
+ L0 ~38 µs vs L2 ~176 µs (4.6× cross-process factor).
174
+ - **CI coverage closed**: the console suite (`npm run test:console`, 97
175
+ cases) now runs in CI alongside the kernel suite; all four examples and all
176
+ four benchmark suites run as smoke checks.
177
+ - **Package contents**: `examples/`, `benchmarks/` and `README.zh-CN.md` are
178
+ now shipped in the npm tarball; `prepublishOnly` also runs the console
179
+ suite.
180
+
181
+ ### Changed
182
+ - `KERNEL_VERSION` and all six `package.json` files bumped to `0.5.0`;
183
+ fingerprint assertion and README example updated.
184
+
185
+ ### Migration
186
+ - No API change. The new npm scripts are additive
187
+ (`npm run example:*`, `npm run benchmark`).
188
+
189
+ ## [0.4.0] — 2026-08-31 · Journal durability (W27)
190
+
191
+ Closes the last architectural gap carried by the v0.3.0 documentation: journals
192
+ lived only in memory, so a restart erased the audit trail and any recorded run.
193
+ A replay-and-audit kernel whose log evaporates on restart is not a complete
194
+ architecture — this wave gives both journals a crash-safe write-ahead log.
195
+
196
+ ### Added
197
+ - **Crash-safe WAL substrate** (`@orbit/core-hub`, `persistence/wal`): one JSON
198
+ line per entry. A write appends a single line, so the only artifact a crash can
199
+ leave is a *partial final line* — recovery drops exactly that, while any corrupt
200
+ or structurally invalid **interior** line is a genuine fault and is rejected as
201
+ `WalFileInvalidError` with its line number. `walAppend` / `walRecover` /
202
+ `walRecoverSync` / `walReset` / `walCompact` / `walLineCount`.
203
+ - **`PersistedTraceJournal`** — the audit/behavior journal mirrored to a WAL.
204
+ `load()` replays it at boot; `entryUid` and `occurredAt` are preserved verbatim,
205
+ so recovered entries are byte-identical and never perturb audit ordering.
206
+ - **`PersistedRecordJournal`** — a recording window mirrored to a WAL. A recovered
207
+ window continues `orderIndex` instead of restarting at 0, so a run split across
208
+ processes replays as one uninterrupted sequence.
209
+ - **Host durability options** — `new OrbitRuntimeHost({ traceJournalPath,
210
+ recordJournalPath, auditRetention })`. `bootHost` recovers, `shutdownHost`
211
+ drains pending writes, `resumeRecording()` reopens a persisted window and
212
+ `currentRecordJournal()` exposes it. Omitting the paths keeps the previous
213
+ purely in-memory behavior, byte for byte.
214
+ - **Bounded audit retention** — `auditRetention` keeps the newest N entries and
215
+ compacts the WAL to match, applied at boot and at shutdown, plus
216
+ `pruneAuditLog()` for on-demand pruning. An audit log that fills the disk is an
217
+ outage, so the bound is explicit and operator-chosen rather than implicit.
218
+ - **Self-healing logs** — `walCompact` / `compact()` / `healIfNeeded()` rewrite a
219
+ log atomically (temp file + rename) from its surviving prefix.
220
+
221
+ ### Fixed
222
+ - `loadTraceJournal` restored only the *last* entry of a saved journal: it called
223
+ `restoreSnapshot([entry])` inside the read loop, replacing the chain on every
224
+ iteration instead of accumulating it.
225
+ - A crash-truncated tail was tolerated by recovery but left on disk, so the next
226
+ run's first append turned it into an **interior** invalid line — which is a hard
227
+ fault, meaning one crash could make every later boot fail. Recovery now heals
228
+ the file before the first append (`healIfNeeded`, a no-op on a healthy log).
229
+ - `bootHost` recovered the audit journal *after* channel setup, so audit entries
230
+ emitted during setup were discarded by the recovered snapshot. Recovery now runs
231
+ before anything can append.
232
+
233
+ ### Verification
234
+ - Clean `tsc -b` composite build, strict mode, zero errors.
235
+ - Kernel suite: **348 cases** green (290 at v0.3.0 + 58 new).
236
+ - Console suite (`npm run test:console`): **89 cases** green.
237
+ - Charter gate A1: `test/replay_compat.test.ts` extended with WAL cases — a
238
+ window persisted by one process replays byte-identically in another, a
239
+ crash-truncated WAL replays its surviving prefix, and durability does not
240
+ perturb the recorded bytes.
241
+
242
+ ### Migration
243
+ - No public API change and no on-disk trace-format change; durability is opt-in
244
+ per path. Existing v0.3.x traces replay unchanged.
245
+ - `KERNEL_VERSION` bumped to `0.4.0`; `DOMAIN_HOST_VERSION` derives from it.
246
+
247
+
248
+ ## [0.3.0] — 2026-08-31 · v0.3.0 General Availability (W15–W26)
249
+
250
+ The v0.3.0 wave delivers the ecosystem-access track end to end: the Plugin
251
+ Adaptation Engine (foreign runtimes surfaced as governed capability channels),
252
+ graph-driven isolation domains with transactional cross-domain calls, the
253
+ TypeScript Project-References monorepo split, and the admin-console packaging.
254
+ The public API (`src/index.ts`) is unchanged across the split.
255
+
256
+ ### Release summary
257
+ - **Plugin Adaptation Engine (W15–W18):** JS / MCP / OpenAPI / Cordis adapters,
258
+ each a capability channel governed by the gateway (W15–W18 detail below).
259
+ - **Isolation domains (W19–W20):** graph-driven L2 domain allocation, an atomic
260
+ cross-domain transaction ledger with orphan/refusal reconciliation, and the
261
+ plan held as host state with diff-based sync.
262
+ - **Monorepo extraction (W21–W23):** the single `src/` tree split into npm
263
+ workspaces (`@orbit/infra-common`, `@orbit/core-hub`, `@orbit/sandbox-runtime`,
264
+ `@orbit/pae-engine`) plus the root host; `tsc -b` composite build.
265
+ - **Admin console packaged (W24–W26):** `web/` is now `@orbit/admin-console`
266
+ (a private app workspace) with `start`/`test` scripts; the bridge imports the
267
+ compiled kernel from `dist/`.
268
+
269
+ ### Verification (v0.3.0 GA)
270
+ - Clean from-scratch `tsc -b` build, strict mode, zero errors.
271
+ - Kernel suite (`node --test dist/test/*.test.js`): **290 cases** green.
272
+ - Console suite (`npm run test:console`): **89 cases** green.
273
+ - No public API change, no replay-contract regression vs v0.2.x.
274
+
275
+ ### Migration
276
+ - Commands unchanged: `npm install`, `npm run build`, `npm test`,
277
+ `npm run test:console`, `npm run start:web`.
278
+ - `KERNEL_VERSION` bumped to `0.3.0`; `DOMAIN_HOST_VERSION` derives from it.
279
+ - pnpm migration (a roadmap refinement) is deferred — npm workspaces already
280
+ satisfy the structural goal; see DEV_PLAN W24–W26.
281
+
282
+ ### W15 — Plugin Adaptation Engine (PAE)
283
+
284
+ First wave of the v0.3.0 ecosystem track: foreign runtimes are mapped onto the
285
+ kernel's capability contract through the Plugin Adaptation Engine (PAE), so the
286
+ kernel's governance, recording and replay machinery covers them without any
287
+ special-casing.
288
+
289
+ ### Added
290
+ - **PAE contract layer** (`src/pae/types.ts`) — `PaeFidelity` (`full | reduced |
291
+ lossy`), `PaeAdapterKind` (`js | mcp | openapi | cordis`), `PaeIsolationLevel`
292
+ (`L0 | L1 | L2`), `PaeToolDescriptor`, `PaeAdapterMeta`, `PaeInvokeCtx`,
293
+ `IPaeAdapter`, and the error types `PaeAdapterRejectError` /
294
+ `PaeToolMissingError` / `PaeFidelityRejectError` (all `OrbitDomainError`).
295
+ `FIDELITY_RANK` orders `full ≻ reduced ≻ lossy`.
296
+ - **`PaeAdapterRegistry`** (`src/pae/PaeAdapterRegistry.ts`) — registration-time
297
+ static validation (complete meta, unique id, semver `sourceEdition`, ≥1 tool,
298
+ name-pattern + reserved-name checks, globally unique tool names, and
299
+ *documented* downgrades), dynamic `PluginUnitPact` derivation (capability union
300
+ + forced `channel:read` + `declareChannelDeps: [PAE_TOOL]`), fidelity
301
+ negotiation (`negotiate` rejects a tool below the caller's `minFidelity`),
302
+ and `configHash()` — a SHA-256 (first 16 chars, order-independent) of the
303
+ adapter surface that feeds the run fingerprint.
304
+ - **`PaeChannel`** (`src/pae/PaeChannel.ts`) — a registered adapter is surfaced
305
+ as a single capability channel. Every foreign call therefore travels
306
+ `capabilityInvoke → ChannelHub → registry → adapter` and lands in the
307
+ `RecordJournal` with its governance decision attached; there is no side door.
308
+ - **`JsPaeAdapter`** (`src/pae/adapters/JsPaeAdapter.ts`) — the first concrete
309
+ adapter family (in-process JS, `L0`, `full` by default). Handlers receive
310
+ `(args, ctx)` where `rng` / `clock` are injected; reaching for `Math.random` /
311
+ `Date.now` is a charter violation. Unknown tools throw `PaeToolMissingError`.
312
+ - **Two architectural invariants** enforced by construction: (1) adapters never
313
+ talk to the kernel directly — they are a capability channel, so record/replay
314
+ covers them; (2) adapters introduce no nondeterminism of their own — sources
315
+ arrive through `PaeInvokeCtx`.
316
+ - **Config hash into fingerprint** — `RunVersionFingerprint.paeAdaptersHash`
317
+ (optional) carries `PaeAdapterRegistry.configHash()`; a changed adapter surface
318
+ is reported as `RunFingerprintDriftError("paeAdaptersHash", …)` rather than as a
319
+ digest mismatch. When no adapter is registered the field is omitted, so v0.2.x
320
+ traces keep their original fingerprint shape (backward compatible).
321
+ - Public API exports: `PaeAdapterRegistry`, `PaeChannel`, `JsPaeAdapter` (+ their
322
+ config/spec types), the three PAE error classes, `FIDELITY_RANK`, and the
323
+ `IPaeAdapter` / `PaeAdapterKind` / `PaeAdapterMeta` / `PaeFidelity` /
324
+ `PaeInvokeCtx` / `PaeIsolationLevel` / `PaeToolDescriptor` types.
325
+
326
+ ### W16 — MCP adapter (cross-process foreign runtimes)
327
+ - **MCP protocol layer** (`src/pae/adapters/mcp/protocol.ts`) — JSON-RPC 2.0
328
+ envelopes, newline framing, `tools/list` validation and `tools/call` result
329
+ normalisation as pure functions. Parsing untrusted peer output is exactly the
330
+ kind of logic that must be testable without I/O, so it lives here.
331
+ - **Transports** (`src/pae/adapters/mcp/transport.ts`) — `IMcpTransport` with a
332
+ stdio implementation (`node:child_process`, newline-delimited JSON, correlated
333
+ responses, caller deadlines, in-flight requests failed when the peer dies) and
334
+ an in-memory one so protocol behaviour is testable without a subprocess. A
335
+ failing peer's stderr is kept as a bounded tail and attached to the error —
336
+ previously a server that died on startup reported only its exit code, which is
337
+ undiagnosable.
338
+ - **`McpPaeAdapter`** (`src/pae/adapters/mcp/McpPaeAdapter.ts`) — `kind: "mcp"`,
339
+ `isolation: "L2"`, determinism `IO_BOUND`. `setup` performs the handshake and
340
+ *then* discovers the tool surface, because a remote peer's capabilities are
341
+ not knowable any earlier. The edition the peer reports is adopted as
342
+ `sourceEdition` once known, so a server upgrade shows up as fingerprint drift
343
+ instead of passing unnoticed.
344
+ - **Honest default fidelity** — MCP tools default to `reduced` with a mandatory
345
+ note: the argument schema is enforced by the *peer*, not the kernel, and
346
+ results are mapped from MCP `content[]` (non-text blocks preserved verbatim
347
+ rather than coerced). Claiming `full` would be shorter; it would also be the
348
+ most damaging false claim this adapter could make, because every downstream
349
+ assumption rests on it.
350
+ - **Host** — `connectPaeToolAdapter` (handshake, then register the surface the
351
+ peer actually announced) and `releasePaeToolAdapter` (unregister, then await
352
+ teardown, so an MCP subprocess does not outlive its registration).
353
+ - **Registry fix** — `unregister` never called `adapter.teardown()`. Harmless for
354
+ in-process adapters, but it meant an MCP peer stayed alive after its adapter
355
+ was removed. Releases are now started on unregister and can be awaited via
356
+ `drainReleases()`.
357
+ - New error type `PaeRemoteError` for peer/transport failures — distinct from
358
+ registration-time rejection and from a missing tool name.
359
+
360
+ ### W17 — OpenAPI adapter (REST APIs as PAE tools)
361
+ - **`spec.ts` — pure document mapping.** An OpenAPI 3.x / Swagger 2.x document
362
+ is parsed into a tool surface (`parseOpenApiDocument`): one tool per
363
+ (method, path) operation, `operationId` used verbatim and a registry-safe
364
+ `method_path` name synthesized when absent, path-level parameters merged into
365
+ each operation, and cookie parameters rejected outright (the kernel never
366
+ attaches ambient credentials). Malformed structure is a hard error, exactly
367
+ like MCP's `parseToolList`. Request building (`buildHttpRequest`) is also
368
+ pure: required path parameters must be present and are URL-encoded in place,
369
+ query keys are serialised in sorted order so identical arguments produce an
370
+ identical URL (digest stability), and remaining keys become the JSON body when
371
+ the operation declares one — leftovers without a body are a hard error, never
372
+ silently dropped. `resolveDocumentBaseUrl` reads `servers[0]` / swagger
373
+ `schemes+host+basePath` as a fallback.
374
+ - **`transport.ts` — injected HTTP seam.** `IHttpTransport` mirrors the MCP
375
+ transport contract; `InMemoryHttpTransport` makes the adapter's semantics
376
+ testable without a network, and `FetchHttpTransport` is the real path
377
+ (platform `fetch`, per-request deadline, default headers, injectable
378
+ `fetchImpl` for tests).
379
+ - **`OpenApiPaeAdapter`** — `kind: "openapi"`, `isolation: "L2"`, remote API is
380
+ `IO_BOUND` like MCP. Unlike MCP there is no live handshake: the surface is
381
+ read statically from the document, so a malformed spec (or an adapter with no
382
+ resolvable base URL) fails at construction, before any call is routed to it.
383
+ `baseUrl` is configuration first, the document's server a fallback. Default
384
+ fidelity is **`reduced`** with an honest note: validation is remote (only
385
+ required path parameters are enforced locally; query/header/body pass
386
+ through), and an HTTP response is collapsed to a single JSON/text value with
387
+ status code and headers dropped; a non-2xx status raises `PaeRemoteError`
388
+ with the status and a bounded body tail. Per-operation overrides
389
+ (`OpenApiOperationOverride`) and `toolNamePrefix` follow the MCP pattern.
390
+ - Public API: `OpenApiPaeAdapter`, `OPENAPI_DEFAULT_FIDELITY_NOTE`,
391
+ `InMemoryHttpTransport`, `FetchHttpTransport`, `parseOpenApiDocument`,
392
+ `buildHttpRequest`, `normaliseHttpResponse`, `resolveDocumentBaseUrl`.
393
+
394
+ ### W18 — Cordis adapter (isolated plugin hosts)
395
+ - **`protocol.ts` — host-defined wire format, pure.** A Cordis isolated
396
+ instance (VISION: 事件锁在域内,跨域为事务) is a plugin host process with no
397
+ standardised protocol, so the kernel defines one. The envelope borrows
398
+ JSON-RPC 2.0's discipline (id, result XOR error) but is deliberately
399
+ self-contained — adapter families stay independent, and a protocol revision
400
+ here cannot ripple into MCP. `decodeFrame` skips blank/log lines and rejects
401
+ envelope violations; `parseCordisToolList` treats a malformed host as a hard
402
+ error; `normaliseCordisToolResult` passes host results through verbatim.
403
+ - **`transport.ts` — injected seam.** `ICordisTransport` + in-memory
404
+ implementation for network-free tests, and `ChildProcessCordisTransport`
405
+ (spawn `node` host, newline-delimited JSON, correlated responses, caller
406
+ deadlines, in-flight requests failed when the host dies, bounded stderr tail
407
+ surfaced on failure). Same responsibilities as the MCP stdio transport.
408
+ - **`CordisPaeAdapter`** — `kind: "cordis"`, `isolation: "L2"`, `IO_BOUND` like
409
+ every cross-process family. `setup()` performs the `initialize` handshake,
410
+ adopts the host-reported version as `sourceEdition` (semver-guarded, `0.0.0`
411
+ placeholder until then), then discovers the tool surface via `tools/list`.
412
+ Default fidelity is **`reduced`** with an honest note: validation is remote
413
+ (the announced `input` shape is not locally enforced), results are whatever
414
+ JSON the host returns, and the host's internal events and services stay
415
+ inside the isolated instance. `toolNamePrefix` and per-tool overrides follow
416
+ the MCP pattern. Closes the W15–W18 difficulty ladder: JS (L0) → MCP (L2,
417
+ standard protocol) → OpenAPI (L2, stateless) → Cordis (L2, host-defined
418
+ protocol).
419
+ - Public API: `CordisPaeAdapter`, `CORDIS_DEFAULT_FIDELITY_NOTE`,
420
+ `InMemoryCordisTransport`, `ChildProcessCordisTransport`, `encodeFrame`,
421
+ `decodeFrame`, `parseCordisToolList`, `normaliseCordisToolResult`.
422
+
423
+ ### W19 — Graph-driven isolation domains (VISION 2.3 double isolation)
424
+ - **`allocate.ts` — pure graph → plan.** `impactClosureSizes` computes every
425
+ node's failure impact (reverse-reachability closure on the impact graph).
426
+ `allocateDomains` turns the graph into a domain plan: a node whose impact
427
+ closure exceeds `maxImpactClosure` is **escalated** to its own L2 domain
428
+ (`iso:<unit>`), the rest are packed into deterministic `shared:<n>` chunks of
429
+ at most `maxDomainSize`. Independence is what makes co-location safe — nodes
430
+ with no path between them cannot affect each other, so sharing a process adds
431
+ no *logical* blast; the threshold is the accepted *process-level* blast
432
+ contract. The plan is a partition, deterministic, and auto-escalates as the
433
+ graph grows.
434
+ - **`protocol.ts` / `transport.ts` — L2 host wire format, pure + injected.**
435
+ `units/list` surface parsing (malformed hosts are a hard error, duplicate
436
+ unit ids rejected, tool names deduplicated globally as `unitId:tool`) and
437
+ `units/call` result pass-through. `IDomainTransport` + in-memory
438
+ implementation + `ChildProcessDomainTransport` (spawn `node`, framing,
439
+ correlation, deadlines, dead-host in-flight failure, stderr tail).
440
+ - **`hostShim.ts` — the built-in pure-unit host.** A source string spawned via
441
+ `node -e` by the default transport factory; serves pure units (`echo`, `calc`)
442
+ selected by the `ORBIT_DOMAIN_UNITS` env var. The kernel never ships code into
443
+ the child — a real deployment swaps this for a bootstrap script that loads its
444
+ own plugins and announces them via the same protocol.
445
+ - **`IsolationDomain` / `IsolationDomainManager`** — the physical layer: setup
446
+ handshake + unit discovery, `invokeUnit` routing, and a **sync that is a
447
+ diff, not a rebuild** — unchanged domains keep their child processes, removed
448
+ domains are awaited before release. `teardownAll` releases everything.
449
+ - **`DomainChannel`** — the gateway surface, the same shape as `PaeChannel`:
450
+ every unit tool is installed as a method named `${unitId}:${tool}` (unit ids
451
+ are globally unique because the plan is a partition), so a domain call travels
452
+ `capabilityInvoke(DOMAIN_TOOL) → hub → channel → manager → host process` and
453
+ lands in the journal as an `IO_BOUND` inject call. Replay needs neither the
454
+ domain nor its child process.
455
+ - Public API: `allocateDomains`, `impactClosureSizes`, `IsolationDomain`,
456
+ `IsolationDomainManager`, `DomainChannel`, `InMemoryDomainTransport`,
457
+ `ChildProcessDomainTransport`, `DOMAIN_HOST_SHIM`, `DOMAIN_HOST_VERSION`,
458
+ the protocol functions, and `ChannelKind.DOMAIN_TOOL`.
459
+
460
+ ### W20 — Cross-domain transactions & graph-driven allocation as host state
461
+ - **`transaction.ts` — the settlement record.** VISION 2.1 declares every
462
+ capability call an atomic transaction; 2.2 adds that interaction *between*
463
+ isolation domains is a gateway transaction whose events can be reconciled.
464
+ `beginTransaction` / `markExecuted` / `settleTransaction` / `reconcileTransactions`
465
+ implement that with no clock, no randomness and no I/O. Transaction ids are
466
+ `dtx:<seq>`, so a run replays to the same id stream. Reconciliation groups by
467
+ (source domain → target domain) and detects two failure shapes from the
468
+ records alone: **orphans** (a hop crossed a boundary and never settled) and
469
+ **refusals** (refused before execution — not an error, but a wall of them
470
+ means the plan no longer matches the graph).
471
+ - **`IsolationDomainManager.invokeUnit` is now transactional.** Every hop opens
472
+ a transaction (decision: is the unit assigned, at what isolation level),
473
+ executes, and settles with its outcome — success or failure. A refused hop is
474
+ *recorded as rejected* rather than thrown away, so "the plan no longer matches
475
+ the graph" is visible in the ledger, not only in a stack trace. Latency is
476
+ measured through an injected clock; `txnLedger()` / `reconcile()` /
477
+ `ledgerHash()` / `clearLedger()` expose the record.
478
+ - **The plan becomes host state.** `OrbitRuntimeHost` owns the domain manager:
479
+ graph mutations (`registerPlugin`, `spawnAgentBox`, `unregisterPaeToolAdapter`)
480
+ mark the plan stale via `domainsStale()`, and `allocateIsolationDomains()`
481
+ syncs it (a diff, so re-running changes nothing) and publishes the surface on
482
+ `ChannelKind.DOMAIN_TOOL` — registering that channel happens only on
483
+ allocation, so a host that never allocates domains keeps its previous hub
484
+ surface and fingerprint byte for byte.
485
+ - **Backward-compatible fingerprint.** `RunVersionFingerprint.domainPlanHash` is
486
+ *omitted* while no plan exists (the W15/W16 PAE rule applied to the physical
487
+ layer), and `host.runFingerprint()` is now public for drift diagnosis.
488
+ - **Replay does not re-enter a domain.** The frozen output is injected at the
489
+ gateway, so the child process is untouched and no transaction is opened —
490
+ asserted directly in the replay gate.
491
+ - Public API: `allocateIsolationDomains`, `domainPlan`, `domains`, `domainsStale`,
492
+ `invokeDomainUnit`, `domainLedger`, `reconcileDomainTransactions`,
493
+ `releaseIsolationDomains`, `runFingerprint`, plus the transaction functions and
494
+ types.
495
+
496
+ ### Console
497
+ - **Adapter Studio** (`web/public/views/pae.js`) — the PAE surface becomes
498
+ operable: pick a tool template, register the adapter, negotiate fidelity, then
499
+ invoke the tool through the gateway and read back the routing decision, the
500
+ elapsed time and the returned value. The view reuses the Bio-Lineage system
501
+ with a new `--coupler` role (接驳橙 `#ff9d4d`) for foreign adapters.
502
+ - **MCP in the console** — a second adapter family alongside JS. Connecting
503
+ spawns the server, completes the handshake and registers only the tools the
504
+ peer actually announced; a failed handshake closes the child and leaves no
505
+ registration behind. Discovered tools are shown with the peer's identity and
506
+ their honest `reduced` fidelity.
507
+ - **12 tool templates** (`web/public/lib.js`) — `echo`, `reverse`, `upper`,
508
+ `lower`, `length`, `hash`, `base64`, `json`, `add`, `now`, `random`, `uuid`.
509
+ Templates are descriptors only; the bridge injects real handlers and routes
510
+ `random` / `now` through `SeededRng` plus an injected clock, so the console
511
+ never smuggles nondeterminism into the kernel.
512
+ - **Honesty gate in the UI** — selecting `reduced` / `lossy` turns
513
+ `fidelityNote` into a required field; an undocumented downgrade cannot be
514
+ registered from the console either.
515
+ - **Bridge server** — `GET|POST /api/pae`, `POST /api/pae/invoke`,
516
+ `POST /api/pae/negotiate`, `DELETE /api/pae/:id`; PAE state surfaced in
517
+ `/api/state` (enabled / adapter+tool counts / config hash) and in `/api/graph`
518
+ (a `pae-tool` channel node plus one node per adapter, edged
519
+ `adapter → pae-tool`).
520
+ - **Graph view** — new `pae` / `pae-adapter` node kinds with the coupler color,
521
+ halo, layout band and legend entry.
522
+ - **Command palette** — `Ctrl/⌘+K` or `/` opens a fuzzy-searchable index of
523
+ every view *and* every host action (boot / shutdown / restart / refresh).
524
+ `↑` `↓` to move, `Enter` to run, `Esc` to close. Ranking is a pure function
525
+ (`fuzzyScore`) and is unit-tested, including Chinese/English mixed queries.
526
+ - **Task-oriented overview** — the front page stopped restating the nine views
527
+ and now answers two questions instead: *can this host work right now* (a health
528
+ verdict with every reason behind it) and *what should I do next* (derived from
529
+ real kernel state, each step a clickable action rather than prose). A stopped
530
+ host gets exactly one suggestion: start it.
531
+ - **Grouped navigation, generated from data** — the sidebar is built from
532
+ `NAV_GROUPS` in `lib.js`, not hand-written in HTML, and the palette indexes the
533
+ same data. Nine flat pages became three intent groups: 运行时 / 构件 / 治理.
534
+ - **Fixes**:
535
+ - `channels` had a route but no nav button — 模型通道 was unreachable.
536
+ - `--accent` / `--accent-2` / `--purple` were referenced by the overview but
537
+ never declared.
538
+ - `/api/health` reported a hard-coded `0.1.0` while the kernel was at `0.2.0`;
539
+ it now reads `KERNEL_VERSION`, so the console cannot go stale again.
540
+ - The overview's 熔断保护 card pointed at a `safeguard` route that does not
541
+ exist, silently sending the user to the sandbox page instead.
542
+ - **Front-end tests** (`web/test/`, `npm run test:console`, 49 cases) —
543
+ `pae-catalog.test.mjs` (pure helpers + template catalog),
544
+ `bridge-pae.test.mjs` (a real `OrbitRuntimeHost`, now including seven MCP cases
545
+ driving a genuine subprocess peer), and `console-core.test.mjs` (navigation
546
+ model, palette ranking, health derivation, next-step suggestions, argv
547
+ parsing). `web/test/fixtures/mcp-stdio-server.mjs` is a minimal but real MCP
548
+ server used to exercise the full cross-process path.
549
+
550
+ ### Tests
551
+ - `test/pae_adapter.test.ts` (22 cases): registration validation, dynamic-pact
552
+ derivation, fidelity-negotiation rejection, order-independent `configHash`,
553
+ JS-adapter determinism, host routing decisions, write-tool lockdown for
554
+ read-only callers, replay zero re-entry, and `paeAdaptersHash` drift.
555
+ - `test/mcp_adapter.test.ts` (27 cases): protocol framing and validation,
556
+ `content[]` normalisation, transport correlation / deadlines / closure,
557
+ handshake-driven discovery, `L2` + `IO_BOUND` defaults, honest `reduced`
558
+ fidelity, `toolNamePrefix` collision avoidance, remote tool errors, host
559
+ registration and drift, a real subprocess over stdio, and a dead or dying peer
560
+ failing in-flight requests with its stderr attached.
561
+ - `test/replay_compat.test.ts` (+3 PAE, +2 MCP merge-gate cases): record→replay is
562
+ byte-identical and the adapter runs exactly once; after unregistering an
563
+ adapter its replay needs no implementation; a `Math.random` poison in the
564
+ adapter body is caught; an MCP trace replays without re-entering the peer; a
565
+ trace replays after its MCP peer has been shut down and released.
566
+ - Full kernel suite: **205 cases** green, strict compile zero errors
567
+ (baseline 151 → 176 after W15 → 205 after W16; only grows).
568
+ - Full console suite (`npm run test:console`): **49 cases** green. Kernel and
569
+ console suites are independent; a change on either side runs both.
570
+
571
+ ### Console Platformization — 2026-08-30 (W16+, continued)
572
+
573
+ The web console stops being a passive viewer and becomes a platform: accounts,
574
+ knowledge, retrieval, orchestration and governance all live behind the bridge
575
+ and share one DOM-free source of truth with the browser.
576
+
577
+ - **Account & access layer** — `scrypt` password hashing with per-user salt,
578
+ seeded administrator (`admin / orbit-admin`, first account is always admin so
579
+ self-registration can never mint another), bearer-token sessions, password
580
+ change with audit. Role matrix (`admin | operator | viewer`) is the single
581
+ `can(role, action)`裁决入口; 403 surfaces and button-disabled states both ask
582
+ it. Bridge routes: `POST /api/auth/{register,login,logout,password}`,
583
+ `GET /api/auth/me`.
584
+ - **Knowledge base** (`web/public/kb.js`, zero deps) — paragraph-aware chunking
585
+ (paragraphs never split, sentence-level fallback, overlap only inside a
586
+ paragraph), a deterministic lexical BM25 index (no vector service, fully
587
+ replayable), and query→chunk highlight ranges for two-way grounding. Chinese
588
+ stop-words are stored as single characters because `tokenize` splits CJK into
589
+ single chars — multi-char stop-words would otherwise never match. Bridge
590
+ routes: `GET/POST/DELETE /api/kb`, `POST /api/kb/:id/docs`,
591
+ `POST /api/kb/:id/search`, `GET /api/kb/:id`, `GET /api/kb/:id/docs/:doc`.
592
+ - **Agentic RAG pipeline** — an eight-step run (`RAG_STEPS`: parse → retrieve →
593
+ assess → refine → rerank → synthesize → ground → audit) with a sufficiency
594
+ gate (`assessSufficiency`) that triggers at most `maxRefines` deterministic
595
+ query rewrites (high-frequency terms from the top hit), then synthesizes
596
+ through the kernel's `llm-access` channel and grounds the answer with citations
597
+ carrying highlight ranges. Bridge: `GET/POST /api/rag`, `GET /api/rag/:id`.
598
+ - **Workflow DAG editor** (`workflow.js`) — a canvas to compose start / agent /
599
+ tool / branch / end nodes with flow and loop edges. Graph rules are pure
600
+ functions: `validateWorkflow` (unique start, required end, no dangling/self
601
+ edges, no flow-cycle — loop edges are exempt, orphan/under-branched warnings),
602
+ `topoOrder` (stable Kahn sort using original node order), `evalBranch`
603
+ (deterministic substring match). Bridge: `GET/POST /api/workflows`,
604
+ `POST /api/workflows/:id/run`, `GET /api/workflow-runs/:id`.
605
+ - **Platform views & pure logic** — 13 view modules (`login, dashboard,
606
+ instances, tasks, workflow, knowledge, rag, templates, market, audit, billing,
607
+ settings, profile`) plus the pre-existing `channels/pae/routing/replay/graph`;
608
+ all "what the user sees next" logic (navigation, command palette ranking,
609
+ health derivation, next-step suggestions, billing aggregation, notification
610
+ derivation, task-status vocabulary, role matrix) lives in DOM-free
611
+ `web/public/lib.js` so it is assertable in Node. Fixed a dead-code bug: the
612
+ multi-character CJK stop-word list could never match after `tokenize`.
613
+ - **Governance & observability** — billing aggregation (`deriveBilling`: balance,
614
+ total, 7-day trend, per-box/per-task ranking, low-balance flag), audit trail
615
+ export (`GET /api/audit/export` md/json), notification center
616
+ (`deriveNotifications`), and a `GET /api/dashboard` roll-up.
617
+ - **Tests** — console suite grew **49 → 80** (`web/test/kb.test.mjs` for the KB /
618
+ RAG / workflow pure logic, `web/test/console-platform.test.mjs` for billing /
619
+ notifications / trends / roles / task vocabulary); an HTTP end-to-end smoke
620
+ exercises login → KB create → upload → search → RAG → workflow save/run →
621
+ billing → audit → notifications → dashboard with a 401 probe on a bad token.
622
+ Full kernel suite unchanged at **205** cases green, strict compile zero errors.
623
+
624
+ ### Console feature transformation — 2026-08-30 (W16+, continued)
625
+
626
+ User-facing completion pass driven by the no-xianxia professional-console
627
+ design doc (`74d2a10`). All product copy, navigation, status and type
628
+ vocabulary is fully de-xianxia'd and professional.
629
+
630
+ - **Knowledge base upload rebuilt** — drag-and-drop / batch / folder upload panel
631
+ with chunk-size + overlap parameters wired end-to-end through `kbUpload`;
632
+ per-file status pipeline (排队 → 解析中 → 切片中 → 向量化中 → 完成/失败),
633
+ global progress and an index-build animation. Contract test added.
634
+ - **Settings extended** — model-adapter section (DeepSeek / OpenAI-compatible
635
+ endpoints, key, model, temperature) and security section (password change,
636
+ logout); permission matrix already present.
637
+ - **Templates & instances** — copy-as-new-template, side-by-side version compare
638
+ (diff vs previous revision), instance detail drawer with the full field set
639
+ and quick actions.
640
+ - **RAG** — slow-motion step replay with replay-focus step selection.
641
+ - **Cross-cutting** — dashboard rebuilt as a data board with charts, global
642
+ responsive breakpoints, 404/403 state pages, login/register completion
643
+ (remember-me, validation, agreement), PDF audit export.
644
+ - Tests: console suite **80 → 81** green; kernel 205 unchanged.
645
+
646
+ ### Console style restoration — 2026-08-30 (fix)
647
+
648
+ Two user-visible defects traced to the `826c150` full stylesheet rewrite
649
+ (`6bf2249`):
650
+
651
+ 1. **Login page leaked register-only fields** (nickname/email/confirm/agree).
652
+ Layered root cause: the fields never received an initial `hidden` state
653
+ (it was only assigned inside `toggle()`), and even with `hidden` set,
654
+ `.field{display:flex}` / `.shell{display:grid}` override the UA stylesheet's
655
+ `[hidden]` rule. Fixed by assigning `hidden = true` at creation plus a
656
+ global `[hidden], .hidden { display:none !important }` rule.
657
+ 2. **Early-wave views (pae / channels / graph / replay / routing) lost all
658
+ styling** — the rewrite dropped every legacy selector still referenced by
659
+ those views. Restored as an explicit compatibility layer (~200 lines) with
660
+ token aliases (`--coupler` / `--purple` / `--text-2` → current tokens) and
661
+ the legacy selectors.
662
+ - **New gate test** `web/test/css-coverage.test.mjs`: every class referenced by
663
+ a view module must be defined in `styles.css`, the `[hidden]` rule must stay
664
+ `!important`, and the legacy compat tokens must remain defined — a full
665
+ stylesheet rewrite can no longer silently strand a view. Console suite
666
+ **81 → 84** green.
667
+
668
+ ## [0.2.0] — 2026-08-29 · Gateway determinism boundary (v0.2.0)
669
+
670
+ The unified gateway (`capabilityInvoke`) is now a complete, faithful determinism
671
+ boundary: every governance decision is recorded and replayed byte-identically,
672
+ and drift is reported in three distinct categories.
673
+
674
+ ### Added
675
+ - **`RateLimiter`** (`src/gateway/RateLimiter.ts`) — pure-function (no
676
+ `Math.random`/`Date.now`) call-count budget. The `rateLimited` decision is
677
+ recorded at record time and replayed verbatim; the limiter is **bypassed** on
678
+ replay so a fresh limiter never perturbs the reconstructed trace (axioms A1/A2).
679
+ - **`BehaviorCollector`** (`src/gateway/BehaviorCollector.ts`) — captures a
680
+ structured `BehaviorNote` per call in three modes:
681
+ - `record` — note is persisted on the `GatewayCallRecord` (with the trace).
682
+ - `live` — note is returned as a proposal, not persisted.
683
+ - `replay` — bypassed; the stored note is restored from the journal.
684
+ - **Three-way drift classification** (W13):
685
+ - Config drift → `RunFingerprintDriftError` (kernel/pact/token/pae fingerprint).
686
+ - Decision drift → `DecisionDriftError` (e.g. a capability pact revoked since
687
+ recording — governance is never weakened on replay).
688
+ - Call drift → `ReplayDriftError` (input/output signature mismatch).
689
+ - `ReconcileReport` now carries `decisionDriftFields` listing the differing
690
+ decision axes, distinct from config/call drift.
691
+ - **`replay_compat` gateway gate** (W12) — 7 CI cases proving byte-identical
692
+ replay under compression / rate-limit / collector / fingerprint-drift /
693
+ decision-drift. The determinism boundary is now a merge gate.
694
+ - `BehaviorNote` domain contract; `GatewayCallRecord.behavior?` field.
695
+ - Public API exports: `RateLimiter`, `DEFAULT_RATE_LIMIT_CONFIG`,
696
+ `BehaviorCollector`, `DecisionDriftError`.
697
+ - `CONTRIBUTING.md` — documents the architecture gate (VISION §5) on every PR.
698
+
699
+ ### Behavior
700
+ - The `compression` checker is now **payload-aware** (`decideCompression(output)`)
701
+ and the recorded `compression.applied`/`bytesSaved` reflect the actual at-rest
702
+ storage decision; small payloads are never bloated by an envelope.
703
+ - Budget/route/rate-limit/`tokenConfigHash` decisions are computed from the real
704
+ `TokenBudgetEngine` and channel registry rather than literal stubs.
705
+
706
+ ## [0.1.0] — 2026-08-29 · Open-source launch wave (v0.1.0)
707
+
708
+ First release-track engineering. The kernel is production-candidate for the
709
+ deterministic-replay use case.
710
+
711
+ ### Added
712
+ - **`orbit` CLI** (`bin/orbit.mjs`, zero extra dependencies) with three commands:
713
+ - `orbit record <script>` — run a script against a live kernel, capture every
714
+ channel call into a JSONL trace + a `.meta.json` sidecar.
715
+ - `orbit replay <trace>` — re-run the recorded script with **zero** real
716
+ channel calls and reconcile the digest chain (bank-style verification).
717
+ - `orbit diff <a> <b>` — compare two traces record-by-record and locate the
718
+ first digest-chain breakpoint.
719
+ - Every command supports `--json` and clean exit codes.
720
+ - **`OpenAICompatChannel` productionization** — 9-class fault taxonomy
721
+ (`LlmChannelFaultError`), deterministic exponential backoff (no `Math.random`,
722
+ `Retry-After` honored), internal retries that never leak into the record
723
+ journal, and `chatRound` overrides (multi-turn `messages`, `seed`,
724
+ `temperature`, `maxTokens`, `responseFormat`).
725
+ - **`FileChannel`** (`FILE_SYSTEM`) — filesystem access jailed to a root
726
+ directory (path-escape / null-byte rejection), read/write/append/list/stat/
727
+ remove/mkdir, size guards.
728
+ - **`ShellChannel`** (`SHELL_EXEC`) — command execution behind an exact-match
729
+ whitelist, argv-array spawn (no shell-injection surface), empty child env
730
+ unless allowlisted, hard timeout kill, per-stream output caps; non-zero exit
731
+ is data, not a fault.
732
+ - **JSONL trace persistence** (`saveRecordJournal` / `loadRecordJournal`) —
733
+ atomic write (tmp + rename), validated header / orderIndex / field checks,
734
+ `TraceFileInvalidError`.
735
+ - **`replay_compat` gate suite** — 11 cases proving the宪章 (VISION) axioms on
736
+ every new channel: delete-disk replay, side-effect non-re-execution,
737
+ zero-HTTP replay, retry isolation, multi-channel ordering, cross-engine
738
+ persistence replay, `Math.random` poison guard.
739
+ - **Developer guide** (`docs/guide.md`) — how to write a replayable channel.
740
+ - **CI** (`.github/workflows/ci.yml`) — Node 20/22 matrix: build, test, demos,
741
+ and an `orbit` CLI smoke (record → replay → diff).
742
+ - **Issue / PR templates** enforcing the architecture gate (VISION) on every PR.
743
+
744
+ ### Kernel fixes surfaced by the replay_compat gate
745
+ - `attachReplayEngine` now resets the replay call counter, so a second replay
746
+ pass over the same journal starts from call #0 again.
747
+ - The replay fast path is checked **before** provider availability, so a trace
748
+ replays on a machine with none of the real channels installed (credentials
749
+ and tools not required). The capability gate still applies first — governance
750
+ is not weakened.