intentdna 1.9.6 → 1.9.8

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.
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "intentdna",
11
11
  "description": "Declarative policy layer for AI agent behavior with plugin-managed hook runtime for Claude Code.",
12
- "version": "1.9.6",
12
+ "version": "1.9.8",
13
13
  "author": {
14
14
  "name": "Samuel"
15
15
  },
@@ -25,5 +25,5 @@
25
25
  ]
26
26
  }
27
27
  ],
28
- "version": "1.9.6"
28
+ "version": "1.9.8"
29
29
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.9.6",
3
+ "version": "1.9.8",
4
4
  "description": "Declarative policy layer for AI agent behavior",
5
5
  "author": {
6
6
  "name": "Samuel"
@@ -3560,7 +3560,14 @@ export class CanonicalRunService {
3560
3560
  resolution = await this.executionAuthority.requestStop(stopRequest);
3561
3561
  }
3562
3562
  catch (error) {
3563
- throw new CanonicalRunServiceError("unsupported", "worker stop authority is unavailable", { cause: error });
3563
+ const details = error !== null && typeof error === "object"
3564
+ ? error
3565
+ : {};
3566
+ const diagnostic = [
3567
+ "control_timeout", "control_connection", "control_empty_response",
3568
+ "identity_conflict", "corrupt_state", "invalid_configuration", "unsupported",
3569
+ ].find((code) => code === details.diagnostic_code || code === details.code);
3570
+ throw new CanonicalRunServiceError("unsupported", `worker stop authority is unavailable${diagnostic === undefined ? "" : ` (${diagnostic})`}`, { cause: error });
3564
3571
  }
3565
3572
  const matches = resolution?.kind === "stopped_by_request"
3566
3573
  ? resolution.authority !== undefined
@@ -185,7 +185,9 @@ export declare class LocalAttemptExecutionAuthority implements AttemptExecutionA
185
185
  private executeSupervised;
186
186
  private observeRecoveredExecution;
187
187
  private recordNormalizedCompletion;
188
+ private recoverControlCompletion;
188
189
  private sendControl;
190
+ private sendControlOnce;
189
191
  private issueReceipt;
190
192
  private issueAlreadyStoppedEvidence;
191
193
  withStoppedExecution<T>(request: AttemptExecutionAuthorityRequest, operation: (authority: StoppedExecutionAuthority) => Promise<T>): Promise<T>;
@@ -31,6 +31,13 @@ export class LocalExecutionAuthorityError extends Error {
31
31
  this.code = code;
32
32
  }
33
33
  }
34
+ class LocalExecutionControlTransportError extends LocalExecutionAuthorityError {
35
+ diagnostic_code;
36
+ constructor(diagnostic_code, message, options) {
37
+ super("unsupported", message, options);
38
+ this.diagnostic_code = diagnostic_code;
39
+ }
40
+ }
34
41
  function durableLockOwnersEqual(left, right) {
35
42
  return left !== null && canonicalizeJson(left) === canonicalizeJson(right);
36
43
  }
@@ -1561,22 +1568,24 @@ export class LocalAttemptExecutionAuthority {
1561
1568
  if (initial.backend.kind !== "windows_job" || initial.raw_completion !== null)
1562
1569
  return initial;
1563
1570
  await this.ensureWindowsBackendTrusted(initial, path);
1564
- let lastError = null;
1571
+ const deadline = Date.now() + this.controlTimeoutMs;
1565
1572
  for (let attempt = 0; attempt < 3; attempt += 1) {
1566
1573
  const before = this.readRecord(path);
1567
1574
  if (before.raw_completion !== null || before.phase === "unsupported")
1568
1575
  return before;
1569
1576
  if (before.phase !== "running" || before.supervisor.pid === null || before.supervisor.ready_at === null) {
1570
- lastError = new Error("Windows execution supervisor is not durably ready");
1571
- break;
1577
+ throw new LocalExecutionControlTransportError("control_connection", "Windows execution supervisor is not durably ready");
1572
1578
  }
1573
1579
  try {
1574
- const response = await this.sendControl(before, { action: "probe" });
1580
+ const response = await this.sendControl(before, { action: "probe" }, deadline);
1575
1581
  const after = this.readRecord(path);
1576
1582
  if (after.raw_completion !== null || after.phase === "unsupported")
1577
1583
  return after;
1578
- if (after.revision !== before.revision)
1584
+ if (after.revision !== before.revision) {
1585
+ if (Date.now() >= deadline)
1586
+ break;
1579
1587
  continue;
1588
+ }
1580
1589
  const identity = response.identity;
1581
1590
  const expected = {
1582
1591
  record_revision: after.revision,
@@ -1597,14 +1606,15 @@ export class LocalAttemptExecutionAuthority {
1597
1606
  return after;
1598
1607
  }
1599
1608
  catch (error) {
1600
- lastError = error;
1601
- break;
1609
+ if (error instanceof LocalExecutionControlTransportError)
1610
+ throw error;
1611
+ const message = error instanceof Error
1612
+ ? error.message
1613
+ : "Windows execution supervisor identity could not be authenticated";
1614
+ return this.failWindowsAuthentication(path, message, error);
1602
1615
  }
1603
1616
  }
1604
- const message = lastError instanceof Error
1605
- ? lastError.message
1606
- : "Windows execution supervisor identity could not be authenticated";
1607
- return this.failWindowsAuthentication(path, message, lastError);
1617
+ throw new LocalExecutionControlTransportError("control_timeout", "Windows execution supervisor identity changed during control authentication");
1608
1618
  }
1609
1619
  readRecoveryRecord(request) {
1610
1620
  const identity = recoveryIdentity(request);
@@ -2619,7 +2629,64 @@ export class LocalAttemptExecutionAuthority {
2619
2629
  };
2620
2630
  });
2621
2631
  }
2622
- sendControl(record, request) {
2632
+ recoverControlCompletion(record, request) {
2633
+ const durable = this.readRecord(this.recordPath(record.identity));
2634
+ if (canonicalizeJson(durable.identity) !== canonicalizeJson(record.identity)
2635
+ || durable.identity_digest !== record.identity_digest
2636
+ || durable.execution_instance_id !== record.execution_instance_id
2637
+ || durable.authority_instance_id !== record.authority_instance_id
2638
+ || durable.challenge !== record.challenge
2639
+ || canonicalizeJson(durable.backend) !== canonicalizeJson(record.backend)
2640
+ || canonicalizeJson(durable.supervisor) !== canonicalizeJson(record.supervisor)
2641
+ || durable.revision < record.revision) {
2642
+ throw new LocalExecutionAuthorityError("identity_conflict", "durable control recovery identity does not match");
2643
+ }
2644
+ if (durable.phase === "unsupported") {
2645
+ throw new LocalExecutionAuthorityError("unsupported", "execution supervisor evidence is unavailable");
2646
+ }
2647
+ if (request.action === "stop") {
2648
+ if (request.stop_idempotency_key !== undefined) {
2649
+ if (durable.stop_fact?.kind === "canonical_stop"
2650
+ && (durable.stop_fact.stop_idempotency_key !== request.stop_idempotency_key
2651
+ || durable.stop_fact.requested_at !== request.requested_at
2652
+ || canonicalizeJson(durable.stop_fact.requested_outcome) !== canonicalizeJson(request.requested_outcome))) {
2653
+ throw new LocalExecutionAuthorityError("identity_conflict", "durable canonical stop conflicts with the request");
2654
+ }
2655
+ // Natural completion can win the race; requestStop issues distinct already-stopped evidence.
2656
+ }
2657
+ else if (durable.stop_fact !== null
2658
+ && (durable.stop_fact.kind !== "local_cancel" || durable.stop_fact.reason !== (request.reason ?? null))) {
2659
+ throw new LocalExecutionAuthorityError("identity_conflict", "durable local cancellation conflicts with the request");
2660
+ }
2661
+ }
2662
+ if (durable.raw_completion === null)
2663
+ return null;
2664
+ if (durable.raw_completion.process_tree_drained !== true) {
2665
+ throw new LocalExecutionAuthorityError("corrupt_state", "trusted process-tree drain evidence is unavailable");
2666
+ }
2667
+ return { ok: true, raw_completion: durable.raw_completion };
2668
+ }
2669
+ async sendControl(record, request, deadline = Date.now() + this.controlTimeoutMs) {
2670
+ let lastError;
2671
+ for (let attempt = 0; attempt < 3; attempt += 1) {
2672
+ const remaining = deadline - Date.now();
2673
+ if (remaining <= 0)
2674
+ break;
2675
+ try {
2676
+ return await this.sendControlOnce(record, request, Math.max(1, Math.floor(remaining / (3 - attempt))));
2677
+ }
2678
+ catch (error) {
2679
+ if (!(error instanceof LocalExecutionControlTransportError))
2680
+ throw error;
2681
+ const completed = this.recoverControlCompletion(record, request);
2682
+ if (completed !== null)
2683
+ return completed;
2684
+ lastError = error;
2685
+ }
2686
+ }
2687
+ throw lastError ?? new LocalExecutionControlTransportError("control_timeout", "execution supervisor control timed out");
2688
+ }
2689
+ sendControlOnce(record, request, timeoutMs) {
2623
2690
  const socketPath = record.supervisor.socket_path;
2624
2691
  if (record.backend.kind !== "windows_job") {
2625
2692
  let metadata;
@@ -2627,6 +2694,9 @@ export class LocalAttemptExecutionAuthority {
2627
2694
  metadata = lstatSync(socketPath);
2628
2695
  }
2629
2696
  catch (error) {
2697
+ if (isErrno(error, "ENOENT") || isErrno(error, "ENOTDIR")) {
2698
+ throw new LocalExecutionControlTransportError("control_connection", "execution supervisor control socket is unavailable", { cause: error });
2699
+ }
2630
2700
  throw new LocalExecutionAuthorityError("unsupported", "execution supervisor control socket is unavailable", { cause: error });
2631
2701
  }
2632
2702
  if (!metadata.isSocket() || metadata.isSymbolicLink() || (metadata.mode & 0o077) !== 0) {
@@ -2639,42 +2709,32 @@ export class LocalAttemptExecutionAuthority {
2639
2709
  let response = "";
2640
2710
  const timer = setTimeout(() => {
2641
2711
  socket.destroy();
2642
- rejectPromise(new LocalExecutionAuthorityError("unsupported", "execution supervisor control timed out"));
2643
- }, this.controlTimeoutMs);
2712
+ rejectPromise(new LocalExecutionControlTransportError("control_timeout", "execution supervisor control timed out"));
2713
+ }, timeoutMs);
2644
2714
  timer.unref?.();
2645
2715
  const fail = (error) => {
2646
2716
  clearTimeout(timer);
2647
- rejectPromise(new LocalExecutionAuthorityError("unsupported", "execution supervisor control failed", { cause: error }));
2717
+ const transient = ["ECONNRESET", "ECONNREFUSED", "ECONNABORTED", "EPIPE", "ENOENT", "ETIMEDOUT"]
2718
+ .some((code) => isErrno(error, code));
2719
+ rejectPromise(transient
2720
+ ? new LocalExecutionControlTransportError("control_connection", "execution supervisor control failed", { cause: error })
2721
+ : new LocalExecutionAuthorityError("unsupported", "execution supervisor control failed", { cause: error }));
2648
2722
  };
2649
2723
  socket.once("error", fail);
2650
2724
  socket.setEncoding("utf8");
2651
2725
  socket.on("data", (chunk) => {
2652
2726
  response += chunk;
2653
2727
  if (Buffer.byteLength(response, "utf8") > MAX_CONTROL_RESPONSE_BYTES) {
2654
- socket.destroy(new Error("execution control response too large"));
2728
+ clearTimeout(timer);
2729
+ rejectPromise(new LocalExecutionAuthorityError("unsupported", "execution control response too large"));
2730
+ socket.destroy();
2655
2731
  }
2656
2732
  });
2657
2733
  socket.once("end", () => {
2658
2734
  clearTimeout(timer);
2659
2735
  try {
2660
2736
  if (response.trim().length === 0) {
2661
- try {
2662
- const durable = this.readRecord(this.recordPath(record.identity));
2663
- const matchesStop = durable.raw_completion !== null
2664
- && (request.stop_idempotency_key === undefined
2665
- || (durable.stop_fact?.kind === "canonical_stop"
2666
- && durable.stop_fact.stop_idempotency_key === request.stop_idempotency_key
2667
- && canonicalizeJson(durable.stop_fact.requested_outcome)
2668
- === canonicalizeJson(request.requested_outcome)));
2669
- if (matchesStop) {
2670
- resolvePromise({ ok: true, raw_completion: durable.raw_completion });
2671
- return;
2672
- }
2673
- }
2674
- catch {
2675
- // The control response remains authoritative unless durable completion proves the stop.
2676
- }
2677
- throw new LocalExecutionAuthorityError("unsupported", "execution supervisor control response was empty");
2737
+ throw new LocalExecutionControlTransportError("control_empty_response", "execution supervisor control response was empty");
2678
2738
  }
2679
2739
  let responseText = response;
2680
2740
  if (framedControl) {
@@ -2909,6 +2969,9 @@ export class LocalAttemptExecutionAuthority {
2909
2969
  record = await this.authenticateWindowsSupervisor(record, path);
2910
2970
  }
2911
2971
  }
2972
+ if (record.phase === "unsupported") {
2973
+ throw new LocalExecutionAuthorityError("unsupported", "execution supervisor evidence is unavailable");
2974
+ }
2912
2975
  if (record.raw_completion === null) {
2913
2976
  await this.sendControl(record, {
2914
2977
  action: "stop",
@@ -2918,9 +2981,14 @@ export class LocalAttemptExecutionAuthority {
2918
2981
  });
2919
2982
  record = this.readRecord(path);
2920
2983
  }
2984
+ if (record.raw_completion !== null && record.stop_fact?.kind !== "canonical_stop") {
2985
+ const evidence = await this.issueAlreadyStoppedEvidence(path, request);
2986
+ return { kind: "already_stopped", drain_evidence: evidence };
2987
+ }
2921
2988
  if (record.raw_completion === null
2922
2989
  || record.stop_fact?.kind !== "canonical_stop"
2923
2990
  || record.stop_fact.stop_idempotency_key !== request.stop_idempotency_key
2991
+ || record.stop_fact.requested_at !== request.requested_at
2924
2992
  || canonicalizeJson(record.stop_fact.requested_outcome) !== canonicalizeJson(request.requested_outcome)) {
2925
2993
  throw new LocalExecutionAuthorityError("unsupported", "canonical stop evidence was not durably published");
2926
2994
  }
@@ -508,17 +508,18 @@ async function main() {
508
508
  }
509
509
 
510
510
  async function registerStop(kind, detail) {
511
- if (rawCompletion !== null || pendingCompletionFact !== null) {
512
- throw controlConflict("execution already completed before the stop request");
513
- }
514
511
  if (stopFact !== null) {
512
+ if (kind === "canonical_stop" && stopFact.kind !== "canonical_stop") return;
515
513
  const same = stopFact.kind === kind && (kind !== "canonical_stop" || (
516
514
  stopFact.stop_idempotency_key === detail.stop_idempotency_key
515
+ && stopFact.requested_at === detail.requested_at
517
516
  && canonical(stopFact.requested_outcome) === canonical(detail.requested_outcome)
518
517
  ));
519
518
  if (!same) throw controlConflict("execution already has a different terminal fact");
520
519
  return;
521
520
  }
521
+ // An authenticated stop can race natural completion; preserve the original completion.
522
+ if (rawCompletion !== null || pendingCompletionFact !== null) return;
522
523
  if (kind === "canonical_stop" && canonicalStopMode(detail.requested_outcome) === null) {
523
524
  throw controlConflict("canonical stop outcome is unsupported by Windows execution");
524
525
  }
@@ -533,6 +534,7 @@ async function main() {
533
534
  const next = await persist({ stop_fact: stopFact });
534
535
  if (next.phase !== "running" || canonical(next.stop_fact) !== canonical(stopFact)) {
535
536
  stopFact = null;
537
+ if (next.phase !== "unsupported" && next.raw_completion !== null && next.stop_fact === null) return;
536
538
  throw controlConflict("execution completion won before the stop request");
537
539
  }
538
540
  sendTerminateIfReleased();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.9.6",
3
+ "version": "1.9.8",
4
4
  "description": "Intent DNA — Declarative policy layer for AI agent behavior",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",