@exaudeus/workrail 0.11.0 → 0.12.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/dist/config/feature-flags.js +1 -1
- package/dist/di/container.js +72 -0
- package/dist/di/tokens.d.ts +13 -0
- package/dist/di/tokens.js +13 -0
- package/dist/manifest.json +109 -93
- package/dist/mcp/error-mapper.d.ts +3 -8
- package/dist/mcp/error-mapper.js +41 -19
- package/dist/mcp/handlers/session.js +25 -11
- package/dist/mcp/handlers/v2-execution-helpers.d.ts +99 -0
- package/dist/mcp/handlers/v2-execution-helpers.js +249 -0
- package/dist/mcp/handlers/v2-execution.d.ts +4 -0
- package/dist/mcp/handlers/v2-execution.js +1061 -0
- package/dist/mcp/handlers/v2-workflow.js +7 -7
- package/dist/mcp/handlers/workflow.js +21 -12
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +4 -1
- package/dist/mcp/output-schemas.d.ts +411 -4
- package/dist/mcp/output-schemas.js +57 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +57 -31
- package/dist/mcp/tool-descriptions.js +17 -0
- package/dist/mcp/tools.js +12 -0
- package/dist/mcp/types/tool-description-types.d.ts +1 -1
- package/dist/mcp/types/tool-description-types.js +2 -0
- package/dist/mcp/types.d.ts +38 -3
- package/dist/mcp/types.js +32 -3
- package/dist/mcp/v2/tool-registry.js +16 -1
- package/dist/mcp/v2/tools.d.ts +45 -0
- package/dist/mcp/v2/tools.js +21 -1
- package/dist/mcp/validation/workflow-next-prevalidate.d.ts +2 -3
- package/dist/mcp/validation/workflow-next-prevalidate.js +38 -27
- package/dist/v2/durable-core/ids/index.d.ts +2 -0
- package/dist/v2/durable-core/ids/index.js +4 -0
- package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +100 -6
- package/dist/v2/durable-core/schemas/compiled-workflow/index.js +18 -3
- package/dist/v2/durable-core/schemas/session/events.d.ts +80 -50
- package/dist/v2/durable-core/schemas/session/events.js +27 -9
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +2 -2
- package/dist/v2/durable-core/tokens/index.d.ts +2 -0
- package/dist/v2/durable-core/tokens/index.js +4 -1
- package/dist/v2/durable-core/tokens/payloads.d.ts +4 -4
- package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +3 -3
- package/dist/v2/infra/local/pinned-workflow-store/index.js +1 -1
- package/dist/v2/infra/local/session-lock/index.js +1 -1
- package/dist/v2/infra/local/session-store/index.d.ts +0 -1
- package/dist/v2/infra/local/session-store/index.js +348 -280
- package/dist/v2/ports/pinned-workflow-store.port.d.ts +3 -3
- package/dist/v2/ports/session-event-log-store.port.d.ts +1 -1
- package/dist/v2/ports/session-lock.port.d.ts +1 -1
- package/dist/v2/read-only/v1-to-v2-shim.d.ts +6 -1
- package/dist/v2/read-only/v1-to-v2-shim.js +16 -4
- package/dist/v2/usecases/execution-session-gate.d.ts +3 -2
- package/dist/v2/usecases/execution-session-gate.js +98 -101
- package/package.json +2 -1
- package/workflows/coding-task-workflow-agentic.json +326 -69
- package/workflows/design-thinking-workflow-autonomous.agentic.json +1 -1
- package/workflows/design-thinking-workflow.json +1 -1
|
@@ -4,12 +4,6 @@ exports.LocalSessionEventLogStoreV2 = void 0;
|
|
|
4
4
|
const neverthrow_1 = require("neverthrow");
|
|
5
5
|
const jsonl_js_1 = require("../../../durable-core/canonical/jsonl.js");
|
|
6
6
|
const index_js_1 = require("../../../durable-core/schemas/session/index.js");
|
|
7
|
-
class StoreFailure extends Error {
|
|
8
|
-
constructor(storeError) {
|
|
9
|
-
super(storeError.message);
|
|
10
|
-
this.storeError = storeError;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
7
|
class LocalSessionEventLogStoreV2 {
|
|
14
8
|
constructor(dataDir, fs, sha256) {
|
|
15
9
|
this.dataDir = dataDir;
|
|
@@ -23,286 +17,356 @@ class LocalSessionEventLogStoreV2 {
|
|
|
23
17
|
message: 'WithHealthySessionLock used after gate callback ended (witness misuse-after-release)',
|
|
24
18
|
});
|
|
25
19
|
}
|
|
26
|
-
return
|
|
27
|
-
if (e instanceof StoreFailure)
|
|
28
|
-
return e.storeError;
|
|
29
|
-
return { code: 'SESSION_STORE_IO_ERROR', message: e instanceof Error ? e.message : String(e) };
|
|
30
|
-
});
|
|
20
|
+
return this.appendImpl(lock.sessionId, plan);
|
|
31
21
|
}
|
|
32
22
|
load(sessionId) {
|
|
33
|
-
return
|
|
34
|
-
if (e instanceof StoreFailure)
|
|
35
|
-
return e.storeError;
|
|
36
|
-
return { code: 'SESSION_STORE_IO_ERROR', message: e instanceof Error ? e.message : String(e) };
|
|
37
|
-
});
|
|
23
|
+
return this.loadImpl(sessionId);
|
|
38
24
|
}
|
|
39
25
|
loadValidatedPrefix(sessionId) {
|
|
40
|
-
return
|
|
41
|
-
if (e instanceof StoreFailure)
|
|
42
|
-
return e.storeError;
|
|
43
|
-
return { code: 'SESSION_STORE_IO_ERROR', message: e instanceof Error ? e.message : String(e) };
|
|
44
|
-
});
|
|
26
|
+
return this.loadValidatedPrefixImpl(sessionId);
|
|
45
27
|
}
|
|
46
|
-
|
|
28
|
+
appendImpl(sessionId, plan) {
|
|
47
29
|
const sessionDir = this.dataDir.sessionDir(sessionId);
|
|
48
30
|
const eventsDir = this.dataDir.sessionEventsDir(sessionId);
|
|
49
31
|
const manifestPath = this.dataDir.sessionManifestPath(sessionId);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
code: 'SESSION_STORE_INVARIANT_VIOLATION',
|
|
62
|
-
message: 'Partial dedupeKey collision detected (some events exist, some do not); this is an invariant violation',
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
const expectedFirstEventIndex = nextEventIndexFromManifest(manifest);
|
|
66
|
-
validateAppendPlanOrThrow(sessionId, plan, expectedFirstEventIndex);
|
|
67
|
-
const first = plan.events[0].eventIndex;
|
|
68
|
-
const last = plan.events[plan.events.length - 1].eventIndex;
|
|
69
|
-
const segmentRelPath = segmentRelPathFor(first, last);
|
|
70
|
-
const segmentPath = `${sessionDir}/${segmentRelPath}`;
|
|
71
|
-
const tmpPath = `${segmentPath}.tmp`;
|
|
72
|
-
const segmentBytes = concatJsonlRecords(plan.events);
|
|
73
|
-
const tmpHandle = await this.unwrap(this.fs.openWriteTruncate(tmpPath), mapFsToStoreError);
|
|
74
|
-
await this.unwrap(this.fs.writeAll(tmpHandle.fd, segmentBytes), mapFsToStoreError);
|
|
75
|
-
await this.unwrap(this.fs.fsyncFile(tmpHandle.fd), mapFsToStoreError);
|
|
76
|
-
await this.unwrap(this.fs.closeFile(tmpHandle.fd), mapFsToStoreError);
|
|
77
|
-
await this.unwrap(this.fs.rename(tmpPath, segmentPath), mapFsToStoreError);
|
|
78
|
-
await this.unwrap(this.fs.fsyncDir(eventsDir), mapFsToStoreError);
|
|
79
|
-
const digest = this.sha256.sha256(segmentBytes);
|
|
80
|
-
const segClosed = {
|
|
81
|
-
v: 1,
|
|
82
|
-
manifestIndex: nextManifestIndex(manifest),
|
|
83
|
-
sessionId,
|
|
84
|
-
kind: 'segment_closed',
|
|
85
|
-
firstEventIndex: first,
|
|
86
|
-
lastEventIndex: last,
|
|
87
|
-
segmentRelPath,
|
|
88
|
-
sha256: digest,
|
|
89
|
-
bytes: segmentBytes.length,
|
|
90
|
-
};
|
|
91
|
-
await this.appendManifestRecords(manifestPath, [segClosed]);
|
|
92
|
-
const pins = sortedPins(plan.snapshotPins);
|
|
93
|
-
if (pins.length > 0) {
|
|
94
|
-
const startIndex = segClosed.manifestIndex + 1;
|
|
95
|
-
const records = pins.map((p, i) => ({
|
|
96
|
-
v: 1,
|
|
97
|
-
manifestIndex: startIndex + i,
|
|
98
|
-
sessionId,
|
|
99
|
-
kind: 'snapshot_pinned',
|
|
100
|
-
eventIndex: p.eventIndex,
|
|
101
|
-
snapshotRef: p.snapshotRef,
|
|
102
|
-
createdByEventId: p.createdByEventId,
|
|
103
|
-
}));
|
|
104
|
-
await this.appendManifestRecords(manifestPath, records);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
async loadImpl(sessionId) {
|
|
108
|
-
const sessionDir = this.dataDir.sessionDir(sessionId);
|
|
109
|
-
const manifest = await this.readManifestOrEmpty(sessionId);
|
|
110
|
-
validateManifestContiguityOrThrow(manifest);
|
|
111
|
-
validateSegmentClosedContiguityOrThrow(manifest);
|
|
112
|
-
const segments = manifest.filter((m) => m.kind === 'segment_closed');
|
|
113
|
-
const events = [];
|
|
114
|
-
for (const seg of segments) {
|
|
115
|
-
const segmentPath = `${sessionDir}/${seg.segmentRelPath}`;
|
|
116
|
-
const bytes = await this.fs.readFileBytes(segmentPath).match((v) => v, (e) => {
|
|
117
|
-
if (e.code === 'FS_NOT_FOUND') {
|
|
118
|
-
throw new StoreFailure({
|
|
119
|
-
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
120
|
-
location: 'tail',
|
|
121
|
-
reason: { code: 'missing_attested_segment', message: `Missing attested segment: ${seg.segmentRelPath}` },
|
|
122
|
-
message: `Missing attested segment: ${seg.segmentRelPath}`,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
throw new StoreFailure(mapFsToStoreError(e));
|
|
126
|
-
});
|
|
127
|
-
const actual = this.sha256.sha256(bytes);
|
|
128
|
-
if (actual !== seg.sha256) {
|
|
129
|
-
throw new StoreFailure({
|
|
130
|
-
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
131
|
-
location: 'tail',
|
|
132
|
-
reason: { code: 'digest_mismatch', message: `Segment digest mismatch: ${seg.segmentRelPath}` },
|
|
133
|
-
message: `Segment digest mismatch: ${seg.segmentRelPath}`,
|
|
134
|
-
});
|
|
32
|
+
return this.fs.mkdirp(eventsDir)
|
|
33
|
+
.mapErr(mapFsToStoreError)
|
|
34
|
+
.andThen(() => this.loadTruthOrEmpty(sessionId))
|
|
35
|
+
.andThen(({ manifest, events: existingEvents }) => {
|
|
36
|
+
const contiguityRes = validateManifestContiguity(manifest);
|
|
37
|
+
if (contiguityRes.isErr())
|
|
38
|
+
return (0, neverthrow_1.errAsync)(contiguityRes.error);
|
|
39
|
+
const existingByDedupeKey = new Set(existingEvents.map((e) => e.dedupeKey));
|
|
40
|
+
const allExist = plan.events.every((e) => existingByDedupeKey.has(e.dedupeKey));
|
|
41
|
+
if (allExist) {
|
|
42
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
135
43
|
}
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
code: '
|
|
140
|
-
|
|
141
|
-
reason: { code: 'non_contiguous_indices', message: `Empty segment referenced by manifest: ${seg.segmentRelPath}` },
|
|
142
|
-
message: `Empty segment referenced by manifest: ${seg.segmentRelPath}`,
|
|
44
|
+
const anyExist = plan.events.some((e) => existingByDedupeKey.has(e.dedupeKey));
|
|
45
|
+
if (anyExist && !allExist) {
|
|
46
|
+
return (0, neverthrow_1.errAsync)({
|
|
47
|
+
code: 'SESSION_STORE_INVARIANT_VIOLATION',
|
|
48
|
+
message: 'Partial dedupeKey collision detected (some events exist, some do not); this is an invariant violation',
|
|
143
49
|
});
|
|
144
50
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
51
|
+
const expectedFirstEventIndex = nextEventIndexFromManifest(manifest);
|
|
52
|
+
const planRes = validateAppendPlan(sessionId, plan, expectedFirstEventIndex);
|
|
53
|
+
if (planRes.isErr())
|
|
54
|
+
return (0, neverthrow_1.errAsync)(planRes.error);
|
|
55
|
+
const first = plan.events[0].eventIndex;
|
|
56
|
+
const last = plan.events[plan.events.length - 1].eventIndex;
|
|
57
|
+
const segmentRelPath = segmentRelPathFor(first, last);
|
|
58
|
+
const segmentPath = `${sessionDir}/${segmentRelPath}`;
|
|
59
|
+
const tmpPath = `${segmentPath}.tmp`;
|
|
60
|
+
const segmentBytesRes = concatJsonlRecords(plan.events);
|
|
61
|
+
if (segmentBytesRes.isErr())
|
|
62
|
+
return (0, neverthrow_1.errAsync)(segmentBytesRes.error);
|
|
63
|
+
const segmentBytes = segmentBytesRes.value;
|
|
64
|
+
return this.fs.openWriteTruncate(tmpPath)
|
|
65
|
+
.mapErr(mapFsToStoreError)
|
|
66
|
+
.andThen((tmpHandle) => this.fs.writeAll(tmpHandle.fd, segmentBytes).mapErr(mapFsToStoreError)
|
|
67
|
+
.andThen(() => this.fs.fsyncFile(tmpHandle.fd).mapErr(mapFsToStoreError))
|
|
68
|
+
.andThen(() => this.fs.closeFile(tmpHandle.fd).mapErr(mapFsToStoreError)))
|
|
69
|
+
.andThen(() => this.fs.rename(tmpPath, segmentPath).mapErr(mapFsToStoreError))
|
|
70
|
+
.andThen(() => this.fs.fsyncDir(eventsDir).mapErr(mapFsToStoreError))
|
|
71
|
+
.andThen(() => {
|
|
72
|
+
const digest = this.sha256.sha256(segmentBytes);
|
|
73
|
+
const segClosed = {
|
|
74
|
+
v: 1,
|
|
75
|
+
manifestIndex: nextManifestIndex(manifest),
|
|
76
|
+
sessionId,
|
|
77
|
+
kind: 'segment_closed',
|
|
78
|
+
firstEventIndex: first,
|
|
79
|
+
lastEventIndex: last,
|
|
80
|
+
segmentRelPath,
|
|
81
|
+
sha256: digest,
|
|
82
|
+
bytes: segmentBytes.length,
|
|
83
|
+
};
|
|
84
|
+
return this.appendManifestRecords(manifestPath, [segClosed])
|
|
85
|
+
.andThen(() => {
|
|
86
|
+
const pins = sortedPins(plan.snapshotPins);
|
|
87
|
+
if (pins.length === 0)
|
|
88
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
89
|
+
const startIndex = segClosed.manifestIndex + 1;
|
|
90
|
+
const records = pins.map((p, i) => ({
|
|
91
|
+
v: 1,
|
|
92
|
+
manifestIndex: startIndex + i,
|
|
93
|
+
sessionId,
|
|
94
|
+
kind: 'snapshot_pinned',
|
|
95
|
+
eventIndex: p.eventIndex,
|
|
96
|
+
snapshotRef: p.snapshotRef,
|
|
97
|
+
createdByEventId: p.createdByEventId,
|
|
98
|
+
}));
|
|
99
|
+
return this.appendManifestRecords(manifestPath, records);
|
|
151
100
|
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
loadImpl(sessionId) {
|
|
105
|
+
const sessionDir = this.dataDir.sessionDir(sessionId);
|
|
106
|
+
return this.readManifestOrEmpty(sessionId)
|
|
107
|
+
.andThen((manifest) => {
|
|
108
|
+
const contRes = validateManifestContiguity(manifest);
|
|
109
|
+
if (contRes.isErr())
|
|
110
|
+
return (0, neverthrow_1.errAsync)(contRes.error);
|
|
111
|
+
const segRes = validateSegmentClosedContiguity(manifest);
|
|
112
|
+
if (segRes.isErr())
|
|
113
|
+
return (0, neverthrow_1.errAsync)(segRes.error);
|
|
114
|
+
const segments = manifest.filter((m) => m.kind === 'segment_closed');
|
|
115
|
+
const loadSegments = (segs) => {
|
|
116
|
+
if (segs.length === 0)
|
|
117
|
+
return (0, neverthrow_1.okAsync)([]);
|
|
118
|
+
const [head, ...tail] = segs;
|
|
119
|
+
const segmentPath = `${sessionDir}/${head.segmentRelPath}`;
|
|
120
|
+
return this.fs.readFileBytes(segmentPath)
|
|
121
|
+
.mapErr((e) => {
|
|
122
|
+
if (e.code === 'FS_NOT_FOUND') {
|
|
123
|
+
return {
|
|
124
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
125
|
+
location: 'tail',
|
|
126
|
+
reason: { code: 'missing_attested_segment', message: `Missing attested segment: ${head.segmentRelPath}` },
|
|
127
|
+
message: `Missing attested segment: ${head.segmentRelPath}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return mapFsToStoreError(e);
|
|
131
|
+
})
|
|
132
|
+
.andThen((bytes) => {
|
|
133
|
+
const actual = this.sha256.sha256(bytes);
|
|
134
|
+
if (actual !== head.sha256) {
|
|
135
|
+
return (0, neverthrow_1.errAsync)({
|
|
136
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
137
|
+
location: 'tail',
|
|
138
|
+
reason: { code: 'digest_mismatch', message: `Segment digest mismatch: ${head.segmentRelPath}` },
|
|
139
|
+
message: `Segment digest mismatch: ${head.segmentRelPath}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const parsedRes = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
|
|
143
|
+
if (parsedRes.isErr())
|
|
144
|
+
return (0, neverthrow_1.errAsync)(parsedRes.error);
|
|
145
|
+
const parsed = parsedRes.value;
|
|
146
|
+
if (parsed.length === 0) {
|
|
147
|
+
return (0, neverthrow_1.errAsync)({
|
|
148
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
149
|
+
location: 'tail',
|
|
150
|
+
reason: { code: 'non_contiguous_indices', message: `Empty segment referenced by manifest: ${head.segmentRelPath}` },
|
|
151
|
+
message: `Empty segment referenced by manifest: ${head.segmentRelPath}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (parsed[0].eventIndex !== head.firstEventIndex || parsed[parsed.length - 1].eventIndex !== head.lastEventIndex) {
|
|
155
|
+
return (0, neverthrow_1.errAsync)({
|
|
156
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
157
|
+
location: 'tail',
|
|
158
|
+
reason: { code: 'non_contiguous_indices', message: `Segment bounds mismatch: ${head.segmentRelPath}` },
|
|
159
|
+
message: `Segment bounds mismatch: ${head.segmentRelPath}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
for (let i = 1; i < parsed.length; i++) {
|
|
163
|
+
if (parsed[i].eventIndex !== parsed[i - 1].eventIndex + 1) {
|
|
164
|
+
return (0, neverthrow_1.errAsync)({
|
|
165
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
166
|
+
location: 'tail',
|
|
167
|
+
reason: { code: 'non_contiguous_indices', message: `Non-contiguous eventIndex inside segment: ${head.segmentRelPath}` },
|
|
168
|
+
message: `Non-contiguous eventIndex inside segment: ${head.segmentRelPath}`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return (0, neverthrow_1.okAsync)(parsed);
|
|
173
|
+
})
|
|
174
|
+
.andThen((events) => loadSegments(tail).map((rest) => [...events, ...rest]));
|
|
175
|
+
};
|
|
176
|
+
return loadSegments(segments).andThen((events) => {
|
|
177
|
+
const expectedPins = extractSnapshotPinsFromEvents(events);
|
|
178
|
+
const actualPins = new Set(manifest
|
|
179
|
+
.filter((m) => m.kind === 'snapshot_pinned')
|
|
180
|
+
.map((p) => `${p.eventIndex}:${p.createdByEventId}:${p.snapshotRef}`));
|
|
181
|
+
for (const ep of expectedPins) {
|
|
182
|
+
const key = `${ep.eventIndex}:${ep.createdByEventId}:${ep.snapshotRef}`;
|
|
183
|
+
if (!actualPins.has(key)) {
|
|
184
|
+
return (0, neverthrow_1.errAsync)({
|
|
185
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
186
|
+
location: 'tail',
|
|
187
|
+
reason: { code: 'missing_attested_segment', message: `Missing snapshot_pinned for introduced snapshotRef: ${key}` },
|
|
188
|
+
message: `Missing snapshot_pinned for introduced snapshotRef: ${key}`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
161
191
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
const expectedPins = extractSnapshotPinsFromEvents(events);
|
|
166
|
-
const actualPins = new Set(manifest
|
|
167
|
-
.filter((m) => m.kind === 'snapshot_pinned')
|
|
168
|
-
.map((p) => `${p.eventIndex}:${p.createdByEventId}:${p.snapshotRef}`));
|
|
169
|
-
for (const ep of expectedPins) {
|
|
170
|
-
const key = `${ep.eventIndex}:${ep.createdByEventId}:${ep.snapshotRef}`;
|
|
171
|
-
if (!actualPins.has(key)) {
|
|
172
|
-
throw new StoreFailure({
|
|
173
|
-
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
174
|
-
location: 'tail',
|
|
175
|
-
reason: { code: 'missing_attested_segment', message: `Missing snapshot_pinned for introduced snapshotRef: ${key}` },
|
|
176
|
-
message: `Missing snapshot_pinned for introduced snapshotRef: ${key}`,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return { manifest, events };
|
|
192
|
+
return (0, neverthrow_1.okAsync)({ manifest, events });
|
|
193
|
+
});
|
|
194
|
+
});
|
|
181
195
|
}
|
|
182
|
-
|
|
196
|
+
readManifestOrEmpty(sessionId) {
|
|
183
197
|
const manifestPath = this.dataDir.sessionManifestPath(sessionId);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
return this.fs
|
|
199
|
+
.readFileUtf8(manifestPath)
|
|
200
|
+
.orElse((e) => (e.code === 'FS_NOT_FOUND' ? (0, neverthrow_1.okAsync)('') : (0, neverthrow_1.errAsync)(mapFsToStoreError(e))))
|
|
201
|
+
.andThen((raw) => {
|
|
202
|
+
if (raw.trim() === '')
|
|
203
|
+
return (0, neverthrow_1.okAsync)([]);
|
|
204
|
+
const parsed = parseJsonlText(raw, index_js_1.ManifestRecordV1Schema);
|
|
205
|
+
return parsed.isErr() ? (0, neverthrow_1.errAsync)(parsed.error) : (0, neverthrow_1.okAsync)(parsed.value);
|
|
188
206
|
});
|
|
189
|
-
if (raw.trim() === '')
|
|
190
|
-
return [];
|
|
191
|
-
return parseJsonlText(raw, index_js_1.ManifestRecordV1Schema);
|
|
192
207
|
}
|
|
193
|
-
|
|
208
|
+
loadValidatedPrefixImpl(sessionId) {
|
|
194
209
|
const sessionDir = this.dataDir.sessionDir(sessionId);
|
|
195
210
|
const manifestPath = this.dataDir.sessionManifestPath(sessionId);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return { truth: { manifest: [], events: [] }, isComplete: true, tailReason: null };
|
|
203
|
-
const lines = raw.split('\n').filter((l) => l.trim() !== '');
|
|
204
|
-
const manifest = [];
|
|
205
|
-
let isComplete = true;
|
|
206
|
-
let tailReason = null;
|
|
207
|
-
for (let i = 0; i < lines.length; i++) {
|
|
208
|
-
const line = lines[i];
|
|
209
|
-
let parsed;
|
|
210
|
-
try {
|
|
211
|
-
parsed = JSON.parse(line);
|
|
211
|
+
return this.fs
|
|
212
|
+
.readFileUtf8(manifestPath)
|
|
213
|
+
.orElse((e) => (e.code === 'FS_NOT_FOUND' ? (0, neverthrow_1.okAsync)('') : (0, neverthrow_1.errAsync)(mapFsToStoreError(e))))
|
|
214
|
+
.andThen((raw) => {
|
|
215
|
+
if (raw.trim() === '') {
|
|
216
|
+
return (0, neverthrow_1.okAsync)({ truth: { manifest: [], events: [] }, isComplete: true, tailReason: null });
|
|
212
217
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
reason: { code: 'non_contiguous_indices', message: 'No validated manifest prefix' },
|
|
236
|
-
message: 'No validated manifest prefix',
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
const segments = manifest.filter((m) => m.kind === 'segment_closed');
|
|
240
|
-
const events = [];
|
|
241
|
-
for (const seg of segments) {
|
|
242
|
-
const segmentPath = `${sessionDir}/${seg.segmentRelPath}`;
|
|
243
|
-
const bytes = await this.fs.readFileBytes(segmentPath).match((v) => v, (e) => {
|
|
244
|
-
if (e.code === 'FS_NOT_FOUND')
|
|
245
|
-
return null;
|
|
246
|
-
throw new StoreFailure(mapFsToStoreError(e));
|
|
247
|
-
});
|
|
248
|
-
if (bytes === null) {
|
|
249
|
-
isComplete = false;
|
|
250
|
-
tailReason ?? (tailReason = { code: 'missing_attested_segment', message: `Missing attested segment: ${seg.segmentRelPath}` });
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
const actual = this.sha256.sha256(bytes);
|
|
254
|
-
if (actual !== seg.sha256) {
|
|
255
|
-
isComplete = false;
|
|
256
|
-
tailReason ?? (tailReason = { code: 'digest_mismatch', message: `Segment digest mismatch: ${seg.segmentRelPath}` });
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
const parsed = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
|
|
260
|
-
if (parsed.length === 0) {
|
|
261
|
-
isComplete = false;
|
|
262
|
-
tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: `Empty segment referenced by manifest: ${seg.segmentRelPath}` });
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
if (parsed[0].eventIndex !== seg.firstEventIndex || parsed[parsed.length - 1].eventIndex !== seg.lastEventIndex) {
|
|
266
|
-
isComplete = false;
|
|
267
|
-
tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: `Segment bounds mismatch: ${seg.segmentRelPath}` });
|
|
268
|
-
break;
|
|
269
|
-
}
|
|
270
|
-
for (let i = 1; i < parsed.length; i++) {
|
|
271
|
-
if (parsed[i].eventIndex !== parsed[i - 1].eventIndex + 1) {
|
|
218
|
+
const lines = raw.split('\n').filter((l) => l.trim() !== '');
|
|
219
|
+
const manifest = [];
|
|
220
|
+
let isComplete = true;
|
|
221
|
+
let tailReason = null;
|
|
222
|
+
for (let i = 0; i < lines.length; i++) {
|
|
223
|
+
const line = lines[i];
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = JSON.parse(line);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
isComplete = false;
|
|
230
|
+
tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: 'Invalid JSON in manifest (corrupt tail)' });
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
const validated = index_js_1.ManifestRecordV1Schema.safeParse(parsed);
|
|
234
|
+
if (!validated.success) {
|
|
235
|
+
isComplete = false;
|
|
236
|
+
tailReason ?? (tailReason = { code: 'unknown_schema_version', message: 'Unknown manifest schema version (corrupt tail)' });
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
if (validated.data.manifestIndex !== i) {
|
|
272
240
|
isComplete = false;
|
|
273
|
-
tailReason ?? (tailReason = { code: 'non_contiguous_indices', message:
|
|
241
|
+
tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: 'Non-contiguous manifestIndex in prefix (corrupt tail)' });
|
|
274
242
|
break;
|
|
275
243
|
}
|
|
244
|
+
manifest.push(validated.data);
|
|
276
245
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
246
|
+
if (manifest.length === 0) {
|
|
247
|
+
return (0, neverthrow_1.errAsync)({
|
|
248
|
+
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
249
|
+
location: 'head',
|
|
250
|
+
reason: { code: 'non_contiguous_indices', message: 'No validated manifest prefix' },
|
|
251
|
+
message: 'No validated manifest prefix',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const segments = manifest.filter((m) => m.kind === 'segment_closed');
|
|
255
|
+
const initial = { events: [], isComplete, tailReason, done: false };
|
|
256
|
+
const processSegment = (seg, state) => {
|
|
257
|
+
if (state.done)
|
|
258
|
+
return (0, neverthrow_1.okAsync)(state);
|
|
259
|
+
const segmentPath = `${sessionDir}/${seg.segmentRelPath}`;
|
|
260
|
+
return this.fs
|
|
261
|
+
.readFileBytes(segmentPath)
|
|
262
|
+
.map((bytes) => ({ kind: 'present', bytes }))
|
|
263
|
+
.orElse((e) => (e.code === 'FS_NOT_FOUND' ? (0, neverthrow_1.okAsync)({ kind: 'missing' }) : (0, neverthrow_1.errAsync)(mapFsToStoreError(e))))
|
|
264
|
+
.andThen((res) => {
|
|
265
|
+
if (res.kind === 'missing') {
|
|
266
|
+
return (0, neverthrow_1.okAsync)({
|
|
267
|
+
...state,
|
|
268
|
+
isComplete: false,
|
|
269
|
+
tailReason: state.tailReason ?? { code: 'missing_attested_segment', message: `Missing attested segment: ${seg.segmentRelPath}` },
|
|
270
|
+
done: true,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const bytes = res.bytes;
|
|
274
|
+
const actual = this.sha256.sha256(bytes);
|
|
275
|
+
if (actual !== seg.sha256) {
|
|
276
|
+
return (0, neverthrow_1.okAsync)({
|
|
277
|
+
...state,
|
|
278
|
+
isComplete: false,
|
|
279
|
+
tailReason: state.tailReason ?? { code: 'digest_mismatch', message: `Segment digest mismatch: ${seg.segmentRelPath}` },
|
|
280
|
+
done: true,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const parsedRes = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
|
|
284
|
+
if (parsedRes.isErr()) {
|
|
285
|
+
return (0, neverthrow_1.okAsync)({
|
|
286
|
+
...state,
|
|
287
|
+
isComplete: false,
|
|
288
|
+
tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Invalid JSONL inside segment: ${seg.segmentRelPath}` },
|
|
289
|
+
done: true,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
const parsed = parsedRes.value;
|
|
293
|
+
if (parsed.length === 0) {
|
|
294
|
+
return (0, neverthrow_1.okAsync)({
|
|
295
|
+
...state,
|
|
296
|
+
isComplete: false,
|
|
297
|
+
tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Empty segment referenced by manifest: ${seg.segmentRelPath}` },
|
|
298
|
+
done: true,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (parsed[0].eventIndex !== seg.firstEventIndex || parsed[parsed.length - 1].eventIndex !== seg.lastEventIndex) {
|
|
302
|
+
return (0, neverthrow_1.okAsync)({
|
|
303
|
+
...state,
|
|
304
|
+
isComplete: false,
|
|
305
|
+
tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Segment bounds mismatch: ${seg.segmentRelPath}` },
|
|
306
|
+
done: true,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
for (let i = 1; i < parsed.length; i++) {
|
|
310
|
+
if (parsed[i].eventIndex !== parsed[i - 1].eventIndex + 1) {
|
|
311
|
+
return (0, neverthrow_1.okAsync)({
|
|
312
|
+
...state,
|
|
313
|
+
isComplete: false,
|
|
314
|
+
tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Non-contiguous eventIndex inside segment: ${seg.segmentRelPath}` },
|
|
315
|
+
done: true,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return (0, neverthrow_1.okAsync)({
|
|
320
|
+
...state,
|
|
321
|
+
events: [...state.events, ...parsed],
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
return segments
|
|
326
|
+
.reduce((acc, seg) => acc.andThen((s) => processSegment(seg, s)), (0, neverthrow_1.okAsync)(initial))
|
|
327
|
+
.map((final) => ({
|
|
328
|
+
truth: { manifest, events: final.events },
|
|
329
|
+
isComplete: final.isComplete,
|
|
330
|
+
tailReason: final.tailReason,
|
|
331
|
+
}));
|
|
332
|
+
});
|
|
295
333
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
334
|
+
loadTruthOrEmpty(sessionId) {
|
|
335
|
+
return this.readManifestOrEmpty(sessionId)
|
|
336
|
+
.andThen((manifest) => {
|
|
337
|
+
if (manifest.length === 0)
|
|
338
|
+
return (0, neverthrow_1.okAsync)({ manifest: [], events: [] });
|
|
339
|
+
const segments = manifest.filter((m) => m.kind === 'segment_closed');
|
|
340
|
+
const sessionDir = this.dataDir.sessionDir(sessionId);
|
|
341
|
+
const loadSegments = (segs) => {
|
|
342
|
+
if (segs.length === 0)
|
|
343
|
+
return (0, neverthrow_1.okAsync)([]);
|
|
344
|
+
const [head, ...tail] = segs;
|
|
345
|
+
const segmentPath = `${sessionDir}/${head.segmentRelPath}`;
|
|
346
|
+
return this.fs.readFileBytes(segmentPath)
|
|
347
|
+
.mapErr(mapFsToStoreError)
|
|
348
|
+
.andThen((bytes) => {
|
|
349
|
+
const parsedRes = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
|
|
350
|
+
if (parsedRes.isErr())
|
|
351
|
+
return (0, neverthrow_1.errAsync)(parsedRes.error);
|
|
352
|
+
return (0, neverthrow_1.okAsync)(parsedRes.value);
|
|
353
|
+
})
|
|
354
|
+
.andThen((events) => loadSegments(tail).map((rest) => [...events, ...rest]));
|
|
355
|
+
};
|
|
356
|
+
return loadSegments(segments).map((events) => ({ manifest, events }));
|
|
357
|
+
});
|
|
302
358
|
}
|
|
303
|
-
|
|
304
|
-
return
|
|
305
|
-
|
|
359
|
+
appendManifestRecords(manifestPath, records) {
|
|
360
|
+
return this.fs.openAppend(manifestPath)
|
|
361
|
+
.mapErr(mapFsToStoreError)
|
|
362
|
+
.andThen((handle) => {
|
|
363
|
+
const bytesRes = concatJsonlRecords(records);
|
|
364
|
+
if (bytesRes.isErr())
|
|
365
|
+
return (0, neverthrow_1.errAsync)(bytesRes.error);
|
|
366
|
+
const bytes = bytesRes.value;
|
|
367
|
+
return this.fs.writeAll(handle.fd, bytes).mapErr(mapFsToStoreError)
|
|
368
|
+
.andThen(() => this.fs.fsyncFile(handle.fd).mapErr(mapFsToStoreError))
|
|
369
|
+
.andThen(() => this.fs.closeFile(handle.fd).mapErr(mapFsToStoreError));
|
|
306
370
|
});
|
|
307
371
|
}
|
|
308
372
|
}
|
|
@@ -321,11 +385,11 @@ function nextEventIndexFromManifest(manifest) {
|
|
|
321
385
|
return 0;
|
|
322
386
|
return segments[segments.length - 1].lastEventIndex + 1;
|
|
323
387
|
}
|
|
324
|
-
function
|
|
388
|
+
function validateManifestContiguity(manifest) {
|
|
325
389
|
for (let i = 0; i < manifest.length; i++) {
|
|
326
390
|
const expected = i;
|
|
327
391
|
if (manifest[i].manifestIndex !== expected) {
|
|
328
|
-
|
|
392
|
+
return (0, neverthrow_1.err)({
|
|
329
393
|
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
330
394
|
location: i === 0 ? 'head' : 'tail',
|
|
331
395
|
reason: {
|
|
@@ -336,14 +400,15 @@ function validateManifestContiguityOrThrow(manifest) {
|
|
|
336
400
|
});
|
|
337
401
|
}
|
|
338
402
|
}
|
|
403
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
339
404
|
}
|
|
340
|
-
function
|
|
405
|
+
function validateSegmentClosedContiguity(manifest) {
|
|
341
406
|
const segments = manifest.filter((m) => m.kind === 'segment_closed');
|
|
342
407
|
for (let i = 1; i < segments.length; i++) {
|
|
343
408
|
const prev = segments[i - 1];
|
|
344
409
|
const cur = segments[i];
|
|
345
410
|
if (cur.firstEventIndex !== prev.lastEventIndex + 1) {
|
|
346
|
-
|
|
411
|
+
return (0, neverthrow_1.err)({
|
|
347
412
|
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
348
413
|
location: 'tail',
|
|
349
414
|
reason: {
|
|
@@ -354,14 +419,15 @@ function validateSegmentClosedContiguityOrThrow(manifest) {
|
|
|
354
419
|
});
|
|
355
420
|
}
|
|
356
421
|
}
|
|
422
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
357
423
|
}
|
|
358
|
-
function
|
|
424
|
+
function validateAppendPlan(sessionId, plan, expectedFirstEventIndex) {
|
|
359
425
|
if (plan.events.length === 0) {
|
|
360
|
-
|
|
426
|
+
return (0, neverthrow_1.err)({ code: 'SESSION_STORE_INVARIANT_VIOLATION', message: 'AppendPlan.events must be non-empty' });
|
|
361
427
|
}
|
|
362
428
|
const first = plan.events[0];
|
|
363
429
|
if (first.eventIndex !== expectedFirstEventIndex) {
|
|
364
|
-
|
|
430
|
+
return (0, neverthrow_1.err)({
|
|
365
431
|
code: 'SESSION_STORE_INVARIANT_VIOLATION',
|
|
366
432
|
message: `AppendPlan.eventIndex must start at ${expectedFirstEventIndex} (got ${first.eventIndex})`,
|
|
367
433
|
});
|
|
@@ -369,16 +435,16 @@ function validateAppendPlanOrThrow(sessionId, plan, expectedFirstEventIndex) {
|
|
|
369
435
|
for (let i = 0; i < plan.events.length; i++) {
|
|
370
436
|
const e = index_js_1.DomainEventV1Schema.safeParse(plan.events[i]);
|
|
371
437
|
if (!e.success) {
|
|
372
|
-
|
|
438
|
+
return (0, neverthrow_1.err)({ code: 'SESSION_STORE_INVARIANT_VIOLATION', message: `Invalid domain event at index ${i}` });
|
|
373
439
|
}
|
|
374
440
|
if (e.data.sessionId !== sessionId) {
|
|
375
|
-
|
|
441
|
+
return (0, neverthrow_1.err)({
|
|
376
442
|
code: 'SESSION_STORE_INVARIANT_VIOLATION',
|
|
377
443
|
message: `Domain event sessionId mismatch at index ${i}`,
|
|
378
444
|
});
|
|
379
445
|
}
|
|
380
446
|
if (i > 0 && plan.events[i].eventIndex !== plan.events[i - 1].eventIndex + 1) {
|
|
381
|
-
|
|
447
|
+
return (0, neverthrow_1.err)({
|
|
382
448
|
code: 'SESSION_STORE_INVARIANT_VIOLATION',
|
|
383
449
|
message: `Non-contiguous eventIndex in AppendPlan at index ${i}`,
|
|
384
450
|
});
|
|
@@ -387,12 +453,13 @@ function validateAppendPlanOrThrow(sessionId, plan, expectedFirstEventIndex) {
|
|
|
387
453
|
const pins = sortedPins(plan.snapshotPins);
|
|
388
454
|
for (const p of pins) {
|
|
389
455
|
if (p.eventIndex < first.eventIndex || p.eventIndex > plan.events[plan.events.length - 1].eventIndex) {
|
|
390
|
-
|
|
456
|
+
return (0, neverthrow_1.err)({
|
|
391
457
|
code: 'SESSION_STORE_INVARIANT_VIOLATION',
|
|
392
458
|
message: `SnapshotPin.eventIndex must refer to an event in the appended segment`,
|
|
393
459
|
});
|
|
394
460
|
}
|
|
395
461
|
}
|
|
462
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
396
463
|
}
|
|
397
464
|
function sortedPins(pins) {
|
|
398
465
|
return [...pins].sort((a, b) => {
|
|
@@ -414,11 +481,12 @@ function concatJsonlRecords(records) {
|
|
|
414
481
|
const parts = [];
|
|
415
482
|
let total = 0;
|
|
416
483
|
for (const r of records) {
|
|
417
|
-
const encoded = (0, jsonl_js_1.toJsonlLineBytes)(r)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
484
|
+
const encoded = (0, jsonl_js_1.toJsonlLineBytes)(r);
|
|
485
|
+
if (encoded.isErr())
|
|
486
|
+
return (0, neverthrow_1.err)({ code: 'SESSION_STORE_INVARIANT_VIOLATION', message: encoded.error.message });
|
|
487
|
+
const val = encoded.value;
|
|
488
|
+
parts.push(val);
|
|
489
|
+
total += val.length;
|
|
422
490
|
}
|
|
423
491
|
const out = new Uint8Array(total);
|
|
424
492
|
let offset = 0;
|
|
@@ -426,7 +494,7 @@ function concatJsonlRecords(records) {
|
|
|
426
494
|
out.set(p, offset);
|
|
427
495
|
offset += p.length;
|
|
428
496
|
}
|
|
429
|
-
return out;
|
|
497
|
+
return (0, neverthrow_1.ok)(out);
|
|
430
498
|
}
|
|
431
499
|
function parseJsonlText(text, schema) {
|
|
432
500
|
const lines = text.split('\n').filter((l) => l.trim() !== '');
|
|
@@ -438,7 +506,7 @@ function parseJsonlText(text, schema) {
|
|
|
438
506
|
parsed = JSON.parse(raw);
|
|
439
507
|
}
|
|
440
508
|
catch {
|
|
441
|
-
|
|
509
|
+
return (0, neverthrow_1.err)({
|
|
442
510
|
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
443
511
|
location: i === 0 ? 'head' : 'tail',
|
|
444
512
|
reason: { code: 'non_contiguous_indices', message: `Invalid JSONL at line ${i}` },
|
|
@@ -447,7 +515,7 @@ function parseJsonlText(text, schema) {
|
|
|
447
515
|
}
|
|
448
516
|
const validated = schema.safeParse(parsed);
|
|
449
517
|
if (!validated.success) {
|
|
450
|
-
|
|
518
|
+
return (0, neverthrow_1.err)({
|
|
451
519
|
code: 'SESSION_STORE_CORRUPTION_DETECTED',
|
|
452
520
|
location: i === 0 ? 'head' : 'tail',
|
|
453
521
|
reason: { code: 'unknown_schema_version', message: `Invalid record at line ${i}` },
|
|
@@ -456,7 +524,7 @@ function parseJsonlText(text, schema) {
|
|
|
456
524
|
}
|
|
457
525
|
out.push(validated.data);
|
|
458
526
|
}
|
|
459
|
-
return out;
|
|
527
|
+
return (0, neverthrow_1.ok)(out);
|
|
460
528
|
}
|
|
461
529
|
function parseJsonlLines(bytes, schema) {
|
|
462
530
|
const text = new TextDecoder().decode(bytes);
|