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
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const node_test_1 = require("node:test");
|
|
40
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
41
|
+
const node_fs_1 = require("node:fs");
|
|
42
|
+
const os = __importStar(require("node:os"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const index_1 = require("../src/index");
|
|
45
|
+
async function tempDir(prefix) {
|
|
46
|
+
return node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
47
|
+
}
|
|
48
|
+
/** Structural guard used by the generic-substrate tests. */
|
|
49
|
+
function isPair(value) {
|
|
50
|
+
if (typeof value !== "object" || value === null)
|
|
51
|
+
return false;
|
|
52
|
+
const v = value;
|
|
53
|
+
return typeof v.k === "string" && typeof v.n === "number";
|
|
54
|
+
}
|
|
55
|
+
function seedRecordJournal(journal) {
|
|
56
|
+
journal.append({
|
|
57
|
+
channelKind: index_1.ChannelKind.LLM_ACCESS,
|
|
58
|
+
funcName: "chatRound",
|
|
59
|
+
inputDigest: (0, index_1.digestInputs)("hello"),
|
|
60
|
+
outputSnapshot: "hello-back",
|
|
61
|
+
durationMs: 12
|
|
62
|
+
});
|
|
63
|
+
journal.append({
|
|
64
|
+
channelKind: index_1.ChannelKind.MEM_KV_STORE,
|
|
65
|
+
funcName: "readEntry",
|
|
66
|
+
inputDigest: (0, index_1.digestInputs)("k"),
|
|
67
|
+
outputSnapshot: "v",
|
|
68
|
+
durationMs: 1
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// --------------------------------------------------------------- WAL substrate
|
|
72
|
+
(0, node_test_1.test)("wal: append then recover roundtrips every line in order", async () => {
|
|
73
|
+
const dir = await tempDir("orbit-wal-");
|
|
74
|
+
const file = path.join(dir, "log.jsonl");
|
|
75
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
76
|
+
await (0, index_1.walAppend)(file, { k: "b", n: 2 });
|
|
77
|
+
await (0, index_1.walAppend)(file, { k: "c", n: 3 });
|
|
78
|
+
const recovered = await (0, index_1.walRecover)(file, isPair);
|
|
79
|
+
strict_1.default.deepEqual(recovered, [
|
|
80
|
+
{ k: "a", n: 1 },
|
|
81
|
+
{ k: "b", n: 2 },
|
|
82
|
+
{ k: "c", n: 3 }
|
|
83
|
+
]);
|
|
84
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
85
|
+
});
|
|
86
|
+
(0, node_test_1.test)("wal: a missing file recovers as empty (first boot is not an error)", async () => {
|
|
87
|
+
const dir = await tempDir("orbit-wal-");
|
|
88
|
+
const missing = path.join(dir, "never-written.jsonl");
|
|
89
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(missing, isPair), []);
|
|
90
|
+
strict_1.default.deepEqual((0, index_1.walRecoverSync)(missing, isPair), []);
|
|
91
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
92
|
+
});
|
|
93
|
+
(0, node_test_1.test)("wal: append creates parent directories", async () => {
|
|
94
|
+
const dir = await tempDir("orbit-wal-");
|
|
95
|
+
const file = path.join(dir, "nested", "deep", "log.jsonl");
|
|
96
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
97
|
+
strict_1.default.deepEqual(await node_fs_1.promises.readdir(path.dirname(file)), ["log.jsonl"]);
|
|
98
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
99
|
+
});
|
|
100
|
+
(0, node_test_1.test)("wal: a truncated trailing line is dropped, not fatal (crash safety)", async () => {
|
|
101
|
+
const dir = await tempDir("orbit-wal-");
|
|
102
|
+
const file = path.join(dir, "log.jsonl");
|
|
103
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
104
|
+
await (0, index_1.walAppend)(file, { k: "b", n: 2 });
|
|
105
|
+
// Simulate a crash mid-append: a partial final line with no newline.
|
|
106
|
+
await node_fs_1.promises.appendFile(file, '{"k":"c","n":', "utf8");
|
|
107
|
+
const recovered = await (0, index_1.walRecover)(file, isPair);
|
|
108
|
+
strict_1.default.deepEqual(recovered, [
|
|
109
|
+
{ k: "a", n: 1 },
|
|
110
|
+
{ k: "b", n: 2 }
|
|
111
|
+
]);
|
|
112
|
+
strict_1.default.deepEqual((0, index_1.walRecoverSync)(file, isPair), recovered);
|
|
113
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
114
|
+
});
|
|
115
|
+
(0, node_test_1.test)("wal: a structurally incomplete trailing line is also dropped", async () => {
|
|
116
|
+
const dir = await tempDir("orbit-wal-");
|
|
117
|
+
const file = path.join(dir, "log.jsonl");
|
|
118
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
119
|
+
// Parses as JSON, but fails the structural guard — a half-written entry.
|
|
120
|
+
await node_fs_1.promises.appendFile(file, '{"k":"b"}\n', "utf8");
|
|
121
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(file, isPair), [{ k: "a", n: 1 }]);
|
|
122
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
123
|
+
});
|
|
124
|
+
(0, node_test_1.test)("wal: a corrupt interior line is a genuine fault, rejected with its line number", async () => {
|
|
125
|
+
const dir = await tempDir("orbit-wal-");
|
|
126
|
+
const file = path.join(dir, "log.jsonl");
|
|
127
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
128
|
+
await node_fs_1.promises.appendFile(file, "not-json\n", "utf8");
|
|
129
|
+
await (0, index_1.walAppend)(file, { k: "c", n: 3 });
|
|
130
|
+
await strict_1.default.rejects((0, index_1.walRecover)(file, isPair), (err) => {
|
|
131
|
+
strict_1.default.ok(err instanceof index_1.WalFileInvalidError);
|
|
132
|
+
strict_1.default.equal(err.lineNo, 2);
|
|
133
|
+
strict_1.default.match(err.message, /not valid JSON/);
|
|
134
|
+
return true;
|
|
135
|
+
});
|
|
136
|
+
strict_1.default.throws(() => (0, index_1.walRecoverSync)(file, isPair), index_1.WalFileInvalidError);
|
|
137
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
138
|
+
});
|
|
139
|
+
(0, node_test_1.test)("wal: an invalid interior entry is rejected by structural validation", async () => {
|
|
140
|
+
const dir = await tempDir("orbit-wal-");
|
|
141
|
+
const file = path.join(dir, "log.jsonl");
|
|
142
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
143
|
+
await node_fs_1.promises.appendFile(file, '{"k":"b"}\n', "utf8"); // missing n
|
|
144
|
+
await (0, index_1.walAppend)(file, { k: "c", n: 3 });
|
|
145
|
+
await strict_1.default.rejects((0, index_1.walRecover)(file, isPair), (err) => {
|
|
146
|
+
strict_1.default.ok(err instanceof index_1.WalFileInvalidError);
|
|
147
|
+
strict_1.default.equal(err.lineNo, 2);
|
|
148
|
+
strict_1.default.match(err.message, /structural validation/);
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
152
|
+
});
|
|
153
|
+
(0, node_test_1.test)("wal: reset truncates the log so a new window starts clean", async () => {
|
|
154
|
+
const dir = await tempDir("orbit-wal-");
|
|
155
|
+
const file = path.join(dir, "log.jsonl");
|
|
156
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
157
|
+
await (0, index_1.walReset)(file);
|
|
158
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(file, isPair), []);
|
|
159
|
+
await (0, index_1.walAppend)(file, { k: "z", n: 9 });
|
|
160
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(file, isPair), [{ k: "z", n: 9 }]);
|
|
161
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
162
|
+
});
|
|
163
|
+
// ------------------------------------------------ PersistedRecordJournal
|
|
164
|
+
(0, node_test_1.test)("persisted record journal: appends are mirrored to the WAL and replay verbatim", async () => {
|
|
165
|
+
const dir = await tempDir("orbit-precj-");
|
|
166
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
167
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
168
|
+
seedRecordJournal(journal);
|
|
169
|
+
await journal.flush();
|
|
170
|
+
const recovered = await index_1.PersistedRecordJournal.recover(file);
|
|
171
|
+
strict_1.default.equal(recovered.size(), 2);
|
|
172
|
+
// Byte-identical recovery: entryUid / orderIndex / payloads all preserved.
|
|
173
|
+
strict_1.default.deepEqual(recovered.snapshot(), journal.snapshot());
|
|
174
|
+
const engine = new index_1.ReplayEngine(recovered);
|
|
175
|
+
strict_1.default.equal(engine.replayCall(index_1.ChannelKind.LLM_ACCESS, "chatRound", (0, index_1.digestInputs)("hello"), 0), "hello-back");
|
|
176
|
+
strict_1.default.equal(engine.replayCall(index_1.ChannelKind.MEM_KV_STORE, "readEntry", (0, index_1.digestInputs)("k"), 1), "v");
|
|
177
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
178
|
+
});
|
|
179
|
+
(0, node_test_1.test)("persisted record journal: writes are serialised so lines never interleave", async () => {
|
|
180
|
+
const dir = await tempDir("orbit-precj-");
|
|
181
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
182
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
183
|
+
for (let i = 0; i < 40; i += 1) {
|
|
184
|
+
journal.append({
|
|
185
|
+
channelKind: index_1.ChannelKind.MEM_KV_STORE,
|
|
186
|
+
funcName: "readEntry",
|
|
187
|
+
inputDigest: (0, index_1.digestInputs)(`k${i}`),
|
|
188
|
+
outputSnapshot: `v${i}`,
|
|
189
|
+
durationMs: i
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
await journal.flush();
|
|
193
|
+
const lines = (await node_fs_1.promises.readFile(file, "utf8")).split("\n").filter((l) => l.trim() !== "");
|
|
194
|
+
strict_1.default.equal(lines.length, 40);
|
|
195
|
+
const recovered = await index_1.PersistedRecordJournal.recover(file);
|
|
196
|
+
strict_1.default.equal(recovered.size(), 40);
|
|
197
|
+
recovered.snapshot().forEach((rec, i) => {
|
|
198
|
+
strict_1.default.equal(rec.orderIndex, i);
|
|
199
|
+
strict_1.default.equal(rec.outputSnapshot, `v${i}`);
|
|
200
|
+
});
|
|
201
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
202
|
+
});
|
|
203
|
+
(0, node_test_1.test)("persisted record journal: recovered window continues orderIndex without a gap", async () => {
|
|
204
|
+
const dir = await tempDir("orbit-precj-");
|
|
205
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
206
|
+
const first = new index_1.PersistedRecordJournal(file);
|
|
207
|
+
seedRecordJournal(first);
|
|
208
|
+
await first.flush();
|
|
209
|
+
const resumed = await index_1.PersistedRecordJournal.recover(file);
|
|
210
|
+
const next = resumed.append({
|
|
211
|
+
channelKind: index_1.ChannelKind.LLM_ACCESS,
|
|
212
|
+
funcName: "chatRound",
|
|
213
|
+
inputDigest: (0, index_1.digestInputs)("again"),
|
|
214
|
+
outputSnapshot: "again-back",
|
|
215
|
+
durationMs: 5
|
|
216
|
+
});
|
|
217
|
+
strict_1.default.equal(next.orderIndex, 2); // continues, does not restart at 0
|
|
218
|
+
await resumed.flush();
|
|
219
|
+
const reloaded = await index_1.PersistedRecordJournal.recover(file);
|
|
220
|
+
strict_1.default.equal(reloaded.size(), 3);
|
|
221
|
+
strict_1.default.deepEqual(reloaded.snapshot().map((r) => r.orderIndex), [0, 1, 2]);
|
|
222
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
223
|
+
});
|
|
224
|
+
(0, node_test_1.test)("persisted record journal: truncate opens a fresh window over the same path", async () => {
|
|
225
|
+
const dir = await tempDir("orbit-precj-");
|
|
226
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
227
|
+
const first = new index_1.PersistedRecordJournal(file);
|
|
228
|
+
seedRecordJournal(first);
|
|
229
|
+
await first.flush();
|
|
230
|
+
const fresh = new index_1.PersistedRecordJournal(file, { truncate: true });
|
|
231
|
+
fresh.append({
|
|
232
|
+
channelKind: index_1.ChannelKind.MEM_KV_STORE,
|
|
233
|
+
funcName: "writeEntry",
|
|
234
|
+
inputDigest: (0, index_1.digestInputs)("k2"),
|
|
235
|
+
outputSnapshot: true,
|
|
236
|
+
durationMs: 1
|
|
237
|
+
});
|
|
238
|
+
await fresh.flush();
|
|
239
|
+
const reloaded = await index_1.PersistedRecordJournal.recover(file);
|
|
240
|
+
strict_1.default.equal(reloaded.size(), 1);
|
|
241
|
+
strict_1.default.equal(reloaded.get(0).funcName, "writeEntry");
|
|
242
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
243
|
+
});
|
|
244
|
+
(0, node_test_1.test)("persisted record journal: no path degrades to pure in-memory (flush is a no-op)", async () => {
|
|
245
|
+
const journal = new index_1.PersistedRecordJournal();
|
|
246
|
+
seedRecordJournal(journal);
|
|
247
|
+
await journal.flush();
|
|
248
|
+
strict_1.default.equal(journal.size(), 2);
|
|
249
|
+
});
|
|
250
|
+
(0, node_test_1.test)("persisted record journal: sync recovery matches async recovery", async () => {
|
|
251
|
+
const dir = await tempDir("orbit-precj-");
|
|
252
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
253
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
254
|
+
seedRecordJournal(journal);
|
|
255
|
+
await journal.flush();
|
|
256
|
+
const sync = index_1.PersistedRecordJournal.recoverSync(file);
|
|
257
|
+
const async_ = await index_1.PersistedRecordJournal.recover(file);
|
|
258
|
+
strict_1.default.deepEqual(sync.snapshot(), async_.snapshot());
|
|
259
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
260
|
+
});
|
|
261
|
+
// ------------------------------------------------- PersistedTraceJournal
|
|
262
|
+
(0, node_test_1.test)("persisted trace journal: audit entries survive a reload byte-identically", async () => {
|
|
263
|
+
const dir = await tempDir("orbit-ptj-");
|
|
264
|
+
const file = path.join(dir, "trace.wal.jsonl");
|
|
265
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
266
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: { stage: "channels" } });
|
|
267
|
+
journal.append({ entryClass: "guard.trip", traceMarkId: "t2", pluginUnitId: "p1", factPayload: { reason: "quota" } });
|
|
268
|
+
await journal.flush();
|
|
269
|
+
const recovered = await index_1.PersistedTraceJournal.recover(file);
|
|
270
|
+
strict_1.default.deepEqual(recovered.snapshot(), journal.snapshot());
|
|
271
|
+
strict_1.default.equal(recovered.byPluginUnit("p1").length, 1);
|
|
272
|
+
strict_1.default.equal(recovered.byEntryClass("boot").length, 1);
|
|
273
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
274
|
+
});
|
|
275
|
+
(0, node_test_1.test)("persisted trace journal: load() replays the WAL into an existing instance", async () => {
|
|
276
|
+
const dir = await tempDir("orbit-ptj-");
|
|
277
|
+
const file = path.join(dir, "trace.wal.jsonl");
|
|
278
|
+
const writer = new index_1.PersistedTraceJournal(file);
|
|
279
|
+
writer.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
280
|
+
await writer.flush();
|
|
281
|
+
const reader = new index_1.PersistedTraceJournal(file);
|
|
282
|
+
strict_1.default.equal(reader.entries().length, 0);
|
|
283
|
+
await reader.load();
|
|
284
|
+
strict_1.default.equal(reader.entries().length, 1);
|
|
285
|
+
strict_1.default.equal(reader.entries()[0].entryClass, "boot");
|
|
286
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
287
|
+
});
|
|
288
|
+
(0, node_test_1.test)("persisted trace journal: a truncated tail is dropped on recovery", async () => {
|
|
289
|
+
const dir = await tempDir("orbit-ptj-");
|
|
290
|
+
const file = path.join(dir, "trace.wal.jsonl");
|
|
291
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
292
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
293
|
+
await journal.flush();
|
|
294
|
+
await node_fs_1.promises.appendFile(file, '{"entryClass":"half', "utf8");
|
|
295
|
+
const recovered = await index_1.PersistedTraceJournal.recover(file);
|
|
296
|
+
strict_1.default.equal(recovered.entries().length, 1);
|
|
297
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
298
|
+
});
|
|
299
|
+
(0, node_test_1.test)("persisted trace journal: no path behaves exactly like the base journal", async () => {
|
|
300
|
+
const journal = new index_1.PersistedTraceJournal();
|
|
301
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
302
|
+
await journal.load();
|
|
303
|
+
await journal.flush();
|
|
304
|
+
strict_1.default.equal(journal.entries().length, 1);
|
|
305
|
+
});
|
|
306
|
+
(0, node_test_1.test)("base journals expose no-op durability hooks (uniform call sites)", async () => {
|
|
307
|
+
const trace = new index_1.TraceJournal();
|
|
308
|
+
const record = new index_1.RecordJournal();
|
|
309
|
+
await trace.load();
|
|
310
|
+
await trace.flush();
|
|
311
|
+
await record.flush();
|
|
312
|
+
strict_1.default.equal(trace.entries().length, 0);
|
|
313
|
+
strict_1.default.equal(record.size(), 0);
|
|
314
|
+
});
|
|
315
|
+
// ------------------------------------------------------- compaction / retention
|
|
316
|
+
(0, node_test_1.test)("wal compact: rewrites the log to exactly the given entries, in order", async () => {
|
|
317
|
+
const dir = await tempDir("orbit-walc-");
|
|
318
|
+
const file = path.join(dir, "log.jsonl");
|
|
319
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
320
|
+
await (0, index_1.walAppend)(file, { k: "b", n: 2 });
|
|
321
|
+
await (0, index_1.walAppend)(file, { k: "c", n: 3 });
|
|
322
|
+
const written = await (0, index_1.walCompact)(file, [
|
|
323
|
+
{ k: "b", n: 2 },
|
|
324
|
+
{ k: "c", n: 3 }
|
|
325
|
+
]);
|
|
326
|
+
strict_1.default.equal(written, 2);
|
|
327
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(file, isPair), [
|
|
328
|
+
{ k: "b", n: 2 },
|
|
329
|
+
{ k: "c", n: 3 }
|
|
330
|
+
]);
|
|
331
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
332
|
+
});
|
|
333
|
+
(0, node_test_1.test)("wal compact: an empty entry list yields an empty (not deleted) log", async () => {
|
|
334
|
+
const dir = await tempDir("orbit-walc-");
|
|
335
|
+
const file = path.join(dir, "log.jsonl");
|
|
336
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
337
|
+
strict_1.default.equal(await (0, index_1.walCompact)(file, []), 0);
|
|
338
|
+
strict_1.default.equal(await node_fs_1.promises.readFile(file, "utf8"), "");
|
|
339
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(file, isPair), []);
|
|
340
|
+
// The log stays usable: a later append starts a clean generation.
|
|
341
|
+
await (0, index_1.walAppend)(file, { k: "z", n: 9 });
|
|
342
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(file, isPair), [{ k: "z", n: 9 }]);
|
|
343
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
344
|
+
});
|
|
345
|
+
(0, node_test_1.test)("wal compact: physically heals a crash-truncated tail", async () => {
|
|
346
|
+
const dir = await tempDir("orbit-walc-");
|
|
347
|
+
const file = path.join(dir, "log.jsonl");
|
|
348
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
349
|
+
await (0, index_1.walAppend)(file, { k: "b", n: 2 });
|
|
350
|
+
await node_fs_1.promises.appendFile(file, '{"k":"c","n":', "utf8");
|
|
351
|
+
// Recovery tolerates the partial line, but it is still on disk...
|
|
352
|
+
const surviving = await (0, index_1.walRecover)(file, isPair);
|
|
353
|
+
strict_1.default.ok((await node_fs_1.promises.readFile(file, "utf8")).includes('{"k":"c","n":'));
|
|
354
|
+
// ...until a compaction rewrites the file from the surviving prefix.
|
|
355
|
+
await (0, index_1.walCompact)(file, surviving);
|
|
356
|
+
const healed = await node_fs_1.promises.readFile(file, "utf8");
|
|
357
|
+
strict_1.default.ok(!healed.includes('{"k":"c","n":'));
|
|
358
|
+
strict_1.default.equal(healed.split("\n").filter((l) => l.trim() !== "").length, 2);
|
|
359
|
+
strict_1.default.deepEqual(await (0, index_1.walRecover)(file, isPair), surviving);
|
|
360
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
361
|
+
});
|
|
362
|
+
(0, node_test_1.test)("wal compact: leaves no temporary file behind and creates parent directories", async () => {
|
|
363
|
+
const dir = await tempDir("orbit-walc-");
|
|
364
|
+
const file = path.join(dir, "nested", "log.jsonl");
|
|
365
|
+
await (0, index_1.walCompact)(file, [{ k: "a", n: 1 }]);
|
|
366
|
+
strict_1.default.deepEqual(await node_fs_1.promises.readdir(path.dirname(file)), ["log.jsonl"]);
|
|
367
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
368
|
+
});
|
|
369
|
+
(0, node_test_1.test)("wal line count: a mismatch against recovery is what flags a log for healing", async () => {
|
|
370
|
+
const dir = await tempDir("orbit-walc-");
|
|
371
|
+
const file = path.join(dir, "log.jsonl");
|
|
372
|
+
strict_1.default.equal(await (0, index_1.walLineCount)(file), 0, "a missing log counts as empty");
|
|
373
|
+
await (0, index_1.walAppend)(file, { k: "a", n: 1 });
|
|
374
|
+
await (0, index_1.walAppend)(file, { k: "b", n: 2 });
|
|
375
|
+
strict_1.default.equal(await (0, index_1.walLineCount)(file), 2);
|
|
376
|
+
strict_1.default.equal((await (0, index_1.walRecover)(file, isPair)).length, 2, "healthy: counts agree");
|
|
377
|
+
await node_fs_1.promises.appendFile(file, '{"k":"c","n":', "utf8");
|
|
378
|
+
strict_1.default.equal(await (0, index_1.walLineCount)(file), 3);
|
|
379
|
+
strict_1.default.equal((await (0, index_1.walRecover)(file, isPair)).length, 2, "damaged: counts disagree");
|
|
380
|
+
await (0, index_1.walCompact)(file, await (0, index_1.walRecover)(file, isPair));
|
|
381
|
+
strict_1.default.equal(await (0, index_1.walLineCount)(file), 2, "healed: counts agree again");
|
|
382
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
383
|
+
});
|
|
384
|
+
(0, node_test_1.test)("persisted journals: healIfNeeded rewrites only a damaged log", async () => {
|
|
385
|
+
const dir = await tempDir("orbit-heal-");
|
|
386
|
+
const recordFile = path.join(dir, "record.wal.jsonl");
|
|
387
|
+
const traceFile = path.join(dir, "trace.wal.jsonl");
|
|
388
|
+
const writer = new index_1.PersistedRecordJournal(recordFile);
|
|
389
|
+
seedRecordJournal(writer);
|
|
390
|
+
await writer.flush();
|
|
391
|
+
// Healthy log: no rewrite, and the bytes are left exactly as they were.
|
|
392
|
+
const healthy = await index_1.PersistedRecordJournal.recover(recordFile);
|
|
393
|
+
const before = await node_fs_1.promises.readFile(recordFile, "utf8");
|
|
394
|
+
strict_1.default.equal(await healthy.healIfNeeded(), false);
|
|
395
|
+
strict_1.default.equal(await node_fs_1.promises.readFile(recordFile, "utf8"), before);
|
|
396
|
+
// Damaged log: rewritten from the surviving prefix.
|
|
397
|
+
await node_fs_1.promises.appendFile(recordFile, '{"entryUid":"half', "utf8");
|
|
398
|
+
const damaged = await index_1.PersistedRecordJournal.recover(recordFile);
|
|
399
|
+
strict_1.default.equal(await damaged.healIfNeeded(), true);
|
|
400
|
+
strict_1.default.equal(await node_fs_1.promises.readFile(recordFile, "utf8"), before);
|
|
401
|
+
// Same contract on the trace side, driven through load().
|
|
402
|
+
const trace = new index_1.PersistedTraceJournal(traceFile);
|
|
403
|
+
trace.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
404
|
+
await trace.flush();
|
|
405
|
+
const traceBefore = await node_fs_1.promises.readFile(traceFile, "utf8");
|
|
406
|
+
await node_fs_1.promises.appendFile(traceFile, '{"entryUid":"half', "utf8");
|
|
407
|
+
const reader = new index_1.PersistedTraceJournal(traceFile);
|
|
408
|
+
await reader.load(); // load() heals as part of recovery
|
|
409
|
+
strict_1.default.equal(await node_fs_1.promises.readFile(traceFile, "utf8"), traceBefore);
|
|
410
|
+
strict_1.default.equal(await reader.healIfNeeded(reader.entries().length), false);
|
|
411
|
+
// No path: healing is a no-op rather than an error.
|
|
412
|
+
strict_1.default.equal(await new index_1.PersistedRecordJournal().healIfNeeded(), false);
|
|
413
|
+
strict_1.default.equal(await new index_1.PersistedTraceJournal().healIfNeeded(0), false);
|
|
414
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
415
|
+
});
|
|
416
|
+
(0, node_test_1.test)("persisted record journal: compact heals a truncated tail without losing calls", async () => {
|
|
417
|
+
const dir = await tempDir("orbit-precj-");
|
|
418
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
419
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
420
|
+
seedRecordJournal(journal);
|
|
421
|
+
await journal.flush();
|
|
422
|
+
await node_fs_1.promises.appendFile(file, '{"entryUid":"half', "utf8");
|
|
423
|
+
const resumed = await index_1.PersistedRecordJournal.recover(file);
|
|
424
|
+
strict_1.default.equal(resumed.size(), 2);
|
|
425
|
+
strict_1.default.equal(await resumed.compact(), 2);
|
|
426
|
+
const raw = await node_fs_1.promises.readFile(file, "utf8");
|
|
427
|
+
strict_1.default.ok(!raw.includes('{"entryUid":"half'));
|
|
428
|
+
const reloaded = await index_1.PersistedRecordJournal.recover(file);
|
|
429
|
+
strict_1.default.deepEqual(reloaded.snapshot(), resumed.snapshot());
|
|
430
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
431
|
+
});
|
|
432
|
+
(0, node_test_1.test)("persisted record journal: compact is ordered after pending appends", async () => {
|
|
433
|
+
const dir = await tempDir("orbit-precj-");
|
|
434
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
435
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
436
|
+
seedRecordJournal(journal);
|
|
437
|
+
// Deliberately do not flush: compaction must queue behind the pending writes.
|
|
438
|
+
strict_1.default.equal(await journal.compact(), 2);
|
|
439
|
+
await journal.flush();
|
|
440
|
+
const reloaded = await index_1.PersistedRecordJournal.recover(file);
|
|
441
|
+
strict_1.default.deepEqual(reloaded.snapshot(), journal.snapshot());
|
|
442
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
443
|
+
});
|
|
444
|
+
(0, node_test_1.test)("persisted record journal: compact is a no-op without a path", async () => {
|
|
445
|
+
const journal = new index_1.PersistedRecordJournal();
|
|
446
|
+
seedRecordJournal(journal);
|
|
447
|
+
strict_1.default.equal(await journal.compact(), 0);
|
|
448
|
+
strict_1.default.equal(journal.size(), 2);
|
|
449
|
+
});
|
|
450
|
+
(0, node_test_1.test)("persisted trace journal: retainLast bounds the durable audit log", async () => {
|
|
451
|
+
const dir = await tempDir("orbit-ptj-");
|
|
452
|
+
const file = path.join(dir, "trace.wal.jsonl");
|
|
453
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
454
|
+
for (let i = 0; i < 10; i += 1) {
|
|
455
|
+
journal.append({ entryClass: "tick", traceMarkId: `t${i}`, factPayload: { i } });
|
|
456
|
+
}
|
|
457
|
+
await journal.flush();
|
|
458
|
+
strict_1.default.equal((await index_1.PersistedTraceJournal.recover(file)).entries().length, 10);
|
|
459
|
+
strict_1.default.equal(await journal.retainLast(3), 3);
|
|
460
|
+
strict_1.default.deepEqual(journal.entries().map((e) => e.traceMarkId), ["t7", "t8", "t9"]);
|
|
461
|
+
const reloaded = await index_1.PersistedTraceJournal.recover(file);
|
|
462
|
+
strict_1.default.deepEqual(reloaded.snapshot(), journal.snapshot());
|
|
463
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
464
|
+
});
|
|
465
|
+
(0, node_test_1.test)("persisted trace journal: retainLast(0) clears memory and disk together", async () => {
|
|
466
|
+
const dir = await tempDir("orbit-ptj-");
|
|
467
|
+
const file = path.join(dir, "trace.wal.jsonl");
|
|
468
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
469
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
470
|
+
await journal.flush();
|
|
471
|
+
strict_1.default.equal(await journal.retainLast(0), 0);
|
|
472
|
+
strict_1.default.equal(journal.entries().length, 0);
|
|
473
|
+
strict_1.default.equal(await node_fs_1.promises.readFile(file, "utf8"), "");
|
|
474
|
+
// Still writable afterwards — retention does not retire the journal.
|
|
475
|
+
journal.append({ entryClass: "boot", traceMarkId: "t2", factPayload: {} });
|
|
476
|
+
await journal.flush();
|
|
477
|
+
strict_1.default.equal((await index_1.PersistedTraceJournal.recover(file)).entries().length, 1);
|
|
478
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
479
|
+
});
|
|
480
|
+
(0, node_test_1.test)("persisted trace journal: retainLast above the current size keeps everything", async () => {
|
|
481
|
+
const dir = await tempDir("orbit-ptj-");
|
|
482
|
+
const file = path.join(dir, "trace.wal.jsonl");
|
|
483
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
484
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
485
|
+
journal.append({ entryClass: "boot", traceMarkId: "t2", factPayload: {} });
|
|
486
|
+
await journal.flush();
|
|
487
|
+
strict_1.default.equal(await journal.retainLast(50), 2);
|
|
488
|
+
strict_1.default.deepEqual((await index_1.PersistedTraceJournal.recover(file)).snapshot(), journal.snapshot());
|
|
489
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
490
|
+
});
|
|
491
|
+
(0, node_test_1.test)("persisted trace journal: retainLast rejects a non-integer or negative bound", async () => {
|
|
492
|
+
const journal = new index_1.PersistedTraceJournal();
|
|
493
|
+
await strict_1.default.rejects(() => journal.retainLast(-1), RangeError);
|
|
494
|
+
await strict_1.default.rejects(() => journal.retainLast(1.5), RangeError);
|
|
495
|
+
await strict_1.default.rejects(() => journal.retainLast(Number.NaN), RangeError);
|
|
496
|
+
});
|
|
497
|
+
(0, node_test_1.test)("persisted trace journal: compact heals a truncated tail and no-ops without a path", async () => {
|
|
498
|
+
const dir = await tempDir("orbit-ptj-");
|
|
499
|
+
const file = path.join(dir, "trace.wal.jsonl");
|
|
500
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
501
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
502
|
+
await journal.flush();
|
|
503
|
+
await node_fs_1.promises.appendFile(file, '{"entryUid":"half', "utf8");
|
|
504
|
+
const resumed = await index_1.PersistedTraceJournal.recover(file);
|
|
505
|
+
strict_1.default.equal(await resumed.compact(), 1);
|
|
506
|
+
strict_1.default.ok(!(await node_fs_1.promises.readFile(file, "utf8")).includes('{"entryUid":"half'));
|
|
507
|
+
strict_1.default.equal(await new index_1.PersistedTraceJournal().compact(), 0);
|
|
508
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
509
|
+
});
|
|
510
|
+
// --------------------------------------------------- failed writes must surface
|
|
511
|
+
/**
|
|
512
|
+
* A WAL path that cannot be written: its parent is a regular file, so the
|
|
513
|
+
* `mkdir` inside every WAL write fails (EEXIST here, ENOTDIR on POSIX). Either
|
|
514
|
+
* way the write is refused — the exact errno is the platform's business.
|
|
515
|
+
*/
|
|
516
|
+
async function unwritableWalPath(prefix) {
|
|
517
|
+
const dir = await tempDir(prefix);
|
|
518
|
+
const blocker = path.join(dir, "blocker");
|
|
519
|
+
await node_fs_1.promises.writeFile(blocker, "not a directory", "utf8");
|
|
520
|
+
return { dir, file: path.join(blocker, "log.jsonl") };
|
|
521
|
+
}
|
|
522
|
+
(0, node_test_1.test)("persisted record journal: a lost append makes flush() reject", async () => {
|
|
523
|
+
const { dir, file } = await unwritableWalPath("orbit-walfail-");
|
|
524
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
525
|
+
// The append itself must not throw: a failed disk write cannot abort a call
|
|
526
|
+
// that is already in flight.
|
|
527
|
+
seedRecordJournal(journal);
|
|
528
|
+
strict_1.default.equal(journal.size(), 2, "the in-memory journal is still the source of truth");
|
|
529
|
+
await strict_1.default.rejects(journal.flush(), (err) => {
|
|
530
|
+
strict_1.default.ok(err instanceof Error);
|
|
531
|
+
return true;
|
|
532
|
+
});
|
|
533
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
534
|
+
});
|
|
535
|
+
(0, node_test_1.test)("persisted trace journal: a lost append makes flush() reject", async () => {
|
|
536
|
+
const { dir, file } = await unwritableWalPath("orbit-walfail-");
|
|
537
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
538
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
539
|
+
await strict_1.default.rejects(journal.flush(), (err) => {
|
|
540
|
+
strict_1.default.ok(err instanceof Error);
|
|
541
|
+
return true;
|
|
542
|
+
});
|
|
543
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
544
|
+
});
|
|
545
|
+
(0, node_test_1.test)("persisted journals: flush() stays rejected so a retry cannot report a false success", async () => {
|
|
546
|
+
const { dir, file } = await unwritableWalPath("orbit-walfail-");
|
|
547
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
548
|
+
seedRecordJournal(journal);
|
|
549
|
+
await strict_1.default.rejects(journal.flush());
|
|
550
|
+
// Sticky on purpose: once a write has been lost, no later flush may claim the
|
|
551
|
+
// record is durable. Otherwise `shutdownHost` could report a clean shutdown
|
|
552
|
+
// for a window it silently dropped.
|
|
553
|
+
await strict_1.default.rejects(journal.flush());
|
|
554
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
555
|
+
});
|
|
556
|
+
(0, node_test_1.test)("persisted record journal: a failed truncate surfaces through flush(), not the constructor", async () => {
|
|
557
|
+
const { dir, file } = await unwritableWalPath("orbit-walfail-");
|
|
558
|
+
// Constructing must not throw — a bad path is a durability problem to report
|
|
559
|
+
// at flush time, not a reason the host cannot boot.
|
|
560
|
+
const journal = new index_1.PersistedRecordJournal(file, { truncate: true });
|
|
561
|
+
await strict_1.default.rejects(journal.flush());
|
|
562
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
563
|
+
});
|
|
564
|
+
(0, node_test_1.test)("persisted record journal: a failed compaction is not swallowed before flush()", async () => {
|
|
565
|
+
const { dir, file } = await unwritableWalPath("orbit-walfail-");
|
|
566
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
567
|
+
seedRecordJournal(journal);
|
|
568
|
+
// compact() rejects for its own caller...
|
|
569
|
+
await strict_1.default.rejects(journal.compact());
|
|
570
|
+
// ...and the failure is still parked for flush(), which is what a shutdown
|
|
571
|
+
// path actually awaits.
|
|
572
|
+
await strict_1.default.rejects(journal.flush());
|
|
573
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
574
|
+
});
|
|
575
|
+
(0, node_test_1.test)("persisted trace journal: a failed compaction is not swallowed before flush()", async () => {
|
|
576
|
+
const { dir, file } = await unwritableWalPath("orbit-walfail-");
|
|
577
|
+
const journal = new index_1.PersistedTraceJournal(file);
|
|
578
|
+
journal.append({ entryClass: "boot", traceMarkId: "t1", factPayload: {} });
|
|
579
|
+
await strict_1.default.rejects(journal.compact());
|
|
580
|
+
await strict_1.default.rejects(journal.flush());
|
|
581
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
582
|
+
});
|
|
583
|
+
(0, node_test_1.test)("persisted record journal: healIfNeeded propagates a failed compaction", async () => {
|
|
584
|
+
const { dir, file } = await unwritableWalPath("orbit-walfail-");
|
|
585
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
586
|
+
seedRecordJournal(journal);
|
|
587
|
+
// The line count (0) disagrees with size() (2), so healing is attempted and
|
|
588
|
+
// the failed rewrite must not be quietly ignored.
|
|
589
|
+
await strict_1.default.rejects(journal.healIfNeeded());
|
|
590
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
591
|
+
});
|
|
592
|
+
(0, node_test_1.test)("persisted journals: a writable path still flushes cleanly (no false positives)", async () => {
|
|
593
|
+
const dir = await tempDir("orbit-walok-");
|
|
594
|
+
const file = path.join(dir, "record.wal.jsonl");
|
|
595
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
596
|
+
seedRecordJournal(journal);
|
|
597
|
+
await strict_1.default.doesNotReject(journal.flush());
|
|
598
|
+
strict_1.default.equal((await index_1.PersistedRecordJournal.recover(file)).size(), 2);
|
|
599
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
600
|
+
});
|
|
601
|
+
(0, node_test_1.test)("persisted journals: appends after a failure still queue (the chain never breaks)", async () => {
|
|
602
|
+
const dir = await tempDir("orbit-walfail-");
|
|
603
|
+
const blocker = path.join(dir, "blocker");
|
|
604
|
+
await node_fs_1.promises.writeFile(blocker, "not a directory", "utf8");
|
|
605
|
+
const file = path.join(blocker, "log.jsonl");
|
|
606
|
+
const journal = new index_1.PersistedRecordJournal(file);
|
|
607
|
+
seedRecordJournal(journal);
|
|
608
|
+
await strict_1.default.rejects(journal.flush());
|
|
609
|
+
// A journal that stopped accepting work after one bad write would break the
|
|
610
|
+
// live run as well as the durable one.
|
|
611
|
+
journal.append({
|
|
612
|
+
channelKind: index_1.ChannelKind.MEM_KV_STORE,
|
|
613
|
+
funcName: "writeEntry",
|
|
614
|
+
inputDigest: (0, index_1.digestInputs)("k3"),
|
|
615
|
+
outputSnapshot: true,
|
|
616
|
+
durationMs: 1
|
|
617
|
+
});
|
|
618
|
+
strict_1.default.equal(journal.size(), 3);
|
|
619
|
+
await strict_1.default.rejects(journal.flush());
|
|
620
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
621
|
+
});
|
|
622
|
+
//# sourceMappingURL=journal_wal.test.js.map
|