@sema-agent/server 7.151.0 → 7.152.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 +266 -0
- package/README.md +15 -3
- package/README.zh-CN.md +10 -2
- package/USAGE.md +93 -64
- package/deploy/sema-up/README.md +5 -4
- package/deploy/sema-up/cluster-up.sh +6 -6
- package/deploy/sema-up/kube-up.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +36 -8
- package/deploy/sema-up/machines.example.yaml +3 -1
- package/deploy/sema-up/sema-up.sh +22 -16
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-ask-machine.d.ts +4 -10
- package/dist/approval-ask-retention.d.ts +1 -1
- package/dist/approval-card.d.ts +10 -12
- package/dist/approval-gate-currency.d.ts +8 -8
- package/dist/approval-gate-currency.js +3 -4
- package/dist/approval-hmac.d.ts +17 -1
- package/dist/approval-hmac.js +7 -1
- package/dist/approval-queue-kind.d.ts +1 -1
- package/dist/approval.d.ts +2 -2
- package/dist/approval.js +2 -2
- package/dist/bind-host.d.ts +1 -3
- package/dist/bind-host.js +2 -3
- package/dist/boot/coordinators.d.ts +1 -1
- package/dist/boot/coordinators.js +9 -10
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/execution-env.d.ts +1 -0
- package/dist/boot/execution-env.js +31 -5
- package/dist/boot/leader.d.ts +31 -30
- package/dist/boot/leader.js +38 -28
- package/dist/boot/leg-path-adjudication.d.ts +40 -0
- package/dist/boot/leg-path-adjudication.js +18 -0
- package/dist/boot/parked-revive-gate.js +3 -15
- package/dist/boot/reapers.d.ts +1 -1
- package/dist/boot/reapers.js +1 -1
- package/dist/boot/resolve-spec.js +12 -26
- package/dist/boot/runner-deps.d.ts +39 -35
- package/dist/boot/runner-deps.js +92 -89
- package/dist/boot/shutdown.d.ts +6 -0
- package/dist/boot/shutdown.js +10 -4
- package/dist/boot/stage-02-web-admin-pull.js +4 -5
- package/dist/boot/stage-05-execution-env.js +2 -1
- package/dist/boot/stage-06-runners.d.ts +2 -0
- package/dist/boot/stage-06-runners.js +10 -4
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +8 -0
- package/dist/boot/stage-10-http-server.js +11 -15
- package/dist/boot/stores.js +7 -4
- package/dist/boot/web-admin.js +2 -7
- package/dist/capabilities/approvals-push.d.ts +3 -3
- package/dist/capabilities/scenarios.d.ts +28 -13
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capability-facts.d.ts +22 -20
- package/dist/capability-facts.js +18 -34
- package/dist/card-rule-offers.d.ts +32 -0
- package/dist/card-rule-offers.js +130 -0
- package/dist/config-catalog.js +12 -11
- package/dist/config-dir-trust.js +4 -2
- package/dist/config-invariants.d.ts +18 -7
- package/dist/config-invariants.js +51 -8
- package/dist/config-lkg.d.ts +5 -19
- package/dist/config-lkg.js +8 -62
- package/dist/config-provider.d.ts +1 -1
- package/dist/config-root-migration.d.ts +2 -2
- package/dist/config-types.d.ts +28 -38
- package/dist/config.d.ts +7 -5
- package/dist/config.js +55 -16
- package/dist/control-socket.d.ts +51 -0
- package/dist/control-socket.js +115 -0
- package/dist/deployment-governance.d.ts +2 -1
- package/dist/device-ws-hub.d.ts +7 -0
- package/dist/device-ws-hub.js +7 -1
- package/dist/elicitation.js +2 -2
- package/dist/entry-problems.d.ts +40 -3
- package/dist/entry-problems.js +3 -1
- package/dist/execution-env-refusal.d.ts +8 -2
- package/dist/execution-env-refusal.js +3 -0
- package/dist/fleet/engine-signal.d.ts +12 -29
- package/dist/fleet/engine-signal.js +1 -7
- package/dist/fleet-client.js +2 -5
- package/dist/gate-settlement.d.ts +1 -8
- package/dist/gate-settlement.js +0 -4
- package/dist/history-anchor.js +1 -1
- package/dist/hitl-delivery.d.ts +2 -3
- package/dist/hitl-delivery.js +0 -2
- package/dist/hooks/hook-llm.js +4 -2
- package/dist/hooks/hook-runner.d.ts +3 -2
- package/dist/hooks/hook-runner.js +16 -1
- package/dist/host-decision.d.ts +11 -11
- package/dist/http/active-run-conflict.d.ts +35 -2
- package/dist/http/active-run-conflict.js +48 -19
- package/dist/http/approval-sweeps.d.ts +1 -1
- package/dist/http/approval-sweeps.js +5 -2
- package/dist/http/checkpoint-ctx.d.ts +2 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/dispatch.d.ts +6 -2
- package/dist/http/dispatch.js +25 -5
- package/dist/http/durable-run-leg.js +0 -1
- package/dist/http/health-reader.d.ts +16 -9
- package/dist/http/health-reader.js +2 -1
- package/dist/http/health-self-report.js +1 -1
- package/dist/http/owner-access.d.ts +2 -3
- package/dist/http/owner-access.js +3 -4
- package/dist/http/principal-gate.d.ts +49 -21
- package/dist/http/principal-gate.js +61 -11
- package/dist/http/request-channel.d.ts +23 -0
- package/dist/http/request-channel.js +9 -0
- package/dist/http/request-guards.js +17 -2
- package/dist/http/resume-ctx-read.js +20 -3
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +111 -41
- package/dist/http/route-ctx.d.ts +73 -7
- package/dist/http/route-ctx.js +17 -0
- package/dist/http/route-table.d.ts +4 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-config-refresh.js +3 -3
- package/dist/http/routes/admin-drain.d.ts +3 -0
- package/dist/http/routes/admin-drain.js +7 -7
- package/dist/http/routes/admin-memory-optout.js +3 -3
- package/dist/http/routes/adoption.js +3 -5
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +95 -48
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +30 -5
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +19 -7
- package/dist/http/routes/images.js +10 -10
- package/dist/http/routes/memory-bundle.js +3 -3
- package/dist/http/routes/memory-compliance.js +3 -3
- package/dist/http/routes/memory-consolidation.js +2 -2
- package/dist/http/routes/memory-origin.js +3 -3
- package/dist/http/routes/memory-policy.js +2 -2
- package/dist/http/routes/observability.js +3 -3
- package/dist/http/routes/retention-ops.js +3 -3
- package/dist/http/routes/rules.js +5 -5
- package/dist/http/routes/run-permission-mode.d.ts +2 -1
- package/dist/http/routes/run-permission-mode.js +2 -2
- package/dist/http/routes/runs.d.ts +4 -9
- package/dist/http/routes/runs.js +73 -33
- package/dist/http/routes/session-sync.d.ts +18 -6
- package/dist/http/routes/session-sync.js +38 -36
- package/dist/http/routes/sessions.js +20 -9
- package/dist/http/routes/tasks.js +28 -37
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/server.d.ts +9 -3
- package/dist/http/server.js +15 -3
- package/dist/http/submit-ctx.d.ts +1 -1
- package/dist/http/submit-ctx.js +2 -0
- package/dist/human-facing-args.d.ts +33 -0
- package/dist/human-facing-args.js +33 -0
- package/dist/index.d.ts +1 -1
- package/dist/k8s-api-credentials.d.ts +29 -0
- package/dist/k8s-api-credentials.js +25 -0
- package/dist/lane-home-probe.d.ts +1 -1
- package/dist/lane-home-probe.js +2 -1
- package/dist/leader/diffup.d.ts +19 -0
- package/dist/leader/diffup.js +7 -0
- package/dist/leader/leader.d.ts +114 -10
- package/dist/leader/leader.js +69 -5
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +2 -1
- package/dist/leader/run-entry.js +1 -1
- package/dist/leader/wire.d.ts +42 -132
- package/dist/leader/wire.js +81 -67
- package/dist/memory-scope.d.ts +20 -9
- package/dist/memory-scope.js +15 -3
- package/dist/model-entry-headers.d.ts +9 -10
- package/dist/model-entry-headers.js +0 -4
- package/dist/model-entry-refusal.d.ts +7 -4
- package/dist/model-entry-refusal.js +11 -5
- package/dist/model-plane-face.d.ts +1 -1
- package/dist/model-plane-face.js +3 -1
- package/dist/model-route-endpoint.d.ts +13 -7
- package/dist/model-route-endpoint.js +14 -11
- package/dist/model-select.d.ts +22 -0
- package/dist/model-select.js +9 -0
- package/dist/model-unreachable-notice.d.ts +11 -0
- package/dist/model-unreachable-notice.js +4 -4
- package/dist/object-store-buckets.d.ts +640 -6
- package/dist/object-store-buckets.js +166 -0
- package/dist/observability/fail-open.d.ts +20 -16
- package/dist/observability/fail-open.js +20 -16
- package/dist/observability/logger.js +3 -1
- package/dist/operator-roster.d.ts +68 -0
- package/dist/operator-roster.js +32 -0
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/outbound-dispatcher.d.ts +71 -8
- package/dist/outbound-dispatcher.js +48 -7
- package/dist/owner-faces.d.ts +1 -1
- package/dist/parked-decide.d.ts +2 -2
- package/dist/permission-mode-admission.d.ts +59 -26
- package/dist/permission-mode-admission.js +37 -10
- package/dist/plugins/approval-ask-store-file.d.ts +4 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
- package/dist/plugins/approval-ask-store-memory.js +7 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
- package/dist/plugins/approval-ask-store-sql.js +12 -1
- package/dist/plugins/blob-backend.d.ts +83 -7
- package/dist/plugins/blob-backend.js +57 -6
- package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
- package/dist/plugins/checkpoint-index-columns.js +3 -6
- package/dist/plugins/checkpoint-store-face.d.ts +119 -61
- package/dist/plugins/checkpoint-store-face.js +134 -55
- package/dist/plugins/landed-attendants.d.ts +2 -2
- package/dist/plugins/local-checkpoint-store.d.ts +13 -3
- package/dist/plugins/local-checkpoint-store.js +38 -49
- package/dist/plugins/one-time-migrations.d.ts +51 -21
- package/dist/plugins/one-time-migrations.js +67 -45
- package/dist/plugins/pg-pool.js +11 -9
- package/dist/plugins/remote-env-host.d.ts +29 -10
- package/dist/plugins/remote-env-host.js +99 -19
- package/dist/plugins/remote-env-k8s.d.ts +68 -2
- package/dist/plugins/remote-env-k8s.js +107 -24
- package/dist/plugins/replica-registration.d.ts +1 -1
- package/dist/plugins/replica-registration.js +1 -1
- package/dist/plugins/s3-presign.d.ts +23 -0
- package/dist/plugins/s3-presign.js +37 -3
- package/dist/plugins/scheduler-support.d.ts +2 -2
- package/dist/plugins/scheduler-support.js +0 -3
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-file-ledger.d.ts +2 -2
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/send-user-file.d.ts +3 -4
- package/dist/plugins/send-user-file.js +56 -52
- package/dist/plugins/session-blob-claim-token.d.ts +15 -0
- package/dist/plugins/session-blob-claim-token.js +20 -0
- package/dist/plugins/session-blob-namespace.d.ts +46 -0
- package/dist/plugins/session-blob-namespace.js +116 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
- package/dist/plugins/snapshot-bucket-presence.js +8 -2
- package/dist/plugins/sql-json-column.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +44 -2
- package/dist/plugins/store-backend.js +75 -53
- package/dist/plugins/task-attachment-store.d.ts +2 -1
- package/dist/plugins/task-attachment-store.js +5 -1
- package/dist/plugins/tidb-pool.js +19 -24
- package/dist/plugins/workspace-custody.d.ts +4 -3
- package/dist/question.d.ts +2 -1
- package/dist/question.js +5 -13
- package/dist/request-form.d.ts +6 -1
- package/dist/request-form.js +4 -1
- package/dist/rule-issue-reading.d.ts +1 -1
- package/dist/rules-consent.d.ts +1 -1
- package/dist/run-local.js +8 -9
- package/dist/runs.d.ts +17 -8
- package/dist/runs.js +15 -2
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/sema-config-dir.d.ts +2 -1
- package/dist/session-sync.d.ts +27 -11
- package/dist/session-sync.js +15 -7
- package/dist/stores-sql/blob-index-sql.d.ts +30 -9
- package/dist/stores-sql/blob-index-sql.js +40 -21
- package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
- package/dist/stores-sql/checkpoint-store-sql.js +70 -146
- package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
- package/dist/stores-sql/file-history-store-sql.js +100 -28
- package/dist/stores-sql/retention-store-sql.js +3 -9
- package/dist/task-settings.d.ts +6 -5
- package/dist/task-settings.js +3 -3
- package/dist/tool-approval.d.ts +342 -518
- package/dist/tool-approval.js +498 -1207
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/ledger-events.d.ts +3 -3
- package/dist/trace/project.d.ts +2 -3
- package/dist/trace/redact.js +3 -2
- package/dist/trace/self-minted-wire.d.ts +287 -157
- package/dist/trace/self-minted-wire.js +93 -48
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/read-warnings.js +1 -1
- package/dist/web-admin-client/types.d.ts +1 -1
- package/dist/workspace-space.d.ts +10 -4
- package/dist/workspace-space.js +4 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
- package/package.json +6 -2
- package/scripts/embed-minimal.mjs +3 -3
- package/wire-changes/server@7.152.0.json +900 -0
- package/dist/config-d-migration.d.ts +0 -37
- package/dist/config-d-migration.js +0 -60
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { presignS3Url } from "./s3-presign.js";
|
|
2
|
+
import { createEgressFetch } from "../egress-redirect.js";
|
|
3
|
+
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
4
|
+
import { SESSION_BLOB_CLAIM_KEY } from "../object-store-buckets.js";
|
|
5
|
+
import { sessionBlobKeyPrefixOf } from "./blob-backend.js";
|
|
6
|
+
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
7
|
+
import { SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS } from "./snapshot-bucket-presence.js";
|
|
8
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
|
+
export const SESSION_BLOB_CLAIM_SETTLE_MS = 1_000;
|
|
10
|
+
export class SessionBlobNamespaceRefusedError extends StoreBootRefusalError {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "SessionBlobNamespaceRefusedError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function namespaceText(store) {
|
|
17
|
+
const prefix = store.keyPrefix === undefined || store.keyPrefix === "" ? "unset" : JSON.stringify(store.keyPrefix);
|
|
18
|
+
return `bucket "${store.bucket}" under SESSION_SNAPSHOT_PREFIX (${prefix}) at MINIO_ENDPOINT → ${buildDisplaySafeUrl(store.endpoint)}`;
|
|
19
|
+
}
|
|
20
|
+
function shownToken(body) {
|
|
21
|
+
const t = body.trim();
|
|
22
|
+
return /^[A-Za-z0-9._-]{1,64}$/.test(t) ? t : `${JSON.stringify(t.slice(0, 64))} (not a claim token)`;
|
|
23
|
+
}
|
|
24
|
+
export function buildSessionBlobNamespaceForeignText(store, holder, mine) {
|
|
25
|
+
const claim = `${store.bucket}/${sessionBlobKeyPrefixOf(store, "claim")}`;
|
|
26
|
+
return (`The session blob namespace — ${namespaceText(store)} — belongs to another database: its claim object ${claim} names ${shownToken(holder)}, this database is ${mine}. ` +
|
|
27
|
+
`Attachments and file history keep their bytes there under content addresses, and each engine deletes the ones its own database no longer references, so two databases on one namespace delete each other's bytes. ` +
|
|
28
|
+
`Next: give this engine its own SESSION_SNAPSHOT_PREFIX (to keep the bytes this database already references, copy the objects under the current prefix to the new one first); ` +
|
|
29
|
+
`if the database named in the claim no longer uses this bucket, delete ${claim} and start again.`);
|
|
30
|
+
}
|
|
31
|
+
export function buildSessionBlobNamespaceUnprovenText(store, detail) {
|
|
32
|
+
const claim = `${store.bucket}/${sessionBlobKeyPrefixOf(store, "claim")}`;
|
|
33
|
+
return (`Could not prove that the session blob namespace — ${namespaceText(store)} — belongs to this database (${detail}): without that proof the attachment and file-history sweeps could delete another database's bytes, so nothing there is deleted until it is proven. ` +
|
|
34
|
+
`Next: check that MINIO_ENDPOINT answers and that the engine's key may GetObject and PutObject ${claim}.`);
|
|
35
|
+
}
|
|
36
|
+
class ClaimUnproven extends Error {
|
|
37
|
+
detail;
|
|
38
|
+
constructor(detail) {
|
|
39
|
+
super(detail);
|
|
40
|
+
this.detail = detail;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function createSessionBlobNamespace(store, opts = {}) {
|
|
44
|
+
const egressFetch = createEgressFetch("declare the final object-store address as MINIO_ENDPOINT / S3_ENDPOINT", opts.fetchImpl);
|
|
45
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
46
|
+
const key = sessionBlobKeyPrefixOf(store, "claim");
|
|
47
|
+
let opened;
|
|
48
|
+
let inFlight;
|
|
49
|
+
const url = (op, method) => presignS3Url({ op, endpoint: store.endpoint, bucket: store.bucket, key, method, accessKey: store.accessKey, secretKey: store.secretKey, ...(store.region ? { region: store.region } : {}) });
|
|
50
|
+
const readClaim = async () => {
|
|
51
|
+
const res = await egressFetch(url("blob-claim/claim:GET", "GET"), { method: "GET", signal: AbortSignal.timeout(SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS) });
|
|
52
|
+
if (res.status === 404)
|
|
53
|
+
return undefined;
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
throw new ClaimUnproven(`claim read answered HTTP ${res.status}`);
|
|
56
|
+
return (await res.text()).trim();
|
|
57
|
+
};
|
|
58
|
+
const attempt = async (mine) => {
|
|
59
|
+
try {
|
|
60
|
+
let holder = await readClaim();
|
|
61
|
+
if (holder === undefined) {
|
|
62
|
+
const res = await egressFetch(url("blob-claim/claim:PUT", "PUT"), { method: "PUT", body: mine, headers: { "if-none-match": "*" }, signal: AbortSignal.timeout(SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS) });
|
|
63
|
+
if (!res.ok && res.status !== 412)
|
|
64
|
+
return { state: "unproven", detail: `claim write answered HTTP ${res.status}` };
|
|
65
|
+
await sleep(SESSION_BLOB_CLAIM_SETTLE_MS);
|
|
66
|
+
holder = await readClaim();
|
|
67
|
+
if (holder === undefined)
|
|
68
|
+
return { state: "unproven", detail: "the claim just written does not read back" };
|
|
69
|
+
}
|
|
70
|
+
return holder === mine ? { state: "mine" } : { state: "foreign", holder };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return { state: "unproven", detail: err instanceof ClaimUnproven ? err.detail : `claim request failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const claimOnce = (token) => (inFlight ??= attempt(token).finally(() => {
|
|
77
|
+
inFlight = undefined;
|
|
78
|
+
}));
|
|
79
|
+
return {
|
|
80
|
+
store,
|
|
81
|
+
claimOnce,
|
|
82
|
+
bind(token) {
|
|
83
|
+
opened = token;
|
|
84
|
+
},
|
|
85
|
+
async establish() {
|
|
86
|
+
const token = opened;
|
|
87
|
+
if (token === undefined)
|
|
88
|
+
throw new SessionBlobNamespaceRefusedError(`the session blob namespace — ${namespaceText(store)} — has no claim token bound (StoreBackend.ensureSchema() binds it) — refusing to open`);
|
|
89
|
+
const r = await claimOnce(token);
|
|
90
|
+
switch (r.state) {
|
|
91
|
+
case "mine":
|
|
92
|
+
return;
|
|
93
|
+
case "foreign":
|
|
94
|
+
throw new SessionBlobNamespaceRefusedError(buildSessionBlobNamespaceForeignText(store, r.holder, token));
|
|
95
|
+
case "unproven":
|
|
96
|
+
recordFailOpen("server.session-blob-namespace.claim-unproven", `bucket=${store.bucket} prefix=${store.keyPrefix === undefined || store.keyPrefix === "" ? "unset" : JSON.stringify(store.keyPrefix)} endpoint=${buildDisplaySafeUrl(store.endpoint)} ${r.detail}`);
|
|
97
|
+
return;
|
|
98
|
+
default: {
|
|
99
|
+
const unknown = r;
|
|
100
|
+
throw new Error(`unknown claim reading ${JSON.stringify(unknown)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
async assertOwned() {
|
|
105
|
+
if (opened === undefined)
|
|
106
|
+
throw new Error(`refusing to delete: the session blob namespace — ${namespaceText(store)} — has no claim token (the store was not opened: StoreBackend.ensureSchema() binds this database's)`);
|
|
107
|
+
const r = await claimOnce(opened);
|
|
108
|
+
if (r.state === "mine")
|
|
109
|
+
return;
|
|
110
|
+
throw new Error(r.state === "foreign"
|
|
111
|
+
? `refusing to delete: ${buildSessionBlobNamespaceForeignText(store, r.holder, opened)}`
|
|
112
|
+
: `refusing to delete: ${buildSessionBlobNamespaceUnprovenText(store, r.detail)}`);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=session-blob-namespace.js.map
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* 被读成「这个空间头一回」,从空目录起;run 末的保存虽失败,报的却是一条不点名桶的 curl 404,没有一行说「桶没建」。
|
|
10
10
|
*
|
|
11
11
|
* ## 形(一条规则,不加特例)
|
|
12
|
-
* · 核的是 {@link MinioBlobBackend.probeBucket}(presigned 列举 `max-keys=1
|
|
12
|
+
* · 核的是 {@link MinioBlobBackend.probeBucket}(presigned 列举 `max-keys=1`,列举前缀 = 角色行的 `probePrefix`:会话桶 = 退役快照纪元的根 `{legacyBlobRoot}`,
|
|
13
|
+
* 工作区桶 = 适配器写入的那只前缀 `<WORKSPACE_SNAPSHOT_PREFIX>/`,与适配器同一读 `workspaceSnapshotPrefixOf` —— 按前缀授列举的最小权限凭据对探针与对适配器同答;
|
|
14
|
+
* 两行各是对象存储动作表的一行 `boot-probe/*:LIST`);**只有** 404 + `NoSuchBucket` 是「缺」,其余证明不了的形(连不上 / 403 / 5xx / 没有
|
|
13
15
|
* NoSuchBucket 的 404)⇒ **不拒**、照常起服,走登记过的 F 类兜底口 `recordFailOpen("server.snapshot-bucket.presence-unproven")`(计数 + 探针行 + 记录器的一次性 warn;
|
|
14
16
|
* 三问写在该 tag 的 note 里)—— 拒启要有证据,没有证据只留痕;
|
|
15
17
|
* · 缺 = 配置好的店资源还没就绪,与「库连不上」同一类处境:在 `STORE_LOCK_WAIT_MS` 窗内重试({@link openWithinWait},同一只窗、同一只节拍、同一条窗尾规则 —— 编排里
|
|
@@ -36,6 +38,16 @@ export declare const SNAPSHOT_BUCKET_ROLES: {
|
|
|
36
38
|
readonly defaultName: "session-snapshots";
|
|
37
39
|
readonly consequence: "every snapshot-offload write and SendUserFile private-track request would answer 404 NoSuchBucket (and the orphan sweep would log one every minute, forever)";
|
|
38
40
|
readonly createHint: "; this version's sema-up scripts create it — re-run them on an existing sema-up deployment";
|
|
41
|
+
readonly probeOp: "boot-probe/session:LIST";
|
|
42
|
+
readonly probePrefix: (store: {
|
|
43
|
+
endpoint: string;
|
|
44
|
+
bucket: string;
|
|
45
|
+
accessKey: string;
|
|
46
|
+
secretKey: string;
|
|
47
|
+
region?: string;
|
|
48
|
+
keyPrefix?: string;
|
|
49
|
+
presignTtlSec?: number;
|
|
50
|
+
}) => string;
|
|
39
51
|
};
|
|
40
52
|
readonly workspace: {
|
|
41
53
|
readonly noun: "workspace snapshot bucket";
|
|
@@ -43,9 +55,20 @@ export declare const SNAPSHOT_BUCKET_ROLES: {
|
|
|
43
55
|
readonly defaultName: "workspaces";
|
|
44
56
|
readonly consequence: "no workspace snapshot could be saved or restored (every run would start from an empty /workspace and every run-end save would fail on a bare 404), and no line would say the bucket is missing";
|
|
45
57
|
readonly createHint: "";
|
|
58
|
+
readonly probeOp: "boot-probe/workspace:LIST";
|
|
59
|
+
readonly probePrefix: (store: {
|
|
60
|
+
endpoint: string;
|
|
61
|
+
bucket: string;
|
|
62
|
+
accessKey: string;
|
|
63
|
+
secretKey: string;
|
|
64
|
+
region?: string;
|
|
65
|
+
keyPrefix?: string;
|
|
66
|
+
presignTtlSec?: number;
|
|
67
|
+
}) => string;
|
|
46
68
|
};
|
|
47
69
|
};
|
|
48
|
-
/** boot 期单次探针的上界(ms):黑洞端点(丢包不拒连)不许把起服拖到对象存储 IO 的 120s;到点 ⇒ `unknown`(证明不了缺桶 ⇒ 不拒、报一次)。
|
|
70
|
+
/** boot 期单次探针的上界(ms):黑洞端点(丢包不拒连)不许把起服拖到对象存储 IO 的 120s;到点 ⇒ `unknown`(证明不了缺桶 ⇒ 不拒、报一次)。
|
|
71
|
+
* 会话 blob 命名空间的认领请求(`session-blob-namespace.ts`,起服的那一次与每次删除前的重读)读同一只数:起服对快照桶的探针只有这一个上界。 */
|
|
49
72
|
export declare const SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS = 10000;
|
|
50
73
|
/** 缺桶的拒启句(唯一铸点)。 */
|
|
51
74
|
/** @param endpoint `MINIO_ENDPOINT` 的**显示安全形**(调用方过 {@link buildDisplaySafeUrl};本函数不碰原串)。 */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
2
|
-
import { MinioBlobBackend } from "./blob-backend.js";
|
|
2
|
+
import { MinioBlobBackend, sessionBlobKeyPrefixOf } from "./blob-backend.js";
|
|
3
|
+
import { workspaceSnapshotPrefixOf } from "../workspace-space.js";
|
|
3
4
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
4
5
|
import { SESSION_SNAPSHOT_BUCKET_DEFAULT, WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "../object-store-buckets.js";
|
|
5
6
|
import { isStoreOpenWindowExpired, openWithinWait } from "./store-open-wait.js";
|
|
@@ -10,6 +11,8 @@ export const SNAPSHOT_BUCKET_ROLES = {
|
|
|
10
11
|
defaultName: SESSION_SNAPSHOT_BUCKET_DEFAULT,
|
|
11
12
|
consequence: "every snapshot-offload write and SendUserFile private-track request would answer 404 NoSuchBucket (and the orphan sweep would log one every minute, forever)",
|
|
12
13
|
createHint: "; this version's sema-up scripts create it — re-run them on an existing sema-up deployment",
|
|
14
|
+
probeOp: "boot-probe/session:LIST",
|
|
15
|
+
probePrefix: (store) => sessionBlobKeyPrefixOf(store, "legacy-blobs"),
|
|
13
16
|
},
|
|
14
17
|
workspace: {
|
|
15
18
|
noun: "workspace snapshot bucket",
|
|
@@ -17,6 +20,8 @@ export const SNAPSHOT_BUCKET_ROLES = {
|
|
|
17
20
|
defaultName: WORKSPACE_SNAPSHOT_BUCKET_DEFAULT,
|
|
18
21
|
consequence: "no workspace snapshot could be saved or restored (every run would start from an empty /workspace and every run-end save would fail on a bare 404), and no line would say the bucket is missing",
|
|
19
22
|
createHint: "",
|
|
23
|
+
probeOp: "boot-probe/workspace:LIST",
|
|
24
|
+
probePrefix: (store) => `${workspaceSnapshotPrefixOf(store)}/`,
|
|
20
25
|
},
|
|
21
26
|
};
|
|
22
27
|
export const SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS = 10_000;
|
|
@@ -34,7 +39,8 @@ export function buildSnapshotBucketRefusal(bucket, endpoint, waitedMs, role = "s
|
|
|
34
39
|
}
|
|
35
40
|
export const SNAPSHOT_BUCKET_PROBE_PENDING = "probe still pending when the window ended";
|
|
36
41
|
export async function ensureSnapshotBucket(store, waitMs, logger, clock, role = "session") {
|
|
37
|
-
const
|
|
42
|
+
const roleRow = SNAPSHOT_BUCKET_ROLES[role];
|
|
43
|
+
const probe = new MinioBlobBackend({ endpoint: store.endpoint, bucket: store.bucket, accessKey: store.accessKey, secretKey: store.secretKey, ...(store.region ? { region: store.region } : {}), keyPrefix: roleRow.probePrefix(store), ops: { list: roleRow.probeOp } });
|
|
38
44
|
let verdict;
|
|
39
45
|
try {
|
|
40
46
|
verdict = await openWithinWait(async () => {
|
|
@@ -347,7 +347,7 @@ export declare const readNonNullJsonValueColumn: JsonColumnReader<unknown>;
|
|
|
347
347
|
* **一只 JSON 数组**的承载形读者(内容判据归各列自己的既有属主,这里只判承载形)。
|
|
348
348
|
*
|
|
349
349
|
* 🔴 为什么在属主里而不是各店各写一遍([ref] 车③a 把车② 的店内私有形上收):今天有三个消费方
|
|
350
|
-
* (checkpoint 的 `
|
|
350
|
+
* (checkpoint 的 `pending_steer_queue`、image-index 的 `bands`、memory-engine 的 `embedding`),判据逐字
|
|
351
351
|
* 相同,而「这一格是不是一只数组」恰好是坏体四形的落点 —— 三份拷贝迟早有一份忘了判 `null` 字面。
|
|
352
352
|
*/
|
|
353
353
|
export declare const readJsonArrayColumn: JsonColumnReader<unknown[]>;
|
|
@@ -27,6 +27,7 @@ import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-stor
|
|
|
27
27
|
import { SqlSessionPolicyStore } from "../stores-sql/session-policy-store-sql.js";
|
|
28
28
|
import { SqlFileHistoryStore } from "../stores-sql/file-history-store-sql.js";
|
|
29
29
|
import { type LandedAttendantSeat } from "./landed-attendants.js";
|
|
30
|
+
import { type SessionBlobNamespace } from "./session-blob-namespace.js";
|
|
30
31
|
import type { ServiceConfig } from "../config-types.js";
|
|
31
32
|
import { TiDBRunStore, PgRunStore } from "./run-store-sql.js";
|
|
32
33
|
import { TiDBCheckpointStore, PgCheckpointStore } from "../stores-sql/checkpoint-store-sql.js";
|
|
@@ -112,9 +113,32 @@ export type SqlBlobPutResult = {
|
|
|
112
113
|
export interface ServiceBlobExchange extends FileHistoryBlobExchange {
|
|
113
114
|
put(hash: string, bytes: Uint8Array): Promise<SqlBlobPutResult>;
|
|
114
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* 一次会话同步推送 / 拉取**落在哪一格**(落点):会话 id + 落点属主格 + 门读时这只 id 有没有住客。路由在门判之后经 `landingOf`(`http/routes/session-sync.ts`)铸一次,
|
|
118
|
+
* 导入租约、blob 授予、blob 可见性三处读同一只;店不解释属主(只按字节存比,无主格记域键 `""`)。
|
|
119
|
+
* · `occupied` = 门读时这只 id 被占着(活行 ∪ 在删行,「被占着」的唯一读法)。只有被占着的 id,它的历史图才是来历:空闲 id 上残留的图(删后残留 / 迟到的重放落下的)
|
|
120
|
+
* 不是任何一位调用方持有过字节的证明,所以空闲落点的可见集只读授予。
|
|
121
|
+
*/
|
|
122
|
+
export interface SyncLanding {
|
|
123
|
+
readonly sessionId: string;
|
|
124
|
+
readonly owner: string | null;
|
|
125
|
+
readonly occupied: boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* blob 的**按落点来历**面(SQL 两孪生有;与 {@link ServiceBlobExchange} 并列,不塞进 core 的交换组 —— core 判官只交回组的三员)。律:一枚 blob 对一个落点可见
|
|
129
|
+
* ⇔ (落点被占着 ∧ 这只会话的图引用它) ∨ 这个落点自己上传过它(授予),且字节在场;字节仍全局去重一份,可见性另表 —— 两个事实两张表。
|
|
130
|
+
* · `putAt`:先核内容地址 → 再写字节(交换组同一写路径)→ **字节写成才**写授予(`granted_at = now`,单调)。答形 = {@link SqlBlobPutResult}(413 / 400 / 502 三臂不变);授予写失败 ⇒ 写失败臂。
|
|
131
|
+
* · `visibleAt`:`hashes` 里对这个落点可见的那几枚。先算候选(图项 ∪ 授予项)、只对候选问字节在不在 —— 耗时只随这个落点自己的状态变,不随别人存过什么变。
|
|
132
|
+
*/
|
|
133
|
+
export interface ServiceBlobLanding {
|
|
134
|
+
putAt(landing: SyncLanding, hash: string, bytes: Uint8Array): Promise<SqlBlobPutResult>;
|
|
135
|
+
visibleAt(landing: SyncLanding, hashes: readonly string[]): Promise<ReadonlySet<string>>;
|
|
136
|
+
}
|
|
115
137
|
export type ServiceFileHistoryStore = FileHistoryStore & {
|
|
116
138
|
/** The blob exchange (SQL twins only; core's local File store carries none). Narrowed per {@link ServiceBlobExchange}. */
|
|
117
139
|
readonly blobExchange?: ServiceBlobExchange;
|
|
140
|
+
/** The per-landing provenance face over the same blob domain (SQL twins only) — see {@link ServiceBlobLanding}. The sync routes read blobs only through it. */
|
|
141
|
+
readonly blobLanding?: ServiceBlobLanding;
|
|
118
142
|
/** E21 purge / overwrite-dst sync wipe: drop the WHOLE scope incl. v1 baselines + DV-14 markers
|
|
119
143
|
* (`reap(scope, [])` deliberately retains those while the scope lives — not a purge). SQL twins only. */
|
|
120
144
|
deleteBySession?(scope: string): Promise<number>;
|
|
@@ -225,8 +249,8 @@ export interface StoreBackend {
|
|
|
225
249
|
* (它承诺的是「稍后还能批、崩了行还在」)。机器钉 = `wiring-governance-operator.test.ts` 的
|
|
226
250
|
* 「三腿 approvalAsk 全持久」格。
|
|
227
251
|
*
|
|
228
|
-
* 无 backend 的 env-only worker 压根没有 `StoreBackend` ⇒
|
|
229
|
-
*
|
|
252
|
+
* 无 backend 的 env-only worker 压根没有 `StoreBackend` ⇒ 呈卡协议不上场 ⇒ 起服不构造审批协调器(没有「无账协调器」这一形,
|
|
253
|
+
* `boot/coordinators.ts`)。 */
|
|
230
254
|
approvalAsk(): ApprovalAskStoreType;
|
|
231
255
|
/**
|
|
232
256
|
* [ref] 车二(core 5.18.0 [ref] + 5.22.0 [ref]):**持久化权限规则店**(「不再询问」车道)
|
|
@@ -303,6 +327,10 @@ export interface StoreBackend {
|
|
|
303
327
|
* epoch is gone (its manifest index table retired with it), so EVERY row in that domain is an orphan and its
|
|
304
328
|
* BYTES (incl. MinIO objects) would otherwise outlive the epoch forever. SQL backends only; reaper-driven. */
|
|
305
329
|
legacySnapshotBlobSweep?(): Promise<number>;
|
|
330
|
+
/** The session blob namespace this backend's database holds (`session-blob-namespace.ts`): `ensureSchema()` proves it (another database's ⇒ refuse to open),
|
|
331
|
+
* and every session blob port of this deployment is built from it (file history and the retired epoch here, attachments at boot). SQL backends with object
|
|
332
|
+
* storage configured; absent otherwise (no object storage, or the local backend — its bytes are on its own disk). */
|
|
333
|
+
sessionBlobNamespace?(): SessionBlobNamespace | undefined;
|
|
306
334
|
/** 🔴 **一次性迁移** —— 落在已写进库里的字节上的退役,升一次级改完、兼容臂当场删干净(硬 breaking 纪律:持久数据面 =
|
|
307
335
|
* 响亮拒 + 一次性迁移,窗过即删,永不长期双读)。逐条语义、幂等与拒启臂见该模块头注。
|
|
308
336
|
*
|
|
@@ -449,11 +477,23 @@ export interface StoreBackend {
|
|
|
449
477
|
fileUsageWindowStore?(): UsageWindowStore;
|
|
450
478
|
/** **local 后端专有**:构造时交给两半店(会话店顶层、检查点店)的那一份声明(唯一读者 `localStoreDeclarationOf`)—— 装配值从这里读回,不重算。SQL 后端省略。 */
|
|
451
479
|
localStoreDeclaration?(): LocalStoreDeclaration;
|
|
480
|
+
/**
|
|
481
|
+
* **本进程此刻持锁独占的物理数据根**(正读数;本机控制 socket 开不开口只读它 —— `control-socket.ts` 的前提律):打开成功的 local 后端 ⇒ 引擎 File 后端取锁的那只根
|
|
482
|
+
* (绝对物理路径,`dataDirLayout.physicalRoot` 同一只),`close()` 之后 ⇒ `undefined`;打开那一刻被拒的 local 后端 ⇒ `undefined`(它的 `kind` 也是 `"local"`,所以判「独占」不读 `kind`)。
|
|
483
|
+
* SQL 后端省略(省略 = 不独占任何数据根,同 `undefined`)。
|
|
484
|
+
*/
|
|
485
|
+
exclusiveDataRoot?(): string | undefined;
|
|
452
486
|
/**
|
|
453
487
|
* 会话店活引用判的 workflow 那一腿(会话种类的老化;`local-session-live-reference.ts`):本后端的会话店读不到 workflow run 店时(local:那只店在 boot 第 6 段才铸),
|
|
454
488
|
* 第 6 段把这台部署的 workflow run 店交过来(不起 workflow ⇒ `"none"`);绑一次。SQL 两孪生不实现(它们的判是一句 SQL,读 `workflow_run` 表)。
|
|
455
489
|
*/
|
|
456
490
|
bindWorkflowRunReader?(reader: LocalWorkflowRunsReader | "none"): void;
|
|
491
|
+
/**
|
|
492
|
+
* 「不再询问」规则 project root 的铸卡快照读法(跑这条 run 的副本的会话 cwd 登记;与活卡铸点同一只函数)—— 第 6 段绑一次,本后端的 checkpoint 店写口 `put`
|
|
493
|
+
* 在停驻那一刻读它落快照(SQL 列 `card_scope_root` / local 快照索引)。未绑 / 不实现(嵌入方的替身后端)⇒ 不可知 ⇒ 不落快照(兑付答 `scope-unresolved`:只会拒)。
|
|
494
|
+
* 本仓三只后端都实现;形同 {@link bindWorkflowRunReader} 的晚绑。
|
|
495
|
+
*/
|
|
496
|
+
bindCardScopeRoot?(read: (sessionId: string) => string | undefined): void;
|
|
457
497
|
}
|
|
458
498
|
/**
|
|
459
499
|
* 托管留存执行器的**组合**:引擎座表那一半(`SqlRetentionStore`,事务属主)+ 部署那一半(hold / 审计 / 认领判 / 十三张部署表,
|
|
@@ -554,6 +594,8 @@ export interface LocalStoreBackend extends StoreBackend {
|
|
|
554
594
|
localStoreDeclaration(): LocalStoreDeclaration;
|
|
555
595
|
/** local 后端恒有(会话店活引用判的 workflow 晚绑座;接口上可选的那一口在这里必在)。 */
|
|
556
596
|
bindWorkflowRunReader(reader: LocalWorkflowRunsReader | "none"): void;
|
|
597
|
+
/** local 后端恒有这一口(接口上可选的那一口在这里必在);答 `undefined` 的是拒启的那一只与关过的那一只(见 {@link StoreBackend.exclusiveDataRoot})。 */
|
|
598
|
+
exclusiveDataRoot(): string | undefined;
|
|
557
599
|
}
|
|
558
600
|
/** 引擎 File 后端上会话店之外、本仓没有自己一份口的店(run-local 的 Runner 接它们;工具结果 / 用量窗 / 会话规则走既有的 `toolResult()` / `fileUsageWindowStore()` / `sessionPolicy()` 口)。 */
|
|
559
601
|
export type LocalEngineFileStores = Pick<FileStorageBackend, "checkpointStore" | "backgroundAgentStore" | "mailboxStore" | "rosterStore">;
|
|
@@ -44,10 +44,12 @@ import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-stor
|
|
|
44
44
|
import { TiDBSessionPolicyStore, PgSessionPolicyStore, SqlSessionPolicyStore } from "../stores-sql/session-policy-store-sql.js";
|
|
45
45
|
import { SQL_SESSION_ROOT_SEAT } from "./session-row-sql.js";
|
|
46
46
|
import { TiDBWorkflowJournalStore, PgWorkflowJournalStore, SqlWorkflowJournalStore } from "../stores-sql/workflow-journal-store-sql.js";
|
|
47
|
-
import { TiDBFileHistoryStore, PgFileHistoryStore,
|
|
47
|
+
import { TiDBFileHistoryStore, PgFileHistoryStore, SqlFileHistoryStore, SqlBlobExchange, SqlBlobLanding } from "../stores-sql/file-history-store-sql.js";
|
|
48
48
|
import { createLocalLandedAttendantSeat, createSqlLandedAttendantSeat } from "./landed-attendants.js";
|
|
49
|
-
import { SqlBlobIndex } from "../stores-sql/blob-index-sql.js";
|
|
50
|
-
import {
|
|
49
|
+
import { SqlBlobIndex, blobGcCutoffAt } from "../stores-sql/blob-index-sql.js";
|
|
50
|
+
import { createSessionBlobBackend } from "./blob-backend.js";
|
|
51
|
+
import { createSessionBlobNamespace } from "./session-blob-namespace.js";
|
|
52
|
+
import { ensureSessionBlobClaimToken } from "./session-blob-claim-token.js";
|
|
51
53
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
52
54
|
import { fileMemoryControlDirFor, memoryEngineRootFor } from "../memory-scope.js";
|
|
53
55
|
import { createTidbPool, ensureSchema as ensureTidbSchema } from "./tidb-pool.js";
|
|
@@ -92,6 +94,7 @@ import { PgMailboxStore, TiDBMailboxStore } from "../stores-sql/mailbox-store-sq
|
|
|
92
94
|
import { PgRosterStore, TiDBRosterStore } from "../stores-sql/roster-store-sql.js";
|
|
93
95
|
import { TiDBSessionStorage } from "../stores-sql/tidb-session-storage.js";
|
|
94
96
|
import { PgUsageWindowStore, TiDBUsageWindowStore } from "../stores-sql/usage-window-store-sql.js";
|
|
97
|
+
import { createCardScopeRootSeat } from "./checkpoint-store-face.js";
|
|
95
98
|
export function createRunStallWiring(config, observers) {
|
|
96
99
|
const count = observers?.runStallVerdicts;
|
|
97
100
|
return { windowMs: () => config.runStaleSec * 1000, onVerdicts: (landed) => count?.(landed) };
|
|
@@ -99,19 +102,10 @@ export function createRunStallWiring(config, observers) {
|
|
|
99
102
|
export function workflowJournalOf(config, backend) {
|
|
100
103
|
return config.selfOrchestrationEnabled && backend ? backend.workflowJournal() : undefined;
|
|
101
104
|
}
|
|
102
|
-
function fileHistoryOptions(config) {
|
|
103
|
-
const
|
|
104
|
-
const placement = s
|
|
105
|
+
function fileHistoryOptions(config, namespace) {
|
|
106
|
+
const placement = namespace
|
|
105
107
|
? {
|
|
106
|
-
blobs:
|
|
107
|
-
endpoint: s.endpoint,
|
|
108
|
-
bucket: s.bucket,
|
|
109
|
-
accessKey: s.accessKey,
|
|
110
|
-
secretKey: s.secretKey,
|
|
111
|
-
...(s.region ? { region: s.region } : {}),
|
|
112
|
-
keyPrefix: `${s.keyPrefix ?? ""}${FILE_HISTORY_BLOB_KEY_PREFIX}`,
|
|
113
|
-
...(s.presignTtlSec ? { presignTtlSec: s.presignTtlSec } : {}),
|
|
114
|
-
}),
|
|
108
|
+
blobs: createSessionBlobBackend(namespace, "file-history"),
|
|
115
109
|
}
|
|
116
110
|
: {
|
|
117
111
|
...(config.snapshotBlobSqlMaxBytes !== undefined ? { blobSqlMaxBytes: config.snapshotBlobSqlMaxBytes } : {}),
|
|
@@ -119,35 +113,21 @@ function fileHistoryOptions(config) {
|
|
|
119
113
|
};
|
|
120
114
|
return { ...config.fileHistory, ...placement };
|
|
121
115
|
}
|
|
122
|
-
function legacyBlobIndex(config, dialect, pool) {
|
|
123
|
-
const
|
|
124
|
-
const bytes = s
|
|
125
|
-
? new MinioBlobBackend({
|
|
126
|
-
endpoint: s.endpoint,
|
|
127
|
-
bucket: s.bucket,
|
|
128
|
-
accessKey: s.accessKey,
|
|
129
|
-
secretKey: s.secretKey,
|
|
130
|
-
...(s.region ? { region: s.region } : {}),
|
|
131
|
-
...(s.keyPrefix ? { keyPrefix: s.keyPrefix } : {}),
|
|
132
|
-
...(s.presignTtlSec ? { presignTtlSec: s.presignTtlSec } : {}),
|
|
133
|
-
})
|
|
134
|
-
: undefined;
|
|
116
|
+
function legacyBlobIndex(config, dialect, pool, namespace) {
|
|
117
|
+
const bytes = namespace ? createSessionBlobBackend(namespace, "legacy-blobs") : undefined;
|
|
135
118
|
return new SqlBlobIndex(dialect, pool, { table: "snapshot_blob", ...(bytes !== undefined ? { bytes } : {}), log: createLogger(config.logLevel) });
|
|
136
119
|
}
|
|
137
|
-
async function legacySweepImpl(config, dialect, pool) {
|
|
120
|
+
async function legacySweepImpl(config, dialect, pool, namespace) {
|
|
138
121
|
const sql = "SELECT blob_hash FROM snapshot_blob";
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
else {
|
|
122
|
+
const collect = async () => {
|
|
123
|
+
if (dialect === "mysql") {
|
|
124
|
+
const [rows] = await pool.query(sql);
|
|
125
|
+
return rows.map((r) => String(r.blob_hash));
|
|
126
|
+
}
|
|
145
127
|
const r = await pool.query(sql);
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return 0;
|
|
150
|
-
return legacyBlobIndex(config, dialect, pool).sweep(orphans);
|
|
128
|
+
return r.rows.map((row) => row.blob_hash);
|
|
129
|
+
};
|
|
130
|
+
return legacyBlobIndex(config, dialect, pool, namespace).sweep(collect, blobGcCutoffAt(Date.now()));
|
|
151
131
|
}
|
|
152
132
|
export function createSqlRetentionCallHooks(db, opts, input) {
|
|
153
133
|
const deployment = createRetentionDeploymentHooks(db.dialect, opts, input);
|
|
@@ -264,9 +244,16 @@ class TiDBBackend {
|
|
|
264
244
|
this.config = config;
|
|
265
245
|
this.observers = observers;
|
|
266
246
|
this.onWarn = onWarn;
|
|
247
|
+
this.blobNamespaceInst = config.snapshotBlobStore ? createSessionBlobNamespace(config.snapshotBlobStore) : undefined;
|
|
248
|
+
}
|
|
249
|
+
async ensureSchema() {
|
|
250
|
+
await ensureTidbSchema(this.pool);
|
|
251
|
+
if (this.blobNamespaceInst)
|
|
252
|
+
this.blobNamespaceInst.bind(await ensureSessionBlobClaimToken(mysqlDriver(this.pool)));
|
|
267
253
|
}
|
|
268
|
-
ensureSchema() { return ensureTidbSchema(this.pool); }
|
|
269
254
|
close() { return this.pool.end(); }
|
|
255
|
+
sessionBlobNamespace() { return this.blobNamespaceInst; }
|
|
256
|
+
blobNamespaceInst;
|
|
270
257
|
run() { return (this.runStoreInst ??= new TiDBRunStore(this.pool, createRunStallWiring(this.config, this.observers))); }
|
|
271
258
|
runStoreInst;
|
|
272
259
|
resumeAnchor() { return (this.resumeAnchorInst ??= new TiDBResumeAnchorStore(this.pool)); }
|
|
@@ -280,14 +267,14 @@ class TiDBBackend {
|
|
|
280
267
|
userInputLedger() { return new TiDBUserInputLedgerStore(this.pool); }
|
|
281
268
|
sessionPolicy() { return (this.sessionPolicyInst ??= new TiDBSessionPolicyStore(this.pool, SQL_SESSION_ROOT_SEAT, policyStoreLoggerOf(this.onWarn))); }
|
|
282
269
|
sessionPolicyInst;
|
|
283
|
-
fileHistory() { return (this.fileHistoryInst ??= new TiDBFileHistoryStore(this.pool, fileHistoryOptions(this.config))); }
|
|
270
|
+
fileHistory() { return (this.fileHistoryInst ??= new TiDBFileHistoryStore(this.pool, fileHistoryOptions(this.config, this.sessionBlobNamespace()))); }
|
|
284
271
|
fileHistoryInst;
|
|
285
272
|
landedAttendants() { return (this.landedAttendantsInst ??= sqlLandedAttendantsOf(mysqlDriver(this.pool), this.fileHistory(), this.sessionPolicy())); }
|
|
286
273
|
landedAttendantsInst;
|
|
287
274
|
memoryOptOutGrant() { return new TiDBMemoryOptOutGrantStore(this.pool); }
|
|
288
275
|
sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new TiDBSessionCaptureRecordStore(this.pool)); }
|
|
289
276
|
captureRecordsInst;
|
|
290
|
-
legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "mysql", this.pool); }
|
|
277
|
+
legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "mysql", this.pool, this.sessionBlobNamespace()); }
|
|
291
278
|
runOneTimeMigrations() { return runSqlOneTimeMigrations("mysql", this.pool, buildSqlOneTimeMigrationInput(this.config)); }
|
|
292
279
|
workflowJournal() { return new TiDBWorkflowJournalStore(this.pool); }
|
|
293
280
|
outcomeSink() { return new TiDBOutcomeLedger(this.pool); }
|
|
@@ -298,7 +285,9 @@ class TiDBBackend {
|
|
|
298
285
|
workflowParkDecisions(records) { return (this.parkDecisionsInst = decisionStoreBoundOnce(this.parkDecisionsInst, records, () => new SqlWorkflowParkDecisionStore(this.run(), sqlRecordFenceOf(records)))); }
|
|
299
286
|
parkDecisionsInst;
|
|
300
287
|
sharedMemoryStore(authorizer) { return new TiDBSharedMemoryStore(this.pool, authorizer); }
|
|
301
|
-
|
|
288
|
+
cardScopeRootSeat = createCardScopeRootSeat();
|
|
289
|
+
bindCardScopeRoot(read) { this.cardScopeRootSeat.bind(read); }
|
|
290
|
+
checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep), cardScopeRoot: this.cardScopeRootSeat }); }
|
|
302
291
|
checkpointReadView() { return this.checkpoint(); }
|
|
303
292
|
toolResult() { return new TiDBToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
|
|
304
293
|
imageIndex() { return new TiDBImageIndex(this.pool); }
|
|
@@ -318,13 +307,13 @@ class TiDBBackend {
|
|
|
318
307
|
async dbNowMs() { const [rows] = await this.pool.query("SELECT ROUND(UNIX_TIMESTAMP(NOW(3))*1000) AS ms"); return Number(rows[0]?.ms); }
|
|
319
308
|
}
|
|
320
309
|
sealStoreClass(TiDBBackend, "tidb-backend", {
|
|
321
|
-
ensureSchema: "boot", close: "boot", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
|
|
310
|
+
ensureSchema: "boot", close: "boot", sessionBlobNamespace: "exit", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
|
|
322
311
|
approvalAsk: "exit", permissionRule: "exit", sendFileLedger: "exit", userInputLedger: "exit", sessionPolicy: "exit", fileHistory: "exit",
|
|
323
312
|
memoryOptOutGrant: "exit", sessionCaptureRecords: "exit", legacySnapshotBlobSweep: "exit", runOneTimeMigrations: "boot", workflowJournal: "exit",
|
|
324
313
|
outcomeSink: "exit", workflowRun: "exit", completionInbox: "exit", notifyJournal: "exit", workflowAgentSessionIndex: "exit", workflowParkDecisions: "exit",
|
|
325
314
|
sharedMemoryStore: "exit", checkpoint: "exit", checkpointReadView: "exit", toolResult: "exit", imageIndex: "exit", imageBake: "exit", leaderRun: "exit",
|
|
326
315
|
device: "exit", retention: "exit", sessionEraser: "exit", retentionLane: "exit", breaker: "exit", costQuota: "exit", rateLimiter: "exit", session: "exit",
|
|
327
|
-
adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit",
|
|
316
|
+
adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit", bindCardScopeRoot: "exit",
|
|
328
317
|
});
|
|
329
318
|
class PgBackend {
|
|
330
319
|
pool;
|
|
@@ -337,9 +326,16 @@ class PgBackend {
|
|
|
337
326
|
this.config = config;
|
|
338
327
|
this.observers = observers;
|
|
339
328
|
this.onWarn = onWarn;
|
|
329
|
+
this.blobNamespaceInst = config.snapshotBlobStore ? createSessionBlobNamespace(config.snapshotBlobStore) : undefined;
|
|
330
|
+
}
|
|
331
|
+
async ensureSchema() {
|
|
332
|
+
await ensurePgSchema(this.pool);
|
|
333
|
+
if (this.blobNamespaceInst)
|
|
334
|
+
this.blobNamespaceInst.bind(await ensureSessionBlobClaimToken(pgDriver(this.pool)));
|
|
340
335
|
}
|
|
341
|
-
ensureSchema() { return ensurePgSchema(this.pool); }
|
|
342
336
|
close() { return this.pool.end(); }
|
|
337
|
+
sessionBlobNamespace() { return this.blobNamespaceInst; }
|
|
338
|
+
blobNamespaceInst;
|
|
343
339
|
run() { return (this.runStoreInst ??= new PgRunStore(this.pool, createRunStallWiring(this.config, this.observers))); }
|
|
344
340
|
runStoreInst;
|
|
345
341
|
resumeAnchor() { return (this.resumeAnchorInst ??= new PgResumeAnchorStore(this.pool)); }
|
|
@@ -353,14 +349,14 @@ class PgBackend {
|
|
|
353
349
|
userInputLedger() { return new PgUserInputLedgerStore(this.pool); }
|
|
354
350
|
sessionPolicy() { return (this.sessionPolicyInst ??= new PgSessionPolicyStore(this.pool, SQL_SESSION_ROOT_SEAT, policyStoreLoggerOf(this.onWarn))); }
|
|
355
351
|
sessionPolicyInst;
|
|
356
|
-
fileHistory() { return (this.fileHistoryInst ??= new PgFileHistoryStore(this.pool, fileHistoryOptions(this.config))); }
|
|
352
|
+
fileHistory() { return (this.fileHistoryInst ??= new PgFileHistoryStore(this.pool, fileHistoryOptions(this.config, this.sessionBlobNamespace()))); }
|
|
357
353
|
fileHistoryInst;
|
|
358
354
|
landedAttendants() { return (this.landedAttendantsInst ??= sqlLandedAttendantsOf(pgDriver(this.pool), this.fileHistory(), this.sessionPolicy())); }
|
|
359
355
|
landedAttendantsInst;
|
|
360
356
|
memoryOptOutGrant() { return new PgMemoryOptOutGrantStore(this.pool); }
|
|
361
357
|
sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new PgSessionCaptureRecordStore(this.pool)); }
|
|
362
358
|
captureRecordsInst;
|
|
363
|
-
legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "pg", this.pool); }
|
|
359
|
+
legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "pg", this.pool, this.sessionBlobNamespace()); }
|
|
364
360
|
runOneTimeMigrations() { return runSqlOneTimeMigrations("pg", this.pool, buildSqlOneTimeMigrationInput(this.config)); }
|
|
365
361
|
workflowJournal() { return new PgWorkflowJournalStore(this.pool); }
|
|
366
362
|
outcomeSink() { return new PgOutcomeLedger(this.pool); }
|
|
@@ -371,7 +367,9 @@ class PgBackend {
|
|
|
371
367
|
workflowParkDecisions(records) { return (this.parkDecisionsInst = decisionStoreBoundOnce(this.parkDecisionsInst, records, () => new SqlWorkflowParkDecisionStore(this.run(), sqlRecordFenceOf(records)))); }
|
|
372
368
|
parkDecisionsInst;
|
|
373
369
|
sharedMemoryStore(authorizer) { return new PgSharedMemoryStore(this.pool, authorizer); }
|
|
374
|
-
|
|
370
|
+
cardScopeRootSeat = createCardScopeRootSeat();
|
|
371
|
+
bindCardScopeRoot(read) { this.cardScopeRootSeat.bind(read); }
|
|
372
|
+
checkpoint(logger) { return new PgCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep), cardScopeRoot: this.cardScopeRootSeat }); }
|
|
375
373
|
checkpointReadView() { return this.checkpoint(); }
|
|
376
374
|
toolResult() { return new PgToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
|
|
377
375
|
imageIndex() { return new PgImageIndex(this.pool); }
|
|
@@ -391,13 +389,13 @@ class PgBackend {
|
|
|
391
389
|
async dbNowMs() { const res = await this.pool.query("SELECT ROUND(EXTRACT(EPOCH FROM now())*1000) AS ms"); return Number(res.rows[0]?.ms); }
|
|
392
390
|
}
|
|
393
391
|
sealStoreClass(PgBackend, "pg-backend", {
|
|
394
|
-
ensureSchema: "boot", close: "boot", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
|
|
392
|
+
ensureSchema: "boot", close: "boot", sessionBlobNamespace: "exit", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
|
|
395
393
|
approvalAsk: "exit", permissionRule: "exit", sendFileLedger: "exit", userInputLedger: "exit", sessionPolicy: "exit", fileHistory: "exit",
|
|
396
394
|
memoryOptOutGrant: "exit", sessionCaptureRecords: "exit", legacySnapshotBlobSweep: "exit", runOneTimeMigrations: "boot", workflowJournal: "exit",
|
|
397
395
|
outcomeSink: "exit", workflowRun: "exit", completionInbox: "exit", notifyJournal: "exit", workflowAgentSessionIndex: "exit", workflowParkDecisions: "exit",
|
|
398
396
|
sharedMemoryStore: "exit", checkpoint: "exit", checkpointReadView: "exit", toolResult: "exit", imageIndex: "exit", imageBake: "exit", leaderRun: "exit",
|
|
399
397
|
device: "exit", retention: "exit", sessionEraser: "exit", retentionLane: "exit", breaker: "exit", costQuota: "exit", rateLimiter: "exit", session: "exit",
|
|
400
|
-
adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit",
|
|
398
|
+
adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit", bindCardScopeRoot: "exit",
|
|
401
399
|
});
|
|
402
400
|
export function localSessionRowsOf(backend) {
|
|
403
401
|
return backend instanceof LocalBackend ? backend.session() : undefined;
|
|
@@ -568,7 +566,12 @@ class LocalBackend {
|
|
|
568
566
|
...importedLayerRoot,
|
|
569
567
|
};
|
|
570
568
|
}
|
|
569
|
+
closing = false;
|
|
570
|
+
exclusiveDataRoot() {
|
|
571
|
+
return this.closing ? undefined : this.fileBackend.root;
|
|
572
|
+
}
|
|
571
573
|
async close() {
|
|
574
|
+
this.closing = true;
|
|
572
575
|
await this.sessionStore.dispose();
|
|
573
576
|
this.runStore.dispose();
|
|
574
577
|
this.resumeAnchorStore.dispose();
|
|
@@ -612,8 +615,11 @@ class LocalBackend {
|
|
|
612
615
|
durability: this.storeDeclaration.durability,
|
|
613
616
|
getActiveTaskId: (sessionId) => this.runStore.getActiveTaskId(sessionId),
|
|
614
617
|
readSessionOwner: async (sessionId) => (await this.sessionStore.headOf(sessionId))?.owner,
|
|
618
|
+
cardScopeRoot: this.cardScopeRootSeat,
|
|
615
619
|
}));
|
|
616
620
|
}
|
|
621
|
+
cardScopeRootSeat = createCardScopeRootSeat();
|
|
622
|
+
bindCardScopeRoot(read) { this.cardScopeRootSeat.bind(read); }
|
|
617
623
|
checkpoint() {
|
|
618
624
|
const cs = this.checkpointStoreOf();
|
|
619
625
|
if (!this.checkpointProbeWired) {
|
|
@@ -709,6 +715,9 @@ class RefusedLocalBackend {
|
|
|
709
715
|
async close() {
|
|
710
716
|
await this.release();
|
|
711
717
|
}
|
|
718
|
+
exclusiveDataRoot() {
|
|
719
|
+
return undefined;
|
|
720
|
+
}
|
|
712
721
|
run() { return this.refused(); }
|
|
713
722
|
resumeAnchor() { return this.refused(); }
|
|
714
723
|
approvalExemption() { return this.refused(); }
|
|
@@ -732,6 +741,7 @@ class RefusedLocalBackend {
|
|
|
732
741
|
fileUsageWindowStore() { return this.refused(); }
|
|
733
742
|
localStoreDeclaration() { return this.refused(); }
|
|
734
743
|
bindWorkflowRunReader() { this.refused(); }
|
|
744
|
+
bindCardScopeRoot() { this.refused(); }
|
|
735
745
|
mysqlPool() { return undefined; }
|
|
736
746
|
pgPool() { return undefined; }
|
|
737
747
|
}
|
|
@@ -802,6 +812,16 @@ export async function openStoreBackendWithFallback(config, logger, observers, lo
|
|
|
802
812
|
sqlEngine === undefined
|
|
803
813
|
? await attempt()
|
|
804
814
|
: await openWithinWait(attempt, config.storeLockWaitMs, (w) => logger.warn("store_sql_open_wait", { engine: sqlEngine, endpoint: buildDisplaySafeUrl(sqlStoreCoordinatesAsUrl(config)), waitMs: w.waitMs, error: w.error }));
|
|
815
|
+
const namespace = backend?.sessionBlobNamespace?.();
|
|
816
|
+
if (namespace !== undefined) {
|
|
817
|
+
try {
|
|
818
|
+
await namespace.establish();
|
|
819
|
+
}
|
|
820
|
+
catch (refusal) {
|
|
821
|
+
await backend?.close().catch(() => undefined);
|
|
822
|
+
throw refusal;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
805
825
|
}
|
|
806
826
|
catch (err) {
|
|
807
827
|
void inFlight?.close().catch(() => undefined);
|
|
@@ -859,13 +879,15 @@ sealStoreClass(SqlCheckpointStore, "sql-checkpoint-store", {
|
|
|
859
879
|
reap: "exit", reapExpired: "exit", listExpiredApprovalGates: "exit", listPending: "exit", listByScope: "exit", listPendingSessionIds: "exit",
|
|
860
880
|
findPendingTokenBySession: "exit", findCheckpointCandidatesForAsk: "exit", findDecidedApprovalsForBinding: "exit", peekScopeByToken: "exit",
|
|
861
881
|
peekPendingScope: "exit", hostSessionOfCard: "exit", putCtx: "exit", getCtx: "exit", rewriteCtxWhileClaimed: "exit", rewriteCtxRows: "exit", reapCtx: "exit",
|
|
862
|
-
deleteBySession: "exit",
|
|
882
|
+
deleteBySession: "exit", cardScopeRootOf: "exit",
|
|
863
883
|
});
|
|
864
884
|
sealStoreClass(SqlFileHistoryStore, "sql-file-history-store", {
|
|
865
885
|
trackEdit: "exit", annulTrack: "exit", snapshot: "exit", restore: "exit", canRestore: "exit", reap: "exit", deleteBySession: "exit", sweepOrphanBlobs: "exit",
|
|
866
886
|
adoptScope: "exit", exportHistory: "exit", importHistory: "exit", importFailureOf: "exit", prepareImport: "exit",
|
|
867
887
|
lockScopeOn: "in-store", publishImportOn: "in-store", deleteScopeOn: "in-store",
|
|
868
888
|
});
|
|
889
|
+
sealStoreClass(SqlBlobExchange, "sql-blob-exchange", { put: "exit", get: "exit", has: "exit" });
|
|
890
|
+
sealStoreClass(SqlBlobLanding, "sql-blob-landing", { putAt: "exit", visibleAt: "exit" });
|
|
869
891
|
sealStoreClass(TiDBMailboxStore, "tidb-mailbox-store", {
|
|
870
892
|
append: "exit", claimLease: "exit", ack: "exit", moveDecision: "exit", releaseLease: "exit", peekCount: "exit", drop: "exit", reap: "exit",
|
|
871
893
|
retireRecipient: "exit", listRetiredRecipients: "exit",
|
|
@@ -83,7 +83,8 @@ export interface TaskAttachmentStore {
|
|
|
83
83
|
deleteBySession(sessionId: string): Promise<number>;
|
|
84
84
|
/** TTL 收割:未绑定且 created_at_ms < olderThanMs 的行。返回删除数。 */
|
|
85
85
|
reapUnbound(olderThanMs: number): Promise<number>;
|
|
86
|
-
/** D-1 孤儿**对象**彻底 GC(clay 拍 2026-07-28):列举对象存储 × meta 行对账,零行且过 grace
|
|
86
|
+
/** D-1 孤儿**对象**彻底 GC(clay 拍 2026-07-28):列举对象存储 × meta 行对账,零行且过 grace 的对象删。只看本域写得出的名(内容地址);
|
|
87
|
+
* 「零行」只对本库成立 ⇒ 命名空间必须只属本库(字节口的删除等命名空间归属证明,`session-blob-namespace.ts`)。
|
|
87
88
|
* 可选——local 店(meta+bytes 同盘同步删)无此孤儿类。 */
|
|
88
89
|
sweepOrphanObjects?(graceMs: number, nowMs?: number): Promise<number>;
|
|
89
90
|
}
|