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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/runtime/canonical-run-service.js +8 -1
- package/dist/runtime/local-execution-authority.d.ts +2 -0
- package/dist/runtime/local-execution-authority.js +101 -33
- package/dist/runtime/local-windows-execution-supervisor-script.js +5 -3
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
28
|
+
"version": "1.9.8"
|
|
29
29
|
}
|
|
@@ -3560,7 +3560,14 @@ export class CanonicalRunService {
|
|
|
3560
3560
|
resolution = await this.executionAuthority.requestStop(stopRequest);
|
|
3561
3561
|
}
|
|
3562
3562
|
catch (error) {
|
|
3563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1601
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2643
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|