@sema-agent/server 7.132.0 → 7.133.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 +8 -9
- package/README.zh-CN.md +7 -6
- package/USAGE.md +174 -55
- package/deploy/sema-up/README.md +19 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
- package/deploy/sema-up/chart/templates/server.yaml +5 -2
- package/deploy/sema-up/stack/compose.yaml +3 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
- package/dist/adoption/plan.d.ts +3 -2
- package/dist/adoption/plan.js +7 -5
- package/dist/adoption/runner.js +3 -10
- package/dist/adoption/sql.d.ts +3 -2
- package/dist/adoption/sql.js +3 -5
- package/dist/adoption/wire.d.ts +4 -1
- package/dist/adoption/wire.js +1 -0
- package/dist/approval-ask-audit-store.d.ts +31 -13
- package/dist/approval-ask-audit-store.js +272 -74
- package/dist/approval-ask-machine.d.ts +15 -0
- package/dist/approval-ask-machine.js +6 -0
- package/dist/approval-ask-retention.d.ts +136 -0
- package/dist/approval-ask-retention.js +102 -0
- package/dist/approval-card.d.ts +79 -19
- package/dist/approval-card.js +44 -13
- package/dist/approval-deny-reasons.d.ts +1 -1
- package/dist/approval.d.ts +9 -0
- package/dist/approval.js +24 -3
- package/dist/audit.d.ts +27 -10
- package/dist/audit.js +12 -16
- package/dist/bake-runner/main.js +3 -2
- package/dist/boot/deferred-sandbox-path-env.js +1 -1
- package/dist/boot/execution-env.js +9 -7
- package/dist/boot/execution-lane.js +11 -0
- package/dist/boot/leader.js +2 -0
- package/dist/boot/memory-boundary.js +2 -2
- package/dist/boot/memory-consolidation.d.ts +5 -4
- package/dist/boot/memory-consolidation.js +7 -6
- package/dist/boot/org-delivery.d.ts +28 -0
- package/dist/boot/org-delivery.js +20 -0
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +21 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +60 -81
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/session-faces.d.ts +1 -0
- package/dist/boot/session-faces.js +4 -2
- package/dist/boot/stage-02-web-admin-pull.js +4 -1
- package/dist/boot/stage-06-runners.d.ts +2 -0
- package/dist/boot/stage-06-runners.js +6 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -0
- package/dist/boot/stage-07-capability-layer.js +24 -3
- package/dist/boot/stage-08-reapers.d.ts +2 -0
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +3 -0
- package/dist/boot/stage-10-http-server.d.ts +3 -0
- package/dist/boot/stage-10-http-server.js +5 -6
- package/dist/boot/stores.js +2 -2
- package/dist/boot/web-admin.d.ts +4 -0
- package/dist/boot/web-admin.js +151 -63
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/brain.js +8 -3
- package/dist/breaker-key.d.ts +6 -0
- package/dist/breaker-key.js +29 -0
- package/dist/caller-text-echo.d.ts +9 -6
- package/dist/caller-text-echo.js +8 -11
- package/dist/capabilities/center-plugins.d.ts +7 -0
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +21 -0
- package/dist/capabilities/hands-lane.js +10 -0
- package/dist/capabilities/mcp-probe.d.ts +36 -4
- package/dist/capabilities/mcp-probe.js +27 -4
- package/dist/capabilities/scenarios.d.ts +63 -7
- package/dist/capabilities/scenarios.js +39 -7
- package/dist/capability-facts.d.ts +34 -4
- package/dist/capability-facts.js +28 -2
- package/dist/center-config-tier.d.ts +69 -0
- package/dist/center-config-tier.js +42 -0
- package/dist/checkpoint-identity.d.ts +16 -0
- package/dist/checkpoint-identity.js +19 -0
- package/dist/config-catalog.d.ts +17 -10
- package/dist/config-catalog.js +43 -28
- package/dist/config-d-migration.d.ts +3 -1
- package/dist/config-d-migration.js +2 -2
- package/dist/config-dir-trust.d.ts +114 -0
- package/dist/config-dir-trust.js +388 -0
- package/dist/config-invariants.d.ts +18 -5
- package/dist/config-invariants.js +35 -3
- package/dist/config-provider.d.ts +6 -1
- package/dist/config-provider.js +6 -2
- package/dist/config-types.d.ts +71 -14
- package/dist/config-types.js +8 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +121 -59
- package/dist/delegated-session-owner.d.ts +185 -0
- package/dist/delegated-session-owner.js +119 -0
- package/dist/device-protocol.d.ts +5 -3
- package/dist/device-protocol.js +1 -1
- package/dist/device-ws-hub.js +21 -2
- package/dist/device-ws-protocol.d.ts +70 -6
- package/dist/device-ws-protocol.js +50 -23
- package/dist/elicitation.js +3 -2
- package/dist/entry-problems.d.ts +34 -0
- package/dist/entry-problems.js +14 -0
- package/dist/execution-lane-caps.d.ts +49 -10
- package/dist/execution-lane-caps.js +43 -4
- package/dist/field-conflict.d.ts +43 -0
- package/dist/field-conflict.js +20 -0
- package/dist/fleet/fleet-bus.d.ts +80 -6
- package/dist/fleet/fleet-bus.js +41 -9
- package/dist/hooks/classic-stations.d.ts +10 -5
- package/dist/hooks/classic-stations.js +4 -4
- package/dist/hooks/command-spawn.d.ts +57 -0
- package/dist/hooks/command-spawn.js +139 -0
- package/dist/hooks/hook-llm.js +2 -2
- package/dist/hooks/hook-runner.d.ts +127 -25
- package/dist/hooks/hook-runner.js +668 -287
- package/dist/hooks/hook-source.d.ts +44 -0
- package/dist/hooks/hook-source.js +40 -0
- package/dist/http/active-run-conflict.d.ts +5 -13
- package/dist/http/active-run-conflict.js +17 -27
- package/dist/http/admission.js +103 -60
- package/dist/http/approval-sweeps.js +55 -18
- package/dist/http/caller-trust.d.ts +77 -0
- package/dist/http/caller-trust.js +143 -0
- package/dist/http/checkpoint-ctx.d.ts +16 -16
- package/dist/http/checkpoint-ctx.js +13 -10
- package/dist/http/claimed-run.d.ts +4 -3
- package/dist/http/device-placement.d.ts +2 -4
- package/dist/http/device-placement.js +2 -1
- package/dist/http/dispatch.js +10 -4
- package/dist/http/durable-run-leg.d.ts +4 -32
- package/dist/http/durable-run-leg.js +34 -10
- package/dist/http/owner-access.d.ts +56 -0
- package/dist/http/owner-access.js +42 -0
- package/dist/http/principal-gate.d.ts +48 -23
- package/dist/http/principal-gate.js +39 -12
- package/dist/http/readiness.d.ts +11 -5
- package/dist/http/readiness.js +10 -3
- package/dist/http/request-guards.d.ts +16 -23
- package/dist/http/request-guards.js +30 -15
- package/dist/http/resume-ctx-read.d.ts +2 -5
- package/dist/http/resume-ctx-read.js +7 -4
- package/dist/http/resume-legs.d.ts +3 -4
- package/dist/http/resume-legs.js +126 -69
- package/dist/http/retired-service-deps.d.ts +43 -0
- package/dist/http/retired-service-deps.js +18 -0
- package/dist/http/route-ctx.d.ts +44 -28
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/a2a-serve.js +3 -4
- package/dist/http/routes/admin-config-refresh.js +1 -1
- package/dist/http/routes/admin-drain.js +1 -1
- package/dist/http/routes/admin-memory-optout.js +5 -1
- package/dist/http/routes/adoption.js +2 -9
- package/dist/http/routes/approvals-assistant.js +49 -48
- package/dist/http/routes/background.js +3 -2
- package/dist/http/routes/capabilities.js +20 -4
- package/dist/http/routes/config-catalog.d.ts +3 -3
- package/dist/http/routes/config-catalog.js +1 -1
- package/dist/http/routes/devices.js +3 -17
- package/dist/http/routes/diagnostics.js +5 -1
- package/dist/http/routes/images.js +6 -7
- package/dist/http/routes/leader.js +1 -8
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +3 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-consolidation.js +1 -1
- package/dist/http/routes/memory-origin.js +1 -1
- package/dist/http/routes/memory-policy.d.ts +1 -1
- package/dist/http/routes/memory-policy.js +3 -2
- package/dist/http/routes/notify-wake.js +8 -21
- package/dist/http/routes/observability.js +2 -2
- package/dist/http/routes/principal-inbox.d.ts +39 -0
- package/dist/http/routes/principal-inbox.js +76 -0
- package/dist/http/routes/retention-ops.js +2 -9
- package/dist/http/routes/rules.js +5 -18
- package/dist/http/routes/runs.d.ts +5 -0
- package/dist/http/routes/runs.js +33 -106
- package/dist/http/routes/session-sync.js +15 -28
- package/dist/http/routes/sessions.js +30 -40
- package/dist/http/routes/side-query.js +6 -4
- package/dist/http/routes/tasks.js +50 -14
- package/dist/http/routes/workflows.d.ts +351 -0
- package/dist/http/routes/workflows.js +153 -119
- package/dist/http/send.d.ts +24 -3
- package/dist/http/send.js +14 -2
- package/dist/http/server.d.ts +17 -18
- package/dist/http/server.js +15 -13
- package/dist/http/stored-body-replay.js +17 -4
- package/dist/http/submit-ctx.js +4 -3
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +5 -4
- package/dist/http/wire-types.d.ts +10 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -1
- package/dist/lane-home-probe.d.ts +78 -9
- package/dist/lane-home-probe.js +78 -12
- package/dist/leader/endpoint.d.ts +4 -1
- package/dist/leader/endpoint.js +11 -10
- package/dist/limit-refusal.d.ts +17 -5
- package/dist/limit-refusal.js +4 -1
- package/dist/mcp-assertion-header.d.ts +31 -10
- package/dist/mcp-assertion-header.js +10 -3
- package/dist/mcp-tool-faces.js +1 -3
- package/dist/memory-project-layer.d.ts +37 -0
- package/dist/memory-project-layer.js +46 -0
- package/dist/memory-scope.d.ts +23 -16
- package/dist/memory-scope.js +24 -8
- package/dist/memory-sync-client.d.ts +27 -13
- package/dist/memory-sync-client.js +107 -18
- package/dist/model-entry-refusal.d.ts +9 -1
- package/dist/model-entry-refusal.js +6 -1
- package/dist/model-plane-face.d.ts +51 -0
- package/dist/model-plane-face.js +24 -0
- package/dist/model-provider.d.ts +5 -0
- package/dist/model-provider.js +4 -2
- package/dist/model-route-endpoint.d.ts +8 -11
- package/dist/model-route-endpoint.js +4 -7
- package/dist/model-route-env.d.ts +22 -0
- package/dist/model-route-env.js +2 -0
- package/dist/model-route-gap.d.ts +60 -30
- package/dist/model-route-gap.js +21 -15
- package/dist/observability/corrupt-read-seat.d.ts +7 -0
- package/dist/observability/corrupt-read-seat.js +9 -0
- package/dist/observability/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +18 -4
- package/dist/observability/fail-open.d.ts +35 -9
- package/dist/observability/fail-open.js +35 -9
- package/dist/observability/metrics.js +1 -0
- package/dist/observability/prompt-manifest.d.ts +3 -17
- package/dist/observability/prompt-manifest.js +45 -42
- package/dist/observability/tool-trace.js +9 -9
- package/dist/operator-ask.d.ts +10 -0
- package/dist/operator-ask.js +3 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
- package/dist/orchestration/workflow-agent-session-index.js +9 -13
- package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
- package/dist/orchestration/workflow-completion-inbox.js +13 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
- package/dist/orchestration/workflow-notify-journal.js +27 -34
- package/dist/orchestration/workflow-park-decision.d.ts +89 -0
- package/dist/orchestration/workflow-park-decision.js +118 -0
- package/dist/org-delivery-gate.d.ts +81 -0
- package/dist/org-delivery-gate.js +59 -0
- package/dist/panel-tool-names.d.ts +23 -0
- package/dist/panel-tool-names.js +8 -0
- package/dist/parked-decide.d.ts +39 -6
- package/dist/parked-decide.js +98 -51
- package/dist/parked-revive-surface.d.ts +9 -2
- package/dist/parked-revive-surface.js +11 -8
- package/dist/permission-rule-list.d.ts +1 -1
- package/dist/permission-rule-list.js +2 -2
- package/dist/plugins/approval-ask-store-file.d.ts +16 -1
- package/dist/plugins/approval-ask-store-file.js +21 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
- package/dist/plugins/approval-ask-store-memory.js +34 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
- package/dist/plugins/approval-ask-store-sql.js +37 -1
- package/dist/plugins/blob-backend.d.ts +38 -25
- package/dist/plugins/blob-backend.js +29 -19
- package/dist/plugins/breaker-state-sql.d.ts +2 -2
- package/dist/plugins/breaker-state-sql.js +4 -4
- package/dist/plugins/caching-session-store.d.ts +3 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
- package/dist/plugins/checkpoint-store-sql.js +20 -2
- package/dist/plugins/file-history-store-sql.d.ts +5 -2
- package/dist/plugins/file-history-store-sql.js +3 -1
- package/dist/plugins/file-run-store.d.ts +5 -13
- package/dist/plugins/file-run-store.js +9 -0
- package/dist/plugins/fork-routing-session-store.d.ts +9 -1
- package/dist/plugins/fork-routing-session-store.js +12 -1
- package/dist/plugins/host-platform.d.ts +2 -0
- package/dist/plugins/host-platform.js +2 -1
- package/dist/plugins/local-checkpoint-store.d.ts +2 -0
- package/dist/plugins/local-checkpoint-store.js +6 -1
- package/dist/plugins/local-session-store.d.ts +1 -1
- package/dist/plugins/local-session-window.d.ts +39 -0
- package/dist/plugins/local-session-window.js +54 -0
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
- package/dist/plugins/mailbox-store-sql.d.ts +30 -48
- package/dist/plugins/mailbox-store-sql.js +77 -52
- package/dist/plugins/memory-embedder-fingerprint.js +2 -1
- package/dist/plugins/memory-run-store.d.ts +5 -13
- package/dist/plugins/memory-run-store.js +8 -0
- package/dist/plugins/one-time-migrations.d.ts +110 -5
- package/dist/plugins/one-time-migrations.js +320 -8
- package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
- package/dist/plugins/org-delivery-quota-store.js +228 -0
- package/dist/plugins/pg-pool.js +39 -7
- package/dist/plugins/pg-session-storage.d.ts +17 -4
- package/dist/plugins/pg-session-storage.js +29 -6
- package/dist/plugins/remote-env-adb.d.ts +6 -12
- package/dist/plugins/remote-env-adb.js +12 -2
- package/dist/plugins/remote-env-k8s.d.ts +8 -12
- package/dist/plugins/remote-env-k8s.js +23 -5
- package/dist/plugins/remote-env-local-docker.d.ts +10 -15
- package/dist/plugins/remote-env-local-docker.js +12 -9
- package/dist/plugins/remote-env-ssh.d.ts +10 -16
- package/dist/plugins/remote-env-ssh.js +17 -11
- package/dist/plugins/run-store-sql.d.ts +16 -14
- package/dist/plugins/run-store-sql.js +22 -2
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-user-file.js +2 -2
- package/dist/plugins/session-epoch-anchor.d.ts +32 -6
- package/dist/plugins/session-epoch-anchor.js +2 -3
- package/dist/plugins/session-store.d.ts +18 -8
- package/dist/plugins/session-store.js +15 -4
- package/dist/plugins/sql-driver.d.ts +74 -11
- package/dist/plugins/sql-driver.js +210 -12
- package/dist/plugins/sql-errors.d.ts +23 -0
- package/dist/plugins/sql-errors.js +21 -0
- package/dist/plugins/store-backend.d.ts +36 -1
- package/dist/plugins/store-backend.js +40 -14
- package/dist/plugins/store-contracts.d.ts +42 -0
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/tidb-pool.d.ts +6 -1
- package/dist/plugins/tidb-pool.js +44 -10
- package/dist/plugins/tidb-session-storage.d.ts +3 -2
- package/dist/plugins/tidb-session-storage.js +2 -2
- package/dist/plugins/tidb-session-store.d.ts +24 -4
- package/dist/plugins/tidb-session-store.js +28 -4
- package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
- package/dist/plugins/workflow-journal-store-sql.js +19 -6
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
- package/dist/plugins/workflow-run-store-sql.js +50 -9
- package/dist/posture-source.d.ts +18 -10
- package/dist/posture-source.js +4 -1
- package/dist/project-identity.d.ts +45 -6
- package/dist/project-identity.js +162 -8
- package/dist/project-layer-key.d.ts +28 -0
- package/dist/project-layer-key.js +29 -0
- package/dist/question.js +3 -2
- package/dist/read-deny-entry.d.ts +25 -0
- package/dist/read-deny-entry.js +12 -0
- package/dist/read-face-posture.d.ts +37 -11
- package/dist/read-face-posture.js +32 -3
- package/dist/request-key-closure.d.ts +7 -0
- package/dist/request-key-closure.js +9 -1
- package/dist/request-value-closure.d.ts +5 -0
- package/dist/request-value-closure.js +11 -3
- package/dist/rule-entry-list.d.ts +26 -2
- package/dist/rule-entry-list.js +72 -12
- package/dist/rules-consent.js +3 -4
- package/dist/run-acceptance.d.ts +19 -3
- package/dist/run-acceptance.js +13 -1
- package/dist/run-local.js +36 -6
- package/dist/runs.d.ts +10 -9
- package/dist/runs.js +21 -3
- package/dist/runtime-caps-resolver.d.ts +16 -6
- package/dist/runtime-caps-resolver.js +18 -5
- package/dist/sandbox-pkg-source.d.ts +19 -1
- package/dist/sandbox-pkg-source.js +29 -7
- package/dist/security.d.ts +87 -9
- package/dist/security.js +52 -9
- package/dist/session-error-wire.d.ts +19 -2
- package/dist/session-error-wire.js +5 -1
- package/dist/session-titled-frame.d.ts +33 -0
- package/dist/session-titled-frame.js +4 -0
- package/dist/session-titler.d.ts +9 -1
- package/dist/session-titler.js +15 -4
- package/dist/spec-fields.js +21 -2
- package/dist/sql-engine-posture.d.ts +3 -1
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-mcp.d.ts +3 -3
- package/dist/task-mcp.js +7 -66
- package/dist/task-settings.d.ts +89 -88
- package/dist/task-settings.js +71 -63
- package/dist/text-bidi.d.ts +0 -4
- package/dist/text-bidi.js +1 -3
- package/dist/tighten-only-seats.d.ts +150 -0
- package/dist/tighten-only-seats.js +196 -0
- package/dist/tool-approval.d.ts +303 -87
- package/dist/tool-approval.js +623 -239
- package/dist/trace/core-keyset-guard.d.ts +104 -22
- package/dist/trace/exit-seat.d.ts +28 -0
- package/dist/trace/exit-seat.js +76 -0
- package/dist/trace/ledger-events.d.ts +3 -2
- package/dist/trace/project.d.ts +80 -12
- package/dist/trace/project.js +41 -28
- package/dist/trace/projection-table.d.ts +100 -0
- package/dist/trace/projection-table.js +130 -0
- package/dist/trace/redact.d.ts +54 -0
- package/dist/trace/redact.js +778 -152
- package/dist/trace/self-minted-wire.d.ts +345 -1
- package/dist/trace/self-minted-wire.js +117 -1
- package/dist/trace/wire-projection-faces.d.ts +298 -2
- package/dist/trace/wire-projection-faces.js +154 -13
- package/dist/url-refusal.js +2 -0
- package/dist/web-admin-client/apply-effective.d.ts +12 -0
- package/dist/web-admin-client/apply-effective.js +56 -5
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/facade.js +1 -1
- package/dist/web-admin-client/read-face.js +5 -9
- package/dist/web-admin-client/skills-mcp.js +4 -5
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
- package/package.json +7 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
2
|
-
import { canAskTransition } from "../approval-ask-machine.js";
|
|
2
|
+
import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
|
|
3
3
|
import { APPROVAL_ENDING_CAUSES, causeOfAskRow } from "../approval-deny-reasons.js";
|
|
4
4
|
import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
|
|
5
5
|
import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, readTriStateJsonValueColumn } from "./sql-json-column.js";
|
|
@@ -9,6 +9,11 @@ export function assertIntegralEpochMs(field, value) {
|
|
|
9
9
|
throw new TypeError(`approval-ask store: ${field} must be an integer epoch-ms (BIGINT column), got ${String(value)}`);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
export function assertReapBounds(cutoffMs, limit) {
|
|
13
|
+
assertIntegralEpochMs("cutoffMs", cutoffMs);
|
|
14
|
+
if (!Number.isSafeInteger(limit) || limit <= 0)
|
|
15
|
+
throw new Error(`approval-ask-store: reap limit must be a positive integer, got ${String(limit)}`);
|
|
16
|
+
}
|
|
12
17
|
export function assertIdempotencyKeyShape(key) {
|
|
13
18
|
if (key.length === 0 || key.length > 255 || key.trim() !== key) {
|
|
14
19
|
throw new Error(`invalid idempotency key: must be 1-255 chars with no leading/trailing whitespace (got ${JSON.stringify(key.slice(0, 32))}${key.length > 32 ? "…" : ""}, length ${key.length})`);
|
|
@@ -722,6 +727,37 @@ export class SqlApprovalAskStore {
|
|
|
722
727
|
await this.db.query(this.q(`DELETE FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ?`, `DELETE FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1`), [taskId]);
|
|
723
728
|
await this.db.query(this.q(`DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE task_id = ?`, `DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE task_id = $1`), [taskId]);
|
|
724
729
|
}
|
|
730
|
+
async reapSettledAsks(cutoffMs, limit, keepAnchors) {
|
|
731
|
+
assertReapBounds(cutoffMs, limit);
|
|
732
|
+
const terminal = [...ASK_TERMINAL];
|
|
733
|
+
const keep = [...keepAnchors];
|
|
734
|
+
const dialect = this.db.dialect;
|
|
735
|
+
let n = 0;
|
|
736
|
+
const statePh = terminal.map(() => ph(dialect, ++n)).join(", ");
|
|
737
|
+
const cutPh = ph(dialect, ++n);
|
|
738
|
+
const ANCHOR_COLUMNS = ["task_id", "session_id", "source_task_id"];
|
|
739
|
+
let keepSql = "";
|
|
740
|
+
let keepParams = [];
|
|
741
|
+
if (keep.length > 0 && dialect === "tidb") {
|
|
742
|
+
const list = keep.map(() => "?").join(", ");
|
|
743
|
+
keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} NOT IN (${list})`).join("");
|
|
744
|
+
keepParams = ANCHOR_COLUMNS.flatMap(() => keep);
|
|
745
|
+
}
|
|
746
|
+
else if (keep.length > 0) {
|
|
747
|
+
const arr = ph(dialect, ++n);
|
|
748
|
+
keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} <> ALL(${arr}::text[])`).join("");
|
|
749
|
+
keepParams = [keep];
|
|
750
|
+
}
|
|
751
|
+
const where = `state IN (${statePh}) AND provisional = 0 AND updated_at_ms < ${cutPh}${keepSql}`;
|
|
752
|
+
const params = [...terminal, cutoffMs, ...keepParams];
|
|
753
|
+
const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_ASK_TABLE} WHERE ${where} LIMIT ${limit}`, `DELETE FROM ${APPROVAL_ASK_TABLE} WHERE ctid IN (SELECT ctid FROM ${APPROVAL_ASK_TABLE} WHERE ${where} LIMIT ${limit})`), params);
|
|
754
|
+
return res.affected;
|
|
755
|
+
}
|
|
756
|
+
async reapEmptyBatches(cutoffMs, limit) {
|
|
757
|
+
assertReapBounds(cutoffMs, limit);
|
|
758
|
+
const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE updated_at_ms < ? AND NOT EXISTS (SELECT 1 FROM ${APPROVAL_ASK_TABLE} a WHERE a.batch_id = ${APPROVAL_BATCH_TABLE}.batch_id) LIMIT ${limit}`, `DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE ctid IN (SELECT b.ctid FROM ${APPROVAL_BATCH_TABLE} b WHERE b.updated_at_ms < $1 AND NOT EXISTS (SELECT 1 FROM ${APPROVAL_ASK_TABLE} a WHERE a.batch_id = b.batch_id) LIMIT ${limit})`), [cutoffMs]);
|
|
759
|
+
return res.affected;
|
|
760
|
+
}
|
|
725
761
|
async getBatch(batchId) {
|
|
726
762
|
const { rows } = await this.db.query(this.q(`SELECT ${BATCH_COLS} FROM ${APPROVAL_BATCH_TABLE} WHERE batch_id = ?`, `SELECT ${BATCH_COLS} FROM ${APPROVAL_BATCH_TABLE} WHERE batch_id = $1`), [batchId]);
|
|
727
763
|
return rows[0] ? mapBatchRow(rows[0]) : null;
|
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
import type { Pool as MysqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
|
-
import { type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
|
|
3
|
+
import { type SqlEngineFacts, type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
|
|
4
|
+
import { type Logger } from "../observability/logger.js";
|
|
4
5
|
/**
|
|
5
6
|
* 文本协议的 blob 膨胀律 —— mysql2 `query()` 把 Buffer 参数写成 `X'<hex>'`,一字节两个字符,所以承载一个 blob 的语句至少是它的 2 倍。
|
|
6
7
|
* 包墙折成「能装下多大的 blob」只用这一只常量,推导里不手写 `/ 2`。
|
|
7
8
|
*/
|
|
8
9
|
export declare const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
|
|
9
10
|
/**
|
|
10
|
-
* 包墙下能装多大的 blob:语句里的载荷量({@link packetWallStatementBytes}
|
|
11
|
-
*
|
|
11
|
+
* 包墙下能装多大的 blob:语句里的载荷量({@link packetWallStatementBytes})再按 {@link SQL_TEXT_PROTOCOL_BLOB_INFLATION} 折半。
|
|
12
|
+
* blob 以十六进制字面进语句、有 2× —— 膨胀律是 blob 腿自己的,余量不是。
|
|
12
13
|
*/
|
|
13
14
|
export declare function packetWallBlobCap(maxPacketBytes: number): number;
|
|
14
|
-
/**
|
|
15
|
-
* **事实本身还没建立**时 MySQL 协议方言的缺省帽(池上还没借出过连接,或最近一次连接初始化被拒:`SqlDriver.facts === null`)= 事实缺席时的墙
|
|
16
|
-
* (`sql-driver.ts` `SQL_FACTS_ABSENT_WALLS`,判官 `sqlWallsOf`;提交受理口读同一只)走下面同一个推导 —— 不是第二份落点,只是那个推导在这组墙上的值
|
|
17
|
-
* (今天 = TiDB 缺省行墙走 blob 行的折算 6 MiB − 1 KiB:最矮的包墙缺省 16 MiB 扣语句余量再折半仍近 8 MiB,行腿更矮)。
|
|
18
|
-
*/
|
|
19
|
-
export declare const SQL_BLOB_FACTS_ABSENT_CAP_BYTES: number;
|
|
20
15
|
/**
|
|
21
16
|
* SQL 快照 blob 的**缺省**帽(运维没设 `SNAPSHOT_BLOB_SQL_MAX_BYTES` 时)的唯一推导 —— 入参是判官 `sqlWallsOf` 给的墙(不读事实、不判缺席、不看方言),**两堵墙取最小**:
|
|
22
17
|
* 行墙腿(`SqlEngineFacts.maxRowBytes`,TiDB:{@link rowWallPayloadBytes} 的 `blob` = 行墙 − blob 行的余量 1 KiB —— 按这一行的真实形算,不借 JSON 提交体行的 64 KiB)
|
|
23
18
|
* 与包墙腿(`SqlEngineFacts.maxPacketBytes`,InnoDB 与 TiDB 都有:{@link packetWallBlobCap} = ⌊(包墙 − 1 KiB 语句余量) ÷ 2⌋)。余量只在 `sql-driver.ts` 定义一次
|
|
24
19
|
* (墙 × 行的形各一只),提交体受理口(`http/request-guards.ts` `refuseSubmitBody`)读同一张表的 `json` 那一只,不各抄。
|
|
25
|
-
* 墙的 `null` 只表示「这堵墙不存在」;两堵都不存在(PG ——
|
|
26
|
-
*
|
|
20
|
+
* 墙的 `null` 只表示「这堵墙不存在」;两堵都不存在(PG —— 扩展协议无这两堵墙 —— 或没有 SQL 后端)⇒ `undefined` 不设帽。
|
|
21
|
+
* 「事实本身缺席」不是这里的入参:店的写前判读的是借出连接自己的事实(恒已建立,见 {@link SqlConnectionReading}),缺席的读法只在提交受理口读池级读口时出现(判官 `sqlWallsOf` 给墙)。
|
|
22
|
+
* 显式旋钮是运维的策略帽,在本推导之上(见 {@link sqlBlobCapBytes})。
|
|
27
23
|
*
|
|
28
24
|
* 两堵墙的真库读数(clay 拍 a+c,2026-07-27;k3s8 tidb-test v7.5.1 默认配置探针):7 MiB / 31 MiB = `entry too large`(行墙,默认 6291456)、
|
|
29
25
|
* 32 MiB = `max_allowed_packet`(包墙,64 MiB ÷ ~2× 文本协议膨胀)。帽=诚实拒绝:超限 blob 在 query 之前 typed 拒(消费面 HTTP 413 / capture warn
|
|
@@ -43,15 +39,29 @@ export interface SqlBlobCapReading {
|
|
|
43
39
|
*/
|
|
44
40
|
export declare function sqlBlobDefaultCapOf(walls: SqlWallsReading): SqlBlobCapReading | undefined;
|
|
45
41
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
42
|
+
* 一条**借出连接**的引擎读口:方言(装配事实)+ 它自己那次初始化回读的事实。与 {@link SqlEngineReading} 的差别只有一处 —— `facts` 恒在:连接是借出时才有的,
|
|
43
|
+
* 借出即已初始化({@link connectJudged} 交给判决的就是借出连接的 `sqlConnectionFacts`),所以「事实缺席」在这条读法上**不可表示**(编译期拒,不是运行期一个没人走的分支)。
|
|
44
|
+
* 事实缺席(池上还没建立过、或最近一次初始化被拒)只出现在读**池级**读口的地方 —— 提交受理口 —— 那一格的墙由判官 `sqlWallsOf` 给,不经过帽的推导。
|
|
49
45
|
*/
|
|
50
|
-
export
|
|
46
|
+
export interface SqlConnectionReading {
|
|
47
|
+
readonly dialect: SqlEngineReading["dialect"];
|
|
48
|
+
readonly facts: SqlEngineFacts;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 一个 SQL 快照 blob 此刻的帽 —— **显式旋钮 ?? 判官给的墙走缺省推导**的唯一一只函数。唯一读者 = 店的写前判({@link SqlBlobBackend.putBlob}),传的读口({@link SqlConnectionReading})是
|
|
52
|
+
* **将要执行写入的那条连接**的(方言 + 它自己那次初始化回读的事实,墙经与提交受理口同一只判官 `sqlWallsOf`):包墙是连接的会话值,所以判决只在写入的那条连接上做,
|
|
53
|
+
* HTTP 面不替店另算一份帽。
|
|
54
|
+
*/
|
|
55
|
+
export declare function sqlBlobCapOf(explicitCapBytes: number | undefined, connection: SqlConnectionReading): SqlBlobCapReading | undefined;
|
|
51
56
|
/** {@link sqlBlobCapOf} 的字节投影(同一个推导,不另读)。 */
|
|
52
|
-
export declare function sqlBlobCapBytes(explicitCapBytes: number | undefined,
|
|
57
|
+
export declare function sqlBlobCapBytes(explicitCapBytes: number | undefined, connection: SqlConnectionReading): number | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* 这一拒是不是**那条连接的包墙**定的:帽是缺省推导出的(不是运维旋钮)、那条连接有包墙、且包墙腿的折算恰是这个帽(包墙腿是最矮的一腿,或与行墙腿并列)。
|
|
60
|
+
* 只有这种拒才值得问「这条连接过时了吗」({@link connectJudged}) —— 旋钮的拒、行墙的拒与连接新旧无关。`walls` = 判出这个帽的那一份墙({@link sqlWallsOf})。
|
|
61
|
+
*/
|
|
62
|
+
export declare function capBoundByPacketWall(cap: SqlBlobCapReading, walls: SqlWallsReading): boolean;
|
|
53
63
|
/**
|
|
54
|
-
* 一次 blob 超帽的拒句 —— **唯一的组句器**(
|
|
64
|
+
* 一次 blob 超帽的拒句 —— **唯一的组句器**(店的 typed {@link BlobTooLargeError} 调它;`PUT /sync/blobs` 的 413 原样转出它的句子):句子按帽的出处({@link SqlBlobCapReading.source})
|
|
55
65
|
* 分三形,不把假定说成库的上限、也不把运维的旋钮说成库的上限。
|
|
56
66
|
* · `operator` —— 帽是运维设的 `SNAPSHOT_BLOB_SQL_MAX_BYTES`;
|
|
57
67
|
* · `reported` —— 帽由库报的两堵墙推出(单行上限与包墙÷文本协议膨胀的较小者);
|
|
@@ -97,21 +107,24 @@ export interface BlobBackend {
|
|
|
97
107
|
export declare class SqlBlobBackend implements BlobBackend {
|
|
98
108
|
private readonly dialect;
|
|
99
109
|
private readonly pool;
|
|
100
|
-
/** Explicit per-blob cap (the operator's `SNAPSHOT_BLOB_SQL_MAX_BYTES`); absent ⇒ the derivation in {@link
|
|
110
|
+
/** Explicit per-blob cap (the operator's `SNAPSHOT_BLOB_SQL_MAX_BYTES`); absent ⇒ the derivation in {@link sqlBlobCapOf} over the walls of the connection each write borrows. */
|
|
101
111
|
private readonly explicitCapBytes;
|
|
102
|
-
/** This pool's engine reading (dialect + live facts, from the pool's own driver) — the one source the cap's walls are read from. */
|
|
103
|
-
private readonly engine;
|
|
104
112
|
/** Blob-domain table ([ref] blob 域隔离:file-history 域用自己的表,reap/GC 不跨域)。代码内字面量,非用户输入。 */
|
|
105
113
|
private readonly table;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
private
|
|
114
|
+
/** The write path's connection source: every `putBlob` borrows ONE connection from it (through {@link connectJudged}), judges on that connection's walls, writes on it. */
|
|
115
|
+
private readonly db;
|
|
116
|
+
/** Where a stale connection's replacement is logged ({@link connectJudged}); the boot assembly passes the service logger. */
|
|
117
|
+
private readonly log;
|
|
118
|
+
constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string, log?: Pick<Logger, "warn">);
|
|
110
119
|
/** The created_at-refreshing content-addressed upsert — VERBATIM the tidb/pg stores' `putBlobRow`. Refreshes
|
|
111
120
|
* created_at even on a dedup hit so a re-used/just-written blob is "touched=now" and GC-grace-protected.
|
|
112
121
|
* (a): an over-cap blob is REJECTED (typed {@link BlobTooLargeError}) BEFORE the driver ever sees the bytes —
|
|
113
122
|
* the alternative is an inscrutable packet-limit error deep in the driver, and on the HTTP face a 502 that
|
|
114
|
-
* invites the peer to retry a blob that can never land.
|
|
123
|
+
* invites the peer to retry a blob that can never land.
|
|
124
|
+
* 🔴 The cap is judged ON THE CONNECTION THAT WRITES (S-1746): `max_allowed_packet` is a session value, fixed per
|
|
125
|
+
* connection, so the write borrows one connection through {@link connectJudged}, which reads ITS walls, judges, and — when
|
|
126
|
+
* the refusal is that connection's packet wall and the server's global wall is higher — replaces the stale connection and
|
|
127
|
+
* judges once more; the INSERT runs on the connection that passed. Never a pool-level reading, never a bare `pool.query`. */
|
|
115
128
|
putBlob(hash: string, bytes: Uint8Array): Promise<void>;
|
|
116
129
|
/** The bytes for `hash` (undefined if absent) — VERBATIM the tidb/pg stores' getBlob SELECT. A pool/query error
|
|
117
130
|
* PROPAGATES (a transient DB fault is loud, not a silent "missing blob" — parity with the MinIO backend's throw). */
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
|
|
3
|
-
import {
|
|
3
|
+
import { connectJudged, mysqlDriver, packetWallStatementBytes, pgDriver, rowWallPayloadBytes, sqlWallsOf } from "./sql-driver.js";
|
|
4
|
+
import { createLogger } from "../observability/logger.js";
|
|
4
5
|
const BLOB_GC_GRACE_MS = 3_600_000;
|
|
5
6
|
export const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
|
|
6
7
|
export function packetWallBlobCap(maxPacketBytes) {
|
|
7
8
|
return Math.floor(packetWallStatementBytes(maxPacketBytes) / SQL_TEXT_PROTOCOL_BLOB_INFLATION);
|
|
8
9
|
}
|
|
9
|
-
export const SQL_BLOB_FACTS_ABSENT_CAP_BYTES = sqlBlobDefaultCapBytes(SQL_FACTS_ABSENT_WALLS);
|
|
10
10
|
export function sqlBlobDefaultCapBytes(walls) {
|
|
11
11
|
return sqlBlobDefaultCapOf(walls)?.capBytes;
|
|
12
12
|
}
|
|
@@ -18,11 +18,14 @@ export function sqlBlobDefaultCapOf(walls) {
|
|
|
18
18
|
legs.push({ capBytes: packetWallBlobCap(walls.maxPacketBytes), source: walls.sources.maxPacketBytes });
|
|
19
19
|
return legs.reduce((best, leg) => (best === undefined || leg.capBytes < best.capBytes ? leg : best), undefined);
|
|
20
20
|
}
|
|
21
|
-
export function sqlBlobCapOf(explicitCapBytes,
|
|
22
|
-
return explicitCapBytes !== undefined ? { capBytes: explicitCapBytes, source: "operator" } : sqlBlobDefaultCapOf(sqlWallsOf(
|
|
21
|
+
export function sqlBlobCapOf(explicitCapBytes, connection) {
|
|
22
|
+
return explicitCapBytes !== undefined ? { capBytes: explicitCapBytes, source: "operator" } : sqlBlobDefaultCapOf(sqlWallsOf(connection));
|
|
23
23
|
}
|
|
24
|
-
export function sqlBlobCapBytes(explicitCapBytes,
|
|
25
|
-
return sqlBlobCapOf(explicitCapBytes,
|
|
24
|
+
export function sqlBlobCapBytes(explicitCapBytes, connection) {
|
|
25
|
+
return sqlBlobCapOf(explicitCapBytes, connection)?.capBytes;
|
|
26
|
+
}
|
|
27
|
+
export function capBoundByPacketWall(cap, walls) {
|
|
28
|
+
return cap.source !== "operator" && walls.maxPacketBytes !== null && packetWallBlobCap(walls.maxPacketBytes) === cap.capBytes;
|
|
26
29
|
}
|
|
27
30
|
export function sqlBlobCapRefusalText(byteLen, cap) {
|
|
28
31
|
const objectStorage = "configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs";
|
|
@@ -64,27 +67,34 @@ export class SqlBlobBackend {
|
|
|
64
67
|
dialect;
|
|
65
68
|
pool;
|
|
66
69
|
explicitCapBytes;
|
|
67
|
-
engine;
|
|
68
70
|
table;
|
|
69
|
-
|
|
71
|
+
db;
|
|
72
|
+
log;
|
|
73
|
+
constructor(dialect, pool, maxBytes, table = "snapshot_blob", log) {
|
|
70
74
|
this.dialect = dialect;
|
|
71
75
|
this.pool = pool;
|
|
72
76
|
this.explicitCapBytes = maxBytes;
|
|
73
77
|
this.table = table;
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
cap() {
|
|
77
|
-
return sqlBlobCapOf(this.explicitCapBytes, this.engine);
|
|
78
|
+
this.db = dialect === "tidb" ? mysqlDriver(pool) : pgDriver(pool);
|
|
79
|
+
this.log = log ?? createLogger();
|
|
78
80
|
}
|
|
79
81
|
async putBlob(hash, bytes) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const judged = await connectJudged(this.db, (facts) => {
|
|
83
|
+
const connection = { dialect: this.dialect, facts };
|
|
84
|
+
const cap = sqlBlobCapOf(this.explicitCapBytes, connection);
|
|
85
|
+
if (cap === undefined || bytes.byteLength <= cap.capBytes)
|
|
86
|
+
return undefined;
|
|
87
|
+
return { cap, packetBound: capBoundByPacketWall(cap, sqlWallsOf(connection)) };
|
|
88
|
+
}, (refusal) => refusal.packetBound, this.log);
|
|
89
|
+
if (judged.refusal !== undefined)
|
|
90
|
+
throw new BlobTooLargeError(bytes.byteLength, judged.refusal.cap);
|
|
91
|
+
try {
|
|
92
|
+
await judged.conn.query(this.dialect === "tidb"
|
|
93
|
+
? `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`
|
|
94
|
+
: `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
|
|
85
95
|
}
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
finally {
|
|
97
|
+
judged.conn.release();
|
|
88
98
|
}
|
|
89
99
|
}
|
|
90
100
|
async getBlob(hash) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* core's `createCircuitBreakerBrain` defaults to a per-PROCESS Map → on a multi-replica deployment
|
|
8
8
|
* every replica re-discovers a provider outage independently (slow fleet-wide reaction). This backs
|
|
9
|
-
* the breaker with the shared `
|
|
9
|
+
* the breaker with the shared `breaker_state` table so a trip on one replica is learned by the others.
|
|
10
10
|
*
|
|
11
11
|
* IMPORTANT — `BreakerState.get/set` are **synchronous** (core calls them on the hot path of every
|
|
12
12
|
* brain invocation; a DB round-trip per call is untenable). So this is a **write-through cache**:
|
|
@@ -40,7 +40,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
|
|
|
40
40
|
import type { Pool as PgPool, PoolClient } from "pg";
|
|
41
41
|
import type { BreakerSnapshot, BreakerState } from "@sema-agent/core";
|
|
42
42
|
import { type SqlDriver } from "./sql-driver.js";
|
|
43
|
-
/** PG translation of the tidb-pool.ts `
|
|
43
|
+
/** PG translation of the tidb-pool.ts `breaker_state` DDL (DATETIME(3)→TIMESTAMPTZ(3)). Disjoint from other
|
|
44
44
|
* stores' tables, so a self-contained ensureSchema is safe (no central-schema shadowing). TiDB's DDL lives
|
|
45
45
|
* centrally in tidb-pool.ts (no PG-side twin needed there — production wires ONE ensureTidbSchema). */
|
|
46
46
|
export declare const PG_BREAKER_STATE_SCHEMA: string[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
2
2
|
export const PG_BREAKER_STATE_SCHEMA = [
|
|
3
|
-
`CREATE TABLE IF NOT EXISTS
|
|
3
|
+
`CREATE TABLE IF NOT EXISTS breaker_state (
|
|
4
4
|
breaker_key VARCHAR(190) COLLATE "C" NOT NULL,
|
|
5
5
|
phase VARCHAR(16) COLLATE "C" NOT NULL,
|
|
6
6
|
failures INTEGER NOT NULL DEFAULT 0,
|
|
@@ -37,8 +37,8 @@ export class SqlBreakerState {
|
|
|
37
37
|
}
|
|
38
38
|
async writeThrough(key, snap) {
|
|
39
39
|
try {
|
|
40
|
-
await this.db.query(this.q("INSERT INTO
|
|
41
|
-
"ON DUPLICATE KEY UPDATE phase=VALUES(phase), failures=VALUES(failures), opened_at=VALUES(opened_at), updated_at=VALUES(updated_at)", "INSERT INTO
|
|
40
|
+
await this.db.query(this.q("INSERT INTO breaker_state (breaker_key, phase, failures, opened_at, updated_at) VALUES (?,?,?,?,?) " +
|
|
41
|
+
"ON DUPLICATE KEY UPDATE phase=VALUES(phase), failures=VALUES(failures), opened_at=VALUES(opened_at), updated_at=VALUES(updated_at)", "INSERT INTO breaker_state (breaker_key, phase, failures, opened_at, updated_at) VALUES ($1,$2,$3,$4,$5) " +
|
|
42
42
|
"ON CONFLICT (breaker_key) DO UPDATE SET phase=EXCLUDED.phase, failures=EXCLUDED.failures, opened_at=EXCLUDED.opened_at, updated_at=EXCLUDED.updated_at"), [key, snap.phase, snap.failures, snap.openedAt != null ? new Date(snap.openedAt) : null, new Date()]);
|
|
43
43
|
this.pending.delete(key);
|
|
44
44
|
if (this.writeFailStreak > 0) {
|
|
@@ -60,7 +60,7 @@ export class SqlBreakerState {
|
|
|
60
60
|
try {
|
|
61
61
|
const pendingAtStart = new Set(this.pending);
|
|
62
62
|
const guarded = (key) => this.pending.has(key) || pendingAtStart.has(key);
|
|
63
|
-
const { rows } = await this.db.query("SELECT breaker_key, phase, failures, opened_at FROM
|
|
63
|
+
const { rows } = await this.db.query("SELECT breaker_key, phase, failures, opened_at FROM breaker_state");
|
|
64
64
|
const seen = new Set();
|
|
65
65
|
for (const r of rows) {
|
|
66
66
|
const key = String(r.breaker_key);
|
|
@@ -4,6 +4,7 @@ import type { StagingHandle } from "../session-sync-kernel.js";
|
|
|
4
4
|
import { type PlacementAcquireOpts } from "./session-placement.js";
|
|
5
5
|
import type { InstructionSourceStore } from "../instruction-source-notice.js";
|
|
6
6
|
import type { SessionFaceReader } from "../session-incarnation.js";
|
|
7
|
+
import type { OwnerlessClaim } from "../delegated-session-owner.js";
|
|
7
8
|
export interface CachingOptions {
|
|
8
9
|
/** Idle seconds a woken session is kept warm. 0 disables caching (pure passthrough). */
|
|
9
10
|
ttlSec: number;
|
|
@@ -59,6 +60,8 @@ export declare class CachingSessionStore implements SessionStore {
|
|
|
59
60
|
* (缓存的会话活得比「另一只店删掉又重建了这一行」长)。🔴 必须转发:k8s 工作区空间的每 run 读与起服门读的就是本类。 */
|
|
60
61
|
readonly incarnationOf?: SessionStore["incarnationOf"];
|
|
61
62
|
readonly register?: (sessionId: string, owner: string | null) => Promise<void>;
|
|
63
|
+
/** S-1770 委派继承律的认领口,纯转发(写的是行上的属主那一格 —— 只 CAS 子行自己,不碰暖缓存里的会话;停驻挂点在默认部署上读的就是本类 —— 与 `placementOf` 同转发同缺:内店记录委派树 ⇒ 两只都在)。 */
|
|
64
|
+
readonly claimOwnerless?: OwnerlessClaim;
|
|
62
65
|
/** Delegated SessionRepo seam (§0.5) — list/fork/delete operate on the DURABLE tables directly (they bypass the
|
|
63
66
|
* warm cache by design; a fork/delete must read/write durable state, not a possibly-stale cached view). */
|
|
64
67
|
readonly listSessions?: (opts: {
|
|
@@ -10,6 +10,7 @@ export class CachingSessionStore {
|
|
|
10
10
|
sessionFaceOf;
|
|
11
11
|
incarnationOf;
|
|
12
12
|
register;
|
|
13
|
+
claimOwnerless;
|
|
13
14
|
listSessions;
|
|
14
15
|
fork;
|
|
15
16
|
deleteSession;
|
|
@@ -57,6 +58,8 @@ export class CachingSessionStore {
|
|
|
57
58
|
this.incarnationOf = inner.incarnationOf.bind(inner);
|
|
58
59
|
if (ownerAware.register)
|
|
59
60
|
this.register = ownerAware.register.bind(inner);
|
|
61
|
+
if (ownerAware.claimOwnerless)
|
|
62
|
+
this.claimOwnerless = ownerAware.claimOwnerless.bind(inner);
|
|
60
63
|
if (ownerAware.listSessions)
|
|
61
64
|
this.listSessions = ownerAware.listSessions.bind(inner);
|
|
62
65
|
if (ownerAware.sessionSummaryOf)
|
|
@@ -37,6 +37,14 @@ export declare function tokenFingerprint(token: string): string;
|
|
|
37
37
|
* 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。 */
|
|
38
38
|
export interface PendingCheckpoint extends ParkRowFacts {
|
|
39
39
|
sessionId: string;
|
|
40
|
+
/**
|
|
41
|
+
* 这道停驻门的**非秘密稳定身份**(core `Checkpoint.checkpointId`,引擎每次停驻铸)—— 三处 durable 读面(本行 = `GET /v1/approvals` 的 `pending[]`
|
|
42
|
+
* 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的 `inbox[]`)共用的 **join 键**,也是 409 载体 A
|
|
43
|
+
* `pendingGate.checkpointId` / `terminal.paused.checkpointId` 的同一个值。**在场就是可信的那一个值,缺席就是缺席**:读法唯一 = `checkpoint-identity.ts` 的
|
|
44
|
+
* `durableRowIdentity`(过信任边界 —— 不含 token、有界),没有它的行(core 5.42.0 之前停驻的老行 / 没过边界的值)**照列**、键缺席(这一行没有可 join 的身份)、
|
|
45
|
+
* 每行计一次 `server.approvals.durable-row-checkpoint-id-missing`。两只店各读自己手里的那一份(SQL 孪生读 `checkpoint` 列的成员,LOCAL 孪生读 blob)。
|
|
46
|
+
*/
|
|
47
|
+
checkpointId?: string;
|
|
40
48
|
scope: string;
|
|
41
49
|
toolName: string | null;
|
|
42
50
|
toolCallId: string | null;
|
|
@@ -705,6 +713,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
705
713
|
* {@link SqlCheckpointStore.pendingRowQuery};语义与三态全文见 {@link PendingRowBinding} 顶注。
|
|
706
714
|
* `scope` 传 `""`(空串)历史上与缺席同义(旧形 `scope ?` 真值判),这里逐字保留。
|
|
707
715
|
*/
|
|
716
|
+
/**
|
|
717
|
+
* 每一只**待决** checkpoint 的会话(去重)—— 审批账本留存律的挂靠源(`approval-ask-retention.ts`:待决 checkpoint = 一个还能动的停驻,
|
|
718
|
+
* 它的会话就是停驻那条腿上问行的 `source_task_id`)。**不按门型过滤**:运维队列 {@link listPending} 不列 `gate_kind` 读不出的行,
|
|
719
|
+
* 而决断口的令牌回落仍认它 —— 留存若跟着队列漏掉它,就会删掉一只还能被决的停驻的问。只读 `session_id`,不读 blob。
|
|
720
|
+
*/
|
|
721
|
+
listPendingSessionIds(): Promise<string[]>;
|
|
708
722
|
findPendingTokenBySession(sessionId: string, scope?: string, binding?: PendingRowBinding): Promise<CheckpointToken | null>;
|
|
709
723
|
/**
|
|
710
724
|
* 🔴 [ref] 车5 §9 C4 —— 流内审批对账收敛器判据 1 的**窄读口**(店面承载;收敛器本体不在本车)。
|
|
@@ -6,6 +6,7 @@ import { redactDeep, redactSecrets } from "../trace/redact.js";
|
|
|
6
6
|
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, RuleOfferRawSchema, backfillLegacyBatchMemberKind, parkRowFacts, screenUncoveredDetailSeat } from "../approval-card.js";
|
|
7
7
|
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
8
8
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
|
+
import { durableRowIdentity } from "../checkpoint-identity.js";
|
|
9
10
|
import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
|
|
10
11
|
import { isDupKeyError, isMissingColumnError, rowTooLargeOrSelf } from "./sql-errors.js";
|
|
11
12
|
import { MANAGED_RETENTION } from "./retention-store-sql.js";
|
|
@@ -183,6 +184,18 @@ function readParkRowFactsCell(cell) {
|
|
|
183
184
|
return parkRowFacts(pendingAction);
|
|
184
185
|
}
|
|
185
186
|
const PARK_ROW_PENDING_ACTION = { key: "pendingAction", as: "pending_action" };
|
|
187
|
+
const PARK_ROW_CHECKPOINT_ID = { key: "checkpointId", as: "checkpoint_id" };
|
|
188
|
+
const readCheckpointIdMemberBody = (body) => (typeof body === "string" ? { value: body } : { problem: "checkpointId is not a JSON string" });
|
|
189
|
+
function readCheckpointIdCell(cell) {
|
|
190
|
+
try {
|
|
191
|
+
return decodeNullableJsonColumn(cell, readCheckpointIdMemberBody, "checkpoint.checkpoint $.checkpointId (operator queue row)");
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
if (!isJsonColumnBodyProblem(e))
|
|
195
|
+
throw e;
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
186
199
|
const PARK_ROW_GATE = { key: "gate", as: "park_gate" };
|
|
187
200
|
function readParkGateCell(cell) {
|
|
188
201
|
try {
|
|
@@ -581,9 +594,9 @@ export class SqlCheckpointStore {
|
|
|
581
594
|
}
|
|
582
595
|
async listPending(scope) {
|
|
583
596
|
const d = this.db.dialect;
|
|
584
|
-
const base = `SELECT c.session_id, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
|
|
597
|
+
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, ` +
|
|
585
598
|
`${jsonColumnText(d, "risk_descriptor")}, ${jsonColumnText(d, "rule_suggestions")}, c.has_bidi_controls, c.created_at_ms, c.deadline, c.gate_kind, ta.task_id, ` +
|
|
586
|
-
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_GATE)} ` +
|
|
599
|
+
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_GATE)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)} ` +
|
|
587
600
|
"FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
|
|
588
601
|
const { rows } = scope
|
|
589
602
|
? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
|
|
@@ -599,6 +612,7 @@ export class SqlCheckpointStore {
|
|
|
599
612
|
const approvalCard = parkedApprovalCard(parkedGateOfRow(r));
|
|
600
613
|
return [{
|
|
601
614
|
sessionId: String(r.session_id),
|
|
615
|
+
...durableRowIdentity("approvals-queue", readCheckpointIdCell(r[PARK_ROW_CHECKPOINT_ID.as]), r.token),
|
|
602
616
|
scope: String(r.scope),
|
|
603
617
|
toolName,
|
|
604
618
|
toolCallId,
|
|
@@ -628,6 +642,10 @@ export class SqlCheckpointStore {
|
|
|
628
642
|
}
|
|
629
643
|
return out;
|
|
630
644
|
}
|
|
645
|
+
async listPendingSessionIds() {
|
|
646
|
+
const { rows } = await this.db.query("SELECT DISTINCT session_id FROM checkpoint WHERE status='pending'", []);
|
|
647
|
+
return rows.map((r) => String(r.session_id));
|
|
648
|
+
}
|
|
631
649
|
async findPendingTokenBySession(sessionId, scope, binding) {
|
|
632
650
|
const { sql, params } = this.pendingRowQuery("token", sessionId, scope || undefined, binding);
|
|
633
651
|
const { rows } = await this.db.query(sql, params);
|
|
@@ -2,6 +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 } from "@sema-agent/core";
|
|
4
4
|
import { type BlobBackend } from "./blob-backend.js";
|
|
5
|
+
import type { SyncBlobPutResult } from "./store-backend.js";
|
|
5
6
|
import { type SqlDriver } from "./sql-driver.js";
|
|
6
7
|
/** The file-history blob domain's SQL table (BlobBackend `table`/MinIO index binding + the A7 assert). */
|
|
7
8
|
export declare const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
|
|
@@ -147,8 +148,10 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
|
|
|
147
148
|
* Domain-isolated by construction (this store's blob table only — [ref] D1). Reaper-driven. */
|
|
148
149
|
sweepOrphanBlobs(): Promise<number>;
|
|
149
150
|
/** Upload ONE content-addressed blob ahead of an importHistory (§5 streamed negotiation). Store-level
|
|
150
|
-
* integrity re-check (the route pre-verifies too — the internal caller might not). Never-throw.
|
|
151
|
-
|
|
151
|
+
* integrity re-check (the route pre-verifies too — the internal caller might not). Never-throw.
|
|
152
|
+
* The SQL blob judge's refusal ({@link BlobTooLargeError}, judged on the connection that would have written the blob)
|
|
153
|
+
* keeps its own code — the route answers it 413 (the sentence names that connection's walls), every other write failure 502 ("retry"). */
|
|
154
|
+
putBlob(hash: string, bytes: Uint8Array): Promise<SyncBlobPutResult>;
|
|
152
155
|
getBlob(hash: string): Promise<Uint8Array | undefined>;
|
|
153
156
|
/** The subset of `hashes` already present — one SQL INDEX probe (the import presence pre-check). */
|
|
154
157
|
hasBlobs(hashes: string[]): Promise<Set<string>>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
|
|
3
|
-
import { SqlBlobBackend } from "./blob-backend.js";
|
|
3
|
+
import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
6
6
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
@@ -594,6 +594,8 @@ export class SqlFileHistoryStore {
|
|
|
594
594
|
return { ok: true };
|
|
595
595
|
}
|
|
596
596
|
catch (err) {
|
|
597
|
+
if (err instanceof BlobTooLargeError)
|
|
598
|
+
return { ok: false, error: { code: err.code, message: err.message, wallSource: err.cap.source } };
|
|
597
599
|
return { ok: false, error: { code: "snapshot_failed", message: `putBlob ${hash} write/upload failed: ${discloseCaughtFailure(err)}` } };
|
|
598
600
|
}
|
|
599
601
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
2
|
import type { TaskStatus } from "@sema-agent/core";
|
|
3
|
-
import { StoreBootRefusalError, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
3
|
+
import { StoreBootRefusalError, type FenceLanded, type InProcessClaimFence, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
4
4
|
import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
|
|
5
5
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
6
6
|
import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
|
|
@@ -22,7 +22,7 @@ import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState }
|
|
|
22
22
|
export declare class RunStoreStrictHydrateError extends StoreBootRefusalError {
|
|
23
23
|
readonly name = "RunStoreStrictHydrateError";
|
|
24
24
|
}
|
|
25
|
-
export declare class FileRunStore {
|
|
25
|
+
export declare class FileRunStore implements InProcessClaimFence {
|
|
26
26
|
private readonly runsDir;
|
|
27
27
|
private readonly activeDir;
|
|
28
28
|
private readonly tmpDir;
|
|
@@ -148,6 +148,8 @@ export declare class FileRunStore {
|
|
|
148
148
|
* [ref](codex R1 [medium]):认领 = 接管 `instanceId`(= 当前持有副本;SQL 孪生同注)。CAS 输不改。 */
|
|
149
149
|
markResuming(taskId: string, instanceId: string): Promise<boolean>;
|
|
150
150
|
getActiveTaskId(sessionId: string): Promise<string | undefined>;
|
|
151
|
+
/** 认领当写栅栏用(契约 = `store-contracts.ts` 的 `InProcessClaimFence`):判、刷新活性(同步落盘)与 `write` 是同一个同步段。 */
|
|
152
|
+
whileHoldingClaim(sessionId: string, taskId: string, write: () => FenceLanded): boolean;
|
|
151
153
|
getRun(taskId: string): Promise<RunRecord | undefined>;
|
|
152
154
|
/** Undo THIS submission's binding (key + body fingerprint → null) — called only where the submit surface refuses the submission
|
|
153
155
|
* before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, a device-placement reject):
|
|
@@ -161,17 +163,7 @@ export declare class FileRunStore {
|
|
|
161
163
|
/** The run a submission's scoped `Idempotency-Key` names (the file twin of `uq_task_run_idem`), if any. */
|
|
162
164
|
getRunByIdem(idemKey: string): Promise<RunRecord | undefined>;
|
|
163
165
|
/** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
|
|
164
|
-
listRuns(opts:
|
|
165
|
-
status?: string;
|
|
166
|
-
jobId?: string;
|
|
167
|
-
source?: string;
|
|
168
|
-
owner?: string;
|
|
169
|
-
cursor?: {
|
|
170
|
-
createdAt: string;
|
|
171
|
-
taskId: string;
|
|
172
|
-
};
|
|
173
|
-
limit: number;
|
|
174
|
-
}): Promise<RunRecord[]>;
|
|
166
|
+
listRuns(opts: RunListQuery): Promise<RunRecord[]>;
|
|
175
167
|
/**
|
|
176
168
|
* S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
|
|
177
169
|
* `createdAt DESC, taskId DESC`,与本店 {@link FileRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
|
|
@@ -525,6 +525,15 @@ export class FileRunStore {
|
|
|
525
525
|
async getActiveTaskId(sessionId) {
|
|
526
526
|
return this.active.get(sessionId);
|
|
527
527
|
}
|
|
528
|
+
whileHoldingClaim(sessionId, taskId, write) {
|
|
529
|
+
const r = this.runs.get(taskId);
|
|
530
|
+
if (this.active.get(sessionId) !== taskId || r === undefined || r.status !== "running")
|
|
531
|
+
return false;
|
|
532
|
+
r.updatedAt = new Date();
|
|
533
|
+
this.persist(r);
|
|
534
|
+
write();
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
528
537
|
async getRun(taskId) {
|
|
529
538
|
const r = this.runs.get(taskId);
|
|
530
539
|
return r ? this.toRecord(r) : undefined;
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* (requireExisting 的 host-first 重试仍落对店),中途 touch/noteTaskRun 缺路由=落 transient(no-op 级)。
|
|
25
25
|
*/
|
|
26
26
|
import { type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionPlacement, type PlacedSessionRow } from "@sema-agent/core";
|
|
27
|
+
import { type DelegationTree } from "../delegated-session-owner.js";
|
|
27
28
|
/** acquire 选项形(core 5.48 SessionStore 契约):[ref] placement=受信 spawner 的分区建会话声明。 */
|
|
28
29
|
type AcquireOpts = {
|
|
29
30
|
requireExisting?: boolean;
|
|
@@ -85,8 +86,15 @@ export declare class ForkRoutingSessionStore implements SessionStore {
|
|
|
85
86
|
* unpin 不落错店)。
|
|
86
87
|
*
|
|
87
88
|
* 已在 host(路由或真行)⇒ 幂等直返。两店都无此会话 ⇒ 抛(调用方=park veto,诚实 fail-closed)。
|
|
89
|
+
*
|
|
90
|
+
* S-1770:宿主行**在建行那一笔里**带属主 `owner` —— 调用方按委派继承律算好(`delegated-session-owner.ts`:委派树根的属主,
|
|
91
|
+
* 推不出 ⇒ 孤行哨兵;`tree` = 判据读到的委派树,判断用:宿主行记着别的树 ⇒ 拒),这里经宿主店的首写登记写下、再按律定夺(core `register` 契约:登记不判授权,判在复读)。
|
|
92
|
+
* 宿主店里同 id 已有一行:记着这一棵委派树且无主 ⇒ 按同一条律认领(只有记录委派树的店);别的树的 / 属主不是它(别人先认领的空壳,或 local 店的无主空壳)⇒ 抛(停驻否决),
|
|
93
|
+
* 转录不拷进别人的行;宿主店记不了 / 读不回属主 ⇒ 同样抛 —— 晋升出一只无主的转录行就是把它交给任何拿到 id 的人。
|
|
88
94
|
*/
|
|
89
|
-
promoteToHost(sessionId: string): Promise<void>;
|
|
95
|
+
promoteToHost(sessionId: string, owner: string | null, tree: DelegationTree): Promise<void>;
|
|
96
|
+
/** 晋升的建行那一笔(S-1770,规则见 {@link promoteToHost} 头注):宿主店首写登记 `owner`,然后按律定下宿主行的属主(与停驻快路同一只 `settleDelegatedOwner`)。 */
|
|
97
|
+
private registerPromotedOwner;
|
|
90
98
|
fork(sourceId: string, owner?: string | null): Promise<string | null>;
|
|
91
99
|
get size(): number;
|
|
92
100
|
dispose(): void | Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SessionError } from "@sema-agent/core";
|
|
2
2
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
3
|
+
import { delegatedOwnerHostOf, settleDelegatedOwner } from "../delegated-session-owner.js";
|
|
3
4
|
const ROUTE_MAX = 8192;
|
|
4
5
|
export class ForkRoutingSessionStore {
|
|
5
6
|
host;
|
|
@@ -170,7 +171,7 @@ export class ForkRoutingSessionStore {
|
|
|
170
171
|
list() {
|
|
171
172
|
return this.transient.list?.() ?? Promise.resolve([]);
|
|
172
173
|
}
|
|
173
|
-
async promoteToHost(sessionId) {
|
|
174
|
+
async promoteToHost(sessionId, owner, tree) {
|
|
174
175
|
if (this.promoted.has(sessionId))
|
|
175
176
|
return;
|
|
176
177
|
if (this.route.get(sessionId) === "host") {
|
|
@@ -190,6 +191,7 @@ export class ForkRoutingSessionStore {
|
|
|
190
191
|
}
|
|
191
192
|
const entries = await src.session.getEntries();
|
|
192
193
|
const leaf = await src.session.getLeafId();
|
|
194
|
+
await this.registerPromotedOwner(sessionId, owner, tree);
|
|
193
195
|
const dst = await this.host.acquire(sessionId);
|
|
194
196
|
try {
|
|
195
197
|
const storage = dst.session.getStorage();
|
|
@@ -217,6 +219,15 @@ export class ForkRoutingSessionStore {
|
|
|
217
219
|
this.promoted.add(sessionId);
|
|
218
220
|
this.remember(sessionId, "host");
|
|
219
221
|
}
|
|
222
|
+
async registerPromotedOwner(sessionId, owner, tree) {
|
|
223
|
+
const register = this.host.register;
|
|
224
|
+
const host = delegatedOwnerHostOf(this.host);
|
|
225
|
+
if (register === undefined || host === undefined) {
|
|
226
|
+
throw new Error(`session "${sessionId}" cannot be promoted: the host session store cannot record or read back a session owner (register / ownerOf / sessionFaceOf; placementOf and claimOwnerless together or neither), so the promoted transcript would land ownerless`);
|
|
227
|
+
}
|
|
228
|
+
await register.call(this.host, sessionId, owner);
|
|
229
|
+
await settleDelegatedOwner(host, sessionId, owner, tree);
|
|
230
|
+
}
|
|
220
231
|
async fork(sourceId, owner) {
|
|
221
232
|
const routed = this.route.get(sourceId);
|
|
222
233
|
const tryFork = async (store, where) => {
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
import type { ChildProcess } from "node:child_process";
|
|
35
35
|
import { killProcessTree, type BackgroundHardKillOutcome } from "@sema-agent/core";
|
|
36
36
|
import type { Logger } from "../observability/logger.js";
|
|
37
|
+
/** 平台读点全仓一处(`test/host-shell-settle-order.test.ts` 源码钉);别处要平台串从这里取,不直读 Node 的平台字段。 */
|
|
38
|
+
export declare const HOST_PLATFORM: NodeJS.Platform;
|
|
37
39
|
export declare const IS_WIN32: boolean;
|
|
38
40
|
export interface HostShell {
|
|
39
41
|
shell: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import { getShellConfig, killProcessTree, signalProcessTree } from "@sema-agent/core";
|
|
3
3
|
import { redactSecrets } from "../trace/redact.js";
|
|
4
|
-
export const
|
|
4
|
+
export const HOST_PLATFORM = process.platform;
|
|
5
|
+
export const IS_WIN32 = HOST_PLATFORM === "win32";
|
|
5
6
|
const POSIX_SH_FALLBACK = { shell: "/bin/sh", args: ["-c"] };
|
|
6
7
|
let cachedPosixShell;
|
|
7
8
|
let winShellState = { kind: "unsettled" };
|
|
@@ -98,6 +98,8 @@ export declare class LocalCheckpointStore {
|
|
|
98
98
|
/** All still-pending checkpoints across the registered scopes (summary + full row via get). */
|
|
99
99
|
private pendings;
|
|
100
100
|
/** Operator approval queue — the same NON-secret card as the SQL twins (never the token). */
|
|
101
|
+
/** 每一只待决 checkpoint 的会话(去重;不按门型过滤 —— 理由与 SQL 孪生 `listPendingSessionIds` 逐字同,见那里)。 */
|
|
102
|
+
listPendingSessionIds(): Promise<string[]>;
|
|
101
103
|
listPending(scope?: string): Promise<PendingCheckpoint[]>;
|
|
102
104
|
/**
|
|
103
105
|
* [ref]:与 SQL 孪生**同判**的 session 键读口 —— `binding.gateKinds` 过滤门型、序恒「最新优先,token 兜平局」
|