@sema-agent/server 7.141.0 → 7.143.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/MIGRATION.md +657 -0
- package/USAGE.md +102 -30
- package/dist/adoption/plan.js +22 -0
- package/dist/approval-ask-audit-store.d.ts +61 -43
- package/dist/approval-ask-audit-store.js +69 -44
- package/dist/approval-ask-machine.d.ts +15 -7
- package/dist/approval-ask-machine.js +10 -4
- package/dist/approval-card.d.ts +126 -27
- package/dist/approval-card.js +103 -23
- package/dist/approval-crash-convergence.d.ts +17 -7
- package/dist/approval-crash-convergence.js +94 -22
- package/dist/approval-deny-reasons.d.ts +34 -30
- package/dist/approval-deny-reasons.js +21 -6
- package/dist/approval-policy-settlement.d.ts +1 -1
- package/dist/approval-queue-kind.d.ts +27 -0
- package/dist/approval-queue-kind.js +6 -0
- package/dist/approval-reconciler.d.ts +41 -9
- package/dist/approval-reconciler.js +45 -42
- package/dist/audit.js +4 -2
- package/dist/auth-bridge.d.ts +9 -6
- package/dist/auth-bridge.js +9 -3
- package/dist/boot/adoption.js +2 -1
- package/dist/boot/budget-tracing.d.ts +17 -1
- package/dist/boot/budget-tracing.js +10 -6
- package/dist/boot/device-lane.d.ts +1 -1
- package/dist/boot/execution-env.d.ts +24 -4
- package/dist/boot/execution-env.js +26 -23
- package/dist/boot/execution-lane.js +2 -1
- package/dist/boot/exit-boundary.js +5 -4
- package/dist/boot/governance-seams.d.ts +26 -41
- package/dist/boot/governance-seams.js +61 -28
- package/dist/boot/late-binds.d.ts +7 -4
- package/dist/boot/late-binds.js +3 -1
- package/dist/boot/leader.js +2 -4
- package/dist/boot/memory-consolidation.d.ts +0 -8
- package/dist/boot/memory-consolidation.js +5 -4
- package/dist/boot/parked-revive-gate.js +2 -1
- package/dist/boot/permission-rules-audit.d.ts +0 -28
- package/dist/boot/permission-rules-audit.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -1
- package/dist/boot/reapers.js +9 -7
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +19 -10
- package/dist/boot/retention-lane.js +5 -3
- package/dist/boot/runner-deps.d.ts +10 -7
- package/dist/boot/runner-deps.js +20 -29
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.js +4 -3
- package/dist/boot/shutdown.d.ts +9 -0
- package/dist/boot/shutdown.js +19 -4
- package/dist/boot/stage-02-web-admin-pull.js +3 -0
- package/dist/boot/stage-03-store-backend.d.ts +4 -1
- package/dist/boot/stage-03-store-backend.js +1 -2
- package/dist/boot/stage-04-stores.d.ts +5 -2
- package/dist/boot/stage-04-stores.js +6 -7
- package/dist/boot/stage-05-execution-env.d.ts +6 -3
- package/dist/boot/stage-05-execution-env.js +8 -6
- package/dist/boot/stage-06-runners.d.ts +6 -3
- package/dist/boot/stage-06-runners.js +3 -4
- package/dist/boot/stage-07-capability-layer.d.ts +6 -3
- package/dist/boot/stage-07-capability-layer.js +7 -2
- package/dist/boot/stage-08-reapers.d.ts +6 -3
- package/dist/boot/stage-08-reapers.js +3 -2
- package/dist/boot/stage-09-leader.d.ts +6 -3
- package/dist/boot/stage-10-http-server.d.ts +6 -3
- package/dist/boot/stage-10-http-server.js +14 -5
- package/dist/boot/stores.d.ts +5 -2
- package/dist/boot/stores.js +10 -9
- package/dist/boot/web-admin.d.ts +5 -1
- package/dist/boot/web-admin.js +48 -36
- package/dist/boot/webfetch-summarize-lane.js +2 -1
- package/dist/boot/workflow-orchestration.js +3 -2
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/builtin-tools.js +2 -4
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/hands-lane.d.ts +9 -39
- package/dist/capabilities/hands-lane.js +8 -13
- package/dist/capabilities/mcp-probe.d.ts +3 -2
- package/dist/capabilities/scenarios.d.ts +11 -1
- package/dist/capabilities/scenarios.js +1 -0
- package/dist/capabilities/skills.d.ts +1 -17
- package/dist/capabilities/skills.js +0 -8
- package/dist/capability-facts.d.ts +31 -7
- package/dist/capability-facts.js +27 -4
- package/dist/card-void.d.ts +6 -9
- package/dist/config-catalog.js +2 -1
- package/dist/config-dir-trust.js +58 -29
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +7 -0
- package/dist/config-provider.d.ts +10 -0
- package/dist/config-provider.js +15 -4
- package/dist/config-root-migration.d.ts +13 -0
- package/dist/config-root-migration.js +99 -0
- package/dist/config-types.d.ts +19 -8
- package/dist/config.d.ts +10 -3
- package/dist/config.js +18 -4
- package/dist/delegated-session-owner.d.ts +5 -3
- package/dist/delegated-session-owner.js +1 -1
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/env-model-table-gate.d.ts +53 -0
- package/dist/env-model-table-gate.js +53 -0
- package/dist/execution-lane-caps.d.ts +2 -1
- package/dist/fleet/fleet-bus.d.ts +34 -55
- package/dist/fleet/fleet-bus.js +35 -100
- package/dist/fleet/fleet-reconciler.d.ts +4 -4
- package/dist/fleet/fleet-reconciler.js +1 -1
- package/dist/fleet/subagent-tail-bus.d.ts +14 -11
- package/dist/fleet/subagent-tail-bus.js +15 -28
- package/dist/hooks/hook-runner.d.ts +6 -0
- package/dist/hooks/hook-runner.js +58 -8
- package/dist/host-decision.d.ts +54 -20
- package/dist/host-decision.js +42 -14
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/admission.js +7 -3
- package/dist/http/approval-sweeps.js +11 -8
- package/dist/http/caller-trust.d.ts +1 -1
- package/dist/http/dispatch.js +4 -2
- package/dist/http/durable-run-leg.js +2 -2
- package/dist/http/engine-refusal.d.ts +16 -0
- package/dist/http/engine-refusal.js +17 -0
- package/dist/http/execution-context.d.ts +2 -2
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
- package/dist/http/memory-compliance-failure-reply.js +4 -6
- package/dist/http/owner-access.d.ts +11 -0
- package/dist/http/owner-access.js +13 -0
- package/dist/http/principal-gate.d.ts +3 -1
- package/dist/http/principal-gate.js +17 -8
- package/dist/http/resume-legs.d.ts +21 -7
- package/dist/http/resume-legs.js +76 -58
- package/dist/http/resume-restatement.d.ts +39 -0
- package/dist/http/resume-restatement.js +25 -0
- package/dist/http/route-ctx.d.ts +35 -10
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/approvals-assistant.d.ts +9 -1
- package/dist/http/routes/approvals-assistant.js +142 -31
- package/dist/http/routes/attachments.js +18 -0
- package/dist/http/routes/capabilities.js +16 -4
- package/dist/http/routes/memory-policy.d.ts +2 -2
- package/dist/http/routes/memory-policy.js +27 -7
- package/dist/http/routes/notify-wake.js +23 -25
- package/dist/http/routes/rules.js +48 -13
- package/dist/http/routes/runs.js +12 -11
- package/dist/http/routes/session-memory-sync.d.ts +91 -0
- package/dist/http/routes/session-memory-sync.js +174 -0
- package/dist/http/routes/session-sync.js +13 -20
- package/dist/http/routes/sessions-list.d.ts +2 -1
- package/dist/http/routes/sessions.d.ts +2 -2
- package/dist/http/routes/sessions.js +22 -23
- package/dist/http/routes/tasks.js +34 -54
- package/dist/http/run-read-face.d.ts +7 -0
- package/dist/http/run-read-face.js +9 -3
- package/dist/http/send.d.ts +5 -0
- package/dist/http/send.js +9 -2
- package/dist/http/server.d.ts +27 -15
- package/dist/http/server.js +4 -6
- package/dist/http/stored-body-replay.js +15 -4
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +2 -2
- package/dist/http/sync-submit-body.d.ts +21 -4
- package/dist/http/sync-submit-body.js +13 -0
- package/dist/http/wire-types.d.ts +17 -7
- package/dist/identity-source.d.ts +21 -2
- package/dist/identity-source.js +6 -0
- package/dist/leader/leader.js +5 -2
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +6 -5
- package/dist/leg-facts.d.ts +37 -0
- package/dist/leg-facts.js +15 -0
- package/dist/memory-bundle-engine.d.ts +1 -4
- package/dist/memory-bundle-engine.js +2 -2
- package/dist/memory-incident-sink.d.ts +19 -6
- package/dist/memory-incident-sink.js +8 -4
- package/dist/memory-operator-faces.d.ts +30 -66
- package/dist/memory-operator-faces.js +17 -88
- package/dist/memory-scope-stores.d.ts +38 -40
- package/dist/memory-scope-stores.js +51 -36
- package/dist/memory-scope.d.ts +0 -9
- package/dist/memory-scope.js +0 -9
- package/dist/memory-sync-client.d.ts +21 -21
- package/dist/memory-sync-client.js +62 -43
- package/dist/memory-sync-identity.d.ts +24 -0
- package/dist/memory-sync-identity.js +57 -0
- package/dist/memory-sync.d.ts +27 -5
- package/dist/memory-sync.js +29 -7
- package/dist/model-plane-face.d.ts +13 -7
- package/dist/model-plane-face.js +2 -1
- package/dist/observability/fail-open.d.ts +47 -14
- package/dist/observability/fail-open.js +37 -11
- package/dist/observability/run-terminal.d.ts +92 -34
- package/dist/observability/run-terminal.js +171 -162
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
- package/dist/orchestration/workflow-completion-inbox.js +0 -12
- package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
- package/dist/orchestration/workflow-park-decision.d.ts +8 -3
- package/dist/orchestration/workflow-park-decision.js +5 -3
- package/dist/org-delivery-gate.d.ts +11 -0
- package/dist/org-delivery-gate.js +19 -1
- package/dist/parked-decide.d.ts +13 -9
- package/dist/parked-decide.js +7 -7
- package/dist/parked-revive-surface.js +1 -1
- package/dist/peer-inbound-face.d.ts +127 -0
- package/dist/peer-inbound-face.js +158 -0
- package/dist/permission-rule-list.d.ts +13 -9
- package/dist/permission-rule-list.js +7 -3
- package/dist/plugins/approval-ask-store-file.d.ts +14 -4
- package/dist/plugins/approval-ask-store-file.js +64 -11
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
- package/dist/plugins/approval-ask-store-memory.js +17 -14
- package/dist/plugins/approval-ask-store-sql.d.ts +70 -23
- package/dist/plugins/approval-ask-store-sql.js +79 -30
- package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
- package/dist/plugins/checkpoint-store-sql.js +9 -7
- package/dist/plugins/file-history-store-sql.d.ts +24 -18
- package/dist/plugins/file-history-store-sql.js +24 -19
- package/dist/plugins/file-run-store.d.ts +35 -1
- package/dist/plugins/file-run-store.js +108 -9
- package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
- package/dist/plugins/imported-layer-root-retirement.js +96 -0
- package/dist/plugins/local-checkpoint-store.d.ts +1 -1
- package/dist/plugins/local-checkpoint-store.js +2 -2
- package/dist/plugins/local-session-store.d.ts +42 -9
- package/dist/plugins/local-session-store.js +83 -33
- package/dist/plugins/local-task-attachment-store.d.ts +12 -2
- package/dist/plugins/local-task-attachment-store.js +67 -43
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
- package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
- package/dist/plugins/mailbox-store-sql.d.ts +12 -8
- package/dist/plugins/mailbox-store-sql.js +76 -7
- package/dist/plugins/memory-key-guards.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +6 -1
- package/dist/plugins/memory-run-store.js +11 -1
- package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
- package/dist/plugins/memory-sync-store-pg.js +21 -0
- package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
- package/dist/plugins/memory-sync-store-tidb.js +13 -1
- package/dist/plugins/one-time-migrations.d.ts +58 -2
- package/dist/plugins/one-time-migrations.js +161 -4
- package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
- package/dist/plugins/org-delivery-quota-store.js +2 -12
- package/dist/plugins/park-commit-watch.d.ts +1 -1
- package/dist/plugins/permission-rule-store-file.d.ts +10 -1
- package/dist/plugins/permission-rule-store-file.js +15 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
- package/dist/plugins/permission-rule-store-sql.js +35 -2
- package/dist/plugins/remote-env-device.d.ts +8 -11
- package/dist/plugins/remote-env-device.js +6 -3
- package/dist/plugins/remote-env-e2b.d.ts +23 -4
- package/dist/plugins/remote-env-e2b.js +111 -28
- package/dist/plugins/remote-env-k8s.d.ts +61 -22
- package/dist/plugins/remote-env-k8s.js +133 -33
- package/dist/plugins/remote-env-local-docker.d.ts +15 -3
- package/dist/plugins/remote-env-local-docker.js +40 -12
- package/dist/plugins/retention-store-sql.d.ts +2 -2
- package/dist/plugins/run-store-sql.d.ts +9 -1
- package/dist/plugins/run-store-sql.js +9 -1
- package/dist/plugins/sandbox-read-floor.d.ts +5 -7
- package/dist/plugins/sandbox-read-floor.js +2 -7
- package/dist/plugins/sql-errors.d.ts +10 -1
- package/dist/plugins/sql-errors.js +13 -0
- package/dist/plugins/store-backend.d.ts +33 -35
- package/dist/plugins/store-backend.js +10 -1
- package/dist/plugins/store-contracts.d.ts +50 -1
- package/dist/plugins/store-contracts.js +6 -0
- package/dist/plugins/task-attachment-store.d.ts +112 -14
- package/dist/plugins/task-attachment-store.js +207 -29
- package/dist/plugins/tool-result-store-sql.d.ts +9 -8
- package/dist/plugins/tool-result-store-sql.js +23 -2
- package/dist/plugins/wiring-section-codec.d.ts +32 -0
- package/dist/plugins/wiring-section-codec.js +55 -0
- package/dist/plugins/workspace-fill.d.ts +60 -0
- package/dist/plugins/workspace-fill.js +106 -0
- package/dist/project-bundle.d.ts +38 -0
- package/dist/project-bundle.js +609 -0
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +5 -1
- package/dist/rules-consent.d.ts +28 -5
- package/dist/rules-consent.js +40 -28
- package/dist/run-local.js +11 -13
- package/dist/runs.d.ts +12 -8
- package/dist/runs.js +6 -8
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/sandbox-egress-policy.d.ts +4 -1
- package/dist/sandbox-egress-policy.js +3 -1
- package/dist/sandbox-git-plane.d.ts +31 -12
- package/dist/sandbox-git-plane.js +24 -4
- package/dist/security.d.ts +9 -0
- package/dist/security.js +8 -0
- package/dist/server-secret-env.d.ts +4 -0
- package/dist/session-error-wire.d.ts +36 -0
- package/dist/session-error-wire.js +13 -0
- package/dist/session-incarnation.d.ts +1 -1
- package/dist/session-notify-door.d.ts +106 -0
- package/dist/session-notify-door.js +93 -0
- package/dist/session-sync.d.ts +9 -14
- package/dist/session-sync.js +5 -5
- package/dist/session-workspace.d.ts +3 -13
- package/dist/session-workspace.js +0 -9
- package/dist/standing-answer-grant.d.ts +5 -0
- package/dist/standing-answer-grant.js +10 -0
- package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
- package/dist/stores-sql/session-policy-store-sql.js +3 -13
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +21 -10
- package/dist/task-settings.js +46 -13
- package/dist/terminal.d.ts +39 -19
- package/dist/terminal.js +65 -22
- package/dist/tool-approval.d.ts +56 -37
- package/dist/tool-approval.js +78 -71
- package/dist/trace/core-keyset-guard.d.ts +134 -14
- package/dist/trace/engine-notice-wire.d.ts +9 -3
- package/dist/trace/engine-notice-wire.js +7 -3
- package/dist/trace/exit-seat.d.ts +9 -2
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +10 -6
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/model-segment.d.ts +55 -25
- package/dist/trace/model-segment.js +101 -33
- package/dist/trace/project.d.ts +25 -5
- package/dist/trace/project.js +22 -0
- package/dist/trace/redact.d.ts +61 -9
- package/dist/trace/redact.js +1490 -468
- package/dist/trace/self-minted-wire.d.ts +745 -24
- package/dist/trace/self-minted-wire.js +239 -9
- package/dist/trace/wire-projection-faces.d.ts +2 -2
- package/dist/trace/wire-projection-faces.js +2 -2
- package/dist/verifier-pause-card.d.ts +33 -5
- package/dist/verifier-pause-card.js +15 -10
- package/dist/web-admin-client/apply-effective.d.ts +12 -16
- package/dist/web-admin-client/apply-effective.js +109 -9
- package/dist/web-admin-client/effective-keys.js +2 -0
- package/dist/web-admin-client/types.d.ts +4 -0
- package/dist/workspace-space.d.ts +19 -3
- package/dist/workspace-space.js +8 -4
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
- package/package.json +3 -2
- package/wire-changes/server@7.142.0.json +639 -0
- package/wire-changes/server@7.143.0.json +1167 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
2
2
|
import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
|
|
3
|
-
import {
|
|
3
|
+
import { DECIDED_VIA } from "@sema-agent/core";
|
|
4
|
+
import { APPROVAL_ENDING_CAUSES, askRowEndingCause, askRowParkTrigger } from "../approval-deny-reasons.js";
|
|
4
5
|
import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
|
|
5
6
|
import { describeProbedTableDrift } from "./schema-drift-sentence.js";
|
|
6
7
|
import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, readTriStateJsonValueColumn } from "./sql-json-column.js";
|
|
@@ -25,7 +26,8 @@ export const MAX_SETTLED_BY_CHARS = 16;
|
|
|
25
26
|
export function settledByFitsColumn(v) {
|
|
26
27
|
return v.length > 0 && v.length <= MAX_SETTLED_BY_CHARS;
|
|
27
28
|
}
|
|
28
|
-
export function
|
|
29
|
+
export function assertDecideSource(decision) {
|
|
30
|
+
assertDecidedViaBesidePolicy(decision);
|
|
29
31
|
const v = decision.settledBy;
|
|
30
32
|
if (v === undefined)
|
|
31
33
|
return;
|
|
@@ -33,6 +35,15 @@ export function assertDecideSettledBy(decision) {
|
|
|
33
35
|
throw new Error(`invalid settledBy: the settlement source is one word from the engine's closed set and lands in a VARCHAR(${MAX_SETTLED_BY_CHARS}) column — must be 1-${MAX_SETTLED_BY_CHARS} chars (got ${JSON.stringify(v.slice(0, 32))}${v.length > 32 ? "…" : ""}, length ${v.length})`);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
38
|
+
export const MAX_DECIDED_VIA_CHARS = 32;
|
|
39
|
+
function assertDecidedViaBesidePolicy(decision) {
|
|
40
|
+
const via = decision.decidedVia;
|
|
41
|
+
if (via === undefined)
|
|
42
|
+
return;
|
|
43
|
+
if (!DECIDED_VIA.some((w) => w === via) || via.length > MAX_DECIDED_VIA_CHARS || decision.settledBy !== "policy") {
|
|
44
|
+
throw new Error(`invalid decidedVia: the machine seat is one of ${DECIDED_VIA.map((w) => `"${w}"`).join(" / ")} and is stated only beside settledBy "policy" (got ${JSON.stringify(String(via).slice(0, 32))} beside ${JSON.stringify(decision.settledBy ?? null)})`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
36
47
|
export class ApprovalAskReadAbortedError extends Error {
|
|
37
48
|
constructor(where) {
|
|
38
49
|
super(`approval-ask read ${where} aborted by caller`);
|
|
@@ -61,6 +72,18 @@ async function withReadSignal(op, signal, where) {
|
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
74
|
export const POLICY_REFUSAL_DECISION = { decision: "deny", settledBy: "policy" };
|
|
75
|
+
export function claimCauseColumn(intent) {
|
|
76
|
+
switch (intent) {
|
|
77
|
+
case "cancel":
|
|
78
|
+
return "ending_cause";
|
|
79
|
+
case "expire":
|
|
80
|
+
return "park_trigger";
|
|
81
|
+
default: {
|
|
82
|
+
const unreachable = intent;
|
|
83
|
+
throw new Error(`approval-ask-store: no cause column for terminal claim intent ${String(unreachable)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
64
87
|
export function claimTargetState(intent) {
|
|
65
88
|
switch (intent) {
|
|
66
89
|
case "expire":
|
|
@@ -78,18 +101,21 @@ export function claimTargetState(intent) {
|
|
|
78
101
|
export function projectAskTerminalClaimTruth(row) {
|
|
79
102
|
if (row === null || row.state === "STREAM_PENDING")
|
|
80
103
|
return { state: "absent" };
|
|
81
|
-
const
|
|
104
|
+
const endingCause = askRowEndingCause(row);
|
|
105
|
+
const parkTrigger = askRowParkTrigger(row);
|
|
82
106
|
return {
|
|
83
107
|
state: row.state,
|
|
84
108
|
rev: row.rev,
|
|
85
109
|
decision: row.decision,
|
|
86
110
|
...(row.updatedInput !== undefined ? { updatedInput: row.updatedInput } : {}),
|
|
87
111
|
settledBy: row.settledBy,
|
|
88
|
-
|
|
112
|
+
decidedVia: row.decidedVia,
|
|
113
|
+
...(endingCause !== undefined ? { endingCause } : {}),
|
|
114
|
+
...(parkTrigger !== undefined ? { parkTrigger } : {}),
|
|
89
115
|
};
|
|
90
116
|
}
|
|
91
117
|
export function askRowOnCall(row, call) {
|
|
92
|
-
return row.taskId === call.taskId && row.legKey === call.legKey && row.toolCallId === call.toolCallId && row.
|
|
118
|
+
return row.taskId === call.taskId && row.legKey === call.legKey && row.toolCallId === call.toolCallId && row.sourceSessionId === call.sourceSessionId;
|
|
93
119
|
}
|
|
94
120
|
export function askOwnerAdmits(owner, principal) {
|
|
95
121
|
return owner === null || owner === principal;
|
|
@@ -105,6 +131,16 @@ export const APPROVAL_ASK_EXECUTION_LANE_COLUMN = {
|
|
|
105
131
|
mysql: "execution_lane VARCHAR(32) NOT NULL DEFAULT ''",
|
|
106
132
|
pg: `execution_lane VARCHAR(32) COLLATE "C" NOT NULL DEFAULT ''`,
|
|
107
133
|
};
|
|
134
|
+
export const APPROVAL_ASK_ENDING_CAUSE_COLUMN = {
|
|
135
|
+
name: "ending_cause",
|
|
136
|
+
mysql: "ending_cause VARCHAR(64) NULL",
|
|
137
|
+
pg: `ending_cause VARCHAR(64) COLLATE "C"`,
|
|
138
|
+
};
|
|
139
|
+
export const APPROVAL_ASK_PARK_TRIGGER_COLUMN = {
|
|
140
|
+
name: "park_trigger",
|
|
141
|
+
mysql: "park_trigger VARCHAR(64) NULL",
|
|
142
|
+
pg: `park_trigger VARCHAR(64) COLLATE "C"`,
|
|
143
|
+
};
|
|
108
144
|
function executionLaneColumnOf(raw, askId) {
|
|
109
145
|
const lane = typeof raw === "string" ? raw : "";
|
|
110
146
|
if (!isExecutionLane(lane)) {
|
|
@@ -155,15 +191,21 @@ export const TIDB_APPROVAL_ASK_STATEMENTS = [
|
|
|
155
191
|
-- settled_by(S-558 / #1016 持久半场):这条决议的结算来源词(NULL = 人答的)。语义、两态读法与
|
|
156
192
|
-- 「为什么是自己一列而不是塞进 decision_actor」的全文见 AskRow.settledBy 顶注。
|
|
157
193
|
-- VARCHAR(16):词表属主是 core(AskOutcome.settledBy,今天最长 7 字符 "timeout"),宽度与同表
|
|
158
|
-
-- decision 同尺同理由 —— 闭集词列,不是自由文本。截断面由入店形门堵死(
|
|
194
|
+
-- decision 同尺同理由 —— 闭集词列,不是自由文本。截断面由入店形门堵死(assertDecideSource),
|
|
159
195
|
-- 不靠「列够宽就不会截」这种运气(MySQL 非严格模式静默截、PG 直接报错 = 两方言不同答)。
|
|
160
196
|
settled_by VARCHAR(16) NULL,
|
|
197
|
+
-- decided_via(core 7.61.0 #3169):settled_by = 'policy' 旁的机器座位词(NULL = 没陈述 = 部署自己的策略;旧行同读)。
|
|
198
|
+
-- 与 settled_by 同一条 UPDATE 落列、同一条理由住在行上(语义全文见 AskRow.decidedVia)。闭集词列,宽度 = MAX_DECIDED_VIA_CHARS。
|
|
199
|
+
decided_via VARCHAR(${MAX_DECIDED_VIA_CHARS}) NULL,
|
|
161
200
|
-- updated_input(S-26 / #304,A-054.15 收口):人批准时带的 ctrl+g 编辑实参,JSON 文本,与决议同一条
|
|
162
201
|
-- UPDATE 原子落列;NULL = 没带编辑(与 JSON "null" = 编辑成 null 分家)。LONGTEXT 照 card_json 先例:
|
|
163
202
|
-- 编辑实参是整只工具入参,VARCHAR/TEXT 会造静默截断面。语义三态与读法见 AskRow.updatedInput 顶注。
|
|
164
203
|
updated_input LONGTEXT NULL,
|
|
165
204
|
decided_at_ms BIGINT NULL,
|
|
166
|
-
|
|
205
|
+
-- 成因两列,一列一义(语义见 AskRow.endingCause / AskRow.parkTrigger):ending_cause = 终态成因(到 VOID / DENIED 的转移写);
|
|
206
|
+
-- park_trigger = 转 park 的触发成因(只由 expire claim 写一次,终态不覆盖)。
|
|
207
|
+
${APPROVAL_ASK_ENDING_CAUSE_COLUMN.mysql},
|
|
208
|
+
${APPROVAL_ASK_PARK_TRIGGER_COLUMN.mysql},
|
|
167
209
|
gate_token VARCHAR(120) NULL,
|
|
168
210
|
gate_bound_call_id VARCHAR(255) NULL,
|
|
169
211
|
gate_bound_input_hash VARCHAR(255) NULL,
|
|
@@ -241,10 +283,14 @@ export async function ensurePgApprovalAskSchema(q) {
|
|
|
241
283
|
decision_note TEXT COLLATE "C",
|
|
242
284
|
-- settled_by(S-558)—— MySQL 孪生逐字同形(宽度/语义/形门的理由写在那侧的行内注)。
|
|
243
285
|
settled_by VARCHAR(16) COLLATE "C",
|
|
286
|
+
-- decided_via(#3169)—— MySQL 孪生逐字同形。
|
|
287
|
+
decided_via VARCHAR(${MAX_DECIDED_VIA_CHARS}) COLLATE "C",
|
|
244
288
|
-- updated_input(S-26 / #304)—— MySQL 孪生逐字同形(语义/类型理由见那侧行内注;PG TEXT 无长度上限)。
|
|
245
289
|
updated_input TEXT COLLATE "C",
|
|
246
290
|
decided_at_ms BIGINT,
|
|
247
|
-
|
|
291
|
+
-- 成因两列 —— MySQL 孪生逐字同形(语义见那侧行内注)。
|
|
292
|
+
${APPROVAL_ASK_ENDING_CAUSE_COLUMN.pg},
|
|
293
|
+
${APPROVAL_ASK_PARK_TRIGGER_COLUMN.pg},
|
|
248
294
|
gate_token VARCHAR(120) COLLATE "C",
|
|
249
295
|
gate_bound_call_id VARCHAR(255) COLLATE "C",
|
|
250
296
|
gate_bound_input_hash VARCHAR(255) COLLATE "C",
|
|
@@ -288,9 +334,9 @@ function askCols(dialect) {
|
|
|
288
334
|
"ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state",
|
|
289
335
|
"provisional, rev, decision",
|
|
290
336
|
jsonColumnText(dialect, "decision_actor"),
|
|
291
|
-
"decision_note, settled_by",
|
|
337
|
+
"decision_note, settled_by, decided_via",
|
|
292
338
|
jsonColumnText(dialect, "updated_input"),
|
|
293
|
-
"decided_at_ms,
|
|
339
|
+
"decided_at_ms, ending_cause, park_trigger, gate_token",
|
|
294
340
|
"gate_bound_call_id, gate_bound_input_hash, idempotency_key",
|
|
295
341
|
jsonColumnText(dialect, "card_json"),
|
|
296
342
|
"rule_command, rule_scope_root, schema_version, expires_at_ms",
|
|
@@ -320,7 +366,7 @@ function mapAskRowBody(r, askId) {
|
|
|
320
366
|
return {
|
|
321
367
|
askId: String(r.ask_id),
|
|
322
368
|
taskId: String(r.task_id),
|
|
323
|
-
|
|
369
|
+
sourceSessionId: String(r.source_task_id),
|
|
324
370
|
sessionId: String(r.session_id),
|
|
325
371
|
owner: strOrNull(r.owner),
|
|
326
372
|
executionLane: executionLaneColumnOf(r.execution_lane, askId),
|
|
@@ -336,9 +382,11 @@ function mapAskRowBody(r, askId) {
|
|
|
336
382
|
decisionActor: r.decision_actor == null ? null : decodeJsonColumn(r.decision_actor, readJsonObjectColumn, `${APPROVAL_ASK_TABLE}.decision_actor for ask ${askId}`),
|
|
337
383
|
decisionNote: strOrNull(r.decision_note),
|
|
338
384
|
settledBy: strOrNull(r.settled_by),
|
|
385
|
+
decidedVia: strOrNull(r.decided_via),
|
|
339
386
|
...(r.updated_input == null ? {} : { updatedInput: decodeJsonColumn(r.updated_input, readTriStateJsonValueColumn, `${APPROVAL_ASK_TABLE}.updated_input for ask ${askId}`) }),
|
|
340
387
|
decidedAtMs: numOrNull(r.decided_at_ms),
|
|
341
|
-
|
|
388
|
+
endingCause: strOrNull(r.ending_cause),
|
|
389
|
+
parkTrigger: strOrNull(r.park_trigger),
|
|
342
390
|
gateToken: strOrNull(r.gate_token),
|
|
343
391
|
gateBoundCallId: strOrNull(r.gate_bound_call_id),
|
|
344
392
|
gateBoundInputHash: strOrNull(r.gate_bound_input_hash),
|
|
@@ -373,8 +421,8 @@ function patchColumns(patch, json) {
|
|
|
373
421
|
out.push(["decision_note", patch.decisionNote]);
|
|
374
422
|
if (patch.decidedAtMs !== undefined)
|
|
375
423
|
out.push(["decided_at_ms", patch.decidedAtMs]);
|
|
376
|
-
if (patch.
|
|
377
|
-
out.push(["
|
|
424
|
+
if (patch.endingCause !== undefined)
|
|
425
|
+
out.push(["ending_cause", patch.endingCause]);
|
|
378
426
|
if (patch.gateToken !== undefined)
|
|
379
427
|
out.push(["gate_token", patch.gateToken]);
|
|
380
428
|
if (patch.gateBoundCallId !== undefined)
|
|
@@ -427,12 +475,12 @@ export class SqlApprovalAskStore {
|
|
|
427
475
|
try {
|
|
428
476
|
await conn.begin();
|
|
429
477
|
await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_BATCH_TABLE} (batch_id, task_id, state, rev, created_at_ms, updated_at_ms) VALUES (?, ?, 'OPEN', 0, ?, ?)`, `INSERT INTO ${APPROVAL_BATCH_TABLE} (batch_id, task_id, state, rev, created_at_ms, updated_at_ms) VALUES ($1, $2, 'OPEN', 0, $3, $4) ON CONFLICT (batch_id) DO NOTHING`), [row.batchId, row.taskId, now, now]);
|
|
430
|
-
const askInsert = await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms,
|
|
431
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)", `INSERT INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms,
|
|
432
|
-
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT (ask_id) DO NOTHING"), [
|
|
478
|
+
const askInsert = await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, ending_cause, park_trigger, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
|
|
479
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)", `INSERT INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, ending_cause, park_trigger, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
|
|
480
|
+
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT (ask_id) DO NOTHING"), [
|
|
433
481
|
row.askId,
|
|
434
482
|
row.taskId,
|
|
435
|
-
row.
|
|
483
|
+
row.sourceSessionId,
|
|
436
484
|
row.sessionId,
|
|
437
485
|
row.owner ?? null,
|
|
438
486
|
row.executionLane,
|
|
@@ -486,10 +534,10 @@ export class SqlApprovalAskStore {
|
|
|
486
534
|
async decideAsk(askId, batchId, owner, decision) {
|
|
487
535
|
if (decision.idempotencyKey != null)
|
|
488
536
|
assertIdempotencyKeyShape(decision.idempotencyKey);
|
|
489
|
-
|
|
537
|
+
assertDecideSource(decision);
|
|
490
538
|
const now = Date.now();
|
|
491
539
|
const dialect = this.db.dialect;
|
|
492
|
-
const ownerWhere = decideOwnerWhere(dialect, owner,
|
|
540
|
+
const ownerWhere = decideOwnerWhere(dialect, owner, 12);
|
|
493
541
|
const conn = await this.db.connect();
|
|
494
542
|
try {
|
|
495
543
|
await conn.begin();
|
|
@@ -500,11 +548,12 @@ export class SqlApprovalAskStore {
|
|
|
500
548
|
}
|
|
501
549
|
let askRes;
|
|
502
550
|
try {
|
|
503
|
-
askRes = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='DECIDED', decision=${ph(dialect, 1)}, decision_actor=${ph(dialect, 2)}, decision_note=${ph(dialect, 3)}, settled_by=${ph(dialect, 4)},
|
|
551
|
+
askRes = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='DECIDED', decision=${ph(dialect, 1)}, decision_actor=${ph(dialect, 2)}, decision_note=${ph(dialect, 3)}, settled_by=${ph(dialect, 4)}, decided_via=${ph(dialect, 5)}, updated_input=${ph(dialect, 6)}, decided_at_ms=${ph(dialect, 7)}, idempotency_key=${ph(dialect, 8)}, rev=rev+1, updated_at_ms=${ph(dialect, 9)} WHERE ${pendingClaimWhere(dialect, 10)} AND ${ownerWhere.sql}`, [
|
|
504
552
|
decision.decision,
|
|
505
553
|
decision.decisionActor == null ? null : this.json(decision.decisionActor),
|
|
506
554
|
decision.decisionNote ?? null,
|
|
507
555
|
decision.settledBy ?? null,
|
|
556
|
+
decision.decidedVia ?? null,
|
|
508
557
|
decision.updatedInput === undefined ? null : this.json(decision.updatedInput),
|
|
509
558
|
now,
|
|
510
559
|
decision.idempotencyKey ?? null,
|
|
@@ -549,7 +598,7 @@ export class SqlApprovalAskStore {
|
|
|
549
598
|
throw new Error(`approval-ask-store.claimTerminal: illegal terminal claim STREAM_PENDING -> ${to} (askId=${askId})`);
|
|
550
599
|
}
|
|
551
600
|
if (intent === "refuse")
|
|
552
|
-
|
|
601
|
+
assertDecideSource(POLICY_REFUSAL_DECISION);
|
|
553
602
|
const now = Date.now();
|
|
554
603
|
const dialect = this.db.dialect;
|
|
555
604
|
const conn = await this.db.connect();
|
|
@@ -566,7 +615,7 @@ export class SqlApprovalAskStore {
|
|
|
566
615
|
? await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state = ${ph(dialect, 1)}, decision = ${ph(dialect, 2)}, settled_by = ${ph(dialect, 3)}, decided_at_ms = ${ph(dialect, 4)}, rev = rev + 1, updated_at_ms = ${ph(dialect, 5)} WHERE ${pendingClaimWhere(dialect, 6)}`, [to, POLICY_REFUSAL_DECISION.decision, POLICY_REFUSAL_DECISION.settledBy, now, now, askId, batchId])
|
|
567
616
|
: (intent === "cancel" || intent === "expire") && opts?.cause !== undefined
|
|
568
617
|
?
|
|
569
|
-
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state = ${ph(dialect, 1)},
|
|
618
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state = ${ph(dialect, 1)}, ${claimCauseColumn(intent)} = ${ph(dialect, 2)}, rev = rev + 1, updated_at_ms = ${ph(dialect, 3)} WHERE ${pendingClaimWhere(dialect, 4)}`, [to, opts.cause, now, askId, batchId])
|
|
570
619
|
: await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state = ${ph(dialect, 1)}, rev = rev + 1, updated_at_ms = ${ph(dialect, 2)} WHERE ${pendingClaimWhere(dialect, 3)}`, [
|
|
571
620
|
to,
|
|
572
621
|
now,
|
|
@@ -585,7 +634,7 @@ export class SqlApprovalAskStore {
|
|
|
585
634
|
await conn.query(this.q(`UPDATE ${APPROVAL_BATCH_TABLE} SET state='ROUTING_UNBOUND', rev=rev+1, updated_at_ms=? WHERE batch_id=? AND state='OPEN'`, `UPDATE ${APPROVAL_BATCH_TABLE} SET state='ROUTING_UNBOUND', rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='OPEN'`), [now, batchId]);
|
|
586
635
|
let voidedSiblings;
|
|
587
636
|
if (this.db.dialect === "pg") {
|
|
588
|
-
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID',
|
|
637
|
+
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', ending_cause=$4, rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='STREAM_PENDING' AND ask_id<>$3 RETURNING ask_id`, [now, batchId, askId, APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN]);
|
|
589
638
|
voidedSiblings = res.rows.map((r) => String(r.ask_id));
|
|
590
639
|
}
|
|
591
640
|
else {
|
|
@@ -593,7 +642,7 @@ export class SqlApprovalAskStore {
|
|
|
593
642
|
voidedSiblings = sel.rows.map((r) => String(r.ask_id));
|
|
594
643
|
if (voidedSiblings.length > 0) {
|
|
595
644
|
const inList = voidedSiblings.map(() => "?").join(", ");
|
|
596
|
-
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID',
|
|
645
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', ending_cause=?, rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='STREAM_PENDING'`, [
|
|
597
646
|
APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN,
|
|
598
647
|
now,
|
|
599
648
|
...voidedSiblings,
|
|
@@ -639,7 +688,7 @@ export class SqlApprovalAskStore {
|
|
|
639
688
|
}
|
|
640
689
|
let voidedSiblings;
|
|
641
690
|
if (this.db.dialect === "pg") {
|
|
642
|
-
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID',
|
|
691
|
+
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', ending_cause=$4, rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='PARKING' AND ask_id<>$3 RETURNING ask_id`, [now, batchId, askId, APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE]);
|
|
643
692
|
voidedSiblings = res.rows.map((r) => String(r.ask_id));
|
|
644
693
|
}
|
|
645
694
|
else {
|
|
@@ -647,7 +696,7 @@ export class SqlApprovalAskStore {
|
|
|
647
696
|
voidedSiblings = sel.rows.map((r) => String(r.ask_id));
|
|
648
697
|
if (voidedSiblings.length > 0) {
|
|
649
698
|
const inList = voidedSiblings.map(() => "?").join(", ");
|
|
650
|
-
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID',
|
|
699
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', ending_cause=?, rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='PARKING'`, [APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE, now, ...voidedSiblings]);
|
|
651
700
|
}
|
|
652
701
|
}
|
|
653
702
|
await conn.commit();
|
|
@@ -677,7 +726,7 @@ export class SqlApprovalAskStore {
|
|
|
677
726
|
}
|
|
678
727
|
let voided;
|
|
679
728
|
if (this.db.dialect === "pg") {
|
|
680
|
-
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID',
|
|
729
|
+
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', ending_cause=$3, rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='STREAM_PENDING' RETURNING ask_id`, [now, batchId, APPROVAL_ENDING_CAUSES.BATCH_ABORTED]);
|
|
681
730
|
voided = res.rows.map((r) => String(r.ask_id));
|
|
682
731
|
}
|
|
683
732
|
else {
|
|
@@ -685,7 +734,7 @@ export class SqlApprovalAskStore {
|
|
|
685
734
|
voided = sel.rows.map((r) => String(r.ask_id));
|
|
686
735
|
if (voided.length > 0) {
|
|
687
736
|
const inList = voided.map(() => "?").join(", ");
|
|
688
|
-
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID',
|
|
737
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', ending_cause=?, rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='STREAM_PENDING'`, [APPROVAL_ENDING_CAUSES.BATCH_ABORTED, now, ...voided]);
|
|
689
738
|
}
|
|
690
739
|
}
|
|
691
740
|
await conn.commit();
|
|
@@ -722,7 +771,7 @@ export class SqlApprovalAskStore {
|
|
|
722
771
|
return rows.map(mapAskRow);
|
|
723
772
|
}
|
|
724
773
|
async listByCall(call) {
|
|
725
|
-
const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND leg_key = ? AND tool_call_id = ? AND source_task_id = ? ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND leg_key = $2 AND tool_call_id = $3 AND source_task_id = $4 ORDER BY created_at_ms ASC, ask_id ASC`), [call.taskId, call.legKey, call.toolCallId, call.
|
|
774
|
+
const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND leg_key = ? AND tool_call_id = ? AND source_task_id = ? ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND leg_key = $2 AND tool_call_id = $3 AND source_task_id = $4 ORDER BY created_at_ms ASC, ask_id ASC`), [call.taskId, call.legKey, call.toolCallId, call.sourceSessionId]);
|
|
726
775
|
return rows.map(mapAskRow).filter((r) => askRowOnCall(r, call));
|
|
727
776
|
}
|
|
728
777
|
async listParkingBySession(sessionId) {
|
|
@@ -343,7 +343,7 @@ export interface CheckpointAskCandidate {
|
|
|
343
343
|
status: string;
|
|
344
344
|
createdAtMs: number;
|
|
345
345
|
/**
|
|
346
|
-
* 🔴 和解三元组的第一维([ref] 件1,黑板 [ref]③①)—— checkpoint 自己带的 `
|
|
346
|
+
* 🔴 和解三元组的第一维([ref] 件1,黑板 [ref]③①)—— checkpoint 自己带的 `sourceSessionId`(core 在
|
|
347
347
|
* suspendAsk 铸行时恒填该腿的 `sessionId`)。**没有对应的列**,只能从 blob 窄读;读不出/缺席 = null。
|
|
348
348
|
*
|
|
349
349
|
* 为什么必须进候选集:读口按 `(scope, session_id, tool_call_id)` 查,而委派子代那条腿的 ask 落行时
|
|
@@ -351,7 +351,7 @@ export interface CheckpointAskCandidate {
|
|
|
351
351
|
* (toolCallId, hash) 两维,一条根腿的 park 会被一只子代的 ask 认领(摘要相同的两次同 args 调用完全
|
|
352
352
|
* 正常)。三元组全同才算同一件事。
|
|
353
353
|
*/
|
|
354
|
-
|
|
354
|
+
sourceSessionId: string | null;
|
|
355
355
|
/** 该 park 绑定的 tool call(= `pendingAction.toolCallId`)。`unparseable` 行为 null。 */
|
|
356
356
|
boundCallId: string | null;
|
|
357
357
|
/** 服务端铸的入参摘要——判据 1 的第二道硬等式(与 `approval_ask.bound_input_hash` 相等才算命中)。 */
|
|
@@ -444,7 +444,7 @@ export declare function pendingActionToolCallId(body: unknown): {
|
|
|
444
444
|
toolCallId: string | null;
|
|
445
445
|
boundInputHash: string | null;
|
|
446
446
|
kind: string | null;
|
|
447
|
-
|
|
447
|
+
sourceSessionId: string | null;
|
|
448
448
|
} | {
|
|
449
449
|
readable: false;
|
|
450
450
|
};
|
|
@@ -781,7 +781,7 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
|
|
|
781
781
|
* 🔴 **没有「列命中即零解析」的快路径**(原注写过,已作废,别照它优化):函数体对**每一行**无条件解
|
|
782
782
|
* blob,原因有二 ——(a) 前向兼容门与 blob 可读性门必须门在**所有**命中路径之前(codex F3 + 确认轮:
|
|
783
783
|
* 列长得对不代表 blob 读得出,放行一条读不出的行去 `bindBatch` 会把 ask 钉成 PARKED + 一张本进程读不出
|
|
784
|
-
* 的 resume 坐标,而 PARKED 不可回滚);(b) `[ref]` 之后 blob 顶层的 `
|
|
784
|
+
* 的 resume 坐标,而 PARKED 不可回滚);(b) `[ref]` 之后 blob 顶层的 `sourceSessionId` 是和解三元组的第一
|
|
785
785
|
* 维,列命中行结构上也必须解 blob 才拿得到它。
|
|
786
786
|
*/
|
|
787
787
|
findCheckpointCandidatesForAsk(scope: string, sessionId: string, toolCallId: string, sinceMs: number): Promise<CheckpointAskCandidate[]>;
|
|
@@ -239,12 +239,14 @@ export function approvalDecisionOfWinner(winner, boundCallId) {
|
|
|
239
239
|
export function pendingActionToolCallId(body) {
|
|
240
240
|
if (typeof body !== "object" || body === null)
|
|
241
241
|
return { readable: false };
|
|
242
|
-
|
|
242
|
+
if ("sourceTaskId" in body)
|
|
243
|
+
return { readable: false };
|
|
244
|
+
const rawSource = "sourceSessionId" in body ? body.sourceSessionId : null;
|
|
243
245
|
if (rawSource !== null && rawSource !== undefined && typeof rawSource !== "string")
|
|
244
246
|
return { readable: false };
|
|
245
|
-
const
|
|
247
|
+
const sourceSessionId = typeof rawSource === "string" ? rawSource : null;
|
|
246
248
|
if (!("pendingAction" in body))
|
|
247
|
-
return { readable: true, toolCallId: null, boundInputHash: null, kind: null,
|
|
249
|
+
return { readable: true, toolCallId: null, boundInputHash: null, kind: null, sourceSessionId };
|
|
248
250
|
const pa = body.pendingAction;
|
|
249
251
|
if (typeof pa !== "object" || pa === null)
|
|
250
252
|
return { readable: false };
|
|
@@ -252,7 +254,7 @@ export function pendingActionToolCallId(body) {
|
|
|
252
254
|
if (kind !== null && typeof kind !== "string")
|
|
253
255
|
return { readable: false };
|
|
254
256
|
if (!("toolCallId" in pa))
|
|
255
|
-
return { readable: true, toolCallId: null, boundInputHash: null, kind,
|
|
257
|
+
return { readable: true, toolCallId: null, boundInputHash: null, kind, sourceSessionId };
|
|
256
258
|
const id = pa.toolCallId;
|
|
257
259
|
if (typeof id !== "string")
|
|
258
260
|
return { readable: false };
|
|
@@ -261,7 +263,7 @@ export function pendingActionToolCallId(body) {
|
|
|
261
263
|
const bih = "boundInputHash" in pa ? pa.boundInputHash : null;
|
|
262
264
|
if (bih !== null && typeof bih !== "string")
|
|
263
265
|
return { readable: false };
|
|
264
|
-
return { readable: true, toolCallId: id, boundInputHash: bih, kind,
|
|
266
|
+
return { readable: true, toolCallId: id, boundInputHash: bih, kind, sourceSessionId };
|
|
265
267
|
}
|
|
266
268
|
function pendingActionOfColumn(cell, where) {
|
|
267
269
|
let body;
|
|
@@ -650,7 +652,7 @@ export class SqlCheckpointStore {
|
|
|
650
652
|
status: String(r.status),
|
|
651
653
|
createdAtMs: Number(r.created_at_ms),
|
|
652
654
|
boundInputHash: r.bound_input_hash == null ? null : String(r.bound_input_hash),
|
|
653
|
-
|
|
655
|
+
sourceSessionId: null,
|
|
654
656
|
};
|
|
655
657
|
if (Number(r.version) > MAX_SUPPORTED_CHECKPOINT_VERSION) {
|
|
656
658
|
out.push({ ...base, boundCallId: null, unparseable: true });
|
|
@@ -673,7 +675,7 @@ export class SqlCheckpointStore {
|
|
|
673
675
|
const effectiveHash = base.boundInputHash ?? derived.boundInputHash;
|
|
674
676
|
const effective = col ?? derived.toolCallId;
|
|
675
677
|
if (effective === toolCallId)
|
|
676
|
-
out.push({ ...base, boundInputHash: effectiveHash,
|
|
678
|
+
out.push({ ...base, boundInputHash: effectiveHash, sourceSessionId: derived.sourceSessionId, boundCallId: effective });
|
|
677
679
|
}
|
|
678
680
|
return out;
|
|
679
681
|
}
|
|
@@ -2,7 +2,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
|
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
3
|
import { type FileHistoryRetentionPolicy, type ExecutionEnv, type FileHistoryStore, type FileHistoryResult, type FileHistoryTrackResult, type FileHistoryRestoreResult, type FileHistoryExport, type FileHistoryExportResult } from "@sema-agent/core";
|
|
4
4
|
import { type BlobBackend } from "./blob-backend.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { ServiceBlobExchange } from "./store-backend.js";
|
|
6
6
|
import { type SqlDriver, type SqlDialect } from "./sql-driver.js";
|
|
7
7
|
/** The file-history blob domain's SQL table (BlobBackend `table`/MinIO index binding + the A7 assert). */
|
|
8
8
|
export declare const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
|
|
@@ -24,12 +24,10 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
|
|
|
24
24
|
/** RAW native pool — handed to blob-backend.ts (outside the SqlDriver seam, dialect-switches on
|
|
25
25
|
* its own string). Not used for this class's own statements (those go through `db`). */
|
|
26
26
|
protected readonly pool: MySqlPool | PgPool;
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
|
|
31
|
-
* undefined. Presence of this literal = the full typed putBlob/getBlob/hasBlobs face exists. */
|
|
32
|
-
readonly syncBlobFaces: true;
|
|
27
|
+
/** The blob exchange (core's `FileHistoryStore.blobExchange` group, see {@link ServiceBlobExchange}): how another machine's bytes
|
|
28
|
+
* come in ahead of an `importHistory`, go out to a puller, and are asked about. Its presence IS the capability — read through
|
|
29
|
+
* `blobExchangeOf` (`session-sync.ts`), which defers to core's judge; there is no second word for it on this class. */
|
|
30
|
+
readonly blobExchange: ServiceBlobExchange;
|
|
33
31
|
protected readonly blobs: BlobBackend;
|
|
34
32
|
/** §10 S-6 in-process lease state (design v1 D2: NOT persisted). Keys = `${scope}\x00${entryId}`. */
|
|
35
33
|
private readonly spentAttempts;
|
|
@@ -58,15 +56,25 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
|
|
|
58
56
|
/**
|
|
59
57
|
* Per-scope WRITE-SERIALIZATION lock (codex R1-critical): `SELECT … FOR UPDATE` over an EMPTY result
|
|
60
58
|
* locks nothing on PG, so neither the empty-target law (import/adopt) nor a multi-statement graph
|
|
61
|
-
* write can be serialized by the data rows alone. Every graph
|
|
59
|
+
* write can be serialized by the data rows alone. Every graph transaction therefore first ensures +
|
|
62
60
|
* row-locks the scope's sentinel row — one lock, both dialects, and disjoint-key writers can no
|
|
63
|
-
* longer interleave into a merged/torn graph.
|
|
64
|
-
* the sentinel: a waiter woken by that delete sees zero rows and must re-ensure.
|
|
61
|
+
* longer interleave into a merged/torn graph.
|
|
65
62
|
*
|
|
66
63
|
* Shape A ([ref]): the MySQL ensure verb is `ON DUPLICATE KEY UPDATE scope = scope`, NOT `INSERT IGNORE`.
|
|
67
64
|
* On InnoDB a duplicate-key IGNORE takes an **S** lock on the existing sentinel and the `FOR UPDATE` right
|
|
68
65
|
* below has to upgrade it to X — two concurrent scope writers each holding an S deadlock ([ref]). ON
|
|
69
66
|
* DUPLICATE takes X on the duplicate key, the same mode the locking read wants, so they serialize instead.
|
|
67
|
+
*
|
|
68
|
+
* **This is the transaction's first statement** (every caller: `begin()` and then this — nothing of the
|
|
69
|
+
* transaction exists before it), and a lost arbitration here restarts the transaction from the top
|
|
70
|
+
* (rollback + begin + the same ensure-and-lock), through the repo's one bound ({@link retryOnDeadlock}). Two shapes:
|
|
71
|
+
* · the engine picked this transaction as a deadlock victim. InnoDB only (REPEATABLE READ): when the sentinel
|
|
72
|
+
* physically disappears while ≥ 2 transactions are queued on it — its minter rolled back, or a whole-scope
|
|
73
|
+
* removal ({@link deleteBySession}) committed and purge took the delete-marked row — the queued locks are
|
|
74
|
+
* inherited as GAP locks on the next record, and each queued ensure's insert-intention waits on the others'
|
|
75
|
+
* gap locks. The engine kills all but one; the survivor's mint goes through and the others restart into it;
|
|
76
|
+
* · the sentinel was gone at the locking read ({@link ScopeSentinelVanishedError}; PG, see there).
|
|
77
|
+
* The lock order itself stays single: sentinels first (sorted), then the scope's rows.
|
|
70
78
|
*/
|
|
71
79
|
private lockScope;
|
|
72
80
|
/** Returns the UPDATE's affected count — 0 = the record is gone (annulled) or was already observed;
|
|
@@ -147,14 +155,12 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
|
|
|
147
155
|
/** Async byte-GC: blobs referenced by NO version row (any scope), past the created_at grace window.
|
|
148
156
|
* Domain-isolated by construction (this store's blob table only — [ref] D1). Reaper-driven. */
|
|
149
157
|
sweepOrphanBlobs(): Promise<number>;
|
|
150
|
-
/**
|
|
151
|
-
*
|
|
152
|
-
* The SQL blob judge's refusal ({@link BlobTooLargeError}, judged on the connection that would have written the blob)
|
|
153
|
-
*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/** The subset of `hashes` already present — one SQL INDEX probe (the import presence pre-check). */
|
|
157
|
-
hasBlobs(hashes: string[]): Promise<Set<string>>;
|
|
158
|
+
/** Store ONE content-addressed blob ahead of an importHistory (§5 streamed negotiation). The content address is judged HERE and
|
|
159
|
+
* nowhere else (core's `put` law: bytes whose sha256 is not `hash` are refused `adopt_failed` / `terminal`, nothing stored).
|
|
160
|
+
* Never-throw. The SQL blob judge's refusal ({@link BlobTooLargeError}, judged on the connection that would have written the blob)
|
|
161
|
+
* is a terminal refusal that carries `blobCap` — the route answers it 413 with the judge's sentence; every other write failure is
|
|
162
|
+
* `snapshot_failed` with the store's judgment of the fault (the route answers 502, "retry"). */
|
|
163
|
+
private putExchangedBlob;
|
|
158
164
|
adoptScope(fromScope: string, toScope: string): Promise<FileHistoryResult>;
|
|
159
165
|
private scopeEmpty;
|
|
160
166
|
/** `forUpdate` — see {@link loadTracked}. Every in-transaction caller passes `true`: the four reads are
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, annulProofFailure, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
|
|
3
3
|
import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
|
-
import { isMissingColumnError } from "./sql-errors.js";
|
|
5
|
+
import { isMissingColumnError, retryOnDeadlock } from "./sql-errors.js";
|
|
6
6
|
import { describeProbedTableDrift } from "./schema-drift-sentence.js";
|
|
7
7
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
8
8
|
import { foldCaughtFailure } from "../file-history-disclosure.js";
|
|
@@ -12,6 +12,8 @@ const sha256hex = (s) => createHash("sha256").update(s).digest("hex");
|
|
|
12
12
|
const truthy = (v) => v === true || v === 1 || v === "1";
|
|
13
13
|
class StagedRefGoneError extends Error {
|
|
14
14
|
}
|
|
15
|
+
class ScopeSentinelVanishedError extends Error {
|
|
16
|
+
}
|
|
15
17
|
function reapedEntryRefusal(entryId) {
|
|
16
18
|
return {
|
|
17
19
|
ok: false,
|
|
@@ -24,7 +26,11 @@ function reapedEntryRefusal(entryId) {
|
|
|
24
26
|
export class SqlFileHistoryStore {
|
|
25
27
|
db;
|
|
26
28
|
pool;
|
|
27
|
-
|
|
29
|
+
blobExchange = {
|
|
30
|
+
put: (hash, bytes) => this.putExchangedBlob(hash, bytes),
|
|
31
|
+
get: (hash) => this.blobs.getBlob(hash),
|
|
32
|
+
has: (hashes) => this.blobs.hasBlobs([...hashes]),
|
|
33
|
+
};
|
|
28
34
|
blobs;
|
|
29
35
|
spentAttempts = new Set();
|
|
30
36
|
activeAttempts = new Set();
|
|
@@ -48,16 +54,21 @@ export class SqlFileHistoryStore {
|
|
|
48
54
|
async beforeObserve(_scope, _keyHash) { }
|
|
49
55
|
async beforePublish(_scope) { }
|
|
50
56
|
async lockScope(conn, ...scopes) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
const ordered = [...new Set(scopes)].sort();
|
|
58
|
+
let restart = false;
|
|
59
|
+
await retryOnDeadlock(this.db.dialect, async () => {
|
|
60
|
+
if (restart) {
|
|
61
|
+
await conn.rollback().catch(() => undefined);
|
|
62
|
+
await conn.begin();
|
|
63
|
+
}
|
|
64
|
+
restart = true;
|
|
65
|
+
for (const scope of ordered) {
|
|
54
66
|
await conn.query(this.q("INSERT INTO file_history_scope (scope, created_at_ms) VALUES (?,?) ON DUPLICATE KEY UPDATE scope = scope", "INSERT INTO file_history_scope (scope, created_at_ms) VALUES ($1,$2) ON CONFLICT (scope) DO NOTHING"), [scope, Date.now()]);
|
|
55
67
|
const r = await conn.query(this.q("SELECT scope FROM file_history_scope WHERE scope = ? FOR UPDATE", "SELECT scope FROM file_history_scope WHERE scope = $1 FOR UPDATE"), [scope]);
|
|
56
|
-
|
|
68
|
+
if (!r.rows[0])
|
|
69
|
+
throw new ScopeSentinelVanishedError(`could not acquire the scope write lock for "${scope}" (sentinel row kept vanishing)`);
|
|
57
70
|
}
|
|
58
|
-
|
|
59
|
-
throw new Error(`could not acquire the scope write lock for "${scope}" (sentinel row kept vanishing)`);
|
|
60
|
-
}
|
|
71
|
+
}, (err) => err instanceof ScopeSentinelVanishedError);
|
|
61
72
|
}
|
|
62
73
|
async markObserved(scope, keyHash) {
|
|
63
74
|
const r = await this.db.query(this.q("UPDATE file_history_tracked SET observed_later = 1 WHERE scope = ? AND key_hash = ? AND observed_later = 0", "UPDATE file_history_tracked SET observed_later = TRUE WHERE scope = $1 AND key_hash = $2 AND observed_later = FALSE"), [scope, keyHash]);
|
|
@@ -585,9 +596,9 @@ export class SqlFileHistoryStore {
|
|
|
585
596
|
return 0;
|
|
586
597
|
return this.blobs.deleteBlobs(orphans);
|
|
587
598
|
}
|
|
588
|
-
async
|
|
599
|
+
async putExchangedBlob(hash, bytes) {
|
|
589
600
|
if (sha256hex(bytes) !== hash) {
|
|
590
|
-
return { ok: false, error: { code: "
|
|
601
|
+
return { ok: false, error: { code: "adopt_failed", message: `blob ${hash}: the bytes do not hash to this address — nothing stored`, permanence: "terminal" } };
|
|
591
602
|
}
|
|
592
603
|
try {
|
|
593
604
|
await this.blobs.putBlob(hash, bytes);
|
|
@@ -595,17 +606,11 @@ export class SqlFileHistoryStore {
|
|
|
595
606
|
}
|
|
596
607
|
catch (err) {
|
|
597
608
|
if (err instanceof BlobTooLargeError)
|
|
598
|
-
return { ok: false, error: { code:
|
|
609
|
+
return { ok: false, error: { code: "adopt_failed", message: err.message, permanence: "terminal", blobCap: { wallSource: err.cap.source } } };
|
|
599
610
|
const f = foldCaughtFailure(err);
|
|
600
|
-
return { ok: false, error: { code: "snapshot_failed", message: `
|
|
611
|
+
return { ok: false, error: { code: "snapshot_failed", message: `blob ${hash} write/upload failed: ${f.text}`, ...f.fault } };
|
|
601
612
|
}
|
|
602
613
|
}
|
|
603
|
-
async getBlob(hash) {
|
|
604
|
-
return this.blobs.getBlob(hash);
|
|
605
|
-
}
|
|
606
|
-
async hasBlobs(hashes) {
|
|
607
|
-
return this.blobs.hasBlobs(hashes);
|
|
608
|
-
}
|
|
609
614
|
async adoptScope(fromScope, toScope) {
|
|
610
615
|
try {
|
|
611
616
|
if (fromScope === toScope)
|