@exaudeus/workrail 1.5.0 → 1.5.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.
Files changed (141) hide show
  1. package/dist/manifest.json +378 -178
  2. package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +37 -0
  3. package/dist/mcp/handlers/v2-advance-core/event-builders.js +114 -0
  4. package/dist/mcp/handlers/v2-advance-core/index.d.ts +67 -0
  5. package/dist/mcp/handlers/v2-advance-core/index.js +100 -0
  6. package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +44 -0
  7. package/dist/mcp/handlers/v2-advance-core/input-validation.js +58 -0
  8. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +16 -0
  9. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +64 -0
  10. package/dist/mcp/handlers/v2-advance-core/outcome-success.d.ts +15 -0
  11. package/dist/mcp/handlers/v2-advance-core/outcome-success.js +136 -0
  12. package/dist/mcp/handlers/v2-advance-core.d.ts +3 -45
  13. package/dist/mcp/handlers/v2-advance-core.js +3 -431
  14. package/dist/mcp/handlers/v2-advance-events.d.ts +61 -0
  15. package/dist/mcp/handlers/v2-advance-events.js +126 -0
  16. package/dist/mcp/handlers/v2-checkpoint.js +5 -4
  17. package/dist/mcp/handlers/v2-context-budget.js +2 -2
  18. package/dist/mcp/handlers/v2-execution/advance.d.ts +32 -0
  19. package/dist/mcp/handlers/v2-execution/advance.js +50 -0
  20. package/dist/mcp/handlers/v2-execution/continue-advance.d.ts +29 -0
  21. package/dist/mcp/handlers/v2-execution/continue-advance.js +170 -0
  22. package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +22 -0
  23. package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +146 -0
  24. package/dist/mcp/handlers/v2-execution/index.d.ts +23 -0
  25. package/dist/mcp/handlers/v2-execution/index.js +113 -0
  26. package/dist/mcp/handlers/v2-execution/replay.d.ts +55 -0
  27. package/dist/mcp/handlers/v2-execution/replay.js +227 -0
  28. package/dist/mcp/handlers/v2-execution/start.d.ts +52 -0
  29. package/dist/mcp/handlers/v2-execution/start.js +350 -0
  30. package/dist/mcp/handlers/v2-execution-helpers.d.ts +23 -0
  31. package/dist/mcp/handlers/v2-execution-helpers.js +42 -1
  32. package/dist/mcp/handlers/v2-execution.d.ts +1 -23
  33. package/dist/mcp/handlers/v2-execution.js +5 -898
  34. package/dist/mcp/handlers/v2-state-conversion.d.ts +0 -5
  35. package/dist/mcp/handlers/v2-state-conversion.js +2 -21
  36. package/dist/mcp/handlers/v2-token-ops.d.ts +1 -1
  37. package/dist/mcp/handlers/v2-workflow.js +76 -64
  38. package/dist/v2/durable-core/constants.d.ts +60 -0
  39. package/dist/v2/durable-core/constants.js +55 -1
  40. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +23 -13
  41. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +160 -113
  42. package/dist/v2/durable-core/domain/blocked-node-builder.js +8 -2
  43. package/dist/v2/durable-core/domain/blocking-decision.d.ts +2 -0
  44. package/dist/v2/durable-core/domain/blocking-decision.js +29 -12
  45. package/dist/v2/durable-core/domain/bundle-builder.d.ts +1 -0
  46. package/dist/v2/durable-core/domain/bundle-builder.js +1 -1
  47. package/dist/v2/durable-core/domain/bundle-validator.js +3 -2
  48. package/dist/v2/durable-core/domain/decision-trace-builder.js +7 -9
  49. package/dist/v2/durable-core/domain/function-definition-expander.js +1 -3
  50. package/dist/v2/durable-core/domain/gap-builder.js +2 -1
  51. package/dist/v2/durable-core/domain/observation-builder.js +2 -1
  52. package/dist/v2/durable-core/domain/outputs.d.ts +2 -1
  53. package/dist/v2/durable-core/domain/outputs.js +3 -2
  54. package/dist/v2/durable-core/domain/reason-model.d.ts +1 -1
  55. package/dist/v2/durable-core/domain/reason-model.js +4 -9
  56. package/dist/v2/durable-core/domain/validation-criteria-validator.js +2 -2
  57. package/dist/v2/durable-core/domain/validation-event-builder.js +4 -6
  58. package/dist/v2/durable-core/domain/validation-loader.js +2 -1
  59. package/dist/v2/durable-core/domain/validation-requirements-extractor.js +12 -18
  60. package/dist/v2/durable-core/encoding/base32-lower.d.ts +13 -1
  61. package/dist/v2/durable-core/encoding/base32-lower.js +13 -3
  62. package/dist/v2/durable-core/encoding/hex-to-bytes.d.ts +6 -0
  63. package/dist/v2/durable-core/encoding/hex-to-bytes.js +19 -0
  64. package/dist/v2/durable-core/ids/attempt-id-derivation.d.ts +6 -1
  65. package/dist/v2/durable-core/ids/attempt-id-derivation.js +9 -19
  66. package/dist/v2/durable-core/ids/event-ids.d.ts +9 -0
  67. package/dist/v2/durable-core/ids/event-ids.js +18 -0
  68. package/dist/v2/durable-core/ids/index.d.ts +13 -33
  69. package/dist/v2/durable-core/ids/index.js +22 -63
  70. package/dist/v2/durable-core/ids/session-ids.d.ts +9 -0
  71. package/dist/v2/durable-core/ids/session-ids.js +18 -0
  72. package/dist/v2/durable-core/ids/snapshot-ids.d.ts +6 -0
  73. package/dist/v2/durable-core/ids/snapshot-ids.js +10 -0
  74. package/dist/v2/durable-core/ids/token-ids.d.ts +3 -0
  75. package/dist/v2/durable-core/ids/token-ids.js +6 -0
  76. package/dist/v2/durable-core/ids/workflow-hash-ref.d.ts +3 -0
  77. package/dist/v2/durable-core/ids/workflow-hash-ref.js +5 -4
  78. package/dist/v2/durable-core/ids/workflow-ids.d.ts +11 -0
  79. package/dist/v2/durable-core/ids/workflow-ids.js +21 -0
  80. package/dist/v2/durable-core/lib/utf8-byte-length.d.ts +1 -0
  81. package/dist/v2/durable-core/lib/utf8-byte-length.js +6 -0
  82. package/dist/v2/durable-core/schemas/artifacts/loop-control.js +2 -1
  83. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +24 -24
  84. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +5 -7
  85. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +180 -180
  86. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +282 -287
  87. package/dist/v2/durable-core/schemas/export-bundle/index.js +0 -8
  88. package/dist/v2/durable-core/schemas/lib/dedupe-key.d.ts +9 -1
  89. package/dist/v2/durable-core/schemas/lib/dedupe-key.js +4 -3
  90. package/dist/v2/durable-core/schemas/lib/utf8-byte-length.d.ts +1 -0
  91. package/dist/v2/durable-core/schemas/lib/utf8-byte-length.js +6 -0
  92. package/dist/v2/durable-core/schemas/session/blockers.d.ts +305 -0
  93. package/dist/v2/durable-core/schemas/session/blockers.js +80 -0
  94. package/dist/v2/durable-core/schemas/session/dag-topology.d.ts +77 -0
  95. package/dist/v2/durable-core/schemas/session/dag-topology.js +45 -0
  96. package/dist/v2/durable-core/schemas/session/events.d.ts +36 -36
  97. package/dist/v2/durable-core/schemas/session/events.js +11 -182
  98. package/dist/v2/durable-core/schemas/session/gaps.d.ts +211 -0
  99. package/dist/v2/durable-core/schemas/session/gaps.js +37 -0
  100. package/dist/v2/durable-core/schemas/session/outputs.d.ts +148 -0
  101. package/dist/v2/durable-core/schemas/session/outputs.js +44 -0
  102. package/dist/v2/durable-core/schemas/session/validation-event.js +5 -7
  103. package/dist/v2/durable-core/tokens/token-codec.d.ts +1 -18
  104. package/dist/v2/durable-core/tokens/token-codec.js +0 -67
  105. package/dist/v2/durable-core/tokens/token-signer.d.ts +1 -8
  106. package/dist/v2/durable-core/tokens/token-signer.js +0 -43
  107. package/dist/v2/infra/local/base32/index.js +1 -23
  108. package/dist/v2/infra/local/bech32m/index.js +1 -1
  109. package/dist/v2/infra/local/data-dir/index.d.ts +7 -6
  110. package/dist/v2/infra/local/data-dir/index.js +3 -3
  111. package/dist/v2/infra/local/directory-listing/index.d.ts +2 -2
  112. package/dist/v2/infra/local/session-store/index.js +198 -182
  113. package/dist/v2/infra/local/session-summary-provider/index.js +5 -2
  114. package/dist/v2/infra/local/snapshot-store/index.js +2 -2
  115. package/dist/v2/ports/data-dir.port.d.ts +7 -6
  116. package/dist/v2/ports/fs.port.d.ts +18 -7
  117. package/dist/v2/ports/session-event-log-store.port.d.ts +5 -2
  118. package/dist/v2/projections/advance-outcomes.d.ts +1 -7
  119. package/dist/v2/projections/advance-outcomes.js +2 -1
  120. package/dist/v2/projections/artifacts.js +3 -2
  121. package/dist/v2/projections/capabilities.d.ts +1 -7
  122. package/dist/v2/projections/capabilities.js +2 -1
  123. package/dist/v2/projections/gaps.d.ts +1 -7
  124. package/dist/v2/projections/gaps.js +2 -1
  125. package/dist/v2/projections/node-outputs.d.ts +1 -7
  126. package/dist/v2/projections/node-outputs.js +4 -3
  127. package/dist/v2/projections/preferences.d.ts +1 -7
  128. package/dist/v2/projections/preferences.js +2 -1
  129. package/dist/v2/projections/projection-error.d.ts +7 -0
  130. package/dist/v2/projections/projection-error.js +2 -0
  131. package/dist/v2/projections/resume-ranking.js +3 -3
  132. package/dist/v2/projections/run-context.d.ts +1 -7
  133. package/dist/v2/projections/run-context.js +4 -2
  134. package/dist/v2/projections/run-dag.d.ts +9 -9
  135. package/dist/v2/projections/run-dag.js +88 -65
  136. package/dist/v2/projections/run-status-signals.d.ts +1 -7
  137. package/dist/v2/projections/run-status-signals.js +3 -2
  138. package/dist/v2/usecases/execution-session-gate.js +2 -5
  139. package/dist/v2/usecases/export-session.js +4 -2
  140. package/dist/v2/usecases/import-session.d.ts +3 -3
  141. package/package.json +1 -1
@@ -16,29 +16,7 @@ class Base32AdapterV2 {
16
16
  position: invalidMatch?.index,
17
17
  });
18
18
  }
19
- try {
20
- const bytes = (0, base32_lower_js_1.decodeBase32LowerNoPad)(encoded);
21
- return (0, neverthrow_1.ok)(bytes);
22
- }
23
- catch (e) {
24
- const message = e instanceof Error ? e.message : String(e);
25
- if (/padding|non-canonical/i.test(message)) {
26
- return (0, neverthrow_1.err)({
27
- code: 'BASE32_NON_CANONICAL',
28
- message: `Non-canonical base32 encoding: ${message}`,
29
- });
30
- }
31
- if (/length/i.test(message)) {
32
- return (0, neverthrow_1.err)({
33
- code: 'BASE32_INVALID_LENGTH',
34
- message: `Invalid base32 length: ${message}`,
35
- });
36
- }
37
- return (0, neverthrow_1.err)({
38
- code: 'BASE32_INVALID_CHARACTERS',
39
- message: `Base32 decode failed: ${message}`,
40
- });
41
- }
19
+ return (0, base32_lower_js_1.decodeBase32LowerNoPad)(encoded);
42
20
  }
43
21
  }
44
22
  exports.Base32AdapterV2 = Base32AdapterV2;
@@ -22,7 +22,7 @@ class Bech32mAdapterV2 {
22
22
  }
23
23
  catch (e) {
24
24
  const msg = e instanceof Error ? e.message : String(e);
25
- if (/checksum/i.test(msg) || /invalid/i.test(msg)) {
25
+ if (/checksum/i.test(msg)) {
26
26
  return (0, neverthrow_1.err)({
27
27
  code: 'BECH32M_CHECKSUM_FAILED',
28
28
  message: `Bech32m checksum validation failed (likely corruption): ${msg}`,
@@ -1,18 +1,19 @@
1
1
  import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
2
+ import type { SessionId, WorkflowHash, SnapshotRef } from '../../../durable-core/ids/index.js';
2
3
  export declare class LocalDataDirV2 implements DataDirPortV2 {
3
4
  private readonly env;
4
5
  constructor(env: Record<string, string | undefined>);
5
6
  private safeFileSegment;
6
7
  private root;
7
8
  snapshotsDir(): string;
8
- snapshotPath(snapshotRef: string): string;
9
+ snapshotPath(snapshotRef: SnapshotRef): string;
9
10
  keysDir(): string;
10
11
  keyringPath(): string;
11
12
  pinnedWorkflowsDir(): string;
12
- pinnedWorkflowPath(workflowHash: string): string;
13
+ pinnedWorkflowPath(workflowHash: WorkflowHash): string;
13
14
  sessionsDir(): string;
14
- sessionDir(sessionId: string): string;
15
- sessionEventsDir(sessionId: string): string;
16
- sessionManifestPath(sessionId: string): string;
17
- sessionLockPath(sessionId: string): string;
15
+ sessionDir(sessionId: SessionId): string;
16
+ sessionEventsDir(sessionId: SessionId): string;
17
+ sessionManifestPath(sessionId: SessionId): string;
18
+ sessionLockPath(sessionId: SessionId): string;
18
19
  }
@@ -51,7 +51,7 @@ class LocalDataDirV2 {
51
51
  return path.join(this.root(), 'snapshots');
52
52
  }
53
53
  snapshotPath(snapshotRef) {
54
- return path.join(this.snapshotsDir(), `${this.safeFileSegment(snapshotRef)}.json`);
54
+ return path.join(this.snapshotsDir(), `${this.safeFileSegment(String(snapshotRef))}.json`);
55
55
  }
56
56
  keysDir() {
57
57
  return path.join(this.root(), 'keys');
@@ -63,13 +63,13 @@ class LocalDataDirV2 {
63
63
  return path.join(this.root(), 'workflows', 'pinned');
64
64
  }
65
65
  pinnedWorkflowPath(workflowHash) {
66
- return path.join(this.pinnedWorkflowsDir(), `${this.safeFileSegment(workflowHash)}.json`);
66
+ return path.join(this.pinnedWorkflowsDir(), `${this.safeFileSegment(String(workflowHash))}.json`);
67
67
  }
68
68
  sessionsDir() {
69
69
  return path.join(this.root(), 'sessions');
70
70
  }
71
71
  sessionDir(sessionId) {
72
- return path.join(this.sessionsDir(), sessionId);
72
+ return path.join(this.sessionsDir(), String(sessionId));
73
73
  }
74
74
  sessionEventsDir(sessionId) {
75
75
  return path.join(this.sessionDir(sessionId), 'events');
@@ -1,8 +1,8 @@
1
1
  import type { ResultAsync } from 'neverthrow';
2
- import type { FsError, FileSystemPortV2 } from '../../../ports/fs.port.js';
2
+ import type { FsError, DirectoryListingOpsPortV2 } from '../../../ports/fs.port.js';
3
3
  import type { DirectoryListingPortV2 } from '../../../ports/directory-listing.port.js';
4
4
  export declare class LocalDirectoryListingV2 implements DirectoryListingPortV2 {
5
5
  private readonly fs;
6
- constructor(fs: FileSystemPortV2);
6
+ constructor(fs: DirectoryListingOpsPortV2);
7
7
  readdir(dirPath: string): ResultAsync<readonly string[], FsError>;
8
8
  }
@@ -37,6 +37,7 @@ exports.LocalSessionEventLogStoreV2 = void 0;
37
37
  const neverthrow_1 = require("neverthrow");
38
38
  const jsonl_js_1 = require("../../../durable-core/canonical/jsonl.js");
39
39
  const index_js_1 = require("../../../durable-core/schemas/session/index.js");
40
+ const constants_js_1 = require("../../../durable-core/constants.js");
40
41
  const path = __importStar(require("path"));
41
42
  class LocalSessionEventLogStoreV2 {
42
43
  constructor(dataDir, fs, sha256) {
@@ -108,7 +109,7 @@ class LocalSessionEventLogStoreV2 {
108
109
  v: 1,
109
110
  manifestIndex: nextManifestIndex(manifest),
110
111
  sessionId,
111
- kind: 'segment_closed',
112
+ kind: constants_js_1.MANIFEST_KIND.SEGMENT_CLOSED,
112
113
  firstEventIndex: first,
113
114
  lastEventIndex: last,
114
115
  segmentRelPath,
@@ -125,7 +126,7 @@ class LocalSessionEventLogStoreV2 {
125
126
  v: 1,
126
127
  manifestIndex: startIndex + i,
127
128
  sessionId,
128
- kind: 'snapshot_pinned',
129
+ kind: constants_js_1.MANIFEST_KIND.SNAPSHOT_PINNED,
129
130
  eventIndex: p.eventIndex,
130
131
  snapshotRef: p.snapshotRef,
131
132
  createdByEventId: p.createdByEventId,
@@ -145,72 +146,16 @@ class LocalSessionEventLogStoreV2 {
145
146
  const segRes = validateSegmentClosedContiguity(manifest);
146
147
  if (segRes.isErr())
147
148
  return (0, neverthrow_1.errAsync)(segRes.error);
148
- const segments = manifest.filter((m) => m.kind === 'segment_closed');
149
- const loadSegments = (segs) => {
150
- if (segs.length === 0)
151
- return (0, neverthrow_1.okAsync)([]);
152
- const [head, ...tail] = segs;
153
- const segmentPath = path.join(sessionDir, head.segmentRelPath);
154
- return this.fs.readFileBytes(segmentPath)
155
- .mapErr((e) => {
156
- if (e.code === 'FS_NOT_FOUND') {
157
- return {
158
- code: 'SESSION_STORE_CORRUPTION_DETECTED',
159
- location: 'tail',
160
- reason: { code: 'missing_attested_segment', message: `Missing attested segment: ${head.segmentRelPath}` },
161
- message: `Missing attested segment: ${head.segmentRelPath}`,
162
- };
163
- }
164
- return mapFsToStoreError(e);
165
- })
166
- .andThen((bytes) => {
167
- const actual = this.sha256.sha256(bytes);
168
- if (actual !== head.sha256) {
169
- return (0, neverthrow_1.errAsync)({
170
- code: 'SESSION_STORE_CORRUPTION_DETECTED',
171
- location: 'tail',
172
- reason: { code: 'digest_mismatch', message: `Segment digest mismatch: ${head.segmentRelPath}` },
173
- message: `Segment digest mismatch: ${head.segmentRelPath}`,
174
- });
175
- }
176
- const parsedRes = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
177
- if (parsedRes.isErr())
178
- return (0, neverthrow_1.errAsync)(parsedRes.error);
179
- const parsed = parsedRes.value;
180
- if (parsed.length === 0) {
181
- return (0, neverthrow_1.errAsync)({
182
- code: 'SESSION_STORE_CORRUPTION_DETECTED',
183
- location: 'tail',
184
- reason: { code: 'non_contiguous_indices', message: `Empty segment referenced by manifest: ${head.segmentRelPath}` },
185
- message: `Empty segment referenced by manifest: ${head.segmentRelPath}`,
186
- });
187
- }
188
- if (parsed[0].eventIndex !== head.firstEventIndex || parsed[parsed.length - 1].eventIndex !== head.lastEventIndex) {
189
- return (0, neverthrow_1.errAsync)({
190
- code: 'SESSION_STORE_CORRUPTION_DETECTED',
191
- location: 'tail',
192
- reason: { code: 'non_contiguous_indices', message: `Segment bounds mismatch: ${head.segmentRelPath}` },
193
- message: `Segment bounds mismatch: ${head.segmentRelPath}`,
194
- });
195
- }
196
- for (let i = 1; i < parsed.length; i++) {
197
- if (parsed[i].eventIndex !== parsed[i - 1].eventIndex + 1) {
198
- return (0, neverthrow_1.errAsync)({
199
- code: 'SESSION_STORE_CORRUPTION_DETECTED',
200
- location: 'tail',
201
- reason: { code: 'non_contiguous_indices', message: `Non-contiguous eventIndex inside segment: ${head.segmentRelPath}` },
202
- message: `Non-contiguous eventIndex inside segment: ${head.segmentRelPath}`,
203
- });
204
- }
205
- }
206
- return (0, neverthrow_1.okAsync)(parsed);
207
- })
208
- .andThen((events) => loadSegments(tail).map((rest) => [...events, ...rest]));
209
- };
210
- return loadSegments(segments).andThen((events) => {
149
+ const segments = manifest.filter((m) => m.kind === constants_js_1.MANIFEST_KIND.SEGMENT_CLOSED);
150
+ return loadSegmentsRecursive({
151
+ segments,
152
+ sessionDir,
153
+ sha256: this.sha256,
154
+ fs: this.fs,
155
+ }).andThen((events) => {
211
156
  const expectedPins = extractSnapshotPinsFromEvents(events);
212
157
  const actualPins = new Set(manifest
213
- .filter((m) => m.kind === 'snapshot_pinned')
158
+ .filter((m) => m.kind === constants_js_1.MANIFEST_KIND.SNAPSHOT_PINNED)
214
159
  .map((p) => `${p.eventIndex}:${p.createdByEventId}:${p.snapshotRef}`));
215
160
  for (const ep of expectedPins) {
216
161
  const key = `${ep.eventIndex}:${ep.createdByEventId}:${ep.snapshotRef}`;
@@ -247,44 +192,10 @@ class LocalSessionEventLogStoreV2 {
247
192
  .orElse((e) => (e.code === 'FS_NOT_FOUND' ? (0, neverthrow_1.okAsync)('') : (0, neverthrow_1.errAsync)(mapFsToStoreError(e))))
248
193
  .andThen((raw) => {
249
194
  if (raw.trim() === '') {
250
- return (0, neverthrow_1.okAsync)({ truth: { manifest: [], events: [] }, isComplete: true, tailReason: null });
195
+ return (0, neverthrow_1.okAsync)({ kind: 'complete', truth: { manifest: [], events: [] } });
251
196
  }
252
197
  const lines = raw.split('\n').filter((l) => l.trim() !== '');
253
- const manifest = [];
254
- let isComplete = true;
255
- let tailReason = null;
256
- for (let i = 0; i < lines.length; i++) {
257
- const line = lines[i];
258
- let parsed;
259
- try {
260
- parsed = JSON.parse(line);
261
- }
262
- catch {
263
- isComplete = false;
264
- tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: 'Invalid JSON in manifest (corrupt tail)' });
265
- break;
266
- }
267
- const validated = index_js_1.ManifestRecordV1Schema.safeParse(parsed);
268
- if (!validated.success) {
269
- isComplete = false;
270
- const rawVersion = (typeof parsed === 'object' && parsed !== null && 'v' in parsed)
271
- ? parsed.v
272
- : undefined;
273
- if (rawVersion !== 1) {
274
- tailReason ?? (tailReason = { code: 'unknown_schema_version', message: `Expected v=1, got v=${rawVersion}` });
275
- }
276
- else {
277
- tailReason ?? (tailReason = { code: 'schema_validation_failed', message: 'Manifest record schema validation failed (corrupt tail)' });
278
- }
279
- break;
280
- }
281
- if (validated.data.manifestIndex !== i) {
282
- isComplete = false;
283
- tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: 'Non-contiguous manifestIndex in prefix (corrupt tail)' });
284
- break;
285
- }
286
- manifest.push(validated.data);
287
- }
198
+ const { manifest, isComplete, tailReason } = parseManifestPrefix(lines);
288
199
  if (manifest.length === 0) {
289
200
  return (0, neverthrow_1.errAsync)({
290
201
  code: 'SESSION_STORE_CORRUPTION_DETECTED',
@@ -293,84 +204,29 @@ class LocalSessionEventLogStoreV2 {
293
204
  message: 'No validated manifest prefix',
294
205
  });
295
206
  }
296
- const segments = manifest.filter((m) => m.kind === 'segment_closed');
207
+ const segments = manifest.filter((m) => m.kind === constants_js_1.MANIFEST_KIND.SEGMENT_CLOSED);
297
208
  const initial = { events: [], isComplete, tailReason, done: false };
298
- const processSegment = (seg, state) => {
299
- if (state.done)
300
- return (0, neverthrow_1.okAsync)(state);
301
- const segmentPath = path.join(sessionDir, seg.segmentRelPath);
302
- return this.fs
303
- .readFileBytes(segmentPath)
304
- .map((bytes) => ({ kind: 'present', bytes }))
305
- .orElse((e) => (e.code === 'FS_NOT_FOUND' ? (0, neverthrow_1.okAsync)({ kind: 'missing' }) : (0, neverthrow_1.errAsync)(mapFsToStoreError(e))))
306
- .andThen((res) => {
307
- if (res.kind === 'missing') {
308
- return (0, neverthrow_1.okAsync)({
309
- ...state,
310
- isComplete: false,
311
- tailReason: state.tailReason ?? { code: 'missing_attested_segment', message: `Missing attested segment: ${seg.segmentRelPath}` },
312
- done: true,
313
- });
314
- }
315
- const bytes = res.bytes;
316
- const actual = this.sha256.sha256(bytes);
317
- if (actual !== seg.sha256) {
318
- return (0, neverthrow_1.okAsync)({
319
- ...state,
320
- isComplete: false,
321
- tailReason: state.tailReason ?? { code: 'digest_mismatch', message: `Segment digest mismatch: ${seg.segmentRelPath}` },
322
- done: true,
323
- });
324
- }
325
- const parsedRes = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
326
- if (parsedRes.isErr()) {
327
- return (0, neverthrow_1.okAsync)({
328
- ...state,
329
- isComplete: false,
330
- tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Invalid JSONL inside segment: ${seg.segmentRelPath}` },
331
- done: true,
332
- });
333
- }
334
- const parsed = parsedRes.value;
335
- if (parsed.length === 0) {
336
- return (0, neverthrow_1.okAsync)({
337
- ...state,
338
- isComplete: false,
339
- tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Empty segment referenced by manifest: ${seg.segmentRelPath}` },
340
- done: true,
341
- });
342
- }
343
- if (parsed[0].eventIndex !== seg.firstEventIndex || parsed[parsed.length - 1].eventIndex !== seg.lastEventIndex) {
344
- return (0, neverthrow_1.okAsync)({
345
- ...state,
346
- isComplete: false,
347
- tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Segment bounds mismatch: ${seg.segmentRelPath}` },
348
- done: true,
349
- });
350
- }
351
- for (let i = 1; i < parsed.length; i++) {
352
- if (parsed[i].eventIndex !== parsed[i - 1].eventIndex + 1) {
353
- return (0, neverthrow_1.okAsync)({
354
- ...state,
355
- isComplete: false,
356
- tailReason: state.tailReason ?? { code: 'non_contiguous_indices', message: `Non-contiguous eventIndex inside segment: ${seg.segmentRelPath}` },
357
- done: true,
358
- });
359
- }
360
- }
361
- return (0, neverthrow_1.okAsync)({
362
- ...state,
363
- events: [...state.events, ...parsed],
364
- });
365
- });
366
- };
367
209
  return segments
368
- .reduce((acc, seg) => acc.andThen((s) => processSegment(seg, s)), (0, neverthrow_1.okAsync)(initial))
369
- .map((final) => ({
370
- truth: { manifest, events: final.events },
371
- isComplete: final.isComplete,
372
- tailReason: final.tailReason,
373
- }));
210
+ .reduce((acc, seg) => acc.andThen((s) => processSegmentForSalvage({
211
+ segment: seg,
212
+ sessionDir,
213
+ sha256: this.sha256,
214
+ fs: this.fs,
215
+ state: s,
216
+ })), (0, neverthrow_1.okAsync)(initial))
217
+ .map((final) => {
218
+ if (final.isComplete) {
219
+ return { kind: 'complete', truth: { manifest, events: final.events } };
220
+ }
221
+ return {
222
+ kind: 'truncated',
223
+ truth: { manifest, events: final.events },
224
+ tailReason: final.tailReason ?? {
225
+ code: 'non_contiguous_indices',
226
+ message: 'Unknown truncation reason',
227
+ },
228
+ };
229
+ });
374
230
  });
375
231
  }
376
232
  loadTruthOrEmpty(sessionId) {
@@ -378,7 +234,7 @@ class LocalSessionEventLogStoreV2 {
378
234
  .andThen((manifest) => {
379
235
  if (manifest.length === 0)
380
236
  return (0, neverthrow_1.okAsync)({ manifest: [], events: [] });
381
- const segments = manifest.filter((m) => m.kind === 'segment_closed');
237
+ const segments = manifest.filter((m) => m.kind === constants_js_1.MANIFEST_KIND.SEGMENT_CLOSED);
382
238
  const sessionDir = this.dataDir.sessionDir(sessionId);
383
239
  const loadSegments = (segs) => {
384
240
  if (segs.length === 0)
@@ -425,7 +281,7 @@ function nextManifestIndex(manifest) {
425
281
  return manifest[manifest.length - 1].manifestIndex + 1;
426
282
  }
427
283
  function nextEventIndexFromManifest(manifest) {
428
- const segments = manifest.filter((m) => m.kind === 'segment_closed');
284
+ const segments = manifest.filter((m) => m.kind === constants_js_1.MANIFEST_KIND.SEGMENT_CLOSED);
429
285
  if (segments.length === 0)
430
286
  return 0;
431
287
  return segments[segments.length - 1].lastEventIndex + 1;
@@ -448,7 +304,7 @@ function validateManifestContiguity(manifest) {
448
304
  return (0, neverthrow_1.ok)(undefined);
449
305
  }
450
306
  function validateSegmentClosedContiguity(manifest) {
451
- const segments = manifest.filter((m) => m.kind === 'segment_closed');
307
+ const segments = manifest.filter((m) => m.kind === constants_js_1.MANIFEST_KIND.SEGMENT_CLOSED);
452
308
  for (let i = 1; i < segments.length; i++) {
453
309
  const prev = segments[i - 1];
454
310
  const cur = segments[i];
@@ -589,9 +445,169 @@ function parseJsonlLines(bytes, schema) {
589
445
  function extractSnapshotPinsFromEvents(events) {
590
446
  const out = [];
591
447
  for (const e of events) {
592
- if (e.kind !== 'node_created')
448
+ if (e.kind !== constants_js_1.EVENT_KIND.NODE_CREATED)
593
449
  continue;
594
450
  out.push({ snapshotRef: e.data.snapshotRef, eventIndex: e.eventIndex, createdByEventId: e.eventId });
595
451
  }
596
452
  return out;
597
453
  }
454
+ function validateSegmentBounds(parsed, segment) {
455
+ if (parsed.length === 0) {
456
+ return (0, neverthrow_1.err)({
457
+ code: 'SESSION_STORE_CORRUPTION_DETECTED',
458
+ location: 'tail',
459
+ reason: { code: 'non_contiguous_indices', message: `Empty segment referenced by manifest: ${segment.segmentRelPath}` },
460
+ message: `Empty segment referenced by manifest: ${segment.segmentRelPath}`,
461
+ });
462
+ }
463
+ if (parsed[0].eventIndex !== segment.firstEventIndex || parsed[parsed.length - 1].eventIndex !== segment.lastEventIndex) {
464
+ return (0, neverthrow_1.err)({
465
+ code: 'SESSION_STORE_CORRUPTION_DETECTED',
466
+ location: 'tail',
467
+ reason: { code: 'non_contiguous_indices', message: `Segment bounds mismatch: ${segment.segmentRelPath}` },
468
+ message: `Segment bounds mismatch: ${segment.segmentRelPath}`,
469
+ });
470
+ }
471
+ for (let i = 1; i < parsed.length; i++) {
472
+ if (parsed[i].eventIndex !== parsed[i - 1].eventIndex + 1) {
473
+ return (0, neverthrow_1.err)({
474
+ code: 'SESSION_STORE_CORRUPTION_DETECTED',
475
+ location: 'tail',
476
+ reason: { code: 'non_contiguous_indices', message: `Non-contiguous eventIndex inside segment: ${segment.segmentRelPath}` },
477
+ message: `Non-contiguous eventIndex inside segment: ${segment.segmentRelPath}`,
478
+ });
479
+ }
480
+ }
481
+ return (0, neverthrow_1.ok)(undefined);
482
+ }
483
+ function parseManifestPrefix(lines) {
484
+ const manifest = [];
485
+ let isComplete = true;
486
+ let tailReason = null;
487
+ for (let i = 0; i < lines.length; i++) {
488
+ const line = lines[i];
489
+ let parsed;
490
+ try {
491
+ parsed = JSON.parse(line);
492
+ }
493
+ catch {
494
+ isComplete = false;
495
+ tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: 'Invalid JSON in manifest (corrupt tail)' });
496
+ break;
497
+ }
498
+ const validated = index_js_1.ManifestRecordV1Schema.safeParse(parsed);
499
+ if (!validated.success) {
500
+ isComplete = false;
501
+ const rawVersion = (typeof parsed === 'object' && parsed !== null && 'v' in parsed)
502
+ ? parsed.v
503
+ : undefined;
504
+ if (rawVersion !== 1) {
505
+ tailReason ?? (tailReason = { code: 'unknown_schema_version', message: `Expected v=1, got v=${rawVersion}` });
506
+ }
507
+ else {
508
+ tailReason ?? (tailReason = { code: 'schema_validation_failed', message: 'Manifest record schema validation failed (corrupt tail)' });
509
+ }
510
+ break;
511
+ }
512
+ if (validated.data.manifestIndex !== i) {
513
+ isComplete = false;
514
+ tailReason ?? (tailReason = { code: 'non_contiguous_indices', message: 'Non-contiguous manifestIndex in prefix (corrupt tail)' });
515
+ break;
516
+ }
517
+ manifest.push(validated.data);
518
+ }
519
+ return { manifest, isComplete, tailReason };
520
+ }
521
+ function processSegmentForSalvage(args) {
522
+ if (args.state.done)
523
+ return (0, neverthrow_1.okAsync)(args.state);
524
+ const segmentPath = path.join(args.sessionDir, args.segment.segmentRelPath);
525
+ return args.fs
526
+ .readFileBytes(segmentPath)
527
+ .map((bytes) => ({ kind: 'present', bytes }))
528
+ .orElse((e) => (e.code === 'FS_NOT_FOUND' ? (0, neverthrow_1.okAsync)({ kind: 'missing' }) : (0, neverthrow_1.errAsync)(mapFsToStoreError(e))))
529
+ .andThen((res) => {
530
+ if (res.kind === 'missing') {
531
+ return (0, neverthrow_1.okAsync)({
532
+ ...args.state,
533
+ isComplete: false,
534
+ tailReason: args.state.tailReason ?? { code: 'missing_attested_segment', message: `Missing attested segment: ${args.segment.segmentRelPath}` },
535
+ done: true,
536
+ });
537
+ }
538
+ const bytes = res.bytes;
539
+ const actual = args.sha256.sha256(bytes);
540
+ if (actual !== args.segment.sha256) {
541
+ return (0, neverthrow_1.okAsync)({
542
+ ...args.state,
543
+ isComplete: false,
544
+ tailReason: args.state.tailReason ?? { code: 'digest_mismatch', message: `Segment digest mismatch: ${args.segment.segmentRelPath}` },
545
+ done: true,
546
+ });
547
+ }
548
+ const parsedRes = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
549
+ if (parsedRes.isErr()) {
550
+ return (0, neverthrow_1.okAsync)({
551
+ ...args.state,
552
+ isComplete: false,
553
+ tailReason: args.state.tailReason ?? { code: 'non_contiguous_indices', message: `Invalid JSONL inside segment: ${args.segment.segmentRelPath}` },
554
+ done: true,
555
+ });
556
+ }
557
+ const parsed = parsedRes.value;
558
+ const boundsResult = validateSegmentBounds(parsed, args.segment);
559
+ if (boundsResult.isErr()) {
560
+ const reason = boundsResult.error.code === 'SESSION_STORE_CORRUPTION_DETECTED'
561
+ ? boundsResult.error.reason
562
+ : { code: 'non_contiguous_indices', message: boundsResult.error.message };
563
+ return (0, neverthrow_1.okAsync)({
564
+ ...args.state,
565
+ isComplete: false,
566
+ tailReason: args.state.tailReason ?? reason,
567
+ done: true,
568
+ });
569
+ }
570
+ return (0, neverthrow_1.okAsync)({
571
+ ...args.state,
572
+ events: [...args.state.events, ...parsed],
573
+ });
574
+ });
575
+ }
576
+ function loadSegmentsRecursive(args) {
577
+ if (args.segments.length === 0)
578
+ return (0, neverthrow_1.okAsync)([]);
579
+ const [head, ...tail] = args.segments;
580
+ const segmentPath = path.join(args.sessionDir, head.segmentRelPath);
581
+ return args.fs.readFileBytes(segmentPath)
582
+ .mapErr((e) => {
583
+ if (e.code === 'FS_NOT_FOUND') {
584
+ return {
585
+ code: 'SESSION_STORE_CORRUPTION_DETECTED',
586
+ location: 'tail',
587
+ reason: { code: 'missing_attested_segment', message: `Missing attested segment: ${head.segmentRelPath}` },
588
+ message: `Missing attested segment: ${head.segmentRelPath}`,
589
+ };
590
+ }
591
+ return mapFsToStoreError(e);
592
+ })
593
+ .andThen((bytes) => {
594
+ const actual = args.sha256.sha256(bytes);
595
+ if (actual !== head.sha256) {
596
+ return (0, neverthrow_1.errAsync)({
597
+ code: 'SESSION_STORE_CORRUPTION_DETECTED',
598
+ location: 'tail',
599
+ reason: { code: 'digest_mismatch', message: `Segment digest mismatch: ${head.segmentRelPath}` },
600
+ message: `Segment digest mismatch: ${head.segmentRelPath}`,
601
+ });
602
+ }
603
+ const parsedRes = parseJsonlLines(bytes, index_js_1.DomainEventV1Schema);
604
+ if (parsedRes.isErr())
605
+ return (0, neverthrow_1.errAsync)(parsedRes.error);
606
+ const parsed = parsedRes.value;
607
+ const boundsResult = validateSegmentBounds(parsed, head);
608
+ if (boundsResult.isErr())
609
+ return (0, neverthrow_1.errAsync)(boundsResult.error);
610
+ return (0, neverthrow_1.okAsync)(parsed);
611
+ })
612
+ .andThen((events) => loadSegmentsRecursive({ ...args, segments: tail }).map((rest) => [...events, ...rest]));
613
+ }
@@ -7,6 +7,7 @@ const enumerate_sessions_js_1 = require("../../../usecases/enumerate-sessions.js
7
7
  const session_health_js_1 = require("../../../projections/session-health.js");
8
8
  const run_dag_js_1 = require("../../../projections/run-dag.js");
9
9
  const node_outputs_js_1 = require("../../../projections/node-outputs.js");
10
+ const constants_js_1 = require("../../../durable-core/constants.js");
10
11
  const MAX_SESSIONS_TO_SCAN = 50;
11
12
  class LocalSessionSummaryProviderV2 {
12
13
  constructor(ports) {
@@ -107,7 +108,7 @@ function extractObservations(events) {
107
108
  let gitBranch = null;
108
109
  let repoRootHash = null;
109
110
  for (const event of events) {
110
- if (event.kind !== 'observation_recorded')
111
+ if (event.kind !== constants_js_1.EVENT_KIND.OBSERVATION_RECORDED)
111
112
  continue;
112
113
  const data = event.data;
113
114
  switch (data.key) {
@@ -120,13 +121,15 @@ function extractObservations(events) {
120
121
  case 'repo_root_hash':
121
122
  repoRootHash = data.value.value;
122
123
  break;
124
+ default:
125
+ break;
123
126
  }
124
127
  }
125
128
  return { gitHeadSha, gitBranch, repoRootHash };
126
129
  }
127
130
  function extractWorkflowIdentity(events, runId) {
128
131
  for (const event of events) {
129
- if (event.kind !== 'run_started')
132
+ if (event.kind !== constants_js_1.EVENT_KIND.RUN_STARTED)
130
133
  continue;
131
134
  const scope = event.scope;
132
135
  if (scope?.runId !== runId)
@@ -20,7 +20,7 @@ class LocalSnapshotStoreV2 {
20
20
  return (0, neverthrow_1.errAsync)(canonical.error);
21
21
  const ref = (0, index_js_1.asSnapshotRef)(this.crypto.sha256(canonical.value));
22
22
  const dir = this.dataDir.snapshotsDir();
23
- const filePath = this.dataDir.snapshotPath(String(ref));
23
+ const filePath = this.dataDir.snapshotPath(ref);
24
24
  const tmpPath = `${filePath}.tmp`;
25
25
  return this.fs
26
26
  .mkdirp(dir)
@@ -35,7 +35,7 @@ class LocalSnapshotStoreV2 {
35
35
  .mapErr((e) => ({ code: 'SNAPSHOT_STORE_IO_ERROR', message: e.message }));
36
36
  }
37
37
  getExecutionSnapshotV1(snapshotRef) {
38
- const filePath = this.dataDir.snapshotPath(String(snapshotRef));
38
+ const filePath = this.dataDir.snapshotPath(snapshotRef);
39
39
  return this.fs
40
40
  .readFileBytes(filePath)
41
41
  .andThen((bytes) => {
@@ -1,13 +1,14 @@
1
+ import type { SessionId, WorkflowHash, SnapshotRef } from '../durable-core/ids/index.js';
1
2
  export interface DataDirPortV2 {
2
3
  pinnedWorkflowsDir(): string;
3
- pinnedWorkflowPath(workflowHash: string): string;
4
+ pinnedWorkflowPath(workflowHash: WorkflowHash): string;
4
5
  snapshotsDir(): string;
5
- snapshotPath(snapshotRef: string): string;
6
+ snapshotPath(snapshotRef: SnapshotRef): string;
6
7
  keysDir(): string;
7
8
  keyringPath(): string;
8
9
  sessionsDir(): string;
9
- sessionDir(sessionId: string): string;
10
- sessionEventsDir(sessionId: string): string;
11
- sessionManifestPath(sessionId: string): string;
12
- sessionLockPath(sessionId: string): string;
10
+ sessionDir(sessionId: SessionId): string;
11
+ sessionEventsDir(sessionId: SessionId): string;
12
+ sessionManifestPath(sessionId: SessionId): string;
13
+ sessionLockPath(sessionId: SessionId): string;
13
14
  }