@sema-agent/server 7.53.0 → 7.55.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.
- package/USAGE.md +5 -0
- package/dist/approval-ask-audit-store.d.ts +129 -0
- package/dist/approval-ask-audit-store.js +284 -0
- package/dist/approval-card.d.ts +18 -6
- package/dist/approval-card.js +2 -2
- package/dist/approval-reconciler.d.ts +2 -1
- package/dist/approval-reconciler.js +1 -1
- package/dist/boot/config-center.js +2 -2
- package/dist/boot/coordinators.d.ts +2 -0
- package/dist/boot/coordinators.js +18 -1
- package/dist/boot/leader.js +2 -0
- package/dist/boot/reapers.d.ts +2 -1
- package/dist/boot/resolve-spec.js +1 -3
- package/dist/boot/runner-deps.d.ts +20 -0
- package/dist/boot/runner-deps.js +14 -8
- package/dist/boot/shutdown.js +1 -1
- package/dist/boot/stores.js +8 -1
- package/dist/capabilities/team.d.ts +10 -9
- package/dist/config-center/apply-effective.d.ts +25 -26
- package/dist/config-center/apply-effective.js +28 -66
- package/dist/config-center/effective-keys.d.ts +30 -0
- package/dist/config-center/effective-keys.js +49 -0
- package/dist/config-center/facade.d.ts +3 -3
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/restart-signal.js +3 -9
- package/dist/config-center/types.d.ts +19 -40
- package/dist/config-lkg.js +7 -1
- package/dist/config-provider.js +1 -1
- package/dist/device-enrollment.d.ts +5 -3
- package/dist/device-store.d.ts +81 -3
- package/dist/device-store.js +37 -0
- package/dist/device-ws-hub.d.ts +15 -2
- package/dist/device-ws-hub.js +31 -5
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/devices.d.ts +64 -0
- package/dist/http/routes/devices.js +173 -0
- package/dist/http/server.d.ts +14 -0
- package/dist/http/server.js +32 -2
- package/dist/http/wire-types.d.ts +19 -0
- package/dist/leader/wire.d.ts +12 -0
- package/dist/leader/wire.js +6 -5
- package/dist/main.js +3 -1
- package/dist/observability/fail-open.d.ts +20 -0
- package/dist/observability/fail-open.js +20 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +11 -1
- package/dist/plugins/approval-ask-store-memory.js +20 -3
- package/dist/plugins/approval-ask-store-sql.d.ts +82 -0
- package/dist/plugins/approval-ask-store-sql.js +41 -10
- package/dist/plugins/device-store-sql.d.ts +38 -1
- package/dist/plugins/device-store-sql.js +82 -2
- package/dist/plugins/pg-pool.d.ts +29 -2
- package/dist/plugins/pg-pool.js +45 -2
- package/dist/plugins/remote-env-device.d.ts +4 -2
- package/dist/plugins/remote-env-device.js +12 -2
- package/dist/plugins/roster-store-sql.d.ts +1 -3
- package/dist/plugins/sql-errors.d.ts +12 -0
- package/dist/plugins/sql-errors.js +10 -0
- package/dist/plugins/store-backend.js +1 -1
- package/dist/plugins/tidb-pool.d.ts +13 -0
- package/dist/runs.js +1 -0
- package/dist/runtime-governance.d.ts +4 -0
- package/dist/runtime-governance.js +23 -1
- package/dist/tool-approval.d.ts +63 -39
- package/dist/tool-approval.js +322 -109
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/injection-tier.d.ts +11 -21
- package/dist/trace/injection-tier.js +3 -4
- package/dist/trace/ledger-events.d.ts +9 -0
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/package.json +3 -3
package/dist/tool-approval.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createLogger } from "./observability/logger.js";
|
|
|
8
8
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
9
9
|
import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
10
10
|
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, } from "./approval-card.js";
|
|
11
|
+
import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
11
12
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
12
13
|
const defaultLogger = createLogger();
|
|
13
14
|
export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
|
|
@@ -43,9 +44,26 @@ function durableRuleMaterial(material) {
|
|
|
43
44
|
return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
|
|
44
45
|
}
|
|
45
46
|
export const ASK_DECISION_CONSUMER_ABSENT = "absent";
|
|
47
|
+
const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
|
|
46
48
|
const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
|
|
47
49
|
const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
|
|
48
50
|
const DURABLE_CALL_TIMEOUT_MS = 5_000;
|
|
51
|
+
const TERMINAL_LADDER_WALL_MS = DURABLE_CALL_TIMEOUT_MS + DURABLE_CONVERGE_READ_TIMEOUT_MS + DURABLE_CONVERGE_BACKOFF_MS.reduce((a, b) => a + b, 0);
|
|
52
|
+
const TERMINAL_CLAIM_CAP_POLL_MS = 50;
|
|
53
|
+
function ambiguousCasFailOpenTag(intent) {
|
|
54
|
+
switch (intent) {
|
|
55
|
+
case "expire":
|
|
56
|
+
return "server.approval.ambiguous-cas.expire";
|
|
57
|
+
case "cancel":
|
|
58
|
+
return "server.approval.ambiguous-cas.cancel";
|
|
59
|
+
case "unreachable":
|
|
60
|
+
return "server.approval.ambiguous-cas.unreachable";
|
|
61
|
+
default: {
|
|
62
|
+
const unreachable = intent;
|
|
63
|
+
throw new Error(`ambiguousCasFailOpenTag: unknown intent ${String(unreachable)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
49
67
|
class ApprovalStoreDeadlineError extends Error {
|
|
50
68
|
constructor(label, timeoutMs) {
|
|
51
69
|
super(`approval store call ${label} timed out after ${timeoutMs}ms`);
|
|
@@ -214,6 +232,30 @@ function settleArgsOf(outcome) {
|
|
|
214
232
|
export class ToolApprovalCoordinator {
|
|
215
233
|
als = new AsyncLocalStorage();
|
|
216
234
|
pending = new Map();
|
|
235
|
+
terminalClaimsInFlight = new Map();
|
|
236
|
+
admitTerminalClaim(askId) {
|
|
237
|
+
const n = this.terminalClaimsInFlight.get(askId) ?? 0;
|
|
238
|
+
if (n >= MAX_INFLIGHT_TERMINAL_CLAIMS)
|
|
239
|
+
return undefined;
|
|
240
|
+
this.terminalClaimsInFlight.set(askId, n + 1);
|
|
241
|
+
let released = false;
|
|
242
|
+
return () => {
|
|
243
|
+
if (released)
|
|
244
|
+
return;
|
|
245
|
+
released = true;
|
|
246
|
+
const cur = this.terminalClaimsInFlight.get(askId) ?? 0;
|
|
247
|
+
if (cur <= 1)
|
|
248
|
+
this.terminalClaimsInFlight.delete(askId);
|
|
249
|
+
else
|
|
250
|
+
this.terminalClaimsInFlight.set(askId, cur - 1);
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
terminalClaimsInFlightCount(askId) {
|
|
254
|
+
return this.terminalClaimsInFlight.get(askId) ?? 0;
|
|
255
|
+
}
|
|
256
|
+
terminalClaimsInFlightForTest(askId) {
|
|
257
|
+
return this.terminalClaimsInFlightCount(askId);
|
|
258
|
+
}
|
|
217
259
|
pendingByAskId = new Map();
|
|
218
260
|
allowAllSessions = new Set();
|
|
219
261
|
streams = new Map();
|
|
@@ -239,6 +281,7 @@ export class ToolApprovalCoordinator {
|
|
|
239
281
|
parkTombstoneTtlMs;
|
|
240
282
|
parkTombstonePerPrincipalMax;
|
|
241
283
|
parkedRedeem;
|
|
284
|
+
askAudit;
|
|
242
285
|
constructor(opts) {
|
|
243
286
|
this.ruleConsent = opts?.ruleConsent;
|
|
244
287
|
this.ruleScopeRootFor = opts?.ruleScopeRootFor;
|
|
@@ -254,6 +297,7 @@ export class ToolApprovalCoordinator {
|
|
|
254
297
|
this.parkTombstoneMax = opts?.parkTombstoneMax ?? DEFAULT_PARK_TOMBSTONE_MAX;
|
|
255
298
|
this.parkTombstoneTtlMs = opts?.parkTombstoneTtlMs ?? DEFAULT_PARK_TOMBSTONE_TTL_MS;
|
|
256
299
|
this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
|
|
300
|
+
this.askAudit = opts?.askAudit;
|
|
257
301
|
}
|
|
258
302
|
unattendedOutcome() {
|
|
259
303
|
return this.unattendedPolicy === "deny" ? false : "unavailable";
|
|
@@ -440,26 +484,39 @@ export class ToolApprovalCoordinator {
|
|
|
440
484
|
return false;
|
|
441
485
|
}
|
|
442
486
|
}
|
|
443
|
-
async
|
|
444
|
-
if (!this.askStore)
|
|
445
|
-
return
|
|
487
|
+
async probeRowWhileYielding(askId, budgetMs) {
|
|
488
|
+
if (!this.askStore || budgetMs <= 0)
|
|
489
|
+
return { kind: "none" };
|
|
490
|
+
const release = this.admitTerminalClaim(askId);
|
|
491
|
+
if (release === undefined)
|
|
492
|
+
return { kind: "denied" };
|
|
493
|
+
const raw = this.askStore.getAsk(askId);
|
|
494
|
+
void raw.then(release, release);
|
|
495
|
+
let row;
|
|
446
496
|
try {
|
|
447
|
-
|
|
448
|
-
if (row === null || row.state !== "DECIDED")
|
|
449
|
-
return undefined;
|
|
450
|
-
if (replayTerminalAskRow(row) === true && this.abandonedEditedDecisionPending(askId)) {
|
|
451
|
-
this.guardRowDerivedApprove(askId, true, "expire-error-recovery");
|
|
452
|
-
return undefined;
|
|
453
|
-
}
|
|
454
|
-
if (this.editedDecisionInFlight(askId))
|
|
455
|
-
return undefined;
|
|
456
|
-
const replayed = settleArgsOf(replayTerminalAskRow(row));
|
|
457
|
-
return replayed === "unavailable" ? undefined : replayed;
|
|
497
|
+
row = await this.withStoreDeadline(raw, "getAsk(yield-probe)", Math.min(DURABLE_CONVERGE_READ_TIMEOUT_MS, budgetMs));
|
|
458
498
|
}
|
|
459
499
|
catch (err) {
|
|
460
|
-
this.noteStoreError(err, "getAsk(
|
|
461
|
-
return
|
|
500
|
+
this.noteStoreError(err, "getAsk(yield-probe)");
|
|
501
|
+
return { kind: "none" };
|
|
462
502
|
}
|
|
503
|
+
if (row === null || row.state === "STREAM_PENDING")
|
|
504
|
+
return { kind: "none" };
|
|
505
|
+
return { kind: "row", current: projectAskTerminalClaimTruth(row) };
|
|
506
|
+
}
|
|
507
|
+
classifyClaimLoss(askId, current, where) {
|
|
508
|
+
if (current.state === "absent")
|
|
509
|
+
return { kind: "absent" };
|
|
510
|
+
const replayed = settleArgsOf(this.guardRowDerivedApprove(askId, replayTerminalAskRow(current), where));
|
|
511
|
+
if (replayed === "unavailable")
|
|
512
|
+
return { kind: "parked" };
|
|
513
|
+
if (!replayed.allowed)
|
|
514
|
+
return { kind: "denied" };
|
|
515
|
+
if (replayed.updatedInput !== undefined)
|
|
516
|
+
return { kind: "approved", updatedInput: replayed.updatedInput };
|
|
517
|
+
if (this.editedDecisionInFlight(askId))
|
|
518
|
+
return { kind: "edit-in-flight" };
|
|
519
|
+
return { kind: "approved" };
|
|
463
520
|
}
|
|
464
521
|
hasOtherSuspendedOwner(askId, self) {
|
|
465
522
|
if (askId === undefined)
|
|
@@ -490,13 +547,17 @@ export class ToolApprovalCoordinator {
|
|
|
490
547
|
const now = Date.now();
|
|
491
548
|
let ownCount = 0;
|
|
492
549
|
let ownExpiredVictim;
|
|
550
|
+
let ownBridgeVictim;
|
|
493
551
|
let ownPlainVictim;
|
|
494
552
|
let ownEvictable;
|
|
495
553
|
let globalExpiredVictim;
|
|
554
|
+
let globalBridgeVictim;
|
|
496
555
|
for (const [k, t] of this.parkTombstones) {
|
|
497
556
|
const expired = now - t.parkedAtMs > this.parkTombstoneTtlMs;
|
|
498
557
|
if (globalExpiredVictim === undefined && expired && t.inflight === undefined)
|
|
499
558
|
globalExpiredVictim = k;
|
|
559
|
+
if (globalBridgeVictim === undefined && t.kind === "voidBridge" && t.inflight === undefined)
|
|
560
|
+
globalBridgeVictim = k;
|
|
500
561
|
if (t.owner !== tomb.owner)
|
|
501
562
|
continue;
|
|
502
563
|
ownCount++;
|
|
@@ -506,13 +567,16 @@ export class ToolApprovalCoordinator {
|
|
|
506
567
|
ownEvictable = k;
|
|
507
568
|
if (ownExpiredVictim === undefined && expired)
|
|
508
569
|
ownExpiredVictim = k;
|
|
509
|
-
if (
|
|
570
|
+
if (ownBridgeVictim === undefined && t.kind === "voidBridge")
|
|
571
|
+
ownBridgeVictim = k;
|
|
572
|
+
if (ownPlainVictim === undefined && t.kind === "park" && t.redeemed === undefined)
|
|
510
573
|
ownPlainVictim = k;
|
|
511
574
|
}
|
|
575
|
+
const insertingBridge = tomb.kind === "voidBridge";
|
|
512
576
|
if (ownCount >= effectivePerPrincipalMax) {
|
|
513
|
-
const victim = ownExpiredVictim ?? ownPlainVictim ?? ownEvictable;
|
|
577
|
+
const victim = insertingBridge ? (ownExpiredVictim ?? ownBridgeVictim) : (ownExpiredVictim ?? ownBridgeVictim ?? ownPlainVictim ?? ownEvictable);
|
|
514
578
|
if (victim === undefined) {
|
|
515
|
-
recordFailOpen("server.approval.park-tombstone-global-cap-refused", `owner=${tomb.owner ?? "<none>"} reason
|
|
579
|
+
recordFailOpen("server.approval.park-tombstone-global-cap-refused", `owner=${tomb.owner ?? "<none>"} reason=${insertingBridge ? "bridge-would-evict-redeemable" : "all-inflight"}`);
|
|
516
580
|
return;
|
|
517
581
|
}
|
|
518
582
|
this.parkTombstones.delete(victim);
|
|
@@ -520,8 +584,11 @@ export class ToolApprovalCoordinator {
|
|
|
520
584
|
else if (this.parkTombstones.size >= this.parkTombstoneMax && globalExpiredVictim !== undefined) {
|
|
521
585
|
this.parkTombstones.delete(globalExpiredVictim);
|
|
522
586
|
}
|
|
587
|
+
else if (this.parkTombstones.size >= this.parkTombstoneMax && !insertingBridge && globalBridgeVictim !== undefined) {
|
|
588
|
+
this.parkTombstones.delete(globalBridgeVictim);
|
|
589
|
+
}
|
|
523
590
|
else if (this.parkTombstones.size >= this.parkTombstoneMax) {
|
|
524
|
-
recordFailOpen("server.approval.park-tombstone-global-cap-refused", `owner=${tomb.owner ?? "<none>"}`);
|
|
591
|
+
recordFailOpen("server.approval.park-tombstone-global-cap-refused", `owner=${tomb.owner ?? "<none>"}${insertingBridge ? " reason=bridge-refused-at-global-cap" : ""}`);
|
|
525
592
|
return;
|
|
526
593
|
}
|
|
527
594
|
this.parkTombstones.set(approvalId, tomb);
|
|
@@ -601,6 +668,7 @@ export class ToolApprovalCoordinator {
|
|
|
601
668
|
if (p.targetCtxs?.has(ctx))
|
|
602
669
|
p.onTargetGone?.(ctx);
|
|
603
670
|
}
|
|
671
|
+
this.askAudit?.noteLegClosed(ctx.taskId);
|
|
604
672
|
}
|
|
605
673
|
}));
|
|
606
674
|
}
|
|
@@ -878,7 +946,7 @@ export class ToolApprovalCoordinator {
|
|
|
878
946
|
sig.removeEventListener("abort", fn);
|
|
879
947
|
this.pending.delete(id);
|
|
880
948
|
if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.unattendedOutcome() !== false) {
|
|
881
|
-
this.recordParkTombstone(wireApprovalId, { askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
949
|
+
this.recordParkTombstone(wireApprovalId, { kind: "park", askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
882
950
|
parkRouted = true;
|
|
883
951
|
}
|
|
884
952
|
if (askId !== undefined) {
|
|
@@ -893,55 +961,171 @@ export class ToolApprovalCoordinator {
|
|
|
893
961
|
releaseAdmission();
|
|
894
962
|
if (selfEntry)
|
|
895
963
|
selfEntry.settledOutcome = outcome;
|
|
964
|
+
this.askAudit?.recordSettle({
|
|
965
|
+
approvalId: wireApprovalId,
|
|
966
|
+
outcome,
|
|
967
|
+
...(windowRouteUnavailable ? { parkRouted: true } : {}),
|
|
968
|
+
...(hostSettledBy !== undefined ? { settledBy: hostSettledBy } : {}),
|
|
969
|
+
settledAtMs: Date.now(),
|
|
970
|
+
});
|
|
896
971
|
resolveAllowed({ allowed, ...(allowed && updatedInput !== undefined ? { updatedInput } : {}) });
|
|
897
972
|
};
|
|
898
|
-
const
|
|
899
|
-
if (
|
|
973
|
+
const broadcastClaimTruth = (read) => {
|
|
974
|
+
if (askId === undefined)
|
|
900
975
|
return;
|
|
901
|
-
|
|
976
|
+
switch (read.kind) {
|
|
977
|
+
case "approved":
|
|
978
|
+
this.notifyExternalDecision(askId, true, read.updatedInput);
|
|
979
|
+
return;
|
|
980
|
+
case "denied":
|
|
981
|
+
this.notifyExternalDecision(askId, false);
|
|
982
|
+
return;
|
|
983
|
+
case "parked":
|
|
984
|
+
this.settleSameAskIdParkRoute(askId);
|
|
985
|
+
return;
|
|
986
|
+
case "absent":
|
|
987
|
+
case "edit-in-flight":
|
|
988
|
+
return;
|
|
989
|
+
default: {
|
|
990
|
+
const unreachable = read;
|
|
991
|
+
throw new Error(`broadcastClaimTruth: unknown recovery read ${String(unreachable)}`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
const settleFromClaimLoss = (read) => {
|
|
996
|
+
switch (read.kind) {
|
|
997
|
+
case "approved":
|
|
998
|
+
settle(true, "allowed", read.updatedInput);
|
|
999
|
+
broadcastClaimTruth(read);
|
|
1000
|
+
return true;
|
|
1001
|
+
case "denied":
|
|
1002
|
+
settle(false, "denied");
|
|
1003
|
+
broadcastClaimTruth(read);
|
|
1004
|
+
return true;
|
|
1005
|
+
case "parked":
|
|
1006
|
+
windowRouteUnavailable = true;
|
|
1007
|
+
settle(false, "expired");
|
|
1008
|
+
broadcastClaimTruth(read);
|
|
1009
|
+
return true;
|
|
1010
|
+
case "absent":
|
|
1011
|
+
case "edit-in-flight":
|
|
1012
|
+
return false;
|
|
1013
|
+
default: {
|
|
1014
|
+
const unreachable = read;
|
|
1015
|
+
throw new Error(`settleFromClaimLoss: unknown recovery read ${String(unreachable)}`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
const lateClaimLossSettle = (current, intent) => {
|
|
1020
|
+
if (askId === undefined)
|
|
1021
|
+
return;
|
|
1022
|
+
const read = this.classifyClaimLoss(askId, current, `late:${intent}`);
|
|
1023
|
+
if (done) {
|
|
1024
|
+
broadcastClaimTruth(read);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
settleFromClaimLoss(read);
|
|
1028
|
+
};
|
|
1029
|
+
const boundedClaim = (mk, label, timeoutMs, late) => {
|
|
1030
|
+
const release = this.admitTerminalClaim(askId ?? wireApprovalId);
|
|
1031
|
+
if (release === undefined)
|
|
1032
|
+
return undefined;
|
|
1033
|
+
const raw = mk();
|
|
1034
|
+
void raw.then(release, release);
|
|
1035
|
+
return this.withStoreDeadline(raw, label, timeoutMs, late);
|
|
1036
|
+
};
|
|
1037
|
+
const resolveLocalOutcome = async (arm) => {
|
|
1038
|
+
if (askId === undefined) {
|
|
1039
|
+
arm.settleByIntent();
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
902
1042
|
const pinnedAskId = askId;
|
|
903
|
-
|
|
1043
|
+
let lastLoss;
|
|
1044
|
+
const ladderStartedAt = Date.now();
|
|
1045
|
+
const remainingMs = () => ladderStartedAt + TERMINAL_LADDER_WALL_MS - Date.now();
|
|
1046
|
+
const nap = async (ms) => {
|
|
1047
|
+
const slice = Math.min(ms, remainingMs());
|
|
1048
|
+
if (slice > 0)
|
|
1049
|
+
await new Promise((r) => setTimeout(r, slice).unref?.());
|
|
1050
|
+
};
|
|
1051
|
+
let attempt = 0;
|
|
1052
|
+
let claimsIssued = 0;
|
|
1053
|
+
let rejects = 0;
|
|
1054
|
+
while (attempt <= DURABLE_CONVERGE_BACKOFF_MS.length && remainingMs() > 0) {
|
|
904
1055
|
if (done)
|
|
905
1056
|
return;
|
|
906
|
-
|
|
907
|
-
|
|
1057
|
+
const backoffAfter = attempt < DURABLE_CONVERGE_BACKOFF_MS.length ? DURABLE_CONVERGE_BACKOFF_MS[attempt] : 0;
|
|
1058
|
+
if (attempt > 0 && this.editedDecisionInFlight(pinnedAskId)) {
|
|
1059
|
+
const probed = await this.probeRowWhileYielding(pinnedAskId, remainingMs());
|
|
908
1060
|
if (done)
|
|
909
1061
|
return;
|
|
910
|
-
if (
|
|
911
|
-
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
if (row.state === "STREAM_PENDING" || this.editedDecisionInFlight(pinnedAskId)) {
|
|
915
|
-
await new Promise((r) => setTimeout(r, DURABLE_CONVERGE_BACKOFF_MS[attempt]).unref?.());
|
|
1062
|
+
if (probed.kind === "denied") {
|
|
1063
|
+
await nap(TERMINAL_CLAIM_CAP_POLL_MS);
|
|
916
1064
|
continue;
|
|
917
1065
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
settle(true, "allowed", outcome.updatedInput);
|
|
1066
|
+
if (probed.kind === "row") {
|
|
1067
|
+
const read = this.classifyClaimLoss(pinnedAskId, probed.current, `yield-probe:${arm.intent}`);
|
|
1068
|
+
if (settleFromClaimLoss(read))
|
|
1069
|
+
return;
|
|
1070
|
+
if (read.kind === "edit-in-flight")
|
|
1071
|
+
lastLoss = probed.current;
|
|
925
1072
|
}
|
|
926
|
-
else {
|
|
927
|
-
|
|
1073
|
+
else if (this.terminalClaimsInFlightCount(pinnedAskId) > 0) {
|
|
1074
|
+
await nap(TERMINAL_CLAIM_CAP_POLL_MS);
|
|
1075
|
+
continue;
|
|
928
1076
|
}
|
|
929
|
-
|
|
1077
|
+
await nap(backoffAfter);
|
|
1078
|
+
attempt++;
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
const inFlight = arm.claim(Math.min(attempt === 0 ? DURABLE_CALL_TIMEOUT_MS : DURABLE_CONVERGE_READ_TIMEOUT_MS, remainingMs()));
|
|
1082
|
+
if (inFlight === undefined) {
|
|
1083
|
+
await nap(TERMINAL_CLAIM_CAP_POLL_MS);
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
claimsIssued++;
|
|
1087
|
+
let outcome;
|
|
1088
|
+
try {
|
|
1089
|
+
outcome = await inFlight;
|
|
930
1090
|
}
|
|
931
1091
|
catch (err) {
|
|
932
|
-
this.noteStoreError(err,
|
|
933
|
-
|
|
1092
|
+
this.noteStoreError(err, arm.storeLabel);
|
|
1093
|
+
rejects++;
|
|
1094
|
+
await nap(backoffAfter);
|
|
1095
|
+
attempt++;
|
|
1096
|
+
continue;
|
|
934
1097
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
windowRouteUnavailable = true;
|
|
939
|
-
settle(false, "expired");
|
|
1098
|
+
if (outcome.claimed) {
|
|
1099
|
+
arm.onWon(outcome);
|
|
1100
|
+
return;
|
|
940
1101
|
}
|
|
941
|
-
|
|
942
|
-
|
|
1102
|
+
arm.onLostTruth?.(outcome.current);
|
|
1103
|
+
const read = this.classifyClaimLoss(pinnedAskId, outcome.current, `claim:${arm.intent}`);
|
|
1104
|
+
if (done) {
|
|
1105
|
+
broadcastClaimTruth(read);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (settleFromClaimLoss(read))
|
|
1109
|
+
return;
|
|
1110
|
+
if (read.kind === "absent") {
|
|
1111
|
+
arm.settleByIntent();
|
|
1112
|
+
return;
|
|
943
1113
|
}
|
|
1114
|
+
lastLoss = outcome.current;
|
|
1115
|
+
await nap(backoffAfter);
|
|
1116
|
+
attempt++;
|
|
944
1117
|
}
|
|
1118
|
+
if (done)
|
|
1119
|
+
return;
|
|
1120
|
+
if (this.editedDecisionInFlight(pinnedAskId)) {
|
|
1121
|
+
windowRouteUnavailable = true;
|
|
1122
|
+
settle(false, "expired");
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
if (lastLoss !== undefined && settleFromClaimLoss(this.classifyClaimLoss(pinnedAskId, lastLoss, `claim-tail:${arm.intent}`)))
|
|
1126
|
+
return;
|
|
1127
|
+
recordFailOpen(ambiguousCasFailOpenTag(arm.intent), `askId=${pinnedAskId} attempts=${attempt} claims=${claimsIssued} rejects=${rejects} elapsedMs=${Date.now() - ladderStartedAt}`);
|
|
1128
|
+
arm.settleByIntent();
|
|
945
1129
|
};
|
|
946
1130
|
const settleParkRouteIfUnsettled = (hostSettledBy) => {
|
|
947
1131
|
if (done)
|
|
@@ -955,34 +1139,30 @@ export class ToolApprovalCoordinator {
|
|
|
955
1139
|
settleParkRouteIfUnsettled(hostSelfReport);
|
|
956
1140
|
return;
|
|
957
1141
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1142
|
+
const store = this.askStore;
|
|
1143
|
+
const pinnedAskId = askId;
|
|
1144
|
+
const pinnedBatchId = batchId;
|
|
1145
|
+
await resolveLocalOutcome({
|
|
1146
|
+
intent: "expire",
|
|
1147
|
+
storeLabel: "claimTerminal(window)",
|
|
1148
|
+
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "expire"), "claimTerminal(window)", timeoutMs, (late) => {
|
|
1149
|
+
if (!late.claimed) {
|
|
1150
|
+
lateClaimLossSettle(late.current, "expire");
|
|
961
1151
|
return;
|
|
962
|
-
|
|
1152
|
+
}
|
|
1153
|
+
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
963
1154
|
this.settleVoidedSiblings(late.voidedSiblings);
|
|
964
|
-
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(
|
|
965
|
-
})
|
|
966
|
-
|
|
1155
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, late.voidedSiblings, "superseded_by_park", Date.now(), origin.taskId));
|
|
1156
|
+
}),
|
|
1157
|
+
onWon: (res) => {
|
|
967
1158
|
windowRouteUnavailable = true;
|
|
968
1159
|
settle(false, "expired");
|
|
969
|
-
this.settleSameAskIdParkRoute(
|
|
1160
|
+
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
970
1161
|
this.settleVoidedSiblings(res.voidedSiblings);
|
|
971
|
-
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
catch (err) {
|
|
978
|
-
this.noteStoreError(err, "expireAsk");
|
|
979
|
-
const decided = await this.readDecidedForRecovery(askId);
|
|
980
|
-
if (decided !== undefined && !done) {
|
|
981
|
-
settle(decided.allowed, decided.allowed ? "allowed" : "denied", decided.updatedInput);
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
settleParkRouteIfUnsettled(hostSelfReport);
|
|
985
|
-
}
|
|
1162
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, res.voidedSiblings, "superseded_by_park", Date.now(), origin.taskId));
|
|
1163
|
+
},
|
|
1164
|
+
settleByIntent: () => settleParkRouteIfUnsettled(hostSelfReport),
|
|
1165
|
+
});
|
|
986
1166
|
})();
|
|
987
1167
|
};
|
|
988
1168
|
const forceParkNow = () => {
|
|
@@ -1005,26 +1185,36 @@ export class ToolApprovalCoordinator {
|
|
|
1005
1185
|
settle(false, "expired");
|
|
1006
1186
|
return;
|
|
1007
1187
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1188
|
+
const store = this.askStore;
|
|
1189
|
+
const pinnedAskId = askId;
|
|
1190
|
+
const pinnedBatchId = batchId;
|
|
1191
|
+
const cancelLostTruth = (current) => {
|
|
1192
|
+
if (current.state !== "VOID")
|
|
1193
|
+
return;
|
|
1194
|
+
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1195
|
+
};
|
|
1196
|
+
await resolveLocalOutcome({
|
|
1197
|
+
intent: "cancel",
|
|
1198
|
+
storeLabel: "claimTerminal(cancel)",
|
|
1199
|
+
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "cancel"), "claimTerminal(cancel)", timeoutMs, (late) => {
|
|
1200
|
+
if (!late.claimed) {
|
|
1201
|
+
cancelLostTruth(late.current);
|
|
1202
|
+
lateClaimLossSettle(late.current, "cancel");
|
|
1011
1203
|
return;
|
|
1012
|
-
|
|
1013
|
-
this.
|
|
1014
|
-
|
|
1015
|
-
|
|
1204
|
+
}
|
|
1205
|
+
this.settleVoidedSiblings([pinnedAskId]);
|
|
1206
|
+
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1207
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1208
|
+
}),
|
|
1209
|
+
onWon: () => {
|
|
1016
1210
|
settle(false, "expired");
|
|
1017
|
-
this.settleVoidedSiblings([
|
|
1018
|
-
this.
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
catch (err) {
|
|
1025
|
-
this.noteStoreError(err, "transitionAsk(cancel)");
|
|
1026
|
-
settle(false, "expired");
|
|
1027
|
-
}
|
|
1211
|
+
this.settleVoidedSiblings([pinnedAskId]);
|
|
1212
|
+
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1213
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1214
|
+
},
|
|
1215
|
+
onLostTruth: cancelLostTruth,
|
|
1216
|
+
settleByIntent: () => settle(false, "expired"),
|
|
1217
|
+
});
|
|
1028
1218
|
};
|
|
1029
1219
|
if (signal) {
|
|
1030
1220
|
const onTaskAbort = () => void runCancel();
|
|
@@ -1082,6 +1272,18 @@ export class ToolApprovalCoordinator {
|
|
|
1082
1272
|
}
|
|
1083
1273
|
siblings.add(pendingEntry);
|
|
1084
1274
|
}
|
|
1275
|
+
this.askAudit?.recordAsk({
|
|
1276
|
+
approvalId: wireApprovalId,
|
|
1277
|
+
taskId: origin.taskId,
|
|
1278
|
+
...(req.sourceTaskId !== undefined ? { sourceTaskId: req.sourceTaskId } : {}),
|
|
1279
|
+
...(origin.sessionId !== undefined ? { sessionId: origin.sessionId } : {}),
|
|
1280
|
+
owner: origin.owner,
|
|
1281
|
+
toolName: req.toolName,
|
|
1282
|
+
toolCallId: req.toolCallId,
|
|
1283
|
+
legTracked: !child && aliveCtxs.some((c) => c.taskId === origin.taskId),
|
|
1284
|
+
expiresAtMs: persistedExpiresAtMs,
|
|
1285
|
+
mintedAtMs: Date.now(),
|
|
1286
|
+
});
|
|
1085
1287
|
const cardFrame = askId !== undefined && cardForFrame !== undefined
|
|
1086
1288
|
? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now() })
|
|
1087
1289
|
: undefined;
|
|
@@ -1114,31 +1316,42 @@ export class ToolApprovalCoordinator {
|
|
|
1114
1316
|
const emitAllP = (async () => {
|
|
1115
1317
|
const results = await Promise.all(openByCtx.values());
|
|
1116
1318
|
const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.legacy || r.card);
|
|
1319
|
+
emitSettled = true;
|
|
1117
1320
|
if (!anySucceeded && !done) {
|
|
1118
1321
|
if (this.askStore && askId !== undefined && batchId !== undefined) {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1322
|
+
const store = this.askStore;
|
|
1323
|
+
const pinnedAskId = askId;
|
|
1324
|
+
const pinnedBatchId = batchId;
|
|
1325
|
+
const settleUnreachable = () => {
|
|
1326
|
+
if (done)
|
|
1327
|
+
return;
|
|
1328
|
+
emitFailed = true;
|
|
1329
|
+
settle(false, "expired");
|
|
1330
|
+
};
|
|
1331
|
+
await resolveLocalOutcome({
|
|
1332
|
+
intent: "unreachable",
|
|
1333
|
+
storeLabel: "claimTerminal(emit-failed)",
|
|
1334
|
+
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "expire"), "claimTerminal(emit-failed)", timeoutMs, (late) => {
|
|
1335
|
+
if (!late.claimed) {
|
|
1336
|
+
lateClaimLossSettle(late.current, "unreachable");
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
1340
|
+
this.settleVoidedSiblings(late.voidedSiblings);
|
|
1341
|
+
}),
|
|
1342
|
+
onWon: (res) => {
|
|
1343
|
+
settleUnreachable();
|
|
1344
|
+
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
1125
1345
|
this.settleVoidedSiblings(res.voidedSiblings);
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
this.noteStoreError(err, "expireAsk(emit-failed)");
|
|
1130
|
-
if (!done) {
|
|
1131
|
-
emitFailed = true;
|
|
1132
|
-
settle(false, "expired");
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1346
|
+
},
|
|
1347
|
+
settleByIntent: settleUnreachable,
|
|
1348
|
+
});
|
|
1135
1349
|
}
|
|
1136
1350
|
else if (!done) {
|
|
1137
1351
|
emitFailed = true;
|
|
1138
1352
|
settle(false, "expired");
|
|
1139
1353
|
}
|
|
1140
1354
|
}
|
|
1141
|
-
emitSettled = true;
|
|
1142
1355
|
})();
|
|
1143
1356
|
await Promise.race([emitAllP, allowedP]);
|
|
1144
1357
|
const settled = await allowedP;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
|
|
17
17
|
* 刚立,立完就在下一次加码时自己咬住了)。
|
|
18
18
|
*/
|
|
19
|
-
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted"];
|
|
19
|
+
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval"];
|
|
20
20
|
export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
|
|
21
21
|
/** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
|
|
22
22
|
export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
|
|
@@ -13,6 +13,8 @@ export const ENGINE_NOTICE_WIRE_CODES = [
|
|
|
13
13
|
"task.user_followup_undrained",
|
|
14
14
|
"memory.capture_opted_out",
|
|
15
15
|
"memory.capture_optout_unpersisted",
|
|
16
|
+
"task.halt_unconsumed",
|
|
17
|
+
"task.late_approval",
|
|
16
18
|
];
|
|
17
19
|
const WIRE_CODES = new Set(ENGINE_NOTICE_WIRE_CODES);
|
|
18
20
|
export function isEngineNoticeWireCode(code) {
|
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [ref] 注入档位(core 5.61.0)的**闭集单点**([ref])
|
|
2
|
+
* [ref] 注入档位(core 5.61.0)的**闭集单点**([ref])——2026-08-31 起改为 **core 根导出直调**。
|
|
3
3
|
*
|
|
4
|
-
* ##
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* · `satisfies` 管「表内不许有词表外的词」—— core 删/改档位 ⇒ 编译红;
|
|
12
|
-
* · 穷举断言管「词表不许有表外的词」—— core 加档位 ⇒ 编译红。
|
|
13
|
-
* 两条合起来 = 上游任何一侧的增删都在编译期被逼着处置,而不是运行时静默漂。
|
|
14
|
-
*
|
|
15
|
-
* ## 为什么单独一个叶子模块
|
|
16
|
-
* 消费点有两个且分属两条依赖链:`trace/project.ts`(wire 投影)与
|
|
17
|
-
* `orchestration/workflow-completion-inbox.ts`(durable park 载荷)。手抄两份就是值拷贝漂移源
|
|
18
|
-
* (本仓 [ref] 起的老病族);把它挂在任一方又会在两条链之间拉一条没必要的边。零依赖叶子最干净。
|
|
19
|
-
*
|
|
20
|
-
* 🔴 上游哪天补了根导出:删掉本文件,两处改成一行谓词调用。
|
|
4
|
+
* ## 收口条款兑现([ref] PM 审计④「core 前提句过期族」)
|
|
5
|
+
* 本文件 5.61 时代的顶注写着「core 没有把 `SYSTEM_INJECTION_PRIORITIES` / `isSystemInjectionPriority`
|
|
6
|
+
* 放进根 barrel ⇒ 本仓自留双向锚词表;上游哪天补了根导出:删掉本文件,两处改成一行谓词调用」。
|
|
7
|
+
* core 7.0.2 已把**表和谓词都放进根导出**(运行期亲证:`SYSTEM_INJECTION_PRIORITIES` 数组 +
|
|
8
|
+
* `isSystemInjectionPriority` 函数均可 require 到)——按条款执行:自留词表退役,改直调;本文件保留为
|
|
9
|
+
* 两个消费点(`trace/project.ts` / `orchestration/workflow-completion-inbox.ts`)的公共缝(语义注 +
|
|
10
|
+
* `injectionTierOrAbsent` 的丢键方向条款仍是本仓资产,不随词表走)。
|
|
21
11
|
*/
|
|
22
|
-
import type
|
|
23
|
-
/**
|
|
24
|
-
export declare const INJECTION_TIERS: readonly [
|
|
12
|
+
import { type SystemInjectionPriority } from "@sema-agent/core";
|
|
13
|
+
/** 三档闭集 —— 现在就是 core 的那份表(单源;保留导出名,两个消费点与既有测试不动)。 */
|
|
14
|
+
export declare const INJECTION_TIERS: readonly SystemInjectionPriority[];
|
|
25
15
|
/**
|
|
26
16
|
* 边界守卫 —— 闭集内的值原样返回,**其余一律 `undefined`**(= 缺席)。
|
|
27
17
|
*
|