@orion-agents/orion-code 0.2.2 → 0.3.1
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 +82 -0
- package/README.md +89 -14
- package/README.zh-CN.md +77 -14
- package/bin/orion +15 -4
- package/dist/cli.js +51 -172
- package/dist/cli.js.map +1 -1
- package/dist/commands/model-command-handlers.d.ts +2 -2
- package/dist/commands/model-command-handlers.d.ts.map +1 -1
- package/dist/commands/model-command-handlers.js +113 -32
- package/dist/commands/model-command-handlers.js.map +1 -1
- package/dist/commands/session-command-handlers.d.ts.map +1 -1
- package/dist/commands/session-command-handlers.js +106 -15
- package/dist/commands/session-command-handlers.js.map +1 -1
- package/dist/commands/types.d.ts +13 -3
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/core/tool-artifacts.d.ts +2 -0
- package/dist/core/tool-artifacts.d.ts.map +1 -1
- package/dist/core/tool-artifacts.js +27 -1
- package/dist/core/tool-artifacts.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/agent-loop.d.ts.map +1 -1
- package/dist/runtime/agent-loop.js +3 -3
- package/dist/runtime/agent-loop.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts +32 -1
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +241 -27
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/agent-runtime-protocol.d.ts +4 -0
- package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-protocol.js.map +1 -1
- package/dist/runtime/agent-runtime-runner.d.ts +2 -1
- package/dist/runtime/agent-runtime-runner.d.ts.map +1 -1
- package/dist/runtime/durable-tool-receipt-reader.d.ts +32 -0
- package/dist/runtime/durable-tool-receipt-reader.d.ts.map +1 -0
- package/dist/runtime/durable-tool-receipt-reader.js +130 -0
- package/dist/runtime/durable-tool-receipt-reader.js.map +1 -0
- package/dist/runtime/legacy-thread-materializer.d.ts +31 -0
- package/dist/runtime/legacy-thread-materializer.d.ts.map +1 -1
- package/dist/runtime/legacy-thread-materializer.js +71 -17
- package/dist/runtime/legacy-thread-materializer.js.map +1 -1
- package/dist/runtime/orion-runtime-v1.d.ts +2 -0
- package/dist/runtime/orion-runtime-v1.d.ts.map +1 -1
- package/dist/runtime/orion-runtime-v1.js +7 -1
- package/dist/runtime/orion-runtime-v1.js.map +1 -1
- package/dist/runtime/orion-session-runner.d.ts +7 -2
- package/dist/runtime/orion-session-runner.d.ts.map +1 -1
- package/dist/runtime/orion-session-runner.js +16 -6
- package/dist/runtime/orion-session-runner.js.map +1 -1
- package/dist/runtime/product-bootstrap.d.ts +22 -0
- package/dist/runtime/product-bootstrap.d.ts.map +1 -0
- package/dist/runtime/product-bootstrap.js +374 -0
- package/dist/runtime/product-bootstrap.js.map +1 -0
- package/dist/runtime/product-orion-runtime.d.ts +2 -1
- package/dist/runtime/product-orion-runtime.d.ts.map +1 -1
- package/dist/runtime/product-orion-runtime.js +83 -13
- package/dist/runtime/product-orion-runtime.js.map +1 -1
- package/dist/runtime/protocol/runtime-protocol-v1.d.ts +1 -1
- package/dist/runtime/protocol/runtime-protocol-v1.js +1 -1
- package/dist/runtime/protocol/runtime-protocol-v1.js.map +1 -1
- package/dist/runtime/release-receipts.d.ts +66 -3
- package/dist/runtime/release-receipts.d.ts.map +1 -1
- package/dist/runtime/release-receipts.js +340 -7
- package/dist/runtime/release-receipts.js.map +1 -1
- package/dist/runtime/step-snapshot.d.ts.map +1 -1
- package/dist/runtime/step-snapshot.js +22 -2
- package/dist/runtime/step-snapshot.js.map +1 -1
- package/dist/runtime/subagents/runtime-integration.d.ts +1 -1
- package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
- package/dist/runtime/subagents/runtime-integration.js +5 -2
- package/dist/runtime/subagents/runtime-integration.js.map +1 -1
- package/dist/runtime/thread-event-store.d.ts +80 -0
- package/dist/runtime/thread-event-store.d.ts.map +1 -1
- package/dist/runtime/thread-event-store.js +456 -22
- package/dist/runtime/thread-event-store.js.map +1 -1
- package/dist/runtime/thread-projection.d.ts +10 -0
- package/dist/runtime/thread-projection.d.ts.map +1 -1
- package/dist/runtime/thread-projection.js +40 -0
- package/dist/runtime/thread-projection.js.map +1 -1
- package/dist/runtime/thread-runtime.d.ts +3 -1
- package/dist/runtime/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/thread-runtime.js +6 -0
- package/dist/runtime/thread-runtime.js.map +1 -1
- package/dist/runtime/thread-session-index.d.ts +84 -0
- package/dist/runtime/thread-session-index.d.ts.map +1 -0
- package/dist/runtime/thread-session-index.js +503 -0
- package/dist/runtime/thread-session-index.js.map +1 -0
- package/dist/runtime/thread-session-view.d.ts +70 -9
- package/dist/runtime/thread-session-view.d.ts.map +1 -1
- package/dist/runtime/thread-session-view.js +200 -82
- package/dist/runtime/thread-session-view.js.map +1 -1
- package/dist/runtime/thread-ui-adapter.d.ts +2 -0
- package/dist/runtime/thread-ui-adapter.d.ts.map +1 -1
- package/dist/runtime/thread-ui-adapter.js +86 -4
- package/dist/runtime/thread-ui-adapter.js.map +1 -1
- package/dist/runtime/tool-detail-repository.d.ts.map +1 -1
- package/dist/runtime/tool-detail-repository.js +23 -19
- package/dist/runtime/tool-detail-repository.js.map +1 -1
- package/dist/runtime/tool-receipt-validator.d.ts +21 -0
- package/dist/runtime/tool-receipt-validator.d.ts.map +1 -0
- package/dist/runtime/tool-receipt-validator.js +157 -0
- package/dist/runtime/tool-receipt-validator.js.map +1 -0
- package/dist/runtime/ui-events.d.ts +35 -0
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js +1 -0
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/runtime/ui-view-model.d.ts +2 -0
- package/dist/runtime/ui-view-model.d.ts.map +1 -1
- package/dist/runtime/ui-view-model.js +7 -3
- package/dist/runtime/ui-view-model.js.map +1 -1
- package/dist/services/global-config.d.ts +25 -0
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js +373 -5
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/redaction.d.ts +4 -0
- package/dist/services/redaction.d.ts.map +1 -1
- package/dist/services/redaction.js +63 -0
- package/dist/services/redaction.js.map +1 -1
- package/dist/services/session-index.d.ts +16 -0
- package/dist/services/session-index.d.ts.map +1 -1
- package/dist/services/session-index.js +45 -36
- package/dist/services/session-index.js.map +1 -1
- package/dist/services/session-storage.d.ts +59 -0
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +306 -107
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/settings-coordinator.d.ts +174 -0
- package/dist/services/settings-coordinator.d.ts.map +1 -0
- package/dist/services/settings-coordinator.js +605 -0
- package/dist/services/settings-coordinator.js.map +1 -0
- package/dist/services/settings-document-repository.d.ts +138 -0
- package/dist/services/settings-document-repository.d.ts.map +1 -0
- package/dist/services/settings-document-repository.js +551 -0
- package/dist/services/settings-document-repository.js.map +1 -0
- package/dist/services/workspace-registry.d.ts +42 -0
- package/dist/services/workspace-registry.d.ts.map +1 -0
- package/dist/services/workspace-registry.js +222 -0
- package/dist/services/workspace-registry.js.map +1 -0
- package/dist/terminal-ui/launch.d.ts.map +1 -1
- package/dist/terminal-ui/launch.js +5 -0
- package/dist/terminal-ui/launch.js.map +1 -1
- package/dist/tui-ui/state.d.ts.map +1 -1
- package/dist/tui-ui/state.js +5 -0
- package/dist/tui-ui/state.js.map +1 -1
- package/dist/web/errors.d.ts +6 -0
- package/dist/web/errors.d.ts.map +1 -0
- package/dist/web/errors.js +13 -0
- package/dist/web/errors.js.map +1 -0
- package/dist/web/event-hub.d.ts +43 -0
- package/dist/web/event-hub.d.ts.map +1 -0
- package/dist/web/event-hub.js +323 -0
- package/dist/web/event-hub.js.map +1 -0
- package/dist/web/file-read-service.d.ts +55 -0
- package/dist/web/file-read-service.d.ts.map +1 -0
- package/dist/web/file-read-service.js +399 -0
- package/dist/web/file-read-service.js.map +1 -0
- package/dist/web/git-read-model-service.d.ts +77 -0
- package/dist/web/git-read-model-service.d.ts.map +1 -0
- package/dist/web/git-read-model-service.js +631 -0
- package/dist/web/git-read-model-service.js.map +1 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +28 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/launch.d.ts +9 -0
- package/dist/web/launch.d.ts.map +1 -0
- package/dist/web/launch.js +64 -0
- package/dist/web/launch.js.map +1 -0
- package/dist/web/protocol.d.ts +408 -0
- package/dist/web/protocol.d.ts.map +1 -0
- package/dist/web/protocol.js +369 -0
- package/dist/web/protocol.js.map +1 -0
- package/dist/web/review-service.d.ts +38 -0
- package/dist/web/review-service.d.ts.map +1 -0
- package/dist/web/review-service.js +74 -0
- package/dist/web/review-service.js.map +1 -0
- package/dist/web/server.d.ts +20 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +744 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/terminal-manager.d.ts +155 -0
- package/dist/web/terminal-manager.d.ts.map +1 -0
- package/dist/web/terminal-manager.js +728 -0
- package/dist/web/terminal-manager.js.map +1 -0
- package/dist/web/terminal-server.d.ts +8 -0
- package/dist/web/terminal-server.d.ts.map +1 -0
- package/dist/web/terminal-server.js +275 -0
- package/dist/web/terminal-server.js.map +1 -0
- package/dist/web/workbench-controller.d.ts +99 -0
- package/dist/web/workbench-controller.d.ts.map +1 -0
- package/dist/web/workbench-controller.js +1267 -0
- package/dist/web/workbench-controller.js.map +1 -0
- package/dist/web-client/assets/DiffViewer-DpKoD07C.js +5 -0
- package/dist/web-client/assets/FilesPanel-BkoLmgjR.js +2 -0
- package/dist/web-client/assets/GitPanel-D20StcdG.js +1 -0
- package/dist/web-client/assets/ReviewPanel-3MVWbMQV.js +1 -0
- package/dist/web-client/assets/TerminalPanel-BLQ592Fb.js +21 -0
- package/dist/web-client/assets/TerminalPanel-DLuoa74B.css +1 -0
- package/dist/web-client/assets/index-BkIDl_xk.js +16 -0
- package/dist/web-client/assets/index-DXMpnWE8.css +1 -0
- package/dist/web-client/index.html +16 -0
- package/docs/architecture/v0.3.0-web-api.yaml +1340 -0
- package/docs/architecture/v0.3.1-web-api.yaml +2290 -0
- package/docs/migration/v0.2.2-to-v0.3.0-settings.md +114 -0
- package/docs/migration/v0.2.2-to-v0.3.0.md +55 -0
- package/docs/migration/v0.3.0-to-v0.3.1.md +88 -0
- package/docs/orion.example.json +9 -2
- package/docs/plan/v0.3.0-node-runtime-compatibility-plan.md +57 -0
- package/docs/plan/v0.3.0-settings-integration-plan.md +740 -0
- package/docs/plan/v0.3.0-web-workbench-plan.md +376 -0
- package/docs/plan/v0.3.1-web-workbench-professional-shell-plan.md +848 -0
- package/docs/readme.md +22 -1
- package/docs/test/v0.3.1-web-workbench-e2e-plan.md +140 -0
- package/npm-shrinkwrap.json +1229 -45
- package/package.json +47 -11
|
@@ -8,6 +8,7 @@ const file_lock_1 = require("../services/file-lock");
|
|
|
8
8
|
const canonical_1 = require("./protocol/canonical");
|
|
9
9
|
const runtime_protocol_v1_1 = require("./protocol/runtime-protocol-v1");
|
|
10
10
|
const thread_projection_1 = require("./thread-projection");
|
|
11
|
+
const thread_session_index_1 = require("./thread-session-index");
|
|
11
12
|
class ThreadEventStoreError extends Error {
|
|
12
13
|
constructor(code, message, offset) {
|
|
13
14
|
super(message);
|
|
@@ -36,6 +37,7 @@ class ThreadEventStore {
|
|
|
36
37
|
}
|
|
37
38
|
this.logPath = (0, path_1.join)(rootDir, `${threadId}.events.v1.jsonl`);
|
|
38
39
|
this.projectionPath = (0, path_1.join)(rootDir, `${threadId}.projection.v1.json`);
|
|
40
|
+
this.headPath = (0, path_1.join)(rootDir, `${threadId}.head.v1.json`);
|
|
39
41
|
this.compactStatePath = (0, path_1.join)(rootDir, `${threadId}.compact-state.v1.json`);
|
|
40
42
|
this.compactCheckpointsDir = (0, path_1.join)(rootDir, `${threadId}.compact-checkpoints.v1`);
|
|
41
43
|
this.maxLogBytes = positiveInteger(options.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES, 'maxLogBytes');
|
|
@@ -44,6 +46,7 @@ class ThreadEventStore {
|
|
|
44
46
|
this.clock = options.clock ?? Date.now;
|
|
45
47
|
this.idFactory = options.idFactory ?? runtime_protocol_v1_1.createRuntimeId;
|
|
46
48
|
this.onBoundary = options.onBoundary;
|
|
49
|
+
this.onLogScan = options.onLogScan;
|
|
47
50
|
}
|
|
48
51
|
appendDurable(input) {
|
|
49
52
|
return this.appendDurableBatch([input]).events[0];
|
|
@@ -54,13 +57,13 @@ class ThreadEventStore {
|
|
|
54
57
|
}
|
|
55
58
|
const commit = this.withLogLock(() => {
|
|
56
59
|
ensurePrivateDirectory(this.rootDir);
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
(0, fs_1.truncateSync)(this.logPath,
|
|
60
|
-
let seq =
|
|
61
|
-
let previousHash =
|
|
60
|
+
const head = this.loadVerifiedHead();
|
|
61
|
+
if (head.discardedTailBytes > 0)
|
|
62
|
+
(0, fs_1.truncateSync)(this.logPath, head.safeByteLength);
|
|
63
|
+
let seq = head.projection.cursor;
|
|
64
|
+
let previousHash = head.lastRecordHash;
|
|
62
65
|
const records = [];
|
|
63
|
-
const eventIds = new Set(
|
|
66
|
+
const eventIds = new Set(head.eventIdSet);
|
|
64
67
|
for (const input of inputs) {
|
|
65
68
|
seq += 1;
|
|
66
69
|
const eventId = this.idFactory();
|
|
@@ -85,10 +88,12 @@ class ThreadEventStore {
|
|
|
85
88
|
records.push({ version: 1, previousHash, hash, event });
|
|
86
89
|
previousHash = hash;
|
|
87
90
|
}
|
|
88
|
-
// Validate the
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
// Validate the new tail against the already verified projection before a
|
|
92
|
+
// byte becomes durable. Historical lifecycle facts were validated when
|
|
93
|
+
// cachedHead was established and are invalidated by any log fingerprint
|
|
94
|
+
// change, so append cost no longer grows with Session age.
|
|
95
|
+
const committedEvents = records.map(record => record.event);
|
|
96
|
+
const projection = (0, thread_projection_1.advanceThreadProjection)(head.projection, committedEvents);
|
|
92
97
|
this.boundary('before_log_write', seq);
|
|
93
98
|
appendRecordsAndFlush(this.logPath, records, () => this.boundary('after_log_write', seq));
|
|
94
99
|
// appendRecordsAndFlush fsyncs before returning; this boundary is the
|
|
@@ -99,7 +104,48 @@ class ThreadEventStore {
|
|
|
99
104
|
writeProjection(this.projectionPath, projection);
|
|
100
105
|
fsyncDirectory(this.rootDir);
|
|
101
106
|
this.boundary('after_projection_write', seq);
|
|
102
|
-
|
|
107
|
+
const nextLogIdentity = readFileIdentity(this.logPath);
|
|
108
|
+
const safeByteLength = nextLogIdentity.bytes;
|
|
109
|
+
const nextScan = head.scan
|
|
110
|
+
? {
|
|
111
|
+
records: [...head.scan.records, ...records],
|
|
112
|
+
events: [...head.scan.events, ...committedEvents],
|
|
113
|
+
safeByteLength,
|
|
114
|
+
discardedTailBytes: 0,
|
|
115
|
+
}
|
|
116
|
+
: undefined;
|
|
117
|
+
const nextHead = {
|
|
118
|
+
generation: head.generation + 1,
|
|
119
|
+
logFingerprint: nextLogIdentity.fingerprint,
|
|
120
|
+
log: storedLogIdentity(nextLogIdentity),
|
|
121
|
+
projectionFingerprint: fileFingerprint(this.projectionPath),
|
|
122
|
+
...(nextScan ? { scan: nextScan } : {}),
|
|
123
|
+
projection,
|
|
124
|
+
lastEventTimestamp: committedEvents.at(-1)?.timestamp ?? head.lastEventTimestamp,
|
|
125
|
+
lastRecordHash: previousHash,
|
|
126
|
+
safeByteLength,
|
|
127
|
+
discardedTailBytes: 0,
|
|
128
|
+
eventIds: [...eventIds],
|
|
129
|
+
eventIdSet: eventIds,
|
|
130
|
+
verifiedPrefixes: head.verifiedPrefixes,
|
|
131
|
+
};
|
|
132
|
+
this.cachedHead = nextHead;
|
|
133
|
+
this.tryPersistVerifiedHead(nextHead);
|
|
134
|
+
try {
|
|
135
|
+
(0, thread_session_index_1.advanceThreadSessionIndexV1)({
|
|
136
|
+
rootDir: this.rootDir,
|
|
137
|
+
threadId: this.threadId,
|
|
138
|
+
previousHead: sessionIndexHead(head),
|
|
139
|
+
nextHead: sessionIndexHead(nextHead),
|
|
140
|
+
projection,
|
|
141
|
+
committedEvents,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// The immutable transcript index is derived. A stale manifest forces
|
|
146
|
+
// a verified rebuild on the next snapshot and cannot veto the fact.
|
|
147
|
+
}
|
|
148
|
+
return deepFreeze({ events: committedEvents, projection });
|
|
103
149
|
});
|
|
104
150
|
this.notifyCommitted(commit.events);
|
|
105
151
|
return commit;
|
|
@@ -135,7 +181,7 @@ class ThreadEventStore {
|
|
|
135
181
|
throw new ThreadEventStoreError('ORION_THREAD_EVENT_LIMIT', `Replay limit ${safeLimit} exceeds ${this.maxReplayEvents}`);
|
|
136
182
|
}
|
|
137
183
|
return this.withLogLock(() => {
|
|
138
|
-
const scan =
|
|
184
|
+
const scan = requireVerifiedScan(this.loadVerifiedHead(true));
|
|
139
185
|
const lastSeq = scan.events.length;
|
|
140
186
|
if (cursor > lastSeq) {
|
|
141
187
|
throw new ThreadEventStoreError('ORION_THREAD_EVENT_CURSOR', `Replay cursor ${cursor} is ahead of durable cursor ${lastSeq}`);
|
|
@@ -150,24 +196,134 @@ class ThreadEventStore {
|
|
|
150
196
|
});
|
|
151
197
|
});
|
|
152
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Verify and seal an immutable imported prefix. The first call may scan the
|
|
201
|
+
* authoritative log; later processes reuse the prefix receipt while the
|
|
202
|
+
* exact log identity/projection checkpoint remains current. Internal append
|
|
203
|
+
* carries the proof forward because the hash-chained prefix cannot change.
|
|
204
|
+
*/
|
|
205
|
+
verifyDurablePrefix(cursor, eventDigest, projectionDigest) {
|
|
206
|
+
if (!Number.isSafeInteger(cursor) ||
|
|
207
|
+
cursor <= 0 ||
|
|
208
|
+
!isSha256(eventDigest) ||
|
|
209
|
+
!isSha256(projectionDigest)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return this.withLogLock(() => {
|
|
213
|
+
let head = this.loadVerifiedHead();
|
|
214
|
+
if (cursor > head.projection.cursor)
|
|
215
|
+
return false;
|
|
216
|
+
const expected = { cursor, eventDigest, projectionDigest };
|
|
217
|
+
if (head.verifiedPrefixes.some(prefix => sameVerifiedPrefix(prefix, expected)))
|
|
218
|
+
return true;
|
|
219
|
+
head = this.loadVerifiedHead(true);
|
|
220
|
+
const scan = requireVerifiedScan(head);
|
|
221
|
+
const events = scan.events.slice(0, cursor);
|
|
222
|
+
if (events.length !== cursor ||
|
|
223
|
+
(0, canonical_1.digestRuntimeValue)(events) !== eventDigest ||
|
|
224
|
+
(0, thread_projection_1.projectThreadEvents)(this.threadId, events).digest !== projectionDigest) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
const next = {
|
|
228
|
+
...head,
|
|
229
|
+
generation: head.generation + 1,
|
|
230
|
+
verifiedPrefixes: [...head.verifiedPrefixes, expected].sort((left, right) => left.cursor - right.cursor),
|
|
231
|
+
};
|
|
232
|
+
this.cachedHead = next;
|
|
233
|
+
this.tryPersistVerifiedHead(next);
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
153
237
|
getCursor() {
|
|
154
|
-
|
|
238
|
+
const cached = this.cachedHead;
|
|
239
|
+
if (cached && cached.logFingerprint === fileFingerprint(this.logPath)) {
|
|
240
|
+
return cached.projection.cursor;
|
|
241
|
+
}
|
|
242
|
+
return this.withLogLock(() => this.loadVerifiedHead().projection.cursor);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Read the compact persisted receipt without parsing the potentially large
|
|
246
|
+
* projection. Callers must fall back to the authoritative scan whenever the
|
|
247
|
+
* receipt is missing, stale, or lacks their required cutover proof.
|
|
248
|
+
*/
|
|
249
|
+
capturePersistedCheckpointHead() {
|
|
250
|
+
return this.withLogLock(() => {
|
|
251
|
+
const identity = readFileIdentity(this.logPath);
|
|
252
|
+
const cached = this.cachedHead;
|
|
253
|
+
if (cached?.logFingerprint === identity.fingerprint) {
|
|
254
|
+
return checkpointHead(cached);
|
|
255
|
+
}
|
|
256
|
+
const stored = readStoredThreadHead(this.headPath, this.threadId);
|
|
257
|
+
if (!stored ||
|
|
258
|
+
!sameStoredLogIdentity(stored.log, identity) ||
|
|
259
|
+
stored.safeByteLength + stored.discardedTailBytes !== identity.bytes) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
return deepFreeze({
|
|
263
|
+
cursor: stored.cursor,
|
|
264
|
+
projectionDigest: stored.projectionDigest,
|
|
265
|
+
lastEventTimestamp: stored.lastEventTimestamp,
|
|
266
|
+
lastRecordHash: stored.lastRecordHash,
|
|
267
|
+
log: { ...stored.log },
|
|
268
|
+
verifiedPrefixes: stored.verifiedPrefixes.map(prefix => ({ ...prefix })),
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/** Capture the catalog read model and its exact verified log identity atomically. */
|
|
273
|
+
captureReadModelHead() {
|
|
274
|
+
return this.withLogLock(() => {
|
|
275
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
276
|
+
const head = this.loadVerifiedHead();
|
|
277
|
+
const identity = readFileIdentity(this.logPath);
|
|
278
|
+
if (identity.fingerprint !== head.logFingerprint) {
|
|
279
|
+
this.cachedHead = undefined;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
return deepFreeze({
|
|
283
|
+
projection: head.projection,
|
|
284
|
+
lastEventTimestamp: head.lastEventTimestamp,
|
|
285
|
+
lastRecordHash: head.lastRecordHash,
|
|
286
|
+
log: {
|
|
287
|
+
bytes: identity.bytes,
|
|
288
|
+
device: identity.device,
|
|
289
|
+
inode: identity.inode,
|
|
290
|
+
mtimeNs: identity.mtimeNs,
|
|
291
|
+
ctimeNs: identity.ctimeNs,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
throw corrupt('Thread event log changed while capturing its read model', 0);
|
|
296
|
+
});
|
|
155
297
|
}
|
|
156
298
|
loadProjection() {
|
|
157
299
|
return this.withLogLock(() => {
|
|
158
300
|
ensurePrivateDirectory(this.rootDir);
|
|
159
|
-
const
|
|
160
|
-
const replayed =
|
|
301
|
+
const head = this.loadVerifiedHead();
|
|
302
|
+
const replayed = head.projection;
|
|
303
|
+
const projectionFingerprint = fileFingerprint(this.projectionPath);
|
|
304
|
+
if (head.projectionFingerprint === projectionFingerprint) {
|
|
305
|
+
this.tryPersistVerifiedHead(head);
|
|
306
|
+
return replayed;
|
|
307
|
+
}
|
|
161
308
|
const cached = readProjection(this.projectionPath);
|
|
162
309
|
if (cached &&
|
|
163
310
|
cached.threadId === this.threadId &&
|
|
164
311
|
cached.cursor === replayed.cursor &&
|
|
165
312
|
cached.digest === replayed.digest &&
|
|
166
313
|
(0, thread_projection_1.verifyThreadProjectionDigest)(cached)) {
|
|
167
|
-
|
|
314
|
+
const current = { ...head, projectionFingerprint };
|
|
315
|
+
this.cachedHead = current;
|
|
316
|
+
this.tryPersistVerifiedHead(current);
|
|
317
|
+
return replayed;
|
|
168
318
|
}
|
|
169
319
|
writeProjection(this.projectionPath, replayed);
|
|
170
320
|
fsyncDirectory(this.rootDir);
|
|
321
|
+
const current = {
|
|
322
|
+
...head,
|
|
323
|
+
projectionFingerprint: fileFingerprint(this.projectionPath),
|
|
324
|
+
};
|
|
325
|
+
this.cachedHead = current;
|
|
326
|
+
this.tryPersistVerifiedHead(current);
|
|
171
327
|
return replayed;
|
|
172
328
|
});
|
|
173
329
|
}
|
|
@@ -177,7 +333,7 @@ class ThreadEventStore {
|
|
|
177
333
|
*/
|
|
178
334
|
loadAuthoritativeModelHistory() {
|
|
179
335
|
return this.withLogLock(() => {
|
|
180
|
-
const scan =
|
|
336
|
+
const scan = requireVerifiedScan(this.loadVerifiedHead(true));
|
|
181
337
|
const state = readCompactState(this.compactStatePath, this.threadId);
|
|
182
338
|
if (state)
|
|
183
339
|
this.assertCompactCheckpoint(state);
|
|
@@ -194,8 +350,9 @@ class ThreadEventStore {
|
|
|
194
350
|
captureCompactSource(turnId) {
|
|
195
351
|
return this.withLogLock(() => {
|
|
196
352
|
ensurePrivateDirectory(this.rootDir);
|
|
197
|
-
const
|
|
198
|
-
const
|
|
353
|
+
const head = this.loadVerifiedHead(true);
|
|
354
|
+
const scan = requireVerifiedScan(head);
|
|
355
|
+
const { projection } = head;
|
|
199
356
|
assertActiveMaintenanceTurn(projection, turnId);
|
|
200
357
|
const state = readCompactState(this.compactStatePath, this.threadId);
|
|
201
358
|
if (state)
|
|
@@ -230,8 +387,9 @@ class ThreadEventStore {
|
|
|
230
387
|
ensurePrivateDirectory(this.compactCheckpointsDir);
|
|
231
388
|
validateCompactCommitInput(input, this.threadId);
|
|
232
389
|
input.onBoundary('before_cas_recheck');
|
|
233
|
-
const
|
|
234
|
-
const
|
|
390
|
+
const head = this.loadVerifiedHead(true);
|
|
391
|
+
const scan = requireVerifiedScan(head);
|
|
392
|
+
const { projection } = head;
|
|
235
393
|
const eventsAfterAnchor = scan.events.slice(input.expectedEventAnchor.cursor);
|
|
236
394
|
const onlyQueuedFollowUps = eventsAfterAnchor.length > 0 &&
|
|
237
395
|
eventsAfterAnchor.every(event => event.payload.type === 'turn.queued');
|
|
@@ -296,7 +454,7 @@ class ThreadEventStore {
|
|
|
296
454
|
}
|
|
297
455
|
listCompactEvents() {
|
|
298
456
|
return this.withLogLock(() => {
|
|
299
|
-
const scan =
|
|
457
|
+
const scan = requireVerifiedScan(this.loadVerifiedHead(true));
|
|
300
458
|
return deepFreeze(scan.events.filter((event) => event.payload.type === 'compact.started' ||
|
|
301
459
|
event.payload.type === 'compact.completed' ||
|
|
302
460
|
event.payload.type === 'compact.failed'));
|
|
@@ -347,6 +505,79 @@ class ThreadEventStore {
|
|
|
347
505
|
}
|
|
348
506
|
return inputs.length > 0 ? this.appendDurableBatch(inputs).projection : projection;
|
|
349
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Return the last verified log head. A fresh process may adopt the persisted
|
|
510
|
+
* receipt only when the exact JSONL identity and independently validated
|
|
511
|
+
* projection still match. Callers that need historical events explicitly
|
|
512
|
+
* request a scan; cursor/projection/append paths stay O(head + projection).
|
|
513
|
+
*/
|
|
514
|
+
loadVerifiedHead(requireScan = false) {
|
|
515
|
+
ensurePrivateDirectory(this.rootDir);
|
|
516
|
+
const logIdentity = readFileIdentity(this.logPath);
|
|
517
|
+
if (this.cachedHead?.logFingerprint === logIdentity.fingerprint &&
|
|
518
|
+
(!requireScan || this.cachedHead.scan)) {
|
|
519
|
+
return this.cachedHead;
|
|
520
|
+
}
|
|
521
|
+
const stored = readStoredThreadHead(this.headPath, this.threadId);
|
|
522
|
+
if (!requireScan && stored) {
|
|
523
|
+
const projection = readProjection(this.projectionPath);
|
|
524
|
+
const persisted = hydratePersistedThreadHead(stored, logIdentity, projection, fileFingerprint(this.projectionPath));
|
|
525
|
+
if (persisted) {
|
|
526
|
+
this.cachedHead = persisted;
|
|
527
|
+
return persisted;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const scan = scanThreadEventLog(this.logPath, this.threadId, this.maxLogBytes);
|
|
531
|
+
this.onLogScan?.({
|
|
532
|
+
threadId: this.threadId,
|
|
533
|
+
bytes: scan.safeByteLength + scan.discardedTailBytes,
|
|
534
|
+
events: scan.events.length,
|
|
535
|
+
});
|
|
536
|
+
const projection = (0, thread_projection_1.projectThreadEvents)(this.threadId, scan.events);
|
|
537
|
+
const eventIds = scan.events.map(event => event.eventId);
|
|
538
|
+
const projectionFingerprint = projectionFileMatches(this.projectionPath, projection)
|
|
539
|
+
? fileFingerprint(this.projectionPath)
|
|
540
|
+
: undefined;
|
|
541
|
+
const head = {
|
|
542
|
+
generation: (stored?.generation ?? 0) + 1,
|
|
543
|
+
logFingerprint: logIdentity.fingerprint,
|
|
544
|
+
log: storedLogIdentity(logIdentity),
|
|
545
|
+
...(projectionFingerprint ? { projectionFingerprint } : {}),
|
|
546
|
+
scan,
|
|
547
|
+
projection,
|
|
548
|
+
lastEventTimestamp: scan.events.at(-1)?.timestamp ?? 0,
|
|
549
|
+
lastRecordHash: scan.records.at(-1)?.hash ?? null,
|
|
550
|
+
safeByteLength: scan.safeByteLength,
|
|
551
|
+
discardedTailBytes: scan.discardedTailBytes,
|
|
552
|
+
eventIds,
|
|
553
|
+
eventIdSet: new Set(eventIds),
|
|
554
|
+
verifiedPrefixes: retainVerifiedPrefixes(stored?.verifiedPrefixes ?? [], scan, this.threadId),
|
|
555
|
+
};
|
|
556
|
+
this.cachedHead = head;
|
|
557
|
+
if (projectionFingerprint)
|
|
558
|
+
this.tryPersistVerifiedHead(head);
|
|
559
|
+
return head;
|
|
560
|
+
}
|
|
561
|
+
tryPersistVerifiedHead(head) {
|
|
562
|
+
try {
|
|
563
|
+
const logIdentity = readFileIdentity(this.logPath);
|
|
564
|
+
if (logIdentity.fingerprint !== head.logFingerprint)
|
|
565
|
+
return;
|
|
566
|
+
if (!projectionFileMatches(this.projectionPath, head.projection))
|
|
567
|
+
return;
|
|
568
|
+
const stored = createStoredThreadHead(head, logIdentity);
|
|
569
|
+
(0, atomic_write_1.atomicWriteFileSync)(this.headPath, `${(0, canonical_1.canonicalRuntimeJson)(stored)}\n`, {
|
|
570
|
+
mode: 0o600,
|
|
571
|
+
fsync: true,
|
|
572
|
+
});
|
|
573
|
+
fsyncDirectory(this.rootDir);
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
// The JSONL fact and projection are already durable. A missing/stale
|
|
577
|
+
// derived head merely makes the next process take the full verification
|
|
578
|
+
// path; it must never turn a committed fact into a reported failure.
|
|
579
|
+
}
|
|
580
|
+
}
|
|
350
581
|
withLogLock(operation) {
|
|
351
582
|
ensurePrivateDirectory((0, path_1.dirname)(this.logPath));
|
|
352
583
|
return (0, file_lock_1.withFileLockSync)(this.logPath, operation, { waitMs: this.lockWaitMs });
|
|
@@ -497,6 +728,209 @@ function readProjection(path) {
|
|
|
497
728
|
return null;
|
|
498
729
|
}
|
|
499
730
|
}
|
|
731
|
+
function projectionFileMatches(path, expected) {
|
|
732
|
+
const projection = readProjection(path);
|
|
733
|
+
return Boolean(projection &&
|
|
734
|
+
projection.threadId === expected.threadId &&
|
|
735
|
+
projection.cursor === expected.cursor &&
|
|
736
|
+
projection.digest === expected.digest &&
|
|
737
|
+
(0, thread_projection_1.verifyThreadProjectionDigest)(projection));
|
|
738
|
+
}
|
|
739
|
+
function readStoredThreadHead(path, threadId) {
|
|
740
|
+
if (!(0, fs_1.existsSync)(path))
|
|
741
|
+
return null;
|
|
742
|
+
try {
|
|
743
|
+
const value = JSON.parse((0, fs_1.readFileSync)(path, 'utf8'));
|
|
744
|
+
if (!isRecord(value))
|
|
745
|
+
return null;
|
|
746
|
+
const { digest: _digest, ...content } = value;
|
|
747
|
+
void _digest;
|
|
748
|
+
if (value.version !== 1 ||
|
|
749
|
+
value.threadId !== threadId ||
|
|
750
|
+
!Number.isSafeInteger(value.generation) ||
|
|
751
|
+
value.generation < 1 ||
|
|
752
|
+
!Number.isSafeInteger(value.cursor) ||
|
|
753
|
+
value.cursor < 0 ||
|
|
754
|
+
typeof value.projectionDigest !== 'string' ||
|
|
755
|
+
!Number.isSafeInteger(value.lastEventTimestamp) ||
|
|
756
|
+
value.lastEventTimestamp < 0 ||
|
|
757
|
+
(value.lastRecordHash !== null && !isSha256(value.lastRecordHash)) ||
|
|
758
|
+
!Number.isSafeInteger(value.safeByteLength) ||
|
|
759
|
+
value.safeByteLength < 0 ||
|
|
760
|
+
!Number.isSafeInteger(value.discardedTailBytes) ||
|
|
761
|
+
value.discardedTailBytes < 0 ||
|
|
762
|
+
!Array.isArray(value.eventIds) ||
|
|
763
|
+
!Array.isArray(value.verifiedPrefixes) ||
|
|
764
|
+
value.verifiedPrefixes.length > 32 ||
|
|
765
|
+
!isStoredLogIdentity(value.log) ||
|
|
766
|
+
typeof value.digest !== 'string' ||
|
|
767
|
+
(0, canonical_1.digestRuntimeValue)(content) !== value.digest) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
const eventIds = value.eventIds;
|
|
771
|
+
if (eventIds.length !== value.cursor ||
|
|
772
|
+
eventIds.some(eventId => typeof eventId !== 'string' || !(0, runtime_protocol_v1_1.isRuntimeId)(eventId)) ||
|
|
773
|
+
new Set(eventIds).size !== eventIds.length ||
|
|
774
|
+
(value.cursor === 0) !== (value.lastRecordHash === null) ||
|
|
775
|
+
(value.cursor === 0 && value.lastEventTimestamp !== 0)) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
const prefixes = value.verifiedPrefixes;
|
|
779
|
+
if (prefixes.some(prefix => !isStoredVerifiedPrefix(prefix, value.cursor)) ||
|
|
780
|
+
new Set(prefixes.map(prefix => prefix.cursor)).size !==
|
|
781
|
+
prefixes.length) {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
return deepFreeze(value);
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function hydratePersistedThreadHead(stored, logIdentity, projection, projectionFingerprint) {
|
|
791
|
+
if (!sameStoredLogIdentity(stored.log, logIdentity) ||
|
|
792
|
+
stored.safeByteLength + stored.discardedTailBytes !== logIdentity.bytes ||
|
|
793
|
+
!projection ||
|
|
794
|
+
projection.threadId !== stored.threadId ||
|
|
795
|
+
projection.cursor !== stored.cursor ||
|
|
796
|
+
projection.digest !== stored.projectionDigest ||
|
|
797
|
+
!(0, thread_projection_1.verifyThreadProjectionDigest)(projection)) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
const eventIds = [...stored.eventIds];
|
|
801
|
+
return {
|
|
802
|
+
generation: stored.generation,
|
|
803
|
+
logFingerprint: logIdentity.fingerprint,
|
|
804
|
+
log: storedLogIdentity(logIdentity),
|
|
805
|
+
projectionFingerprint,
|
|
806
|
+
projection,
|
|
807
|
+
lastEventTimestamp: stored.lastEventTimestamp,
|
|
808
|
+
lastRecordHash: stored.lastRecordHash,
|
|
809
|
+
safeByteLength: stored.safeByteLength,
|
|
810
|
+
discardedTailBytes: stored.discardedTailBytes,
|
|
811
|
+
eventIds,
|
|
812
|
+
eventIdSet: new Set(eventIds),
|
|
813
|
+
verifiedPrefixes: stored.verifiedPrefixes.map(prefix => ({ ...prefix })),
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function createStoredThreadHead(head, logIdentity) {
|
|
817
|
+
const content = {
|
|
818
|
+
version: 1,
|
|
819
|
+
generation: head.generation,
|
|
820
|
+
threadId: head.projection.threadId,
|
|
821
|
+
cursor: head.projection.cursor,
|
|
822
|
+
projectionDigest: head.projection.digest,
|
|
823
|
+
lastEventTimestamp: head.lastEventTimestamp,
|
|
824
|
+
lastRecordHash: head.lastRecordHash,
|
|
825
|
+
safeByteLength: head.safeByteLength,
|
|
826
|
+
discardedTailBytes: head.discardedTailBytes,
|
|
827
|
+
eventIds: [...head.eventIds],
|
|
828
|
+
verifiedPrefixes: head.verifiedPrefixes.map(prefix => ({ ...prefix })),
|
|
829
|
+
log: storedLogIdentity(logIdentity),
|
|
830
|
+
};
|
|
831
|
+
return deepFreeze({ ...content, digest: (0, canonical_1.digestRuntimeValue)(content) });
|
|
832
|
+
}
|
|
833
|
+
function retainVerifiedPrefixes(prefixes, scan, threadId) {
|
|
834
|
+
return prefixes.filter(prefix => {
|
|
835
|
+
if (prefix.cursor > scan.events.length)
|
|
836
|
+
return false;
|
|
837
|
+
const events = scan.events.slice(0, prefix.cursor);
|
|
838
|
+
return ((0, canonical_1.digestRuntimeValue)(events) === prefix.eventDigest &&
|
|
839
|
+
(0, thread_projection_1.projectThreadEvents)(threadId, events).digest === prefix.projectionDigest);
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
function requireVerifiedScan(head) {
|
|
843
|
+
if (!head.scan)
|
|
844
|
+
throw new Error('Thread event history was not loaded');
|
|
845
|
+
return head.scan;
|
|
846
|
+
}
|
|
847
|
+
function sessionIndexHead(head) {
|
|
848
|
+
return {
|
|
849
|
+
cursor: head.projection.cursor,
|
|
850
|
+
projectionDigest: head.projection.digest,
|
|
851
|
+
lastEventTimestamp: head.lastEventTimestamp,
|
|
852
|
+
lastRecordHash: head.lastRecordHash,
|
|
853
|
+
log: head.log,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function checkpointHead(head) {
|
|
857
|
+
return deepFreeze({
|
|
858
|
+
cursor: head.projection.cursor,
|
|
859
|
+
projectionDigest: head.projection.digest,
|
|
860
|
+
lastEventTimestamp: head.lastEventTimestamp,
|
|
861
|
+
lastRecordHash: head.lastRecordHash,
|
|
862
|
+
log: { ...head.log },
|
|
863
|
+
verifiedPrefixes: head.verifiedPrefixes.map(prefix => ({ ...prefix })),
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
function isStoredVerifiedPrefix(value, headCursor) {
|
|
867
|
+
return (isRecord(value) &&
|
|
868
|
+
Number.isSafeInteger(value.cursor) &&
|
|
869
|
+
value.cursor > 0 &&
|
|
870
|
+
value.cursor <= headCursor &&
|
|
871
|
+
isSha256(value.eventDigest) &&
|
|
872
|
+
isSha256(value.projectionDigest));
|
|
873
|
+
}
|
|
874
|
+
function sameVerifiedPrefix(left, right) {
|
|
875
|
+
return (left.cursor === right.cursor &&
|
|
876
|
+
left.eventDigest === right.eventDigest &&
|
|
877
|
+
left.projectionDigest === right.projectionDigest);
|
|
878
|
+
}
|
|
879
|
+
function isStoredLogIdentity(value) {
|
|
880
|
+
return (isRecord(value) &&
|
|
881
|
+
Number.isSafeInteger(value.bytes) &&
|
|
882
|
+
value.bytes >= 0 &&
|
|
883
|
+
['device', 'inode', 'mtimeNs', 'ctimeNs'].every(key => typeof value[key] === 'string' && /^\d+$/.test(value[key])));
|
|
884
|
+
}
|
|
885
|
+
function sameStoredLogIdentity(stored, current) {
|
|
886
|
+
return (stored.bytes === current.bytes &&
|
|
887
|
+
stored.device === current.device &&
|
|
888
|
+
stored.inode === current.inode &&
|
|
889
|
+
stored.mtimeNs === current.mtimeNs &&
|
|
890
|
+
stored.ctimeNs === current.ctimeNs);
|
|
891
|
+
}
|
|
892
|
+
function storedLogIdentity(identity) {
|
|
893
|
+
return {
|
|
894
|
+
bytes: identity.bytes,
|
|
895
|
+
device: identity.device,
|
|
896
|
+
inode: identity.inode,
|
|
897
|
+
mtimeNs: identity.mtimeNs,
|
|
898
|
+
ctimeNs: identity.ctimeNs,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function isSha256(value) {
|
|
902
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
903
|
+
}
|
|
904
|
+
function readFileIdentity(path) {
|
|
905
|
+
try {
|
|
906
|
+
const stats = (0, fs_1.statSync)(path, { bigint: true });
|
|
907
|
+
const device = stats.dev.toString();
|
|
908
|
+
const inode = stats.ino.toString();
|
|
909
|
+
const mtimeNs = stats.mtimeNs.toString();
|
|
910
|
+
const ctimeNs = stats.ctimeNs.toString();
|
|
911
|
+
return {
|
|
912
|
+
fingerprint: [device, inode, stats.size, mtimeNs, ctimeNs].join(':'),
|
|
913
|
+
bytes: Number(stats.size),
|
|
914
|
+
device,
|
|
915
|
+
inode,
|
|
916
|
+
mtimeNs,
|
|
917
|
+
ctimeNs,
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
catch {
|
|
921
|
+
return {
|
|
922
|
+
fingerprint: 'missing',
|
|
923
|
+
bytes: 0,
|
|
924
|
+
device: '0',
|
|
925
|
+
inode: '0',
|
|
926
|
+
mtimeNs: '0',
|
|
927
|
+
ctimeNs: '0',
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function fileFingerprint(path) {
|
|
932
|
+
return readFileIdentity(path).fingerprint;
|
|
933
|
+
}
|
|
500
934
|
function ensurePrivateDirectory(path) {
|
|
501
935
|
(0, fs_1.mkdirSync)(path, { recursive: true, mode: 0o700 });
|
|
502
936
|
}
|