@exaudeus/workrail 0.16.0 → 0.17.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.
@@ -181,6 +181,7 @@ async function registerV2Services() {
181
181
  const { NodeBase64UrlV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/base64url/index.js')));
182
182
  const { NodeRandomEntropyV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/random-entropy/index.js')));
183
183
  const { NodeTimeClockV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/time-clock/index.js')));
184
+ const { IdFactoryV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/id-factory/index.js')));
184
185
  tsyringe_1.container.register(tokens_js_1.DI.V2.DataDir, {
185
186
  useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new LocalDataDirV2(process.env)),
186
187
  });
@@ -205,6 +206,12 @@ async function registerV2Services() {
205
206
  tsyringe_1.container.register(tokens_js_1.DI.V2.TimeClock, {
206
207
  useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new NodeTimeClockV2()),
207
208
  });
209
+ tsyringe_1.container.register(tokens_js_1.DI.V2.IdFactory, {
210
+ useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => {
211
+ const entropy = c.resolve(tokens_js_1.DI.V2.RandomEntropy);
212
+ return new IdFactoryV2(entropy);
213
+ }),
214
+ });
208
215
  const { LocalKeyringV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/keyring/index.js')));
209
216
  const { LocalSessionEventLogStoreV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/session-store/index.js')));
210
217
  const { LocalSnapshotStoreV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/snapshot-store/index.js')));
@@ -30,6 +30,7 @@ export declare const DI: {
30
30
  readonly Base64Url: symbol;
31
31
  readonly RandomEntropy: symbol;
32
32
  readonly TimeClock: symbol;
33
+ readonly IdFactory: symbol;
33
34
  readonly Keyring: symbol;
34
35
  readonly SessionStore: symbol;
35
36
  readonly SnapshotStore: symbol;
package/dist/di/tokens.js CHANGED
@@ -33,6 +33,7 @@ exports.DI = {
33
33
  Base64Url: Symbol('V2.Base64Url'),
34
34
  RandomEntropy: Symbol('V2.RandomEntropy'),
35
35
  TimeClock: Symbol('V2.TimeClock'),
36
+ IdFactory: Symbol('V2.IdFactory'),
36
37
  Keyring: Symbol('V2.Keyring'),
37
38
  SessionStore: Symbol('V2.SessionStore'),
38
39
  SnapshotStore: Symbol('V2.SnapshotStore'),
@@ -254,16 +254,16 @@
254
254
  "bytes": 565
255
255
  },
256
256
  "di/container.js": {
257
- "sha256": "ab2e782ae89f52655598140677903c37257ee14d15dab77fcaa2ca43afb9a260",
258
- "bytes": 18492
257
+ "sha256": "200c41528c64e4e8cf0e04046bfd254cdbe0900c157be2dfd8c302a4a096d193",
258
+ "bytes": 18890
259
259
  },
260
260
  "di/tokens.d.ts": {
261
- "sha256": "2be51a547ab9bfdd2026e7e9cfdc79ee693ad5454fa3dd773e72659a49837fde",
262
- "bytes": 1846
261
+ "sha256": "3b9e46e178107ad65575350da5d99c238e7a4389d3537e67ff516a04df1191fe",
262
+ "bytes": 1882
263
263
  },
264
264
  "di/tokens.js": {
265
- "sha256": "a8774e7c93f07a829d4770b33f6aef663cc68a055ed88236a085f553bf161297",
266
- "bytes": 2284
265
+ "sha256": "aa90a1af7eba9941663beeeb13a25e2f3ba877ba37cb786bac46b468cc840f1b",
266
+ "bytes": 2327
267
267
  },
268
268
  "domain/execution/error.d.ts": {
269
269
  "sha256": "2eac85c42ec399a23724f868641eeadd0d196b4d324ee4caaff82a6b46155bd9",
@@ -534,8 +534,8 @@
534
534
  "bytes": 399
535
535
  },
536
536
  "mcp/handlers/v2-execution.js": {
537
- "sha256": "58502dd04d88634578de5e32af45fc1671e2decc90b8eb9d8a6819a37b9d1bce",
538
- "bytes": 52776
537
+ "sha256": "4f3f7d9009007a6795e2dfe998165722f660354323f7e856b26f1a7f62a136be",
538
+ "bytes": 53400
539
539
  },
540
540
  "mcp/handlers/v2-workflow.d.ts": {
541
541
  "sha256": "9fbd4d44854e2060c54982b21e72c608970bb2bd107bb15a8388b26c6b492e55",
@@ -574,8 +574,8 @@
574
574
  "bytes": 168
575
575
  },
576
576
  "mcp/server.js": {
577
- "sha256": "cf128711a6d74bb604653431c9e3bac4fac9fed94bd4da8326438e86809501e3",
578
- "bytes": 13866
577
+ "sha256": "f6bc4b1f3416d03add7b715a5bdae7955a877eae8008c0d2f8b8490dd8ae5708",
578
+ "bytes": 14097
579
579
  },
580
580
  "mcp/tool-description-provider.d.ts": {
581
581
  "sha256": "1d46abc3112e11b68e57197e846f5708293ec9b2281fa71a9124ee2aad71e41b",
@@ -610,8 +610,8 @@
610
610
  "bytes": 8020
611
611
  },
612
612
  "mcp/types.d.ts": {
613
- "sha256": "4ab4a4af1eeedf9ba9bcdc70476a5adcc24ce05b3d7d715d70979052b1eb7246",
614
- "bytes": 3473
613
+ "sha256": "3b7d7d7d30d65cd22b47bc9d559820b3f51984476fce11d4f3b6d820e2ce1eb8",
614
+ "bytes": 3683
615
615
  },
616
616
  "mcp/types.js": {
617
617
  "sha256": "0c12576fd0053115ff096fe26b38f77f1e830b7ec4781aaf94564827c4c9e81a",
@@ -993,6 +993,22 @@
993
993
  "sha256": "ae16a0df03c3dec30e9ffca0066b6148568966f6660a3c660f82853e077a0cbd",
994
994
  "bytes": 817
995
995
  },
996
+ "v2/durable-core/encoding/base32-lower.d.ts": {
997
+ "sha256": "849ef1984db20984f1fce7f40b15d3680c888e01411932d30af6fae4d2132a67",
998
+ "bytes": 175
999
+ },
1000
+ "v2/durable-core/encoding/base32-lower.js": {
1001
+ "sha256": "9065da38936496a34c2808388b5fb68d8aaac6bd3f0f05de184abc101e07c419",
1002
+ "bytes": 934
1003
+ },
1004
+ "v2/durable-core/ids/attempt-id-derivation.d.ts": {
1005
+ "sha256": "baee295f114d8d4a74e9fa529e7c72d458a7c7244f50fad9ebbaea06c57dfe92",
1006
+ "bytes": 207
1007
+ },
1008
+ "v2/durable-core/ids/attempt-id-derivation.js": {
1009
+ "sha256": "c13da44f23890d85edfb64a471f4f4817e973a63b41287d79144a3c9c6a5dcea",
1010
+ "bytes": 1309
1011
+ },
996
1012
  "v2/durable-core/ids/index.d.ts": {
997
1013
  "sha256": "5525efc423ad30635996907f7560f53559e49e33a64411a684370ebd1ae56410",
998
1014
  "bytes": 1792
@@ -1134,8 +1150,8 @@
1134
1150
  "bytes": 6756
1135
1151
  },
1136
1152
  "v2/durable-core/tokens/payloads.js": {
1137
- "sha256": "f1026ba47d9b2e0ab3bbec47aa9e8afd5695805f6f4a83ecddd4610616ea4def",
1138
- "bytes": 2343
1153
+ "sha256": "24978999201e0413cb193e1fee2b9977335ecaa9e2170b901358c9119fac6c3a",
1154
+ "bytes": 2479
1139
1155
  },
1140
1156
  "v2/durable-core/tokens/token-codec.d.ts": {
1141
1157
  "sha256": "44bd65c6102b3aa5fcd82d2854f5466263a20c0b9cbd31aa25f169dfeed8e045",
@@ -1193,6 +1209,14 @@
1193
1209
  "sha256": "11917c1d29f34efcd7139f5573067ad20a4093a1c5190e6069ccdc25acf53f24",
1194
1210
  "bytes": 578
1195
1211
  },
1212
+ "v2/infra/local/id-factory/index.d.ts": {
1213
+ "sha256": "ffcbdcce6a9c6a93c2703e0d844f84fef59e6ef8a3f657d50750e885b88e879b",
1214
+ "bytes": 432
1215
+ },
1216
+ "v2/infra/local/id-factory/index.js": {
1217
+ "sha256": "bd7d8cde4fb63b299f11ff51721c9b468c887856d581e95867bbbee6547642e4",
1218
+ "bytes": 1044
1219
+ },
1196
1220
  "v2/infra/local/keyring/index.d.ts": {
1197
1221
  "sha256": "e8698dab64327f994bf78e1a6c30131062d8418847417610e6b071ce7e873763",
1198
1222
  "bytes": 932
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleV2StartWorkflow = handleV2StartWorkflow;
37
37
  exports.handleV2ContinueWorkflow = handleV2ContinueWorkflow;
38
38
  const os = __importStar(require("os"));
39
- const crypto_1 = require("crypto");
40
39
  const types_js_1 = require("../types.js");
41
40
  const output_schemas_js_1 = require("../output-schemas.js");
42
41
  const snapshot_state_js_1 = require("../../v2/durable-core/projections/snapshot-state.js");
@@ -45,6 +44,7 @@ const index_js_1 = require("../../v2/durable-core/tokens/index.js");
45
44
  const index_js_2 = require("../../v2/durable-core/tokens/index.js");
46
45
  const workflow_js_1 = require("../../types/workflow.js");
47
46
  const index_js_3 = require("../../v2/durable-core/ids/index.js");
47
+ const attempt_id_derivation_js_1 = require("../../v2/durable-core/ids/attempt-id-derivation.js");
48
48
  const neverthrow_1 = require("neverthrow");
49
49
  const v1_to_v2_shim_js_1 = require("../../v2/read-only/v1-to-v2-shim.js");
50
50
  const hashing_js_1 = require("../../v2/durable-core/canonical/hashing.js");
@@ -238,7 +238,7 @@ function mapInternalErrorToToolError(e) {
238
238
  }
239
239
  }
240
240
  function replayFromRecordedAdvance(args) {
241
- const { recordedEvent, truth, sessionId, runId, nodeId, workflowHash, attemptId, inputStateToken, inputAckToken, pinnedWorkflow, snapshotStore, keyring, hmac, base64url, } = args;
241
+ const { recordedEvent, truth, sessionId, runId, nodeId, workflowHash, attemptId, inputStateToken, inputAckToken, pinnedWorkflow, snapshotStore, keyring, sha256, hmac, base64url, } = args;
242
242
  const checkpointTokenRes = signTokenOrErr({
243
243
  unsignedPrefix: 'chk.v1.',
244
244
  payload: { tokenVersion: 1, tokenKind: 'checkpoint', sessionId, runId, nodeId, attemptId },
@@ -295,7 +295,7 @@ function replayFromRecordedAdvance(args) {
295
295
  }
296
296
  const pending = (0, snapshot_state_js_1.derivePendingStep)(snap.enginePayload.engineState);
297
297
  const isComplete = (0, snapshot_state_js_1.deriveIsComplete)(snap.enginePayload.engineState);
298
- const nextAttemptId = attemptIdForNextNode(attemptId);
298
+ const nextAttemptId = attemptIdForNextNode(attemptId, sha256);
299
299
  const nextAckTokenRes = signTokenOrErr({
300
300
  unsignedPrefix: 'ack.v1.',
301
301
  payload: { tokenVersion: 1, tokenKind: 'ack', sessionId, runId, nodeId: toNodeIdBranded, attemptId: nextAttemptId },
@@ -338,7 +338,7 @@ function replayFromRecordedAdvance(args) {
338
338
  });
339
339
  }
340
340
  function advanceAndRecord(args) {
341
- const { truth, sessionId, runId, nodeId, attemptId, workflowHash, dedupeKey, inputContext, inputOutput, lock, pinnedWorkflow, snapshotStore, sessionStore } = args;
341
+ const { truth, sessionId, runId, nodeId, attemptId, workflowHash, dedupeKey, inputContext, inputOutput, lock, pinnedWorkflow, snapshotStore, sessionStore, idFactory } = args;
342
342
  const hasRun = truth.events.some((e) => e.kind === 'run_started' && e.scope?.runId === String(runId));
343
343
  const hasNode = truth.events.some((e) => e.kind === 'node_created' && e.scope?.runId === String(runId) && e.scope?.nodeId === String(nodeId));
344
344
  if (!hasRun || !hasNode) {
@@ -385,11 +385,11 @@ function advanceAndRecord(args) {
385
385
  enginePayload: { v: 1, engineState: newEngineState },
386
386
  };
387
387
  return snapshotStore.putExecutionSnapshotV1(snapshotFile).andThen((newSnapshotRef) => {
388
- const toNodeId = `node_${(0, crypto_1.randomUUID)()}`;
388
+ const toNodeId = String(idFactory.mintNodeId());
389
389
  const nextEventIndex = truth.events.length === 0 ? 0 : truth.events[truth.events.length - 1].eventIndex + 1;
390
- const evtAdvanceRecorded = `evt_${(0, crypto_1.randomUUID)()}`;
391
- const evtNodeCreated = `evt_${(0, crypto_1.randomUUID)()}`;
392
- const evtEdgeCreated = `evt_${(0, crypto_1.randomUUID)()}`;
390
+ const evtAdvanceRecorded = idFactory.mintEventId();
391
+ const evtNodeCreated = idFactory.mintEventId();
392
+ const evtEdgeCreated = idFactory.mintEventId();
393
393
  const hasChildren = truth.events.some((e) => e.kind === 'edge_created' && e.data.fromNodeId === String(nodeId));
394
394
  const causeKind = hasChildren ? 'non_tip_advance' : 'intentional_fork';
395
395
  const outputId = (0, index_js_1.asOutputId)(`out_recap_${String(attemptId)}`);
@@ -406,7 +406,7 @@ function advanceAndRecord(args) {
406
406
  ]
407
407
  : [];
408
408
  const normalizedOutputs = (0, outputs_js_1.normalizeOutputsForAppend)(outputsToAppend);
409
- const outputEventIds = normalizedOutputs.map(() => `evt_${(0, crypto_1.randomUUID)()}`);
409
+ const outputEventIds = normalizedOutputs.map(() => idFactory.mintEventId());
410
410
  const planRes = (0, ack_advance_append_plan_js_1.buildAckAdvanceAppendPlanV1)({
411
411
  sessionId: String(sessionId),
412
412
  runId: String(runId),
@@ -534,11 +534,11 @@ function parseAckTokenOrFail(raw, keyring, hmac, base64url) {
534
534
  }
535
535
  return { ok: true, token: parsedRes.value };
536
536
  }
537
- function newAttemptId() {
538
- return (0, index_js_1.asAttemptId)(`attempt_${(0, crypto_1.randomUUID)()}`);
537
+ function newAttemptId(idFactory) {
538
+ return idFactory.mintAttemptId();
539
539
  }
540
- function attemptIdForNextNode(parentAttemptId) {
541
- return (0, index_js_1.asAttemptId)(`next_${parentAttemptId}`);
540
+ function attemptIdForNextNode(parentAttemptId, sha256) {
541
+ return (0, attempt_id_derivation_js_1.deriveChildAttemptId)(parentAttemptId, sha256);
542
542
  }
543
543
  function signTokenOrErr(args) {
544
544
  const bytes = (0, index_js_2.encodeTokenPayloadV1)(args.payload);
@@ -630,7 +630,14 @@ function executeStartWorkflow(input, ctx) {
630
630
  if (!ctx.v2) {
631
631
  return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'v2 tools disabled', suggestion: 'Enable v2Tools flag' });
632
632
  }
633
- const { gate, sessionStore, snapshotStore, pinnedStore, keyring, crypto, hmac, base64url } = ctx.v2;
633
+ const { gate, sessionStore, snapshotStore, pinnedStore, keyring, crypto, hmac, base64url, idFactory } = ctx.v2;
634
+ if (!idFactory) {
635
+ return (0, neverthrow_1.errAsync)({
636
+ kind: 'precondition_failed',
637
+ message: 'v2 context missing idFactory',
638
+ suggestion: 'Reinitialize v2 tool context (idFactory must be provided when v2Tools are enabled).',
639
+ });
640
+ }
634
641
  const ctxCheck = checkContextBudget({ tool: 'start_workflow', context: input.context });
635
642
  if (!ctxCheck.ok)
636
643
  return (0, neverthrow_1.errAsync)({ kind: 'validation_failed', failure: ctxCheck.error });
@@ -678,9 +685,9 @@ function executeStartWorkflow(input, ctx) {
678
685
  });
679
686
  })
680
687
  .andThen(({ workflow, firstStep, workflowHash, pinnedWorkflow }) => {
681
- const sessionId = (0, index_js_3.asSessionId)(`sess_${(0, crypto_1.randomUUID)()}`);
682
- const runId = (0, index_js_3.asRunId)(`run_${(0, crypto_1.randomUUID)()}`);
683
- const nodeId = (0, index_js_3.asNodeId)(`node_${(0, crypto_1.randomUUID)()}`);
688
+ const sessionId = idFactory.mintSessionId();
689
+ const runId = idFactory.mintRunId();
690
+ const nodeId = idFactory.mintNodeId();
684
691
  const snapshot = {
685
692
  v: 1,
686
693
  kind: 'execution_snapshot',
@@ -697,9 +704,9 @@ function executeStartWorkflow(input, ctx) {
697
704
  return snapshotStore.putExecutionSnapshotV1(snapshot)
698
705
  .mapErr((cause) => ({ kind: 'snapshot_creation_failed', cause }))
699
706
  .andThen((snapshotRef) => {
700
- const evtSessionCreated = `evt_${(0, crypto_1.randomUUID)()}`;
701
- const evtRunStarted = `evt_${(0, crypto_1.randomUUID)()}`;
702
- const evtNodeCreated = `evt_${(0, crypto_1.randomUUID)()}`;
707
+ const evtSessionCreated = idFactory.mintEventId();
708
+ const evtRunStarted = idFactory.mintEventId();
709
+ const evtNodeCreated = idFactory.mintEventId();
703
710
  return gate.withHealthySessionLock(sessionId, (lock) => {
704
711
  const eventsArray = [
705
712
  {
@@ -768,7 +775,7 @@ function executeStartWorkflow(input, ctx) {
768
775
  nodeId,
769
776
  workflowHash,
770
777
  };
771
- const attemptId = newAttemptId();
778
+ const attemptId = newAttemptId(idFactory);
772
779
  const ackPayload = {
773
780
  tokenVersion: 1,
774
781
  tokenKind: 'ack',
@@ -812,7 +819,14 @@ function executeContinueWorkflow(input, ctx) {
812
819
  if (!ctx.v2) {
813
820
  return (0, neverthrow_1.errAsync)({ kind: 'precondition_failed', message: 'v2 tools disabled', suggestion: 'Enable v2Tools flag' });
814
821
  }
815
- const { gate, sessionStore, snapshotStore, pinnedStore, keyring, crypto, hmac, base64url } = ctx.v2;
822
+ const { gate, sessionStore, snapshotStore, pinnedStore, keyring, sha256, crypto, hmac, base64url, idFactory } = ctx.v2;
823
+ if (!sha256 || !idFactory) {
824
+ return (0, neverthrow_1.errAsync)({
825
+ kind: 'precondition_failed',
826
+ message: 'v2 context missing required dependencies',
827
+ suggestion: 'Reinitialize v2 tool context (sha256 and idFactory must be provided when v2Tools are enabled).',
828
+ });
829
+ }
816
830
  const stateRes = parseStateTokenOrFail(input.stateToken, keyring, hmac, base64url);
817
831
  if (!stateRes.ok)
818
832
  return (0, neverthrow_1.errAsync)({ kind: 'validation_failed', failure: stateRes.failure });
@@ -864,7 +878,7 @@ function executeContinueWorkflow(input, ctx) {
864
878
  const engineState = snapshot.enginePayload.engineState;
865
879
  const pending = (0, snapshot_state_js_1.derivePendingStep)(engineState);
866
880
  const isComplete = (0, snapshot_state_js_1.deriveIsComplete)(engineState);
867
- const attemptId = newAttemptId();
881
+ const attemptId = newAttemptId(idFactory);
868
882
  const ackTokenRes = signTokenOrErr({
869
883
  unsignedPrefix: 'ack.v1.',
870
884
  payload: { tokenVersion: 1, tokenKind: 'ack', sessionId, runId, nodeId, attemptId },
@@ -963,6 +977,7 @@ function executeContinueWorkflow(input, ctx) {
963
977
  pinnedWorkflow,
964
978
  snapshotStore,
965
979
  keyring,
980
+ sha256,
966
981
  hmac,
967
982
  base64url,
968
983
  });
@@ -986,6 +1001,7 @@ function executeContinueWorkflow(input, ctx) {
986
1001
  pinnedWorkflow,
987
1002
  snapshotStore,
988
1003
  sessionStore,
1004
+ idFactory,
989
1005
  }).andThen(() => sessionStore
990
1006
  .load(sessionId)
991
1007
  .map((truthAfter) => ({ kind: 'replay', truth: truthAfter, recordedEvent: null })));
@@ -1035,6 +1051,7 @@ function executeContinueWorkflow(input, ctx) {
1035
1051
  pinnedWorkflow,
1036
1052
  snapshotStore,
1037
1053
  keyring,
1054
+ sha256,
1038
1055
  hmac,
1039
1056
  base64url,
1040
1057
  });
@@ -95,18 +95,22 @@ async function createToolContext() {
95
95
  console.error('[FeatureFlags] v2 tools disabled due to keyring initialization failure');
96
96
  }
97
97
  else {
98
+ const sha256 = container_js_1.container.resolve(tokens_js_1.DI.V2.Sha256);
98
99
  const crypto = container_js_1.container.resolve(tokens_js_1.DI.V2.Crypto);
99
100
  const hmac = container_js_1.container.resolve(tokens_js_1.DI.V2.HmacSha256);
100
101
  const base64url = container_js_1.container.resolve(tokens_js_1.DI.V2.Base64Url);
102
+ const idFactory = container_js_1.container.resolve(tokens_js_1.DI.V2.IdFactory);
101
103
  v2 = {
102
104
  gate,
103
105
  sessionStore,
104
106
  snapshotStore,
105
107
  pinnedStore,
106
108
  keyring: keyringResult.value,
109
+ sha256,
107
110
  crypto,
108
111
  hmac,
109
112
  base64url,
113
+ idFactory,
110
114
  };
111
115
  console.error('[FeatureFlags] v2 tools enabled');
112
116
  }
@@ -8,9 +8,11 @@ import type { SessionEventLogAppendStorePortV2, SessionEventLogReadonlyStorePort
8
8
  import type { SnapshotStorePortV2 } from '../v2/ports/snapshot-store.port.js';
9
9
  import type { PinnedWorkflowStorePortV2 } from '../v2/ports/pinned-workflow-store.port.js';
10
10
  import type { KeyringV1 } from '../v2/ports/keyring.port.js';
11
+ import type { Sha256PortV2 } from '../v2/ports/sha256.port.js';
11
12
  import type { CryptoPortV2 } from '../v2/durable-core/canonical/hashing.js';
12
13
  import type { HmacSha256PortV2 } from '../v2/ports/hmac-sha256.port.js';
13
14
  import type { Base64UrlPortV2 } from '../v2/ports/base64url.port.js';
15
+ import type { IdFactoryV2 } from '../v2/infra/local/id-factory/index.js';
14
16
  import type { JsonValue } from './output-schemas.js';
15
17
  export interface SessionHealthDetails {
16
18
  readonly health: SessionHealthV2;
@@ -48,9 +50,11 @@ export interface V2Dependencies {
48
50
  readonly snapshotStore: SnapshotStorePortV2;
49
51
  readonly pinnedStore: PinnedWorkflowStorePortV2;
50
52
  readonly keyring: KeyringV1;
53
+ readonly sha256: Sha256PortV2;
51
54
  readonly crypto: CryptoPortV2;
52
55
  readonly hmac: HmacSha256PortV2;
53
56
  readonly base64url: Base64UrlPortV2;
57
+ readonly idFactory: IdFactoryV2;
54
58
  }
55
59
  export interface ToolContext {
56
60
  readonly workflowService: WorkflowService;
@@ -0,0 +1,4 @@
1
+ export type Base32LowerNoPad = string & {
2
+ readonly __brand: 'v2.Base32LowerNoPad';
3
+ };
4
+ export declare function encodeBase32LowerNoPad(bytes: Uint8Array): Base32LowerNoPad;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeBase32LowerNoPad = encodeBase32LowerNoPad;
4
+ const BASE32_LOWER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';
5
+ function encodeBase32LowerNoPad(bytes) {
6
+ let out = '';
7
+ let buffer = 0n;
8
+ let bits = 0;
9
+ for (const b of bytes) {
10
+ buffer = (buffer << 8n) | BigInt(b);
11
+ bits += 8;
12
+ while (bits >= 5) {
13
+ const shift = BigInt(bits - 5);
14
+ const index = Number((buffer >> shift) & 31n);
15
+ out += BASE32_LOWER_ALPHABET[index];
16
+ bits -= 5;
17
+ if (bits === 0) {
18
+ buffer = 0n;
19
+ }
20
+ else {
21
+ buffer = buffer & ((1n << BigInt(bits)) - 1n);
22
+ }
23
+ }
24
+ }
25
+ if (bits > 0) {
26
+ const index = Number((buffer << BigInt(5 - bits)) & 31n);
27
+ out += BASE32_LOWER_ALPHABET[index];
28
+ }
29
+ return out;
30
+ }
@@ -0,0 +1,3 @@
1
+ import type { Sha256PortV2 } from '../../ports/sha256.port.js';
2
+ import type { AttemptId } from './index.js';
3
+ export declare function deriveChildAttemptId(parent: AttemptId, sha256: Sha256PortV2): AttemptId;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveChildAttemptId = deriveChildAttemptId;
4
+ const index_js_1 = require("./index.js");
5
+ const base32_lower_js_1 = require("../encoding/base32-lower.js");
6
+ const PREFIX = 'wr_attempt_next_v1:';
7
+ function hexToBytes(hex) {
8
+ if (hex.length % 2 !== 0) {
9
+ throw new Error('hex string must have even length');
10
+ }
11
+ const out = new Uint8Array(hex.length / 2);
12
+ for (let i = 0; i < out.length; i++) {
13
+ const byteHex = hex.slice(i * 2, i * 2 + 2);
14
+ const n = Number.parseInt(byteHex, 16);
15
+ if (Number.isNaN(n)) {
16
+ throw new Error(`invalid hex byte: ${byteHex}`);
17
+ }
18
+ out[i] = n;
19
+ }
20
+ return out;
21
+ }
22
+ const SHA256_DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
23
+ function deriveChildAttemptId(parent, sha256) {
24
+ const bytes = new TextEncoder().encode(`${PREFIX}${String(parent)}`);
25
+ const digest = String(sha256.sha256(bytes));
26
+ if (!SHA256_DIGEST_RE.test(digest)) {
27
+ throw new Error(`expected sha256:<64hex> digest, got: ${digest}`);
28
+ }
29
+ const first16 = hexToBytes(digest.slice('sha256:'.length, 'sha256:'.length + 32));
30
+ const suffix = (0, base32_lower_js_1.encodeBase32LowerNoPad)(first16);
31
+ return (0, index_js_1.asAttemptId)(`attempt_${suffix}`);
32
+ }
@@ -8,10 +8,11 @@ const index_js_1 = require("../ids/index.js");
8
8
  const sha256DigestSchema = zod_1.z.string().regex(/^sha256:[0-9a-f]{64}$/, 'Expected sha256:<64 hex chars>');
9
9
  const workflowHashSchema = sha256DigestSchema.transform((v) => (0, index_js_1.asWorkflowHash)((0, index_js_1.asSha256Digest)(v)));
10
10
  const nonEmpty = zod_1.z.string().min(1);
11
- exports.AttemptIdSchema = nonEmpty.transform(index_js_1.asAttemptId);
12
- exports.SessionIdSchema = nonEmpty.transform(index_js_1.asSessionId);
13
- exports.RunIdSchema = nonEmpty.transform(index_js_1.asRunId);
14
- exports.NodeIdSchema = nonEmpty.transform(index_js_1.asNodeId);
11
+ const delimiterSafeId = nonEmpty.regex(/^[^:\s]+$/, 'Expected a delimiter-safe ID (no ":" or whitespace)');
12
+ exports.AttemptIdSchema = delimiterSafeId.transform(index_js_1.asAttemptId);
13
+ exports.SessionIdSchema = delimiterSafeId.transform(index_js_1.asSessionId);
14
+ exports.RunIdSchema = delimiterSafeId.transform(index_js_1.asRunId);
15
+ exports.NodeIdSchema = delimiterSafeId.transform(index_js_1.asNodeId);
15
16
  exports.StateTokenPayloadV1Schema = zod_1.z.object({
16
17
  tokenVersion: zod_1.z.literal(1),
17
18
  tokenKind: zod_1.z.literal('state'),
@@ -0,0 +1,11 @@
1
+ import type { RandomEntropyPortV2 } from '../../../ports/random-entropy.port.js';
2
+ import type { AttemptId, NodeId, RunId, SessionId } from '../../../durable-core/ids/index.js';
3
+ export declare class IdFactoryV2 {
4
+ private readonly entropy;
5
+ constructor(entropy: RandomEntropyPortV2);
6
+ mintSessionId(): SessionId;
7
+ mintRunId(): RunId;
8
+ mintNodeId(): NodeId;
9
+ mintAttemptId(): AttemptId;
10
+ mintEventId(): string;
11
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IdFactoryV2 = void 0;
4
+ const index_js_1 = require("../../../durable-core/ids/index.js");
5
+ const base32_lower_js_1 = require("../../../durable-core/encoding/base32-lower.js");
6
+ const BYTES = 16;
7
+ function mint(prefix, entropy) {
8
+ const bytes = entropy.generateBytes(BYTES);
9
+ const suffix = (0, base32_lower_js_1.encodeBase32LowerNoPad)(bytes);
10
+ return `${prefix}_${suffix}`;
11
+ }
12
+ class IdFactoryV2 {
13
+ constructor(entropy) {
14
+ this.entropy = entropy;
15
+ }
16
+ mintSessionId() {
17
+ return (0, index_js_1.asSessionId)(mint('sess', this.entropy));
18
+ }
19
+ mintRunId() {
20
+ return (0, index_js_1.asRunId)(mint('run', this.entropy));
21
+ }
22
+ mintNodeId() {
23
+ return (0, index_js_1.asNodeId)(mint('node', this.entropy));
24
+ }
25
+ mintAttemptId() {
26
+ return (0, index_js_1.asAttemptId)(mint('attempt', this.entropy));
27
+ }
28
+ mintEventId() {
29
+ return mint('evt', this.entropy);
30
+ }
31
+ }
32
+ exports.IdFactoryV2 = IdFactoryV2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {