@exaudeus/workrail 0.11.0 → 0.13.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.
Files changed (131) hide show
  1. package/dist/application/services/enhanced-loop-validator.js +3 -3
  2. package/dist/application/services/step-output-decoder.d.ts +6 -0
  3. package/dist/application/services/step-output-decoder.js +49 -0
  4. package/dist/application/services/validation-engine.d.ts +9 -0
  5. package/dist/application/services/validation-engine.js +142 -18
  6. package/dist/application/services/workflow-interpreter.d.ts +1 -1
  7. package/dist/application/services/workflow-interpreter.js +147 -81
  8. package/dist/application/services/workflow-service.d.ts +2 -0
  9. package/dist/application/services/workflow-service.js +3 -3
  10. package/dist/application/use-cases/validate-step-output.d.ts +2 -0
  11. package/dist/config/feature-flags.js +1 -1
  12. package/dist/di/container.js +88 -0
  13. package/dist/di/tokens.d.ts +16 -0
  14. package/dist/di/tokens.js +16 -0
  15. package/dist/domain/execution/state.d.ts +6 -6
  16. package/dist/domain/workflow-id-policy.d.ts +17 -0
  17. package/dist/domain/workflow-id-policy.js +57 -0
  18. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +33 -6
  19. package/dist/infrastructure/storage/file-workflow-storage.js +3 -1
  20. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +13 -8
  21. package/dist/manifest.json +329 -161
  22. package/dist/mcp/error-mapper.d.ts +3 -8
  23. package/dist/mcp/error-mapper.js +41 -19
  24. package/dist/mcp/handlers/session.js +25 -11
  25. package/dist/mcp/handlers/v2-execution-helpers.d.ts +99 -0
  26. package/dist/mcp/handlers/v2-execution-helpers.js +249 -0
  27. package/dist/mcp/handlers/v2-execution.d.ts +4 -0
  28. package/dist/mcp/handlers/v2-execution.js +1044 -0
  29. package/dist/mcp/handlers/v2-workflow.js +21 -16
  30. package/dist/mcp/handlers/workflow.js +21 -12
  31. package/dist/mcp/index.d.ts +1 -1
  32. package/dist/mcp/index.js +4 -1
  33. package/dist/mcp/output-schemas.d.ts +411 -4
  34. package/dist/mcp/output-schemas.js +57 -1
  35. package/dist/mcp/server.d.ts +1 -1
  36. package/dist/mcp/server.js +96 -65
  37. package/dist/mcp/tool-descriptions.js +32 -15
  38. package/dist/mcp/tools.js +26 -14
  39. package/dist/mcp/types/tool-description-types.d.ts +1 -1
  40. package/dist/mcp/types/tool-description-types.js +7 -5
  41. package/dist/mcp/types.d.ts +40 -3
  42. package/dist/mcp/types.js +32 -3
  43. package/dist/mcp/v2/tool-registry.js +16 -1
  44. package/dist/mcp/v2/tools.d.ts +45 -0
  45. package/dist/mcp/v2/tools.js +21 -1
  46. package/dist/mcp/validation/workflow-next-prevalidate.d.ts +2 -3
  47. package/dist/mcp/validation/workflow-next-prevalidate.js +38 -27
  48. package/dist/v2/durable-core/constants.d.ts +15 -0
  49. package/dist/v2/durable-core/constants.js +18 -0
  50. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +32 -0
  51. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +95 -0
  52. package/dist/v2/durable-core/domain/loop-runtime.d.ts +50 -0
  53. package/dist/v2/durable-core/domain/loop-runtime.js +95 -0
  54. package/dist/v2/durable-core/domain/notes-markdown.d.ts +4 -0
  55. package/dist/v2/durable-core/domain/notes-markdown.js +46 -0
  56. package/dist/v2/durable-core/domain/outputs.d.ts +12 -0
  57. package/dist/v2/durable-core/domain/outputs.js +18 -0
  58. package/dist/v2/durable-core/ids/index.d.ts +2 -0
  59. package/dist/v2/durable-core/ids/index.js +4 -0
  60. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +100 -6
  61. package/dist/v2/durable-core/schemas/compiled-workflow/index.js +18 -3
  62. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +113 -113
  63. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +11 -10
  64. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +7129 -0
  65. package/dist/v2/durable-core/schemas/export-bundle/index.js +82 -0
  66. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.d.ts +80 -0
  67. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.js +38 -0
  68. package/dist/v2/durable-core/schemas/lib/dedupe-key.d.ts +8 -0
  69. package/dist/v2/durable-core/schemas/lib/dedupe-key.js +28 -0
  70. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.d.ts +6 -0
  71. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.js +12 -0
  72. package/dist/v2/durable-core/schemas/session/events.d.ts +238 -62
  73. package/dist/v2/durable-core/schemas/session/events.js +74 -29
  74. package/dist/v2/durable-core/schemas/session/manifest.d.ts +3 -3
  75. package/dist/v2/durable-core/schemas/session/manifest.js +6 -1
  76. package/dist/v2/durable-core/schemas/session/preferences.d.ts +5 -0
  77. package/dist/v2/durable-core/schemas/session/preferences.js +6 -0
  78. package/dist/v2/durable-core/schemas/session/session-health.d.ts +3 -0
  79. package/dist/v2/durable-core/tokens/index.d.ts +2 -1
  80. package/dist/v2/durable-core/tokens/index.js +4 -4
  81. package/dist/v2/durable-core/tokens/payloads.d.ts +4 -4
  82. package/dist/v2/durable-core/tokens/token-codec.d.ts +3 -2
  83. package/dist/v2/durable-core/tokens/token-codec.js +12 -6
  84. package/dist/v2/durable-core/tokens/token-signer.d.ts +3 -2
  85. package/dist/v2/durable-core/tokens/token-signer.js +8 -9
  86. package/dist/v2/infra/local/base64url/index.d.ts +5 -0
  87. package/dist/v2/infra/local/base64url/index.js +48 -0
  88. package/dist/v2/infra/local/fs/index.js +8 -4
  89. package/dist/v2/infra/local/keyring/index.d.ts +5 -1
  90. package/dist/v2/infra/local/keyring/index.js +41 -32
  91. package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +6 -4
  92. package/dist/v2/infra/local/pinned-workflow-store/index.js +50 -62
  93. package/dist/v2/infra/local/random-entropy/index.d.ts +4 -0
  94. package/dist/v2/infra/local/random-entropy/index.js +10 -0
  95. package/dist/v2/infra/local/session-lock/index.d.ts +3 -1
  96. package/dist/v2/infra/local/session-lock/index.js +5 -4
  97. package/dist/v2/infra/local/session-store/index.d.ts +0 -1
  98. package/dist/v2/infra/local/session-store/index.js +372 -282
  99. package/dist/v2/infra/local/snapshot-store/index.js +20 -25
  100. package/dist/v2/infra/local/time-clock/index.d.ts +5 -0
  101. package/dist/v2/infra/local/time-clock/index.js +12 -0
  102. package/dist/v2/infra/local/utf8/index.d.ts +5 -0
  103. package/dist/v2/infra/local/utf8/index.js +12 -0
  104. package/dist/v2/ports/base64url.port.d.ts +12 -0
  105. package/dist/v2/ports/base64url.port.js +2 -0
  106. package/dist/v2/ports/pinned-workflow-store.port.d.ts +3 -3
  107. package/dist/v2/ports/random-entropy.port.d.ts +3 -0
  108. package/dist/v2/ports/random-entropy.port.js +2 -0
  109. package/dist/v2/ports/session-event-log-store.port.d.ts +1 -1
  110. package/dist/v2/ports/session-lock.port.d.ts +1 -1
  111. package/dist/v2/ports/time-clock.port.d.ts +4 -0
  112. package/dist/v2/ports/time-clock.port.js +2 -0
  113. package/dist/v2/ports/utf8.port.d.ts +3 -0
  114. package/dist/v2/ports/utf8.port.js +2 -0
  115. package/dist/v2/projections/node-outputs.js +28 -11
  116. package/dist/v2/projections/preferences.d.ts +1 -2
  117. package/dist/v2/projections/preferences.js +11 -4
  118. package/dist/v2/projections/run-dag.js +40 -28
  119. package/dist/v2/projections/run-status-signals.d.ts +1 -2
  120. package/dist/v2/read-only/v1-to-v2-shim.d.ts +6 -1
  121. package/dist/v2/read-only/v1-to-v2-shim.js +16 -4
  122. package/dist/v2/usecases/execution-session-gate.d.ts +3 -2
  123. package/dist/v2/usecases/execution-session-gate.js +81 -85
  124. package/package.json +4 -1
  125. package/spec/workflow.schema.json +2 -2
  126. package/workflows/coding-task-workflow-agentic.json +498 -78
  127. package/workflows/design-thinking-workflow-autonomous.agentic.json +1 -1
  128. package/workflows/design-thinking-workflow.json +1 -1
  129. package/workflows/relocation-workflow-us.json +430 -0
  130. package/dist/v2/durable-core/tokens/base64url.d.ts +0 -7
  131. package/dist/v2/durable-core/tokens/base64url.js +0 -16
@@ -4,10 +4,21 @@ exports.DomainEventV1Schema = exports.DomainEventEnvelopeV1Schema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const json_zod_js_1 = require("../../canonical/json-zod.js");
6
6
  const index_js_1 = require("../../ids/index.js");
7
+ const preferences_js_1 = require("./preferences.js");
8
+ const constants_js_1 = require("../../constants.js");
9
+ const decision_trace_ref_js_1 = require("../lib/decision-trace-ref.js");
10
+ const dedupe_key_js_1 = require("../lib/dedupe-key.js");
11
+ const utf8_bounded_string_js_1 = require("../lib/utf8-bounded-string.js");
12
+ function utf8ByteLength(s) {
13
+ return new TextEncoder().encode(s).length;
14
+ }
7
15
  const sha256DigestSchema = zod_1.z
8
16
  .string()
9
- .regex(/^sha256:[0-9a-f]{64}$/, 'Expected sha256:<64 hex chars>')
17
+ .regex(constants_js_1.SHA256_DIGEST_PATTERN, 'Expected sha256:<64 hex chars>')
10
18
  .describe('sha256 digest in WorkRail v2 format');
19
+ const workflowHashSchema = sha256DigestSchema
20
+ .transform((v) => (0, index_js_1.asWorkflowHash)((0, index_js_1.asSha256Digest)(v)))
21
+ .describe('WorkflowHash (sha256 digest of workflow definition)');
11
22
  const snapshotRefSchema = sha256DigestSchema
12
23
  .transform((v) => (0, index_js_1.asSnapshotRef)((0, index_js_1.asSha256Digest)(v)))
13
24
  .describe('SnapshotRef (content-addressed sha256 ref)');
@@ -17,7 +28,7 @@ exports.DomainEventEnvelopeV1Schema = zod_1.z.object({
17
28
  eventIndex: zod_1.z.number().int().nonnegative(),
18
29
  sessionId: zod_1.z.string().min(1),
19
30
  kind: zod_1.z.string().min(1),
20
- dedupeKey: zod_1.z.string().min(1),
31
+ dedupeKey: dedupe_key_js_1.DedupeKeyV1Schema,
21
32
  scope: zod_1.z
22
33
  .object({
23
34
  runId: zod_1.z.string().min(1).optional(),
@@ -29,7 +40,7 @@ exports.DomainEventEnvelopeV1Schema = zod_1.z.object({
29
40
  const WorkflowSourceKindSchema = zod_1.z.enum(['bundled', 'user', 'project', 'remote', 'plugin']);
30
41
  const RunStartedDataV1Schema = zod_1.z.object({
31
42
  workflowId: zod_1.z.string().min(1),
32
- workflowHash: sha256DigestSchema,
43
+ workflowHash: workflowHashSchema,
33
44
  workflowSourceKind: WorkflowSourceKindSchema,
34
45
  workflowSourceRef: zod_1.z.string().min(1),
35
46
  });
@@ -37,7 +48,7 @@ const NodeKindSchema = zod_1.z.enum(['step', 'checkpoint']);
37
48
  const NodeCreatedDataV1Schema = zod_1.z.object({
38
49
  nodeKind: NodeKindSchema,
39
50
  parentNodeId: zod_1.z.string().min(1).nullable(),
40
- workflowHash: sha256DigestSchema,
51
+ workflowHash: workflowHashSchema,
41
52
  snapshotRef: snapshotRefSchema,
42
53
  });
43
54
  const EdgeKindSchema = zod_1.z.enum(['acked_step', 'checkpoint']);
@@ -65,7 +76,12 @@ const EdgeCreatedDataV1Schema = zod_1.z
65
76
  const OutputChannelSchema = zod_1.z.enum(['recap', 'artifact']);
66
77
  const NotesPayloadV1Schema = zod_1.z.object({
67
78
  payloadKind: zod_1.z.literal('notes'),
68
- notesMarkdown: zod_1.z.string().min(1),
79
+ notesMarkdown: zod_1.z
80
+ .string()
81
+ .min(1)
82
+ .refine((s) => utf8ByteLength(s) <= constants_js_1.MAX_OUTPUT_NOTES_MARKDOWN_BYTES, {
83
+ message: `notesMarkdown exceeds max ${constants_js_1.MAX_OUTPUT_NOTES_MARKDOWN_BYTES} UTF-8 bytes`,
84
+ }),
69
85
  });
70
86
  const ArtifactRefPayloadV1Schema = zod_1.z.object({
71
87
  payloadKind: zod_1.z.literal('artifact_ref'),
@@ -100,31 +116,57 @@ const BlockerCodeSchema = zod_1.z.enum([
100
116
  'STORAGE_CORRUPTION_DETECTED',
101
117
  ]);
102
118
  const BlockerPointerSchema = zod_1.z.discriminatedUnion('kind', [
103
- zod_1.z.object({ kind: zod_1.z.literal('context_key'), key: zod_1.z.string().min(1) }),
119
+ zod_1.z.object({ kind: zod_1.z.literal('context_key'), key: zod_1.z.string().min(1).regex(constants_js_1.DELIMITER_SAFE_ID_PATTERN, 'context_key must be delimiter-safe: [a-z0-9_-]+') }),
120
+ zod_1.z.object({ kind: zod_1.z.literal('context_budget') }),
104
121
  zod_1.z.object({ kind: zod_1.z.literal('output_contract'), contractRef: zod_1.z.string().min(1) }),
105
122
  zod_1.z.object({ kind: zod_1.z.literal('capability'), capability: zod_1.z.enum(['delegation', 'web_browsing']) }),
106
- zod_1.z.object({ kind: zod_1.z.literal('workflow_step'), stepId: zod_1.z.string().min(1) }),
123
+ zod_1.z.object({ kind: zod_1.z.literal('workflow_step'), stepId: zod_1.z.string().min(1).regex(constants_js_1.DELIMITER_SAFE_ID_PATTERN, 'stepId must be delimiter-safe: [a-z0-9_-]+') }),
107
124
  ]);
108
125
  const BlockerSchema = zod_1.z.object({
109
126
  code: BlockerCodeSchema,
110
127
  pointer: BlockerPointerSchema,
111
- message: zod_1.z.string().min(1),
112
- suggestedFix: zod_1.z.string().min(1).optional(),
128
+ message: zod_1.z
129
+ .string()
130
+ .min(1)
131
+ .refine((s) => utf8ByteLength(s) <= constants_js_1.MAX_BLOCKER_MESSAGE_BYTES, {
132
+ message: `Blocker message exceeds ${constants_js_1.MAX_BLOCKER_MESSAGE_BYTES} bytes (UTF-8)`,
133
+ }),
134
+ suggestedFix: zod_1.z
135
+ .string()
136
+ .min(1)
137
+ .refine((s) => utf8ByteLength(s) <= constants_js_1.MAX_BLOCKER_SUGGESTED_FIX_BYTES, {
138
+ message: `Blocker suggestedFix exceeds ${constants_js_1.MAX_BLOCKER_SUGGESTED_FIX_BYTES} bytes (UTF-8)`,
139
+ })
140
+ .optional(),
113
141
  });
114
142
  const BlockerReportV1Schema = zod_1.z
115
143
  .object({
116
- blockers: zod_1.z.array(BlockerSchema).min(1).max(10),
144
+ blockers: zod_1.z.array(BlockerSchema).min(1).max(constants_js_1.MAX_BLOCKERS),
117
145
  })
118
146
  .superRefine((v, ctx) => {
119
147
  const keyFor = (b) => {
120
148
  const p = b.pointer;
121
- const ptrStable = p.kind === 'context_key'
122
- ? p.key
123
- : p.kind === 'output_contract'
124
- ? p.contractRef
125
- : p.kind === 'capability'
126
- ? p.capability
127
- : p.stepId;
149
+ let ptrStable;
150
+ switch (p.kind) {
151
+ case 'context_key':
152
+ ptrStable = p.key;
153
+ break;
154
+ case 'output_contract':
155
+ ptrStable = p.contractRef;
156
+ break;
157
+ case 'capability':
158
+ ptrStable = p.capability;
159
+ break;
160
+ case 'workflow_step':
161
+ ptrStable = p.stepId;
162
+ break;
163
+ case 'context_budget':
164
+ ptrStable = '';
165
+ break;
166
+ default:
167
+ const _exhaustive = p;
168
+ ptrStable = _exhaustive;
169
+ }
128
170
  return `${b.code}|${p.kind}|${String(ptrStable)}`;
129
171
  };
130
172
  for (let i = 1; i < v.blockers.length; i++) {
@@ -147,21 +189,19 @@ const AdvanceRecordedDataV1Schema = zod_1.z.object({
147
189
  intent: zod_1.z.literal('ack_pending'),
148
190
  outcome: AdvanceRecordedOutcomeV1Schema,
149
191
  });
150
- const AutonomySchema = zod_1.z.enum(['guided', 'full_auto_stop_on_user_deps', 'full_auto_never_stop']);
151
- const RiskPolicySchema = zod_1.z.enum(['conservative', 'balanced', 'aggressive']);
152
192
  const PreferencesChangedDataV1Schema = zod_1.z
153
193
  .object({
154
194
  changeId: zod_1.z.string().min(1),
155
195
  source: zod_1.z.enum(['user', 'workflow_recommendation', 'system']),
156
196
  delta: zod_1.z
157
197
  .array(zod_1.z.discriminatedUnion('key', [
158
- zod_1.z.object({ key: zod_1.z.literal('autonomy'), value: AutonomySchema }),
159
- zod_1.z.object({ key: zod_1.z.literal('riskPolicy'), value: RiskPolicySchema }),
198
+ zod_1.z.object({ key: zod_1.z.literal('autonomy'), value: preferences_js_1.AutonomyV2Schema }),
199
+ zod_1.z.object({ key: zod_1.z.literal('riskPolicy'), value: preferences_js_1.RiskPolicyV2Schema }),
160
200
  ]))
161
201
  .min(1),
162
202
  effective: zod_1.z.object({
163
- autonomy: AutonomySchema,
164
- riskPolicy: RiskPolicySchema,
203
+ autonomy: preferences_js_1.AutonomyV2Schema,
204
+ riskPolicy: preferences_js_1.RiskPolicyV2Schema,
165
205
  }),
166
206
  })
167
207
  .superRefine((v, ctx) => {
@@ -212,7 +252,7 @@ exports.DomainEventV1Schema = zod_1.z.discriminatedUnion('kind', [
212
252
  data: zod_1.z.object({
213
253
  key: zod_1.z.enum(['git_branch', 'git_head_sha', 'repo_root_hash']),
214
254
  value: zod_1.z.discriminatedUnion('type', [
215
- zod_1.z.object({ type: zod_1.z.literal('short_string'), value: zod_1.z.string().min(1).max(80) }),
255
+ zod_1.z.object({ type: zod_1.z.literal('short_string'), value: zod_1.z.string().min(1).max(constants_js_1.MAX_OBSERVATION_SHORT_STRING_LENGTH) }),
216
256
  zod_1.z.object({ type: zod_1.z.literal('git_sha1'), value: zod_1.z.string().regex(/^[0-9a-f]{40}$/) }),
217
257
  zod_1.z.object({ type: zod_1.z.literal('sha256'), value: sha256DigestSchema }),
218
258
  ]),
@@ -317,16 +357,21 @@ exports.DomainEventV1Schema = zod_1.z.discriminatedUnion('kind', [
317
357
  exports.DomainEventEnvelopeV1Schema.extend({
318
358
  kind: zod_1.z.literal('decision_trace_appended'),
319
359
  scope: zod_1.z.object({ runId: zod_1.z.string().min(1), nodeId: zod_1.z.string().min(1) }),
320
- data: zod_1.z.object({
360
+ data: zod_1.z
361
+ .object({
321
362
  traceId: zod_1.z.string().min(1),
322
363
  entries: zod_1.z
323
364
  .array(zod_1.z.object({
324
365
  kind: zod_1.z.enum(['selected_next_step', 'evaluated_condition', 'entered_loop', 'exited_loop', 'detected_non_tip_advance']),
325
- summary: zod_1.z.string().min(1).max(512),
326
- refs: zod_1.z.record(zod_1.z.unknown()).optional(),
366
+ summary: (0, utf8_bounded_string_js_1.utf8BoundedString)({ maxBytes: constants_js_1.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES, label: 'decision trace entry summary', minLength: 1 }),
367
+ refs: decision_trace_ref_js_1.DecisionTraceRefsV1Schema,
327
368
  }))
328
369
  .min(1)
329
- .max(25),
330
- }),
370
+ .max(constants_js_1.MAX_DECISION_TRACE_ENTRIES),
371
+ })
372
+ .refine((data) => {
373
+ const totalBytes = data.entries.reduce((sum, entry) => sum + utf8ByteLength(entry.summary), 0);
374
+ return totalBytes <= constants_js_1.MAX_DECISION_TRACE_TOTAL_BYTES;
375
+ }, { message: `Decision trace total bytes exceeds ${constants_js_1.MAX_DECISION_TRACE_TOTAL_BYTES}` }),
331
376
  }),
332
377
  ]);
@@ -6,7 +6,7 @@ export declare const ManifestRecordV1Schema: z.ZodDiscriminatedUnion<"kind", [z.
6
6
  kind: z.ZodLiteral<"segment_closed">;
7
7
  firstEventIndex: z.ZodNumber;
8
8
  lastEventIndex: z.ZodNumber;
9
- segmentRelPath: z.ZodString;
9
+ segmentRelPath: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
10
10
  sha256: z.ZodString;
11
11
  bytes: z.ZodNumber;
12
12
  }, "strip", z.ZodTypeAny, {
@@ -41,17 +41,17 @@ export declare const ManifestRecordV1Schema: z.ZodDiscriminatedUnion<"kind", [z.
41
41
  kind: "snapshot_pinned";
42
42
  sessionId: string;
43
43
  v: 1;
44
+ manifestIndex: number;
44
45
  eventIndex: number;
45
46
  snapshotRef: string;
46
- manifestIndex: number;
47
47
  createdByEventId: string;
48
48
  }, {
49
49
  kind: "snapshot_pinned";
50
50
  sessionId: string;
51
51
  v: 1;
52
+ manifestIndex: number;
52
53
  eventIndex: number;
53
54
  snapshotRef: string;
54
- manifestIndex: number;
55
55
  createdByEventId: string;
56
56
  }>]>;
57
57
  export type ManifestRecordV1 = z.infer<typeof ManifestRecordV1Schema>;
@@ -6,6 +6,11 @@ const sha256DigestSchema = zod_1.z
6
6
  .string()
7
7
  .regex(/^sha256:[0-9a-f]{64}$/, 'Expected sha256:<64 hex chars>')
8
8
  .describe('sha256 digest in WorkRail v2 format');
9
+ const relativePathSchema = zod_1.z
10
+ .string()
11
+ .min(1)
12
+ .refine((path) => !path.startsWith('/') && !path.startsWith('\\'), 'Path must be relative (no absolute paths)')
13
+ .refine((path) => !path.includes('../') && !path.includes('..\\'), 'Path must not contain path traversal (..)');
9
14
  exports.ManifestRecordV1Schema = zod_1.z.discriminatedUnion('kind', [
10
15
  zod_1.z.object({
11
16
  v: zod_1.z.literal(1),
@@ -14,7 +19,7 @@ exports.ManifestRecordV1Schema = zod_1.z.discriminatedUnion('kind', [
14
19
  kind: zod_1.z.literal('segment_closed'),
15
20
  firstEventIndex: zod_1.z.number().int().nonnegative(),
16
21
  lastEventIndex: zod_1.z.number().int().nonnegative(),
17
- segmentRelPath: zod_1.z.string().min(1),
22
+ segmentRelPath: relativePathSchema,
18
23
  sha256: sha256DigestSchema,
19
24
  bytes: zod_1.z.number().int().nonnegative(),
20
25
  }),
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+ export type AutonomyV2 = 'guided' | 'full_auto_stop_on_user_deps' | 'full_auto_never_stop';
3
+ export type RiskPolicyV2 = 'conservative' | 'balanced' | 'aggressive';
4
+ export declare const AutonomyV2Schema: z.ZodEnum<["guided", "full_auto_stop_on_user_deps", "full_auto_never_stop"]>;
5
+ export declare const RiskPolicyV2Schema: z.ZodEnum<["conservative", "balanced", "aggressive"]>;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RiskPolicyV2Schema = exports.AutonomyV2Schema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.AutonomyV2Schema = zod_1.z.enum(['guided', 'full_auto_stop_on_user_deps', 'full_auto_never_stop']);
6
+ exports.RiskPolicyV2Schema = zod_1.z.enum(['conservative', 'balanced', 'aggressive']);
@@ -10,6 +10,9 @@ export type CorruptionReasonV2 = {
10
10
  } | {
11
11
  readonly code: 'unknown_schema_version';
12
12
  readonly message: string;
13
+ } | {
14
+ readonly code: 'schema_validation_failed';
15
+ readonly message: string;
13
16
  };
14
17
  export type SessionHealthV2 = {
15
18
  readonly kind: 'healthy';
@@ -1,7 +1,8 @@
1
- export { encodeBase64Url, decodeBase64Url } from './base64url.js';
2
1
  export { TokenPayloadV1Schema, StateTokenPayloadV1Schema, AckTokenPayloadV1Schema, CheckpointTokenPayloadV1Schema, expectedPrefixForTokenKind, } from './payloads.js';
3
2
  export type { TokenPayloadV1, StateTokenPayloadV1, AckTokenPayloadV1, CheckpointTokenPayloadV1 } from './payloads.js';
4
3
  export { encodeTokenPayloadV1, encodeUnsignedTokenV1, parseTokenV1 } from './token-codec.js';
5
4
  export type { TokenDecodeErrorV2, ParsedTokenV1 } from './token-codec.js';
6
5
  export { signTokenV1, verifyTokenSignatureV1, assertTokenScopeMatchesState } from './token-signer.js';
7
6
  export type { TokenVerifyErrorV2 } from './token-signer.js';
7
+ export type { AttemptId, OutputId } from '../ids/index.js';
8
+ export { asAttemptId, asOutputId } from '../ids/index.js';
@@ -1,9 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertTokenScopeMatchesState = exports.verifyTokenSignatureV1 = exports.signTokenV1 = exports.parseTokenV1 = exports.encodeUnsignedTokenV1 = exports.encodeTokenPayloadV1 = exports.expectedPrefixForTokenKind = exports.CheckpointTokenPayloadV1Schema = exports.AckTokenPayloadV1Schema = exports.StateTokenPayloadV1Schema = exports.TokenPayloadV1Schema = exports.decodeBase64Url = exports.encodeBase64Url = void 0;
4
- var base64url_js_1 = require("./base64url.js");
5
- Object.defineProperty(exports, "encodeBase64Url", { enumerable: true, get: function () { return base64url_js_1.encodeBase64Url; } });
6
- Object.defineProperty(exports, "decodeBase64Url", { enumerable: true, get: function () { return base64url_js_1.decodeBase64Url; } });
3
+ exports.asOutputId = exports.asAttemptId = exports.assertTokenScopeMatchesState = exports.verifyTokenSignatureV1 = exports.signTokenV1 = exports.parseTokenV1 = exports.encodeUnsignedTokenV1 = exports.encodeTokenPayloadV1 = exports.expectedPrefixForTokenKind = exports.CheckpointTokenPayloadV1Schema = exports.AckTokenPayloadV1Schema = exports.StateTokenPayloadV1Schema = exports.TokenPayloadV1Schema = void 0;
7
4
  var payloads_js_1 = require("./payloads.js");
8
5
  Object.defineProperty(exports, "TokenPayloadV1Schema", { enumerable: true, get: function () { return payloads_js_1.TokenPayloadV1Schema; } });
9
6
  Object.defineProperty(exports, "StateTokenPayloadV1Schema", { enumerable: true, get: function () { return payloads_js_1.StateTokenPayloadV1Schema; } });
@@ -18,3 +15,6 @@ var token_signer_js_1 = require("./token-signer.js");
18
15
  Object.defineProperty(exports, "signTokenV1", { enumerable: true, get: function () { return token_signer_js_1.signTokenV1; } });
19
16
  Object.defineProperty(exports, "verifyTokenSignatureV1", { enumerable: true, get: function () { return token_signer_js_1.verifyTokenSignatureV1; } });
20
17
  Object.defineProperty(exports, "assertTokenScopeMatchesState", { enumerable: true, get: function () { return token_signer_js_1.assertTokenScopeMatchesState; } });
18
+ var index_js_1 = require("../ids/index.js");
19
+ Object.defineProperty(exports, "asAttemptId", { enumerable: true, get: function () { return index_js_1.asAttemptId; } });
20
+ Object.defineProperty(exports, "asOutputId", { enumerable: true, get: function () { return index_js_1.asOutputId; } });
@@ -17,20 +17,20 @@ export declare const StateTokenPayloadV1Schema: z.ZodObject<{
17
17
  sessionId: string & {
18
18
  readonly __brand: "v2.SessionId";
19
19
  };
20
- workflowHash: never;
21
20
  runId: string & {
22
21
  readonly __brand: "v2.RunId";
23
22
  };
24
23
  nodeId: string & {
25
24
  readonly __brand: "v2.NodeId";
26
25
  };
26
+ workflowHash: never;
27
27
  tokenVersion: 1;
28
28
  tokenKind: "state";
29
29
  }, {
30
30
  sessionId: string;
31
- workflowHash: string;
32
31
  runId: string;
33
32
  nodeId: string;
33
+ workflowHash: string;
34
34
  tokenVersion: 1;
35
35
  tokenKind: "state";
36
36
  }>;
@@ -129,20 +129,20 @@ export declare const TokenPayloadV1Schema: z.ZodDiscriminatedUnion<"tokenKind",
129
129
  sessionId: string & {
130
130
  readonly __brand: "v2.SessionId";
131
131
  };
132
- workflowHash: never;
133
132
  runId: string & {
134
133
  readonly __brand: "v2.RunId";
135
134
  };
136
135
  nodeId: string & {
137
136
  readonly __brand: "v2.NodeId";
138
137
  };
138
+ workflowHash: never;
139
139
  tokenVersion: 1;
140
140
  tokenKind: "state";
141
141
  }, {
142
142
  sessionId: string;
143
- workflowHash: string;
144
143
  runId: string;
145
144
  nodeId: string;
145
+ workflowHash: string;
146
146
  tokenVersion: 1;
147
147
  tokenKind: "state";
148
148
  }>, z.ZodObject<{
@@ -1,6 +1,7 @@
1
1
  import type { Result } from 'neverthrow';
2
2
  import type { CanonicalBytes } from '../ids/index.js';
3
3
  import type { TokenStringV1 } from '../ids/index.js';
4
+ import type { Base64UrlPortV2 } from '../../ports/base64url.port.js';
4
5
  import { type TokenPayloadV1, type TokenPrefixV1 } from './payloads.js';
5
6
  export type TokenDecodeErrorV2 = {
6
7
  readonly code: 'TOKEN_INVALID_FORMAT';
@@ -24,8 +25,8 @@ export interface ParsedTokenV1 {
24
25
  readonly payload: TokenPayloadV1;
25
26
  }
26
27
  export declare function encodeTokenPayloadV1(payload: TokenPayloadV1): Result<CanonicalBytes, TokenDecodeErrorV2>;
27
- export declare function encodeUnsignedTokenV1(payload: TokenPayloadV1): Result<{
28
+ export declare function encodeUnsignedTokenV1(payload: TokenPayloadV1, base64url: Base64UrlPortV2): Result<{
28
29
  readonly token: TokenStringV1;
29
30
  readonly payloadBytes: CanonicalBytes;
30
31
  }, TokenDecodeErrorV2>;
31
- export declare function parseTokenV1(token: string): Result<ParsedTokenV1, TokenDecodeErrorV2>;
32
+ export declare function parseTokenV1(token: string, base64url: Base64UrlPortV2): Result<ParsedTokenV1, TokenDecodeErrorV2>;
@@ -6,7 +6,6 @@ exports.parseTokenV1 = parseTokenV1;
6
6
  const neverthrow_1 = require("neverthrow");
7
7
  const jcs_js_1 = require("../canonical/jcs.js");
8
8
  const index_js_1 = require("../ids/index.js");
9
- const base64url_js_1 = require("./base64url.js");
10
9
  const payloads_js_1 = require("./payloads.js");
11
10
  function encodeTokenPayloadV1(payload) {
12
11
  return (0, jcs_js_1.toCanonicalBytes)(payload).mapErr((e) => ({
@@ -14,15 +13,15 @@ function encodeTokenPayloadV1(payload) {
14
13
  message: e.message,
15
14
  }));
16
15
  }
17
- function encodeUnsignedTokenV1(payload) {
16
+ function encodeUnsignedTokenV1(payload, base64url) {
18
17
  const bytes = encodeTokenPayloadV1(payload);
19
18
  if (bytes.isErr())
20
19
  return (0, neverthrow_1.err)(bytes.error);
21
20
  const prefix = (0, payloads_js_1.expectedPrefixForTokenKind)(payload.tokenKind);
22
- const token = `${prefix}.v1.${(0, base64url_js_1.encodeBase64Url)(bytes.value)}.`;
21
+ const token = `${prefix}.v1.${base64url.encodeBase64Url(bytes.value)}.`;
23
22
  return (0, neverthrow_1.ok)({ token: (0, index_js_1.asTokenStringV1)(token), payloadBytes: bytes.value });
24
23
  }
25
- function parseTokenV1(token) {
24
+ function parseTokenV1(token, base64url) {
26
25
  const parts = token.split('.');
27
26
  if (parts.length !== 4)
28
27
  return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Expected 4 dot-separated segments' });
@@ -35,13 +34,20 @@ function parseTokenV1(token) {
35
34
  if (payloadB64.trim() === '' || sigB64.trim() === '') {
36
35
  return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Missing payload or signature segment' });
37
36
  }
38
- const decoded = (0, base64url_js_1.decodeBase64Url)(payloadB64);
37
+ const decoded = base64url.decodeBase64Url(payloadB64);
39
38
  if (decoded.isErr())
40
39
  return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: decoded.error.message });
41
40
  const payloadBytes = (0, index_js_1.asCanonicalBytes)(decoded.value);
41
+ let payloadText;
42
+ try {
43
+ payloadText = new TextDecoder('utf-8', { fatal: true }).decode(payloadBytes);
44
+ }
45
+ catch {
46
+ return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Payload is not valid UTF-8' });
47
+ }
42
48
  let payloadJson;
43
49
  try {
44
- payloadJson = JSON.parse(new TextDecoder().decode(payloadBytes));
50
+ payloadJson = JSON.parse(payloadText);
45
51
  }
46
52
  catch {
47
53
  return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Payload is not valid JSON' });
@@ -1,6 +1,7 @@
1
1
  import type { Result } from 'neverthrow';
2
2
  import type { HmacSha256PortV2 } from '../../ports/hmac-sha256.port.js';
3
3
  import type { KeyringV1 } from '../../ports/keyring.port.js';
4
+ import type { Base64UrlPortV2 } from '../../ports/base64url.port.js';
4
5
  import type { CanonicalBytes, TokenStringV1 } from '../ids/index.js';
5
6
  import type { ParsedTokenV1, TokenDecodeErrorV2 } from './token-codec.js';
6
7
  export type TokenVerifyErrorV2 = {
@@ -10,6 +11,6 @@ export type TokenVerifyErrorV2 = {
10
11
  readonly code: 'TOKEN_INVALID_FORMAT';
11
12
  readonly message: string;
12
13
  };
13
- export declare function signTokenV1(unsignedTokenPrefix: 'st.v1.' | 'ack.v1.' | 'chk.v1.', payloadBytes: CanonicalBytes, keyring: KeyringV1, hmac: HmacSha256PortV2): Result<TokenStringV1, TokenVerifyErrorV2>;
14
- export declare function verifyTokenSignatureV1(parsed: ParsedTokenV1, keyring: KeyringV1, hmac: HmacSha256PortV2): Result<void, TokenVerifyErrorV2>;
14
+ export declare function signTokenV1(unsignedTokenPrefix: 'st.v1.' | 'ack.v1.' | 'chk.v1.', payloadBytes: CanonicalBytes, keyring: KeyringV1, hmac: HmacSha256PortV2, base64url: Base64UrlPortV2): Result<TokenStringV1, TokenVerifyErrorV2>;
15
+ export declare function verifyTokenSignatureV1(parsed: ParsedTokenV1, keyring: KeyringV1, hmac: HmacSha256PortV2, base64url: Base64UrlPortV2): Result<void, TokenVerifyErrorV2>;
15
16
  export declare function assertTokenScopeMatchesState(state: ParsedTokenV1, other: ParsedTokenV1): Result<void, TokenDecodeErrorV2>;
@@ -4,26 +4,25 @@ exports.signTokenV1 = signTokenV1;
4
4
  exports.verifyTokenSignatureV1 = verifyTokenSignatureV1;
5
5
  exports.assertTokenScopeMatchesState = assertTokenScopeMatchesState;
6
6
  const neverthrow_1 = require("neverthrow");
7
- const base64url_js_1 = require("./base64url.js");
8
7
  const index_js_1 = require("../ids/index.js");
9
- function decodeKeyBytes(keyBase64Url) {
10
- const decoded = (0, base64url_js_1.decodeBase64Url)(keyBase64Url);
8
+ function decodeKeyBytes(keyBase64Url, base64url) {
9
+ const decoded = base64url.decodeBase64Url(keyBase64Url);
11
10
  if (decoded.isErr())
12
11
  return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid key encoding' });
13
12
  if (decoded.value.length !== 32)
14
13
  return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid key length' });
15
14
  return (0, neverthrow_1.ok)(decoded.value);
16
15
  }
17
- function signTokenV1(unsignedTokenPrefix, payloadBytes, keyring, hmac) {
18
- const key = decodeKeyBytes(keyring.current.keyBase64Url);
16
+ function signTokenV1(unsignedTokenPrefix, payloadBytes, keyring, hmac, base64url) {
17
+ const key = decodeKeyBytes(keyring.current.keyBase64Url, base64url);
19
18
  if (key.isErr())
20
19
  return (0, neverthrow_1.err)(key.error);
21
20
  const sig = hmac.hmacSha256(key.value, payloadBytes);
22
- const token = `${unsignedTokenPrefix}${(0, base64url_js_1.encodeBase64Url)(payloadBytes)}.${(0, base64url_js_1.encodeBase64Url)(sig)}`;
21
+ const token = `${unsignedTokenPrefix}${base64url.encodeBase64Url(payloadBytes)}.${base64url.encodeBase64Url(sig)}`;
23
22
  return (0, neverthrow_1.ok)((0, index_js_1.asTokenStringV1)(token));
24
23
  }
25
- function verifyTokenSignatureV1(parsed, keyring, hmac) {
26
- const sigBytes = (0, base64url_js_1.decodeBase64Url)(parsed.sigBase64Url);
24
+ function verifyTokenSignatureV1(parsed, keyring, hmac, base64url) {
25
+ const sigBytes = base64url.decodeBase64Url(parsed.sigBase64Url);
27
26
  if (sigBytes.isErr())
28
27
  return (0, neverthrow_1.err)({ code: 'TOKEN_INVALID_FORMAT', message: 'Invalid signature encoding' });
29
28
  if (sigBytes.value.length !== 32)
@@ -32,7 +31,7 @@ function verifyTokenSignatureV1(parsed, keyring, hmac) {
32
31
  if (keyring.previous)
33
32
  keys.push(keyring.previous.keyBase64Url);
34
33
  for (const k of keys) {
35
- const key = decodeKeyBytes(k);
34
+ const key = decodeKeyBytes(k, base64url);
36
35
  if (key.isErr())
37
36
  continue;
38
37
  const expected = hmac.hmacSha256(key.value, parsed.payloadBytes);
@@ -0,0 +1,5 @@
1
+ import type { Base64UrlPortV2 } from '../../../ports/base64url.port.js';
2
+ export declare class NodeBase64UrlV2 implements Base64UrlPortV2 {
3
+ encodeBase64Url(bytes: Uint8Array): string;
4
+ decodeBase64Url(input: string): ReturnType<Base64UrlPortV2['decodeBase64Url']>;
5
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeBase64UrlV2 = void 0;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const node_buffer_1 = require("node:buffer");
6
+ class NodeBase64UrlV2 {
7
+ encodeBase64Url(bytes) {
8
+ return node_buffer_1.Buffer.from(bytes).toString('base64url');
9
+ }
10
+ decodeBase64Url(input) {
11
+ if (input.trim() === '') {
12
+ return (0, neverthrow_1.err)({
13
+ code: 'INVALID_BASE64URL_CHARACTERS',
14
+ message: 'Invalid base64url: empty input',
15
+ });
16
+ }
17
+ if (input.includes('=')) {
18
+ return (0, neverthrow_1.err)({
19
+ code: 'INVALID_BASE64URL_PADDING',
20
+ message: 'Invalid base64url: padding is not allowed',
21
+ });
22
+ }
23
+ const base64UrlRe = /^[A-Za-z0-9_-]+$/;
24
+ if (!base64UrlRe.test(input)) {
25
+ return (0, neverthrow_1.err)({
26
+ code: 'INVALID_BASE64URL_CHARACTERS',
27
+ message: 'Invalid base64url: invalid characters',
28
+ });
29
+ }
30
+ try {
31
+ const decoded = node_buffer_1.Buffer.from(input, 'base64url');
32
+ if (decoded.toString('base64url') !== input) {
33
+ return (0, neverthrow_1.err)({
34
+ code: 'INVALID_BASE64URL_CHARACTERS',
35
+ message: 'Invalid base64url: non-canonical encoding',
36
+ });
37
+ }
38
+ return (0, neverthrow_1.ok)(new Uint8Array(decoded));
39
+ }
40
+ catch (e) {
41
+ return (0, neverthrow_1.err)({
42
+ code: 'INVALID_BASE64URL_CHARACTERS',
43
+ message: `Invalid base64url: ${e instanceof Error ? e.message : String(e)}`,
44
+ });
45
+ }
46
+ }
47
+ }
48
+ exports.NodeBase64UrlV2 = NodeBase64UrlV2;
@@ -38,9 +38,14 @@ const fs = __importStar(require("fs/promises"));
38
38
  const fsCb = __importStar(require("fs"));
39
39
  const fs_1 = require("fs");
40
40
  const neverthrow_1 = require("neverthrow");
41
+ function nodeErrorCode(e) {
42
+ if (typeof e !== 'object' || e === null)
43
+ return undefined;
44
+ const code = e.code;
45
+ return typeof code === 'string' ? code : undefined;
46
+ }
41
47
  function mapFsError(e, filePath) {
42
- const any = e;
43
- const code = any?.code;
48
+ const code = nodeErrorCode(e);
44
49
  if (code === 'ENOENT')
45
50
  return { code: 'FS_NOT_FOUND', message: `Not found: ${filePath}` };
46
51
  if (code === 'EEXIST')
@@ -130,8 +135,7 @@ class NodeFileSystemV2 {
130
135
  await dirHandle.close();
131
136
  }
132
137
  })(), (e) => {
133
- const any = e;
134
- const code = any?.code;
138
+ const code = nodeErrorCode(e);
135
139
  if (code === 'EINVAL' || code === 'ENOTSUP') {
136
140
  return { code: 'FS_UNSUPPORTED', message: `Directory fsync unsupported for: ${dirPath}` };
137
141
  }
@@ -2,10 +2,14 @@ import type { ResultAsync } from 'neverthrow';
2
2
  import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
3
3
  import type { FileSystemPortV2 } from '../../../ports/fs.port.js';
4
4
  import type { KeyringError, KeyringPortV2, KeyringV1 } from '../../../ports/keyring.port.js';
5
+ import type { Base64UrlPortV2 } from '../../../ports/base64url.port.js';
6
+ import type { RandomEntropyPortV2 } from '../../../ports/random-entropy.port.js';
5
7
  export declare class LocalKeyringV2 implements KeyringPortV2 {
6
8
  private readonly dataDir;
7
9
  private readonly fs;
8
- constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2);
10
+ private readonly base64url;
11
+ private readonly entropy;
12
+ constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2, base64url: Base64UrlPortV2, entropy: RandomEntropyPortV2);
9
13
  loadOrCreate(): ResultAsync<KeyringV1, KeyringError>;
10
14
  rotate(): ResultAsync<KeyringV1, KeyringError>;
11
15
  private createAndPersistFresh;