agentic-workflow-manager 9.12.4 → 9.12.5

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.
@@ -542,11 +542,18 @@ function applyRequestToState(s, env, digest, repoRoot, eventScope) {
542
542
  { taskId, entity: 'dispatch', dispatchId: existing.id },
543
543
  { taskId, dispatchId: existing.id, entity: 'dispatch' },
544
544
  ].map(payload => (0, requests_1.digestOf)(payload));
545
- const oldAckApplied = Object.values(s.appliedRequests).some(ack => ack.outcome === 'applied'
545
+ const appliedAcks = Object.values(s.appliedRequests);
546
+ const oldAckIndex = appliedAcks.findIndex(ack => ack.outcome === 'applied'
546
547
  && ack.resultRef === existing.id && legacyDigests.includes(ack.payloadDigest));
548
+ const reservationAckIndex = appliedAcks.findIndex(ack => ack.requestId === attempt.reservationRequestId);
549
+ // The single writer appends ACKs to appliedRequests and persists
550
+ // their insertion order. Equal millisecond timestamps need this
551
+ // additional durable ordering evidence, not an inferred child.
552
+ const legacyPrecedesReservation = oldAt < reservedAt
553
+ || (oldAt === reservedAt && oldAckIndex >= 0 && reservationAckIndex > oldAckIndex);
547
554
  const safeLegacy = reservedForNewDispatch && Number.isFinite(oldAt) && Number.isFinite(reservedAt)
548
- && oldAt < reservedAt
549
- && oldAckApplied
555
+ && legacyPrecedesReservation
556
+ && oldAckIndex >= 0
550
557
  && s.dispatches.filter(d => d.taskId === taskId).length === 1
551
558
  && s.tasks.find(t => t.id === taskId)?.attempts === 1
552
559
  && s.tasks.find(t => t.id === taskId)?.status === 'in-progress'
@@ -224,6 +224,37 @@ describe('issue #196 reopened: supervised S1 handoff', () => {
224
224
  .toThrow(/conflictivo/);
225
225
  expect(state.dispatches).toHaveLength(2);
226
226
  });
227
+ test('equal-millisecond legacy timestamps reconcile only when the durable dispatch ACK precedes the reservation ACK', () => {
228
+ const state = v2State();
229
+ const at = state.routingAttempts[0].reservedAt;
230
+ state.dispatches.push({ id: 'dispatch-S1', taskId: 'S1', at });
231
+ state.tasks[0].attempts = 1;
232
+ state.tasks[0].status = 'in-progress';
233
+ const reservationAck = state.appliedRequests['reserve-1'];
234
+ const historicalAck = { requestId: 'old-dispatch-request', idempotencyKey: 'old-dispatch-key',
235
+ payloadDigest: legacyDispatchDigest(), outcome: 'applied', resultRef: 'dispatch-S1' };
236
+ state.appliedRequests = { 'old-dispatch-request': historicalAck, 'reserve-1': reservationAck };
237
+ const durable = JSON.parse(JSON.stringify(state));
238
+ expect(Object.keys(durable.appliedRequests)).toEqual(['old-dispatch-request', 'reserve-1']);
239
+ dispatch(durable, { routingAttemptId: 'route-1' });
240
+ expect(durable.dispatches).toHaveLength(2);
241
+ expect(durable.dispatches[0]).toEqual({ id: 'dispatch-S1', taskId: 'S1', at });
242
+ expect(durable.appliedRequests['old-dispatch-request']).toEqual(historicalAck);
243
+ expect(durable.tasks[0].attempts).toBe(1);
244
+ expect((0, types_1.isWellFormedState)(durable)).toBe(true);
245
+ });
246
+ test('equal-millisecond legacy timestamps do not infer order from a later dispatch ACK', () => {
247
+ const state = v2State();
248
+ state.dispatches.push({ id: 'dispatch-S1', taskId: 'S1', at: state.routingAttempts[0].reservedAt });
249
+ state.tasks[0].attempts = 1;
250
+ state.tasks[0].status = 'in-progress';
251
+ state.appliedRequests['old-dispatch-request'] = { requestId: 'old-dispatch-request',
252
+ idempotencyKey: 'old-dispatch-key', payloadDigest: legacyDispatchDigest(),
253
+ outcome: 'applied', resultRef: 'dispatch-S1' };
254
+ expect(() => dispatch(state, { routingAttemptId: 'route-1' })).toThrow(/legacy handoff/);
255
+ expect(state.dispatches).toHaveLength(1);
256
+ expect(state.appliedRequests['dispatch-request']).toBeUndefined();
257
+ });
227
258
  test('historical ACK matching is independent of JSON key order but not payload identity', () => {
228
259
  for (const rawJson of [
229
260
  '{"taskId":"S1","dispatchId":"dispatch-S1"}',
@@ -972,10 +1003,10 @@ describe('issue #196 reopened: supervised S1 handoff', () => {
972
1003
  (0, apply_1.consumePendingRequests)(repo, 'main', generation.token, scope);
973
1004
  expect((0, store_1.readJournal)(repo, 'main').state.requestProblems).toEqual([]);
974
1005
  expect(cli('ack', task.requestId).state).toBe('applied');
975
- // Durable 9.12.2 intent predates the route reservation. Neither
976
- // the record nor its old ACK may be rewritten to imply a child.
1006
+ // Durable 9.12.2 intent and ACK precede the route reservation.
1007
+ // Equal timestamp granularity alone must not imply a child.
977
1008
  const historical = (0, store_1.readJournal)(repo, 'main').state;
978
- const historicalAt = new Date(Date.now() - 60_000).toISOString();
1009
+ let historicalAt = new Date(Date.now() - 60_000).toISOString();
979
1010
  historical.dispatches.push({ id: 'dispatch-S1', taskId: 'S1', at: historicalAt });
980
1011
  historical.tasks[0].attempts = 1;
981
1012
  historical.tasks[0].status = 'in-progress';
@@ -989,6 +1020,14 @@ describe('issue #196 reopened: supervised S1 handoff', () => {
989
1020
  const reservation = cli('ack', reserve.requestId);
990
1021
  expect(reservation.state).toBe('applied');
991
1022
  const attemptId = reservation.resultRef;
1023
+ // Reproduce the real journal's millisecond tie without changing
1024
+ // the durable ACK order or adding any native evidence.
1025
+ const tied = (0, store_1.readJournal)(repo, 'main').state;
1026
+ historicalAt = tied.routingAttempts.find(attempt => attempt.id === attemptId).reservedAt;
1027
+ tied.dispatches[0].at = historicalAt;
1028
+ expect(Object.keys(tied.appliedRequests).indexOf('old-dispatch-request'))
1029
+ .toBeLessThan(Object.keys(tied.appliedRequests).indexOf(reserve.requestId));
1030
+ (0, store_1.writeJournal)(repo, 'main', tied);
992
1031
  const dispatchPayload = JSON.stringify({ dispatchId: 'dispatch-S1', taskId: 'S1', routingAttemptId: attemptId });
993
1032
  const queuedDispatch = cli('register', '--generation', generation.token, '--entity', 'dispatch', '--json', dispatchPayload);
994
1033
  expect(cli('ack', queuedDispatch.requestId).state).toBe('pending');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.12.4",
3
+ "version": "9.12.5",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
Binary file
Binary file