@sema-agent/server 7.142.0 → 7.144.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 +608 -0
- package/USAGE.md +63 -36
- package/dist/adoption/plan.js +22 -0
- package/dist/approval-ask-audit-store.d.ts +7 -5
- package/dist/approval-ask-audit-store.js +12 -6
- package/dist/approval-ask-machine.d.ts +15 -7
- package/dist/approval-ask-machine.js +10 -4
- package/dist/approval-card.d.ts +155 -39
- package/dist/approval-card.js +111 -31
- package/dist/approval-queue-kind.d.ts +27 -0
- package/dist/approval-queue-kind.js +6 -0
- package/dist/approval-reconciler.d.ts +8 -8
- package/dist/approval-reconciler.js +3 -3
- package/dist/audit.js +4 -2
- package/dist/boot/budget-tracing.d.ts +1 -1
- package/dist/boot/execution-env.js +1 -1
- 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 +3 -3
- package/dist/boot/reapers.js +5 -4
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +8 -8
- package/dist/boot/runner-deps.d.ts +14 -6
- package/dist/boot/runner-deps.js +6 -17
- package/dist/boot/shutdown.d.ts +9 -0
- package/dist/boot/shutdown.js +18 -2
- package/dist/boot/stage-01-config.js +2 -0
- 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 +3 -5
- package/dist/boot/stage-05-execution-env.d.ts +5 -2
- package/dist/boot/stage-06-runners.d.ts +5 -2
- package/dist/boot/stage-06-runners.js +1 -3
- package/dist/boot/stage-07-capability-layer.d.ts +5 -2
- package/dist/boot/stage-07-capability-layer.js +6 -2
- package/dist/boot/stage-08-reapers.d.ts +5 -2
- package/dist/boot/stage-09-leader.d.ts +5 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -2
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/boot/stores.d.ts +5 -2
- package/dist/boot/stores.js +7 -7
- package/dist/boot/web-admin.d.ts +5 -1
- package/dist/boot/web-admin.js +31 -19
- 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 +23 -17
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capabilities/skills.d.ts +1 -17
- package/dist/capabilities/skills.js +0 -8
- package/dist/capability-facts.d.ts +8 -8
- package/dist/capability-facts.js +4 -4
- package/dist/card-void.d.ts +6 -9
- package/dist/config-catalog.js +1 -1
- package/dist/config-d-migration.d.ts +2 -0
- 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 +5 -5
- package/dist/config.d.ts +4 -3
- package/dist/config.js +1 -1
- package/dist/delegated-session-owner.d.ts +35 -151
- package/dist/delegated-session-owner.js +1 -83
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/entry-mode-clamp-notice.d.ts +1 -1
- package/dist/env-model-table-gate.d.ts +53 -0
- package/dist/env-model-table-gate.js +53 -0
- package/dist/execution-placement.d.ts +7 -4
- package/dist/fleet/fleet-bus.d.ts +28 -52
- 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/classic-stations.d.ts +3 -1
- package/dist/hooks/classic-stations.js +2 -0
- package/dist/hooks/hook-runner.js +28 -7
- 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/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/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 +1 -1
- package/dist/http/refusal-body.d.ts +7 -3
- package/dist/http/refusal-body.js +1 -0
- package/dist/http/resume-legs.d.ts +21 -7
- package/dist/http/resume-legs.js +82 -53
- 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/routes/approvals-assistant.d.ts +9 -1
- package/dist/http/routes/approvals-assistant.js +143 -31
- package/dist/http/routes/capabilities.js +11 -2
- package/dist/http/routes/memory-policy.d.ts +2 -2
- package/dist/http/routes/memory-policy.js +1 -1
- package/dist/http/routes/notify-wake.js +23 -25
- package/dist/http/routes/rules.js +48 -13
- package/dist/http/routes/runs.js +9 -8
- package/dist/http/routes/session-memory-sync.d.ts +34 -6
- package/dist/http/routes/session-memory-sync.js +27 -10
- package/dist/http/routes/session-sync.js +13 -20
- package/dist/http/routes/sessions.d.ts +2 -2
- package/dist/http/routes/sessions.js +22 -23
- package/dist/http/routes/tasks.js +27 -49
- package/dist/http/server.d.ts +13 -7
- package/dist/http/server.js +2 -2
- package/dist/http/stored-body-replay.js +15 -4
- package/dist/http/wire-types.d.ts +17 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lane-home-probe.d.ts +15 -0
- package/dist/lane-home-probe.js +4 -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-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.js +14 -3
- package/dist/memory-sync.d.ts +20 -4
- package/dist/memory-sync.js +23 -4
- package/dist/model-plane-face.d.ts +13 -7
- package/dist/model-plane-face.js +2 -1
- package/dist/observability/corrupt-read-seat.d.ts +5 -9
- package/dist/observability/corrupt-read-seat.js +1 -2
- package/dist/observability/fail-open.d.ts +30 -10
- package/dist/observability/fail-open.js +27 -10
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +6 -2
- 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.d.ts +6 -7
- package/dist/parked-revive-surface.js +1 -1
- package/dist/peer-inbound-face.d.ts +122 -0
- package/dist/peer-inbound-face.js +123 -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.js +18 -6
- package/dist/plugins/approval-ask-store-memory.js +8 -6
- package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
- package/dist/plugins/approval-ask-store-sql.js +30 -11
- package/dist/plugins/caching-session-store.d.ts +0 -3
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +45 -38
- package/dist/plugins/checkpoint-store-sql.js +108 -45
- 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 +29 -0
- package/dist/plugins/file-run-store.js +97 -8
- package/dist/plugins/fork-routing-session-store.d.ts +8 -1
- package/dist/plugins/fork-routing-session-store.js +6 -0
- 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 +6 -8
- package/dist/plugins/local-checkpoint-store.js +5 -4
- package/dist/plugins/local-session-store.d.ts +69 -30
- package/dist/plugins/local-session-store.js +133 -57
- package/dist/plugins/local-session-window.d.ts +4 -2
- 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-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 +44 -14
- package/dist/plugins/one-time-migrations.js +118 -15
- 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/pg-session-storage.d.ts +1 -9
- package/dist/plugins/pg-session-storage.js +2 -19
- package/dist/plugins/remote-env-device.d.ts +8 -11
- package/dist/plugins/remote-env-device.js +6 -3
- package/dist/plugins/remote-env-host.d.ts +8 -4
- package/dist/plugins/remote-env-host.js +7 -9
- package/dist/plugins/remote-env-k8s.d.ts +37 -24
- package/dist/plugins/remote-env-k8s.js +50 -17
- package/dist/plugins/remote-shell.js +12 -2
- package/dist/plugins/retention-store-sql.d.ts +2 -2
- package/dist/plugins/sandbox-read-floor.d.ts +5 -7
- package/dist/plugins/sandbox-read-floor.js +2 -7
- package/dist/plugins/schema-consistency.d.ts +5 -1
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-ownership.d.ts +9 -0
- package/dist/plugins/session-ownership.js +6 -0
- package/dist/plugins/session-store.d.ts +13 -16
- package/dist/plugins/session-store.js +9 -10
- package/dist/plugins/sql-errors.d.ts +10 -1
- package/dist/plugins/sql-errors.js +13 -0
- package/dist/plugins/sql-json-column.d.ts +24 -2
- package/dist/plugins/sql-json-column.js +38 -3
- package/dist/plugins/store-backend.d.ts +23 -28
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +29 -5
- package/dist/plugins/store-contracts.js +4 -1
- package/dist/plugins/task-attachment-store.d.ts +2 -2
- package/dist/plugins/task-attachment-store.js +37 -52
- package/dist/plugins/tidb-session-store.d.ts +2 -14
- package/dist/plugins/tidb-session-store.js +2 -20
- 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 +1 -1
- 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 -6
- package/dist/runs.d.ts +12 -8
- package/dist/runs.js +10 -7
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/security.d.ts +0 -4
- package/dist/server-secret-env.d.ts +4 -0
- package/dist/session-error-wire.d.ts +34 -23
- package/dist/session-error-wire.js +8 -3
- package/dist/session-incarnation.d.ts +3 -3
- package/dist/session-notify-door.d.ts +106 -0
- package/dist/session-notify-door.js +97 -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 +11 -8
- package/dist/task-settings.js +31 -11
- package/dist/tool-approval.d.ts +55 -35
- package/dist/tool-approval.js +112 -74
- package/dist/trace/core-keyset-guard.d.ts +12 -9
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +4 -2
- package/dist/trace/exit-seat.d.ts +7 -1
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +2 -2
- package/dist/trace/ledger-sink.js +12 -4
- package/dist/trace/model-segment.d.ts +74 -25
- package/dist/trace/model-segment.js +141 -32
- package/dist/trace/project.d.ts +15 -6
- package/dist/trace/project.js +12 -4
- package/dist/trace/redact.d.ts +72 -9
- package/dist/trace/redact.js +1509 -471
- package/dist/trace/self-minted-wire.d.ts +598 -47
- package/dist/trace/self-minted-wire.js +190 -20
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- 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 +90 -10
- 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.143.0.json +1167 -0
- package/wire-changes/server@7.144.0.json +291 -0
- package/dist/session-records-skipped-notice.d.ts +0 -21
- package/dist/session-records-skipped-notice.js +0 -27
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { parkedApprovalCard } from "../approval-content-kind.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, judgeCheckpointIndexCells } from "./checkpoint-index-columns.js";
|
|
4
|
-
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf,
|
|
4
|
+
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, judgeCheckpointRow, CONTRACT_KIT_ENGINE_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
5
5
|
import { redactDeep, redactSecrets } from "../trace/redact.js";
|
|
6
|
-
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, countKindlessBatchOffers,
|
|
7
|
-
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
6
|
+
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, countKindlessBatchOffers, parkRowFacts, readRuleOfferList, skillCardFacts } from "../approval-card.js";
|
|
7
|
+
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, jsonColumnTextSha256, readJsonArrayColumn, readJsonColumnTextSha256, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
8
8
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
9
|
import { durableRowIdentity, withoutCheckpointToken } from "../checkpoint-identity.js";
|
|
10
10
|
import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
|
|
@@ -21,6 +21,35 @@ export const TERMINAL_GRACE_MS = 3_600_000;
|
|
|
21
21
|
export function tokenFingerprint(token) {
|
|
22
22
|
return "sha256:" + createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
23
23
|
}
|
|
24
|
+
const STORED_ROW_MEMO_CAP = 8192;
|
|
25
|
+
class StoredRowProjectionMemo {
|
|
26
|
+
rows = new Map();
|
|
27
|
+
keyOf(sha256) {
|
|
28
|
+
return `${sha256}@${CONTRACT_KIT_ENGINE_VERSION}`;
|
|
29
|
+
}
|
|
30
|
+
get(sha256) {
|
|
31
|
+
const key = this.keyOf(sha256);
|
|
32
|
+
const hit = this.rows.get(key);
|
|
33
|
+
if (hit !== undefined) {
|
|
34
|
+
this.rows.delete(key);
|
|
35
|
+
this.rows.set(key, hit);
|
|
36
|
+
}
|
|
37
|
+
return hit;
|
|
38
|
+
}
|
|
39
|
+
set(sha256, projection) {
|
|
40
|
+
const key = this.keyOf(sha256);
|
|
41
|
+
this.rows.delete(key);
|
|
42
|
+
this.rows.set(key, projection);
|
|
43
|
+
while (this.rows.size > STORED_ROW_MEMO_CAP) {
|
|
44
|
+
const oldest = this.rows.keys().next().value;
|
|
45
|
+
if (oldest === undefined)
|
|
46
|
+
break;
|
|
47
|
+
this.rows.delete(oldest);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const STORED_ROW_READ_BATCH = 100;
|
|
52
|
+
const CHECKPOINT_SHA256_AS = "checkpoint_sha256";
|
|
24
53
|
const GATE_KIND_IS_LIVE_RUN_PARK = {
|
|
25
54
|
human: true,
|
|
26
55
|
irreversible_ask: true,
|
|
@@ -236,15 +265,17 @@ export function approvalDecisionOfWinner(winner, boundCallId) {
|
|
|
236
265
|
}
|
|
237
266
|
}
|
|
238
267
|
}
|
|
239
|
-
|
|
268
|
+
function pendingActionToolCallId(body) {
|
|
240
269
|
if (typeof body !== "object" || body === null)
|
|
241
270
|
return { readable: false };
|
|
242
|
-
|
|
271
|
+
if ("sourceTaskId" in body)
|
|
272
|
+
return { readable: false };
|
|
273
|
+
const rawSource = "sourceSessionId" in body ? body.sourceSessionId : null;
|
|
243
274
|
if (rawSource !== null && rawSource !== undefined && typeof rawSource !== "string")
|
|
244
275
|
return { readable: false };
|
|
245
|
-
const
|
|
276
|
+
const sourceSessionId = typeof rawSource === "string" ? rawSource : null;
|
|
246
277
|
if (!("pendingAction" in body))
|
|
247
|
-
return { readable: true, toolCallId: null, boundInputHash: null, kind: null,
|
|
278
|
+
return { readable: true, toolCallId: null, boundInputHash: null, kind: null, sourceSessionId };
|
|
248
279
|
const pa = body.pendingAction;
|
|
249
280
|
if (typeof pa !== "object" || pa === null)
|
|
250
281
|
return { readable: false };
|
|
@@ -252,7 +283,7 @@ export function pendingActionToolCallId(body) {
|
|
|
252
283
|
if (kind !== null && typeof kind !== "string")
|
|
253
284
|
return { readable: false };
|
|
254
285
|
if (!("toolCallId" in pa))
|
|
255
|
-
return { readable: true, toolCallId: null, boundInputHash: null, kind,
|
|
286
|
+
return { readable: true, toolCallId: null, boundInputHash: null, kind, sourceSessionId };
|
|
256
287
|
const id = pa.toolCallId;
|
|
257
288
|
if (typeof id !== "string")
|
|
258
289
|
return { readable: false };
|
|
@@ -261,19 +292,24 @@ export function pendingActionToolCallId(body) {
|
|
|
261
292
|
const bih = "boundInputHash" in pa ? pa.boundInputHash : null;
|
|
262
293
|
if (bih !== null && typeof bih !== "string")
|
|
263
294
|
return { readable: false };
|
|
264
|
-
return { readable: true, toolCallId: id, boundInputHash: bih, kind,
|
|
295
|
+
return { readable: true, toolCallId: id, boundInputHash: bih, kind, sourceSessionId };
|
|
265
296
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
297
|
+
export function candidateIdentityOf(cp) {
|
|
298
|
+
if (judgeCheckpointRow(cp).kind === "lacking")
|
|
299
|
+
return { readable: false };
|
|
300
|
+
return pendingActionToolCallId(cp);
|
|
301
|
+
}
|
|
302
|
+
function candidateIdentityOfColumn(cell, where) {
|
|
303
|
+
let cp;
|
|
268
304
|
try {
|
|
269
|
-
|
|
305
|
+
cp = decodeCheckpointColumn(cell, where);
|
|
270
306
|
}
|
|
271
307
|
catch (e) {
|
|
272
308
|
if (!isJsonColumnBodyProblem(e))
|
|
273
309
|
throw e;
|
|
274
310
|
return { readable: false };
|
|
275
311
|
}
|
|
276
|
-
return
|
|
312
|
+
return candidateIdentityOf(cp);
|
|
277
313
|
}
|
|
278
314
|
function readActorAssertion(v) {
|
|
279
315
|
if (typeof v !== "object" || v === null)
|
|
@@ -344,6 +380,7 @@ export class SqlCheckpointStore {
|
|
|
344
380
|
fidelity = "json";
|
|
345
381
|
execution = { outcome: true };
|
|
346
382
|
claims;
|
|
383
|
+
storedRowMemo = new StoredRowProjectionMemo();
|
|
347
384
|
constructor(db, logger, opts = {}) {
|
|
348
385
|
this.db = db;
|
|
349
386
|
this.logger = logger;
|
|
@@ -431,16 +468,12 @@ export class SqlCheckpointStore {
|
|
|
431
468
|
}
|
|
432
469
|
}
|
|
433
470
|
async get(token) {
|
|
434
|
-
const { rows } = await this.db.query(this.q(`SELECT
|
|
435
|
-
`${jsonColumnText("mysql", "pending_steer_queue")}, ${jsonColumnText("mysql", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT
|
|
471
|
+
const { rows } = await this.db.query(this.q(`SELECT status, ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnText("mysql", "outcome")}, reopen_reason, rev, ` +
|
|
472
|
+
`${jsonColumnText("mysql", "pending_steer_queue")}, ${jsonColumnText("mysql", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT status, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnText("pg", "outcome")}, reopen_reason, rev, ` +
|
|
436
473
|
`${jsonColumnText("pg", "pending_steer_queue")}, ${jsonColumnText("pg", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = $1`), [token]);
|
|
437
474
|
const r = rows[0];
|
|
438
475
|
if (!r)
|
|
439
476
|
return null;
|
|
440
|
-
const version = Number(r.version);
|
|
441
|
-
if (version > MAX_SUPPORTED_CHECKPOINT_VERSION) {
|
|
442
|
-
throw new Error(`checkpoint format version ${version} is newer than this build supports (max ${MAX_SUPPORTED_CHECKPOINT_VERSION}) — upgrade the service to resume it`);
|
|
443
|
-
}
|
|
444
477
|
const cp = refusing("a checkpoint nobody can resume", () => decodeCheckpointColumn(r.checkpoint, `for token ${tokenFingerprint(token)}`));
|
|
445
478
|
cp.status = r.status;
|
|
446
479
|
const outcomeRaw = refusing("a decision record nobody can read", () => decodeOutcomeColumn(r.outcome, `for token ${tokenFingerprint(token)}`));
|
|
@@ -466,7 +499,6 @@ export class SqlCheckpointStore {
|
|
|
466
499
|
this.logger?.info?.("checkpoint_get", {
|
|
467
500
|
tokenFp: tokenFingerprint(token),
|
|
468
501
|
status: cp.status,
|
|
469
|
-
version,
|
|
470
502
|
hasWorkspaceHandle: !!cp.state?.workspaceHandle,
|
|
471
503
|
snapshotId: cp.state?.workspaceHandle?.snapshotId,
|
|
472
504
|
});
|
|
@@ -556,7 +588,8 @@ export class SqlCheckpointStore {
|
|
|
556
588
|
const d = this.db.dialect;
|
|
557
589
|
const base = `SELECT c.session_id, c.token, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
|
|
558
590
|
`${jsonColumnText(d, "risk_descriptor")}, ${jsonColumnText(d, "rule_suggestions")}, c.has_bidi_controls, c.created_at_ms, c.deadline, c.gate_kind, c.approval_card, ta.task_id, ` +
|
|
559
|
-
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)} ` +
|
|
591
|
+
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)}, ` +
|
|
592
|
+
`${jsonColumnTextSha256(d, "checkpoint", CHECKPOINT_SHA256_AS)} ` +
|
|
560
593
|
"FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
|
|
561
594
|
const { rows } = scope
|
|
562
595
|
? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
|
|
@@ -590,43 +623,77 @@ export class SqlCheckpointStore {
|
|
|
590
623
|
...(approvalCard !== undefined ? { approvalCard } : {}),
|
|
591
624
|
...parkRowFacts(pendingAction),
|
|
592
625
|
};
|
|
593
|
-
return [{ row, token: String(r.token),
|
|
626
|
+
return [{ row, token: String(r.token), sha256: readJsonColumnTextSha256(r[CHECKPOINT_SHA256_AS], "checkpoint.checkpoint_sha256 (operator queue row)") }];
|
|
627
|
+
});
|
|
628
|
+
const projections = await this.storedRowProjections(entries);
|
|
629
|
+
const out = entries.map((e) => {
|
|
630
|
+
const p = projections.get(e.token);
|
|
631
|
+
return p === undefined ? e.row : { ...e.row, ...p.skillCardFacts, rowVerdict: p.rowVerdict };
|
|
594
632
|
});
|
|
595
|
-
const out = [];
|
|
596
|
-
for (const e of entries)
|
|
597
|
-
out.push(e.readSkillCard ? { ...e.row, ...(await this.readParkedSkillCardFacts(e.token)) } : e.row);
|
|
598
633
|
return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
|
|
599
634
|
}
|
|
600
|
-
async
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
635
|
+
async storedRowProjections(rows) {
|
|
636
|
+
const out = new Map();
|
|
637
|
+
const misses = [];
|
|
638
|
+
const expected = new Map();
|
|
639
|
+
for (const { token, sha256 } of rows) {
|
|
640
|
+
expected.set(token, sha256);
|
|
641
|
+
const hit = this.storedRowMemo.get(sha256);
|
|
642
|
+
if (hit === undefined)
|
|
643
|
+
misses.push(token);
|
|
644
|
+
else
|
|
645
|
+
out.set(token, hit);
|
|
646
|
+
}
|
|
647
|
+
for (let i = 0; i < misses.length; i += STORED_ROW_READ_BATCH) {
|
|
648
|
+
const batch = misses.slice(i, i + STORED_ROW_READ_BATCH);
|
|
649
|
+
const { rows: read } = await this.db.query(this.q(`SELECT token, ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnTextSha256("mysql", "checkpoint", CHECKPOINT_SHA256_AS)} FROM checkpoint ` +
|
|
650
|
+
`WHERE status = 'pending' AND token IN (${batch.map(() => "?").join(", ")})`, `SELECT token, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnTextSha256("pg", "checkpoint", CHECKPOINT_SHA256_AS)} FROM checkpoint ` +
|
|
651
|
+
`WHERE status = 'pending' AND token IN (${batch.map((_, j) => `$${j + 1}`).join(", ")})`), batch);
|
|
652
|
+
for (const r of read) {
|
|
653
|
+
const token = String(r.token);
|
|
654
|
+
const sha256 = readJsonColumnTextSha256(r[CHECKPOINT_SHA256_AS], "checkpoint.checkpoint_sha256 (whole-row read)");
|
|
655
|
+
const projection = this.projectStoredRow(r, sha256);
|
|
656
|
+
if (projection !== undefined && sha256 === expected.get(token))
|
|
657
|
+
out.set(token, projection);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return out;
|
|
661
|
+
}
|
|
662
|
+
projectStoredRow(r, sha256) {
|
|
605
663
|
let cp;
|
|
606
664
|
try {
|
|
607
|
-
cp = decodeCheckpointColumn(r.checkpoint, "(operator queue row,
|
|
665
|
+
cp = decodeCheckpointColumn(r.checkpoint, "(operator queue row, whole-row read)");
|
|
608
666
|
}
|
|
609
667
|
catch (e) {
|
|
610
668
|
if (!isJsonColumnBodyProblem(e))
|
|
611
669
|
throw e;
|
|
612
670
|
recordFailOpen("server.approvals.park-row-blob-unreadable");
|
|
613
|
-
return
|
|
671
|
+
return undefined;
|
|
614
672
|
}
|
|
673
|
+
let summary;
|
|
615
674
|
try {
|
|
616
|
-
|
|
675
|
+
summary = summarizeCheckpoint(cp);
|
|
617
676
|
}
|
|
618
677
|
catch {
|
|
619
678
|
recordFailOpen("server.approvals.park-row-blob-unreadable");
|
|
620
|
-
return {};
|
|
679
|
+
return { rowVerdict: judgeCheckpointRow(cp), skillCardFacts: {} };
|
|
621
680
|
}
|
|
681
|
+
const projection = { rowVerdict: summary.rowVerdict, skillCardFacts: skillCardFacts(summary) };
|
|
682
|
+
this.storedRowMemo.set(sha256, projection);
|
|
683
|
+
return projection;
|
|
622
684
|
}
|
|
623
685
|
async listByScope(scope) {
|
|
624
|
-
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at_ms ASC LIMIT 500`, `SELECT ${jsonColumnText("pg", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at_ms ASC LIMIT 500`), [scope]);
|
|
686
|
+
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnTextSha256("mysql", "checkpoint", CHECKPOINT_SHA256_AS)}, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at_ms ASC LIMIT 500`, `SELECT ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnTextSha256("pg", "checkpoint", CHECKPOINT_SHA256_AS)}, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at_ms ASC LIMIT 500`), [scope]);
|
|
625
687
|
const out = [];
|
|
626
688
|
for (const r of rows) {
|
|
689
|
+
const sha256 = readJsonColumnTextSha256(r[CHECKPOINT_SHA256_AS], `checkpoint.checkpoint_sha256 in the ${scope} scheduler view`);
|
|
627
690
|
const cp = refusing("a checkpoint nobody can resume", () => decodeCheckpointColumn(r.checkpoint, `in the ${scope} scheduler view`));
|
|
628
691
|
cp.status = r.status;
|
|
629
|
-
|
|
692
|
+
const memo = this.storedRowMemo.get(sha256);
|
|
693
|
+
const summary = summarizeCheckpoint(cp, (sentence) => recordFailOpen("server.checkpoint.inbox-risk-axes-damaged", withoutCheckpointToken(sentence, cp.token)), memo?.rowVerdict);
|
|
694
|
+
if (memo === undefined)
|
|
695
|
+
this.storedRowMemo.set(sha256, { rowVerdict: summary.rowVerdict, skillCardFacts: skillCardFacts(summary) });
|
|
696
|
+
out.push(summary);
|
|
630
697
|
}
|
|
631
698
|
return out;
|
|
632
699
|
}
|
|
@@ -640,8 +707,8 @@ export class SqlCheckpointStore {
|
|
|
640
707
|
return rows[0] ? String(rows[0].token) : null;
|
|
641
708
|
}
|
|
642
709
|
async findCheckpointCandidatesForAsk(scope, sessionId, toolCallId, sinceMs) {
|
|
643
|
-
const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash,
|
|
644
|
-
"WHERE scope=? AND session_id=? AND created_at_ms>=? AND (tool_call_id=? OR tool_call_id IS NULL) ORDER BY created_at_ms ASC", `SELECT token, status, created_at_ms, tool_call_id, bound_input_hash,
|
|
710
|
+
const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, ${jsonColumnText("mysql", "checkpoint")} FROM checkpoint ` +
|
|
711
|
+
"WHERE scope=? AND session_id=? AND created_at_ms>=? AND (tool_call_id=? OR tool_call_id IS NULL) ORDER BY created_at_ms ASC", `SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, ${jsonColumnText("pg", "checkpoint")} FROM checkpoint ` +
|
|
645
712
|
"WHERE scope=$1 AND session_id=$2 AND created_at_ms>=$3 AND (tool_call_id=$4 OR tool_call_id IS NULL) ORDER BY created_at_ms ASC"), [scope, sessionId, sinceMs, toolCallId]);
|
|
646
713
|
const out = [];
|
|
647
714
|
for (const r of rows) {
|
|
@@ -650,13 +717,9 @@ export class SqlCheckpointStore {
|
|
|
650
717
|
status: String(r.status),
|
|
651
718
|
createdAtMs: Number(r.created_at_ms),
|
|
652
719
|
boundInputHash: r.bound_input_hash == null ? null : String(r.bound_input_hash),
|
|
653
|
-
|
|
720
|
+
sourceSessionId: null,
|
|
654
721
|
};
|
|
655
|
-
|
|
656
|
-
out.push({ ...base, boundCallId: null, unparseable: true });
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
const derived = pendingActionOfColumn(r.checkpoint, `for token ${tokenFingerprint(String(r.token))}`);
|
|
722
|
+
const derived = candidateIdentityOfColumn(r.checkpoint, `for token ${tokenFingerprint(String(r.token))}`);
|
|
660
723
|
if (!derived.readable) {
|
|
661
724
|
out.push({ ...base, boundCallId: null, unparseable: true });
|
|
662
725
|
continue;
|
|
@@ -673,7 +736,7 @@ export class SqlCheckpointStore {
|
|
|
673
736
|
const effectiveHash = base.boundInputHash ?? derived.boundInputHash;
|
|
674
737
|
const effective = col ?? derived.toolCallId;
|
|
675
738
|
if (effective === toolCallId)
|
|
676
|
-
out.push({ ...base, boundInputHash: effectiveHash,
|
|
739
|
+
out.push({ ...base, boundInputHash: effectiveHash, sourceSessionId: derived.sourceSessionId, boundCallId: effective });
|
|
677
740
|
}
|
|
678
741
|
return out;
|
|
679
742
|
}
|
|
@@ -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)
|
|
@@ -41,6 +41,14 @@ export declare class FileRunStore implements InProcessClaimFence {
|
|
|
41
41
|
private readonly events;
|
|
42
42
|
/** taskId → the open append log (one O_APPEND fd per run, lazily opened, held for lifetime). */
|
|
43
43
|
private readonly eventLogs;
|
|
44
|
+
/** `<root>/run-blobs/` — one section-blob ledger per session (plane 4 of the header). */
|
|
45
|
+
private readonly blobsDir;
|
|
46
|
+
/** sessionId → (digest → section body): the index of plane 4 (hydrated per session that has run rows). */
|
|
47
|
+
private readonly sectionBlobs;
|
|
48
|
+
/** sessionId → the open section-blob ledger (lazily opened, held until the session is deleted / dispose). */
|
|
49
|
+
private readonly blobLogs;
|
|
50
|
+
/** `(taskId, seq)` of manifest rows already reported as unresolvable (one report per row per store instance = per process). */
|
|
51
|
+
private readonly unresolvedReported;
|
|
44
52
|
/** [ref] the checkpoint-table stand-in (see memory-run-store.ts {@link RunStoreCheckpointProbe} — the full
|
|
45
53
|
* rationale lives on the interface); absent ⇒ the suspended-run reapers stay honest NO-OPs. */
|
|
46
54
|
private checkpointProbe?;
|
|
@@ -61,6 +69,11 @@ export declare class FileRunStore implements InProcessClaimFence {
|
|
|
61
69
|
private runJsonPath;
|
|
62
70
|
private eventsPath;
|
|
63
71
|
private activePath;
|
|
72
|
+
/** A session's section-blob ledger. The file name is core's `sanitizeScope` form (readable prefix + sha256 of the id), not the
|
|
73
|
+
* bare id: two ids that differ only in letter case are one file on a case-insensitive file system (macOS by default), and a
|
|
74
|
+
* deleted session must never take another session's blocks with it. `undefined` only for a run row read back from a damaged
|
|
75
|
+
* run.json whose session id is not a string — such a row has no ledger (its manifest rows are stored whole). */
|
|
76
|
+
private blobLedgerPath;
|
|
64
77
|
private spaceCommitPath;
|
|
65
78
|
/** One space's commit record, or `undefined` (no file). A record that is not this store's shape is a loud read failure
|
|
66
79
|
* (the reader — the next run's connect — refuses; it never guesses that no commit is in flight). */
|
|
@@ -129,6 +142,22 @@ export declare class FileRunStore implements InProcessClaimFence {
|
|
|
129
142
|
isPreemptRequested(taskId: string, owner: string | null): Promise<boolean>;
|
|
130
143
|
heartbeat(taskId: string, owner: string | null): Promise<void>;
|
|
131
144
|
appendEvent(taskId: string, seq: number, type: LedgerEventType, data: unknown): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* 写口里第四个盘面的那一段(与事件行的追加在**同一同步调用**里,块在前):帧经编解码拆成引用形 + 段体,本会话块账本里还没有的段体先追加
|
|
147
|
+
* (一批一次 fsync:事件行本身 fsync:false,而一只块被同会话之后每一条清单行引用 —— 丢一行丢一帧,丢一块丢一串帧),再把引用形交回给
|
|
148
|
+
* 事件行的追加。块追加抛 ⇒ 原样上抛:事件行不写,不退回整帧(与事件行自己写失败同一条路,汇点的串行写口留 seq 洞)。
|
|
149
|
+
* 这条 run 没有行(会话不详)⇒ 没有块账本可挂 ⇒ 帧原样存:和类型里「没有一段用引用」的合法实例,读口原样交出。
|
|
150
|
+
*/
|
|
151
|
+
private storeSections;
|
|
152
|
+
private blobLogFor;
|
|
153
|
+
/** Drop a session's section-blob ledger: the file first, then the open fd and the index. The file removal is NOT best-effort
|
|
154
|
+
* (unlike {@link dropRun}'s directory): it is the delete's first destructive step, so a removal that fails throws before
|
|
155
|
+
* anything else changed — the session delete answers an error and a retry starts from the same state, instead of leaving
|
|
156
|
+
* an index that no longer matches the disk. (`AppendLog.close` never throws: it swallows its own `closeSync`.) */
|
|
157
|
+
private dropSectionBlobs;
|
|
158
|
+
/** 读口的悬空引用臂:清单行有一段装不回 ⇒ 这一帧这一次不交出(永不交出缺段的清单),每 `(taskId, seq)` 每只店实例(= 每进程)留一次 F 类痕;
|
|
159
|
+
* 帧仍在索引里,`maxSeq` / `retainedFrom` 照旧按存位算(下一个写者的 seq 不会撞上它)。 */
|
|
160
|
+
private reportUnresolved;
|
|
132
161
|
maxSeq(taskId: string): Promise<number>;
|
|
133
162
|
retainedFrom(taskId: string): Promise<number>;
|
|
134
163
|
getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
|