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.
- package/CHANGELOG.md +750 -0
- package/LICENSE +201 -0
- package/README.md +492 -0
- package/README.zh-CN.md +389 -0
- package/benchmarks/README.md +24 -0
- package/benchmarks/gateway.mjs +68 -0
- package/benchmarks/pae.mjs +90 -0
- package/benchmarks/replay.mjs +51 -0
- package/benchmarks/run-all.mjs +24 -0
- package/benchmarks/wal.mjs +50 -0
- package/bin/orbit.mjs +523 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/demo-deepseek.d.ts +2 -0
- package/dist/demo-deepseek.d.ts.map +1 -0
- package/dist/demo-deepseek.js +66 -0
- package/dist/demo-deepseek.js.map +1 -0
- package/dist/demo-host.d.ts +2 -0
- package/dist/demo-host.d.ts.map +1 -0
- package/dist/demo-host.js +98 -0
- package/dist/demo-host.js.map +1 -0
- package/dist/demo-replay.d.ts +2 -0
- package/dist/demo-replay.d.ts.map +1 -0
- package/dist/demo-replay.js +68 -0
- package/dist/demo-replay.js.map +1 -0
- package/dist/src/core/orbitRuntimeHost.d.ts +349 -0
- package/dist/src/core/orbitRuntimeHost.d.ts.map +1 -0
- package/dist/src/core/orbitRuntimeHost.js +706 -0
- package/dist/src/core/orbitRuntimeHost.js.map +1 -0
- package/dist/src/index.d.ts +92 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +226 -0
- package/dist/src/index.js.map +1 -0
- package/dist/test/audit_chain.test.d.ts +2 -0
- package/dist/test/audit_chain.test.d.ts.map +1 -0
- package/dist/test/audit_chain.test.js +283 -0
- package/dist/test/audit_chain.test.js.map +1 -0
- package/dist/test/channel_hub.test.d.ts +2 -0
- package/dist/test/channel_hub.test.d.ts.map +1 -0
- package/dist/test/channel_hub.test.js +66 -0
- package/dist/test/channel_hub.test.js.map +1 -0
- package/dist/test/cordis_adapter.test.d.ts +2 -0
- package/dist/test/cordis_adapter.test.d.ts.map +1 -0
- package/dist/test/cordis_adapter.test.js +280 -0
- package/dist/test/cordis_adapter.test.js.map +1 -0
- package/dist/test/cost_routing.test.d.ts +2 -0
- package/dist/test/cost_routing.test.d.ts.map +1 -0
- package/dist/test/cost_routing.test.js +31 -0
- package/dist/test/cost_routing.test.js.map +1 -0
- package/dist/test/domain_transaction.test.d.ts +2 -0
- package/dist/test/domain_transaction.test.d.ts.map +1 -0
- package/dist/test/domain_transaction.test.js +351 -0
- package/dist/test/domain_transaction.test.js.map +1 -0
- package/dist/test/file_channel.test.d.ts +2 -0
- package/dist/test/file_channel.test.d.ts.map +1 -0
- package/dist/test/file_channel.test.js +179 -0
- package/dist/test/file_channel.test.js.map +1 -0
- package/dist/test/gateway.test.d.ts +2 -0
- package/dist/test/gateway.test.d.ts.map +1 -0
- package/dist/test/gateway.test.js +548 -0
- package/dist/test/gateway.test.js.map +1 -0
- package/dist/test/governance_profile.test.d.ts +2 -0
- package/dist/test/governance_profile.test.d.ts.map +1 -0
- package/dist/test/governance_profile.test.js +214 -0
- package/dist/test/governance_profile.test.js.map +1 -0
- package/dist/test/governance_schema.test.d.ts +2 -0
- package/dist/test/governance_schema.test.d.ts.map +1 -0
- package/dist/test/governance_schema.test.js +200 -0
- package/dist/test/governance_schema.test.js.map +1 -0
- package/dist/test/host_integration.test.d.ts +2 -0
- package/dist/test/host_integration.test.d.ts.map +1 -0
- package/dist/test/host_integration.test.js +104 -0
- package/dist/test/host_integration.test.js.map +1 -0
- package/dist/test/host_journal_persistence.test.d.ts +2 -0
- package/dist/test/host_journal_persistence.test.d.ts.map +1 -0
- package/dist/test/host_journal_persistence.test.js +367 -0
- package/dist/test/host_journal_persistence.test.js.map +1 -0
- package/dist/test/impact_domain.test.d.ts +2 -0
- package/dist/test/impact_domain.test.d.ts.map +1 -0
- package/dist/test/impact_domain.test.js +48 -0
- package/dist/test/impact_domain.test.js.map +1 -0
- package/dist/test/injected_clock.test.d.ts +13 -0
- package/dist/test/injected_clock.test.d.ts.map +1 -0
- package/dist/test/injected_clock.test.js +200 -0
- package/dist/test/injected_clock.test.js.map +1 -0
- package/dist/test/isolation_domain.test.d.ts +2 -0
- package/dist/test/isolation_domain.test.d.ts.map +1 -0
- package/dist/test/isolation_domain.test.js +302 -0
- package/dist/test/isolation_domain.test.js.map +1 -0
- package/dist/test/journal_wal.test.d.ts +2 -0
- package/dist/test/journal_wal.test.d.ts.map +1 -0
- package/dist/test/journal_wal.test.js +622 -0
- package/dist/test/journal_wal.test.js.map +1 -0
- package/dist/test/mcp_adapter.test.d.ts +2 -0
- package/dist/test/mcp_adapter.test.d.ts.map +1 -0
- package/dist/test/mcp_adapter.test.js +440 -0
- package/dist/test/mcp_adapter.test.js.map +1 -0
- package/dist/test/openai_compat_channel.test.d.ts +2 -0
- package/dist/test/openai_compat_channel.test.d.ts.map +1 -0
- package/dist/test/openai_compat_channel.test.js +325 -0
- package/dist/test/openai_compat_channel.test.js.map +1 -0
- package/dist/test/openapi_adapter.test.d.ts +2 -0
- package/dist/test/openapi_adapter.test.d.ts.map +1 -0
- package/dist/test/openapi_adapter.test.js +270 -0
- package/dist/test/openapi_adapter.test.js.map +1 -0
- package/dist/test/orbit_cli.test.d.ts +2 -0
- package/dist/test/orbit_cli.test.d.ts.map +1 -0
- package/dist/test/orbit_cli.test.js +137 -0
- package/dist/test/orbit_cli.test.js.map +1 -0
- package/dist/test/pact_verifier.test.d.ts +2 -0
- package/dist/test/pact_verifier.test.d.ts.map +1 -0
- package/dist/test/pact_verifier.test.js +41 -0
- package/dist/test/pact_verifier.test.js.map +1 -0
- package/dist/test/pae_adapter.test.d.ts +2 -0
- package/dist/test/pae_adapter.test.d.ts.map +1 -0
- package/dist/test/pae_adapter.test.js +469 -0
- package/dist/test/pae_adapter.test.js.map +1 -0
- package/dist/test/plugin_sandbox_guard.test.d.ts +2 -0
- package/dist/test/plugin_sandbox_guard.test.d.ts.map +1 -0
- package/dist/test/plugin_sandbox_guard.test.js +57 -0
- package/dist/test/plugin_sandbox_guard.test.js.map +1 -0
- package/dist/test/rate_limit_behavior.test.d.ts +2 -0
- package/dist/test/rate_limit_behavior.test.d.ts.map +1 -0
- package/dist/test/rate_limit_behavior.test.js +85 -0
- package/dist/test/rate_limit_behavior.test.js.map +1 -0
- package/dist/test/replay_compat.test.d.ts +2 -0
- package/dist/test/replay_compat.test.d.ts.map +1 -0
- package/dist/test/replay_compat.test.js +1122 -0
- package/dist/test/replay_compat.test.js.map +1 -0
- package/dist/test/replay_engine.test.d.ts +2 -0
- package/dist/test/replay_engine.test.d.ts.map +1 -0
- package/dist/test/replay_engine.test.js +85 -0
- package/dist/test/replay_engine.test.js.map +1 -0
- package/dist/test/shell_channel.test.d.ts +2 -0
- package/dist/test/shell_channel.test.d.ts.map +1 -0
- package/dist/test/shell_channel.test.js +173 -0
- package/dist/test/shell_channel.test.js.map +1 -0
- package/dist/test/teardown_isolation.test.d.ts +10 -0
- package/dist/test/teardown_isolation.test.d.ts.map +1 -0
- package/dist/test/teardown_isolation.test.js +279 -0
- package/dist/test/teardown_isolation.test.js.map +1 -0
- package/dist/test/token_budget.test.d.ts +2 -0
- package/dist/test/token_budget.test.d.ts.map +1 -0
- package/dist/test/token_budget.test.js +113 -0
- package/dist/test/token_budget.test.js.map +1 -0
- package/dist/test/trace_journal.test.d.ts +2 -0
- package/dist/test/trace_journal.test.d.ts.map +1 -0
- package/dist/test/trace_journal.test.js +43 -0
- package/dist/test/trace_journal.test.js.map +1 -0
- package/dist/test/trace_journal_persistence.test.d.ts +2 -0
- package/dist/test/trace_journal_persistence.test.d.ts.map +1 -0
- package/dist/test/trace_journal_persistence.test.js +175 -0
- package/dist/test/trace_journal_persistence.test.js.map +1 -0
- package/dist/test/trace_persistence.test.d.ts +2 -0
- package/dist/test/trace_persistence.test.d.ts.map +1 -0
- package/dist/test/trace_persistence.test.js +216 -0
- package/dist/test/trace_persistence.test.js.map +1 -0
- package/dist/test/trip_protector.test.d.ts +2 -0
- package/dist/test/trip_protector.test.d.ts.map +1 -0
- package/dist/test/trip_protector.test.js +42 -0
- package/dist/test/trip_protector.test.js.map +1 -0
- package/examples/README.md +34 -0
- package/examples/cli-record-replay.mjs +74 -0
- package/examples/custom-channel.mjs +102 -0
- package/examples/js-pae-plugin.mjs +128 -0
- package/examples/mcp-adapter.mjs +102 -0
- package/package.json +60 -0
- package/src/core/orbitRuntimeHost.ts +853 -0
- 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.
|