@sema-agent/server 7.150.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 +451 -0
- package/README.md +15 -3
- package/README.zh-CN.md +10 -2
- package/USAGE.md +144 -87
- package/deploy/sema-up/README.md +11 -4
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
- package/deploy/sema-up/chart/templates/server.yaml +5 -3
- package/deploy/sema-up/chart/values-full-single.yaml +3 -1
- package/deploy/sema-up/chart/values.yaml +8 -1
- package/deploy/sema-up/cluster-up.sh +7 -6
- package/deploy/sema-up/kube-up.sh +6 -6
- package/deploy/sema-up/lib/flagshape.sh +36 -8
- package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
- package/deploy/sema-up/lib/helm-values-kube.py +2 -0
- package/deploy/sema-up/machines.example.yaml +3 -1
- package/deploy/sema-up/sema-up.sh +22 -16
- package/dist/adoption/import.js +3 -3
- 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 +12 -14
- package/dist/approval-gate-currency.d.ts +9 -9
- 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/auth-bridge.d.ts +20 -0
- package/dist/auth-bridge.js +9 -0
- package/dist/background-session-reap.d.ts +1 -1
- package/dist/bake-runner/main.d.ts +4 -2
- package/dist/bind-host.d.ts +3 -6
- package/dist/bind-host.js +4 -7
- package/dist/boot/budget-tracing.js +1 -1
- 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/engine-lease.d.ts +5 -0
- package/dist/boot/engine-lease.js +1 -0
- package/dist/boot/execution-env.d.ts +9 -14
- package/dist/boot/execution-env.js +85 -22
- package/dist/boot/execution-lane.js +32 -5
- package/dist/boot/late-binds.d.ts +8 -4
- package/dist/boot/late-binds.js +7 -1
- 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/memory-consolidation.d.ts +3 -2
- package/dist/boot/memory-consolidation.js +6 -6
- package/dist/boot/parked-revive-gate.js +3 -15
- package/dist/boot/reapers.d.ts +6 -1
- package/dist/boot/reapers.js +13 -2
- package/dist/boot/resolve-spec.d.ts +3 -3
- package/dist/boot/resolve-spec.js +43 -48
- package/dist/boot/runner-deps.d.ts +46 -35
- package/dist/boot/runner-deps.js +94 -91
- package/dist/boot/session-age-out.d.ts +36 -0
- package/dist/boot/session-age-out.js +101 -0
- package/dist/boot/session-faces.d.ts +5 -14
- package/dist/boot/session-faces.js +9 -5
- package/dist/boot/shutdown.d.ts +6 -0
- package/dist/boot/shutdown.js +10 -4
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-02-web-admin-pull.js +4 -5
- package/dist/boot/stage-05-execution-env.d.ts +5 -3
- package/dist/boot/stage-05-execution-env.js +3 -2
- package/dist/boot/stage-06-runners.d.ts +9 -4
- package/dist/boot/stage-06-runners.js +24 -9
- package/dist/boot/stage-07-capability-layer.d.ts +8 -4
- package/dist/boot/stage-07-capability-layer.js +3 -2
- package/dist/boot/stage-08-reapers.d.ts +8 -4
- package/dist/boot/stage-08-reapers.js +4 -2
- package/dist/boot/stage-09-leader.d.ts +12 -5
- package/dist/boot/stage-09-leader.js +14 -5
- package/dist/boot/stage-10-http-server.d.ts +19 -5
- package/dist/boot/stage-10-http-server.js +14 -21
- package/dist/boot/stores.js +7 -4
- package/dist/boot/web-admin.js +5 -8
- package/dist/boot/workflow-orchestration.d.ts +7 -2
- package/dist/boot/workflow-orchestration.js +24 -7
- package/dist/bounded-rotation.d.ts +29 -0
- package/dist/bounded-rotation.js +24 -0
- 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 +43 -32
- package/dist/capability-facts.js +45 -51
- package/dist/card-rule-offers.d.ts +32 -0
- package/dist/card-rule-offers.js +130 -0
- package/dist/center-credential.d.ts +25 -6
- package/dist/center-credential.js +25 -5
- package/dist/config-catalog.d.ts +26 -7
- package/dist/config-catalog.js +26 -22
- package/dist/config-dir-trust.js +4 -2
- package/dist/config-invariants.d.ts +20 -8
- package/dist/config-invariants.js +55 -12
- 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 +38 -42
- package/dist/config.d.ts +58 -7
- package/dist/config.js +90 -40
- 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/deployment-shape.d.ts +9 -1
- package/dist/deployment-shape.js +2 -1
- package/dist/device-protocol.d.ts +5 -4
- package/dist/device-protocol.js +1 -1
- package/dist/device-ws-hub.d.ts +7 -0
- package/dist/device-ws-hub.js +10 -3
- package/dist/device-ws-protocol.d.ts +63 -10
- package/dist/device-ws-protocol.js +56 -25
- package/dist/elicitation.js +2 -2
- package/dist/engine-identity.d.ts +19 -7
- package/dist/engine-identity.js +10 -2
- package/dist/entry-mode-clamp-notice.d.ts +2 -1
- package/dist/entry-problems.d.ts +40 -3
- package/dist/entry-problems.js +3 -1
- package/dist/execution-env-refusal.d.ts +29 -0
- package/dist/execution-env-refusal.js +8 -0
- package/dist/execution-lane-caps.d.ts +3 -1
- package/dist/field-conflict.d.ts +1 -1
- package/dist/file-history-capture.d.ts +11 -4
- package/dist/file-history-capture.js +9 -6
- package/dist/file-history-disclosure.d.ts +7 -5
- package/dist/file-history-disclosure.js +7 -5
- package/dist/fleet/drain-entry.d.ts +4 -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 +18 -3
- package/dist/host-decision.d.ts +11 -11
- package/dist/host-lsp-manager.d.ts +2 -2
- package/dist/http/active-run-conflict.d.ts +35 -2
- package/dist/http/active-run-conflict.js +48 -19
- package/dist/http/admission.js +17 -2
- package/dist/http/approval-sweeps.d.ts +1 -1
- package/dist/http/approval-sweeps.js +10 -7
- package/dist/http/checkpoint-ctx.d.ts +4 -0
- package/dist/http/checkpoint-ctx.js +2 -0
- package/dist/http/dispatch.d.ts +8 -3
- package/dist/http/dispatch.js +59 -32
- package/dist/http/durable-run-leg.js +16 -17
- package/dist/http/health-reader.d.ts +16 -9
- package/dist/http/health-reader.js +2 -1
- package/dist/http/health-self-report.d.ts +9 -1
- package/dist/http/health-self-report.js +3 -1
- package/dist/http/owner-access.d.ts +33 -14
- package/dist/http/owner-access.js +40 -10
- 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 +42 -3
- package/dist/http/resume-legs.d.ts +4 -3
- package/dist/http/resume-legs.js +367 -117
- package/dist/http/route-ctx.d.ts +91 -15
- package/dist/http/route-ctx.js +17 -0
- package/dist/http/route-table.d.ts +5 -1
- 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.d.ts +1 -1
- package/dist/http/routes/admin-memory-optout.js +3 -3
- package/dist/http/routes/adoption.js +3 -5
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +99 -51
- package/dist/http/routes/background.d.ts +3 -3
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +38 -10
- 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/fleet.js +10 -8
- 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/projects.d.ts +3 -2
- package/dist/http/routes/projects.js +4 -5
- 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 +171 -65
- package/dist/http/routes/session-sync.d.ts +22 -9
- package/dist/http/routes/session-sync.js +204 -107
- package/dist/http/routes/sessions-list.js +13 -7
- package/dist/http/routes/sessions.js +30 -15
- package/dist/http/routes/tasks.js +46 -41
- package/dist/http/routes/trace-usage.js +2 -2
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/server.d.ts +30 -30
- package/dist/http/server.js +16 -4
- package/dist/http/submit-ctx.d.ts +1 -1
- package/dist/http/submit-ctx.js +7 -4
- package/dist/http/wire-types.d.ts +14 -4
- package/dist/human-facing-args.d.ts +33 -0
- package/dist/human-facing-args.js +33 -0
- package/dist/identity-source.d.ts +20 -3
- package/dist/identity-source.js +14 -0
- package/dist/index.d.ts +5 -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 +3 -7
- package/dist/lane-home-probe.js +2 -2
- package/dist/leader/diffup.d.ts +19 -0
- package/dist/leader/diffup.js +7 -0
- package/dist/leader/leader.d.ts +116 -11
- 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/leg-facts.d.ts +13 -5
- package/dist/leg-facts.js +2 -1
- package/dist/memory-scope.d.ts +23 -11
- package/dist/memory-scope.js +18 -6
- package/dist/memory-sync-client.d.ts +30 -2
- package/dist/memory-sync-client.js +16 -9
- 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/err-text.d.ts +7 -0
- package/dist/observability/err-text.js +5 -0
- package/dist/observability/fail-open.d.ts +25 -17
- package/dist/observability/fail-open.js +25 -17
- package/dist/observability/logger.js +3 -1
- package/dist/observability/metrics.js +1 -0
- package/dist/operator-roster.d.ts +68 -0
- package/dist/operator-roster.js +32 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
- package/dist/orchestration/workflow-notify-journal.js +12 -1
- package/dist/orchestration/workflow-park-decision.d.ts +70 -18
- package/dist/orchestration/workflow-park-decision.js +49 -10
- package/dist/orchestration/workflow-park-holder.d.ts +65 -0
- package/dist/orchestration/workflow-park-holder.js +98 -0
- package/dist/orchestration/workflow-record-fence.d.ts +36 -0
- package/dist/orchestration/workflow-record-fence.js +42 -0
- package/dist/outbound-dispatcher.d.ts +71 -8
- package/dist/outbound-dispatcher.js +48 -7
- package/dist/owner-faces.d.ts +2 -2
- package/dist/parked-decide.d.ts +106 -55
- package/dist/parked-decide.js +153 -117
- package/dist/peer-directory.d.ts +2 -2
- package/dist/peer-inbound-face.d.ts +2 -6
- package/dist/peer-inbound-face.js +0 -3
- package/dist/permission-mode-admission.d.ts +74 -5
- package/dist/permission-mode-admission.js +56 -3
- package/dist/plugins/adoption-log-sql.d.ts +3 -1
- package/dist/plugins/adoption-log-sql.js +7 -1
- package/dist/plugins/approval-ask-store-file.d.ts +14 -2
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +10 -1
- package/dist/plugins/approval-ask-store-memory.js +35 -10
- package/dist/plugins/approval-ask-store-sql.d.ts +69 -4
- package/dist/plugins/approval-ask-store-sql.js +45 -5
- package/dist/plugins/approval-exemption-store.js +3 -0
- package/dist/plugins/approval-nonce-store.js +2 -0
- package/dist/plugins/blob-backend.d.ts +83 -7
- package/dist/plugins/blob-backend.js +57 -6
- package/dist/plugins/breaker-state-sql.js +2 -0
- package/dist/plugins/caching-session-store.d.ts +9 -15
- package/dist/plugins/caching-session-store.js +6 -0
- 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/device-store-sql.js +6 -0
- package/dist/plugins/error-origin.d.ts +18 -0
- package/dist/plugins/error-origin.js +10 -0
- package/dist/plugins/execution-placement-sql.js +6 -6
- package/dist/plugins/image-bake-store-sql.js +6 -0
- package/dist/plugins/image-index-sql.js +4 -0
- package/dist/plugins/landed-attendants.d.ts +212 -0
- package/dist/plugins/landed-attendants.js +161 -0
- package/dist/plugins/leader-run-store-sql.js +4 -0
- package/dist/plugins/local-checkpoint-store.d.ts +13 -3
- package/dist/plugins/local-checkpoint-store.js +38 -49
- package/dist/plugins/local-session-live-reference.d.ts +40 -0
- package/dist/plugins/local-session-live-reference.js +39 -0
- package/dist/plugins/local-session-store.d.ts +71 -33
- package/dist/plugins/local-session-store.js +129 -27
- package/dist/plugins/memory-engine-pg.js +4 -0
- package/dist/plugins/memory-engine-sql-apply.js +5 -2
- package/dist/plugins/memory-engine-tidb.js +4 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
- package/dist/plugins/memory-sync-store-pg.js +5 -0
- package/dist/plugins/memory-sync-store-tidb.js +5 -0
- package/dist/plugins/one-time-migrations.d.ts +104 -80
- package/dist/plugins/one-time-migrations.js +226 -132
- package/dist/plugins/org-delivery-quota-store.js +4 -1
- package/dist/plugins/outcome-ledger-sql.js +2 -0
- package/dist/plugins/permission-rule-store-sql.js +11 -4
- package/dist/plugins/pg-cost-quota.js +4 -0
- package/dist/plugins/pg-pool.js +35 -10
- package/dist/plugins/pg-rate-limiter.js +2 -0
- package/dist/plugins/posix-shell-fs.d.ts +6 -1
- package/dist/plugins/posix-shell-fs.js +4 -1
- package/dist/plugins/remote-env-adb.d.ts +19 -15
- package/dist/plugins/remote-env-adb.js +41 -15
- package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
- package/dist/plugins/remote-env-connect-seat.js +72 -0
- package/dist/plugins/remote-env-device.d.ts +22 -5
- package/dist/plugins/remote-env-device.js +106 -8
- package/dist/plugins/remote-env-e2b.d.ts +15 -6
- package/dist/plugins/remote-env-e2b.js +42 -32
- package/dist/plugins/remote-env-file-error.js +6 -2
- package/dist/plugins/remote-env-host.d.ts +46 -10
- package/dist/plugins/remote-env-host.js +124 -52
- package/dist/plugins/remote-env-k8s.d.ts +109 -41
- package/dist/plugins/remote-env-k8s.js +160 -54
- package/dist/plugins/remote-env-local-docker.d.ts +13 -8
- package/dist/plugins/remote-env-local-docker.js +34 -41
- package/dist/plugins/remote-env-ssh.d.ts +18 -12
- package/dist/plugins/remote-env-ssh.js +60 -59
- package/dist/plugins/remote-shell.d.ts +98 -1
- package/dist/plugins/remote-shell.js +218 -0
- package/dist/plugins/replica-registration.d.ts +1 -1
- package/dist/plugins/replica-registration.js +1 -1
- package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
- package/dist/plugins/resume-anchor-store-sql.js +12 -5
- package/dist/plugins/retention-lane-store-sql.js +4 -0
- package/dist/plugins/retention-sql.d.ts +20 -2
- package/dist/plugins/retention-sql.js +11 -0
- package/dist/plugins/run-store-sql.d.ts +21 -2
- package/dist/plugins/run-store-sql.js +31 -7
- package/dist/plugins/s3-presign.d.ts +23 -0
- package/dist/plugins/s3-presign.js +37 -3
- package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
- package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
- package/dist/plugins/scheduler-support.d.ts +2 -2
- package/dist/plugins/scheduler-support.js +0 -3
- package/dist/plugins/schema-consistency.d.ts +4 -2
- package/dist/plugins/schema-consistency.js +19 -2
- 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 +5 -2
- package/dist/plugins/send-user-file.d.ts +3 -4
- package/dist/plugins/send-user-file.js +56 -52
- package/dist/plugins/serving-requirement.d.ts +26 -7
- package/dist/plugins/serving-requirement.js +11 -4
- package/dist/plugins/session-birth.d.ts +6 -2
- 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/session-capture-record-store-sql.js +2 -0
- package/dist/plugins/session-ext-table.d.ts +14 -1
- package/dist/plugins/session-ext-table.js +5 -1
- package/dist/plugins/session-row-sql.d.ts +49 -2
- package/dist/plugins/session-row-sql.js +56 -13
- package/dist/plugins/session-store-sql.d.ts +34 -18
- package/dist/plugins/session-store-sql.js +98 -17
- package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
- package/dist/plugins/shared-memory-store-sql.js +6 -2
- package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
- package/dist/plugins/snapshot-bucket-presence.js +8 -2
- package/dist/plugins/sql-driver.d.ts +2 -1
- package/dist/plugins/sql-driver.js +18 -3
- package/dist/plugins/sql-errors.d.ts +11 -6
- package/dist/plugins/sql-errors.js +5 -6
- package/dist/plugins/sql-json-column.d.ts +1 -1
- package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
- package/dist/plugins/stop-then-start-sentence.js +14 -10
- package/dist/plugins/stop-then-start.d.ts +109 -0
- package/dist/plugins/stop-then-start.js +104 -0
- package/dist/plugins/store-backend.d.ts +84 -15
- package/dist/plugins/store-backend.js +236 -72
- package/dist/plugins/store-boundary.d.ts +57 -0
- package/dist/plugins/store-boundary.js +139 -0
- package/dist/plugins/store-contracts.d.ts +17 -0
- package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
- package/dist/plugins/store-fault-sentence-m53.js +25 -0
- package/dist/plugins/store-fault.d.ts +26 -7
- package/dist/plugins/store-fault.js +18 -2
- package/dist/plugins/task-attachment-store.d.ts +2 -1
- package/dist/plugins/task-attachment-store.js +14 -1
- package/dist/plugins/task-list-store-sql.d.ts +2 -2
- package/dist/plugins/task-list-store-sql.js +10 -8
- package/dist/plugins/tidb-cost-quota.js +4 -0
- package/dist/plugins/tidb-pool.js +21 -25
- package/dist/plugins/tidb-rate-limiter.js +4 -0
- package/dist/plugins/user-input-ledger-sql.js +4 -0
- package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
- package/dist/plugins/workflow-run-store-sql.js +64 -4
- package/dist/plugins/workspace-custody.d.ts +4 -3
- package/dist/plugins/workspace-fill.d.ts +4 -7
- package/dist/plugins/workspace-fill.js +2 -1
- package/dist/plugins/write-behind-counter.js +5 -0
- package/dist/project-context.d.ts +0 -2
- package/dist/project-context.js +4 -8
- package/dist/project-registry.d.ts +46 -0
- package/dist/project-registry.js +44 -0
- 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/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +4 -0
- package/dist/request-value-closure.js +10 -0
- package/dist/rule-issue-reading.d.ts +1 -1
- package/dist/rules-consent.d.ts +1 -1
- package/dist/run-acceptance.d.ts +3 -2
- package/dist/run-acceptance.js +4 -6
- package/dist/run-local.js +9 -9
- package/dist/runs.d.ts +54 -16
- package/dist/runs.js +67 -13
- package/dist/runtime-governance.d.ts +5 -4
- package/dist/security.d.ts +30 -32
- package/dist/security.js +41 -7
- package/dist/sema-config-dir.d.ts +2 -1
- package/dist/server-secret-env.d.ts +12 -10
- package/dist/server-secret-env.js +24 -15
- package/dist/session-bindings.d.ts +82 -0
- package/dist/session-bindings.js +18 -0
- package/dist/session-children-retire.d.ts +2 -2
- package/dist/session-children-retire.js +2 -6
- package/dist/session-deletion.d.ts +16 -1
- package/dist/session-error-wire.d.ts +3 -3
- package/dist/session-error-wire.js +3 -3
- package/dist/session-incarnation.d.ts +10 -0
- package/dist/session-incarnation.js +10 -1
- package/dist/session-kind.d.ts +57 -0
- package/dist/session-kind.js +22 -0
- package/dist/session-row.d.ts +21 -1
- package/dist/session-row.js +7 -1
- package/dist/session-sync-kernel.d.ts +24 -14
- package/dist/session-sync-kernel.js +3 -6
- package/dist/session-sync.d.ts +25 -103
- package/dist/session-sync.js +14 -76
- package/dist/session-titler.d.ts +10 -3
- package/dist/session-titler.js +19 -6
- package/dist/ssh-host-key.d.ts +5 -0
- package/dist/ssh-host-key.js +3 -0
- 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 +113 -15
- package/dist/stores-sql/file-history-store-sql.js +205 -95
- package/dist/stores-sql/pg-session-storage.d.ts +34 -7
- package/dist/stores-sql/pg-session-storage.js +37 -13
- package/dist/stores-sql/retention-store-sql.d.ts +1 -9
- package/dist/stores-sql/retention-store-sql.js +5 -19
- package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
- package/dist/stores-sql/session-policy-store-sql.js +61 -44
- package/dist/stores-sql/session-write-hooks.d.ts +11 -0
- package/dist/stores-sql/tidb-session-store.d.ts +58 -7
- package/dist/stores-sql/tidb-session-store.js +37 -13
- package/dist/submission-exit.d.ts +3 -2
- package/dist/submission-exit.js +9 -1
- package/dist/task-cwd.d.ts +2 -2
- package/dist/task-settings.d.ts +7 -5
- package/dist/task-settings.js +3 -3
- package/dist/terminal.d.ts +33 -2
- package/dist/terminal.js +139 -9
- package/dist/tool-approval.d.ts +465 -507
- package/dist/tool-approval.js +606 -1222
- 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 +571 -178
- package/dist/trace/self-minted-wire.js +189 -56
- 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 +79 -28
- package/package.json +7 -3
- package/scripts/embed-minimal.mjs +3 -3
- package/wire-changes/server@7.151.0.json +762 -0
- 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
- package/dist/http/session-inherit-env.d.ts +0 -36
- package/dist/http/session-inherit-env.js +0 -6
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHmac } from "node:crypto";
|
|
2
2
|
import { uuidv7 } from "@sema-agent/core";
|
|
3
3
|
import { stripControlAndBidi } from "@sema-agent/settings-schema";
|
|
4
|
-
import { presignS3CopyUrl, presignS3Url } from "./s3-presign.js";
|
|
4
|
+
import { buildPublicObjectUrl, presignS3CopyUrl, presignS3Url, rowTtlSec } from "./s3-presign.js";
|
|
5
|
+
import { SENDFILE_STAGING_SEGMENT } from "../object-store-buckets.js";
|
|
5
6
|
import { OBJECT_STORE_IO_TIMEOUT_MS } from "./blob-backend.js";
|
|
6
7
|
import { createEgressFetch } from "../egress-redirect.js";
|
|
7
|
-
export const PRESIGN_MAX_TTL_SEC = 604_800;
|
|
8
8
|
export function scopeSegment(secretKey, scope) {
|
|
9
9
|
if (scope === undefined || scope === "")
|
|
10
10
|
return "_";
|
|
@@ -30,23 +30,27 @@ export function sanitizeSendFileName(name) {
|
|
|
30
30
|
export function createSendUserFileIssuer(cfg, deps) {
|
|
31
31
|
const f = createEgressFetch("declare the final object-store address as MINIO_ENDPOINT / S3_ENDPOINT", deps?.fetchImpl);
|
|
32
32
|
const bounded = () => ({ signal: AbortSignal.timeout(OBJECT_STORE_IO_TIMEOUT_MS) });
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
const trackOps = (track) => track === "public"
|
|
34
|
+
? { put: "sendfile/public:PUT", copy: "sendfile/public:COPY", head: "sendfile/public:HEAD", delete: "sendfile/public:DELETE" }
|
|
35
|
+
: { put: "sendfile/private:PUT", copy: "sendfile/private:COPY", head: "sendfile/private:HEAD", delete: "sendfile/private:DELETE" };
|
|
36
|
+
const upload = async (op, bucket, key, bytes) => {
|
|
37
|
+
if (deps?.upload)
|
|
38
|
+
return deps.upload(bucket, key, bytes);
|
|
39
|
+
const url = presignS3Url({
|
|
40
|
+
op,
|
|
41
|
+
endpoint: cfg.endpoint,
|
|
42
|
+
bucket,
|
|
43
|
+
key,
|
|
44
|
+
method: "PUT",
|
|
45
|
+
accessKey: cfg.accessKey,
|
|
46
|
+
secretKey: cfg.secretKey,
|
|
47
|
+
...(cfg.region !== undefined ? { region: cfg.region } : {}),
|
|
48
|
+
...(deps?.now !== undefined ? { now: deps.now() } : {}),
|
|
49
49
|
});
|
|
50
|
+
const res = await f(url, { method: "PUT", body: Buffer.from(bytes), ...bounded() });
|
|
51
|
+
if (!res.ok)
|
|
52
|
+
throw new Error(`SendUserFile upload failed: ${res.status} ${res.statusText} (bucket=${bucket})`);
|
|
53
|
+
};
|
|
50
54
|
const issue = async (bytes, filename, ttlSec, scope) => {
|
|
51
55
|
if (cfg.publicEndpoint === undefined) {
|
|
52
56
|
throw new Error("SendUserFile is not configured for public links: set S3_PUBLIC_ENDPOINT (the S3/MinIO base URL users can reach; falls back to S3_ENDPOINT when that is already public)");
|
|
@@ -60,16 +64,15 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
60
64
|
const scopeField = scope !== undefined && scope !== "" ? { scope } : {};
|
|
61
65
|
if (ttl === 0) {
|
|
62
66
|
const key = `${seg}/${uuidv7()}/${name}`;
|
|
63
|
-
await upload(cfg.publicBucket, key, bytes);
|
|
64
|
-
const url =
|
|
67
|
+
await upload(trackOps("public").put, cfg.publicBucket, key, bytes);
|
|
68
|
+
const url = buildPublicObjectUrl({ op: "sendfile/public:GET", endpoint: publicBase, bucket: cfg.publicBucket, key });
|
|
65
69
|
return { url, filename: name, size: bytes.byteLength, ttlSec: 0, track: "public", bucket: cfg.publicBucket, key, ...scopeField };
|
|
66
70
|
}
|
|
67
|
-
|
|
68
|
-
throw new Error(`SendUserFile ttl ${ttl}s exceeds the SigV4 presign ceiling (${PRESIGN_MAX_TTL_SEC}s = 7 days) — use ttl=0 for a permanent link`);
|
|
69
|
-
}
|
|
71
|
+
const expiresSec = rowTtlSec("sendfile/private:GET", ttl);
|
|
70
72
|
const key = `${cfg.privateKeyPrefix}${seg}/${uuidv7()}/${name}`;
|
|
71
|
-
await upload(cfg.privateBucket, key, bytes);
|
|
73
|
+
await upload(trackOps("presigned").put, cfg.privateBucket, key, bytes);
|
|
72
74
|
const url = presignS3Url({
|
|
75
|
+
op: "sendfile/private:GET",
|
|
73
76
|
endpoint: publicBase,
|
|
74
77
|
bucket: cfg.privateBucket,
|
|
75
78
|
key,
|
|
@@ -77,10 +80,10 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
77
80
|
accessKey: cfg.accessKey,
|
|
78
81
|
secretKey: cfg.secretKey,
|
|
79
82
|
...(cfg.region !== undefined ? { region: cfg.region } : {}),
|
|
80
|
-
expiresSec
|
|
83
|
+
expiresSec,
|
|
81
84
|
...(deps?.now !== undefined ? { now: deps.now() } : {}),
|
|
82
85
|
});
|
|
83
|
-
return { url, filename: name, size: bytes.byteLength, ttlSec:
|
|
86
|
+
return { url, filename: name, size: bytes.byteLength, ttlSec: expiresSec, track: "presigned", bucket: cfg.privateBucket, key, ...scopeField };
|
|
84
87
|
};
|
|
85
88
|
const prepareDirectUpload = (filename, ttlSec, scope) => {
|
|
86
89
|
if (cfg.publicEndpoint === undefined) {
|
|
@@ -89,19 +92,20 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
89
92
|
const ttl = ttlSec ?? cfg.defaultTtlSec;
|
|
90
93
|
if (!Number.isInteger(ttl) || ttl < 0)
|
|
91
94
|
throw new Error(`SendUserFile ttl must be a non-negative INTEGER of seconds (0 = permanent; SigV4 X-Amz-Expires is integer-only); got ${ttl}`);
|
|
92
|
-
if (ttl >
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
+
if (ttl > 0)
|
|
96
|
+
rowTtlSec("sendfile/private:GET", ttl);
|
|
95
97
|
const name = sanitizeSendFileName(filename);
|
|
96
98
|
const publicBase = cfg.publicEndpoint.replace(/\/+$/, "");
|
|
97
99
|
const putBase = (cfg.sandboxPutEndpoint ?? cfg.publicEndpoint).replace(/\/+$/, "");
|
|
98
100
|
const bucket = ttl === 0 ? cfg.publicBucket : cfg.privateBucket;
|
|
101
|
+
const finalOps = trackOps(ttl === 0 ? "public" : "presigned");
|
|
99
102
|
const seg = scopeSegment(cfg.secretKey, scope);
|
|
100
103
|
const scopeField = scope !== undefined && scope !== "" ? { scope } : {};
|
|
101
104
|
const key = ttl === 0 ? `${seg}/${uuidv7()}/${name}` : `${cfg.privateKeyPrefix}${seg}/${uuidv7()}/${name}`;
|
|
102
105
|
const stagingBucket = cfg.privateBucket;
|
|
103
|
-
const stagingKey = `${cfg.privateKeyPrefix}
|
|
106
|
+
const stagingKey = `${cfg.privateKeyPrefix}${SENDFILE_STAGING_SEGMENT}${seg}/${uuidv7()}`;
|
|
104
107
|
const putUrl = presignS3Url({
|
|
108
|
+
op: "sendfile/staging:PUT",
|
|
105
109
|
endpoint: putBase,
|
|
106
110
|
bucket: stagingBucket,
|
|
107
111
|
key: stagingKey,
|
|
@@ -109,13 +113,13 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
109
113
|
accessKey: cfg.accessKey,
|
|
110
114
|
secretKey: cfg.secretKey,
|
|
111
115
|
...(cfg.region !== undefined ? { region: cfg.region } : {}),
|
|
112
|
-
expiresSec: 120,
|
|
113
116
|
...(deps?.now !== undefined ? { now: deps.now() } : {}),
|
|
114
117
|
});
|
|
115
118
|
const verifySize = async (maxBytes) => {
|
|
116
119
|
if (deps?.verify)
|
|
117
120
|
return deps.verify(bucket, key, maxBytes);
|
|
118
|
-
const sign = (method, b, k) => presignS3Url({
|
|
121
|
+
const sign = (op, method, b, k) => presignS3Url({
|
|
122
|
+
op,
|
|
119
123
|
endpoint: cfg.endpoint,
|
|
120
124
|
bucket: b,
|
|
121
125
|
key: k,
|
|
@@ -123,11 +127,10 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
123
127
|
accessKey: cfg.accessKey,
|
|
124
128
|
secretKey: cfg.secretKey,
|
|
125
129
|
...(cfg.region !== undefined ? { region: cfg.region } : {}),
|
|
126
|
-
expiresSec: 60,
|
|
127
130
|
});
|
|
128
|
-
const reclaim = async (b, k) => {
|
|
131
|
+
const reclaim = async (op, b, k) => {
|
|
129
132
|
try {
|
|
130
|
-
const r = await f(sign("DELETE", b, k), { method: "DELETE", ...bounded() });
|
|
133
|
+
const r = await f(sign(op, "DELETE", b, k), { method: "DELETE", ...bounded() });
|
|
131
134
|
return r.ok || r.status === 404;
|
|
132
135
|
}
|
|
133
136
|
catch {
|
|
@@ -137,28 +140,29 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
137
140
|
const outcome = (ok, what) => (ok ? `${what} reclaimed` : `${what} reclaim FAILED (orphan; operator cleanup needed)`);
|
|
138
141
|
let head;
|
|
139
142
|
try {
|
|
140
|
-
head = await f(sign("HEAD", stagingBucket, stagingKey), { method: "HEAD", ...bounded() });
|
|
143
|
+
head = await f(sign("sendfile/staging:HEAD", "HEAD", stagingBucket, stagingKey), { method: "HEAD", ...bounded() });
|
|
141
144
|
}
|
|
142
145
|
catch (e) {
|
|
143
|
-
const r = await reclaim(stagingBucket, stagingKey);
|
|
146
|
+
const r = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
|
|
144
147
|
throw new Error(`SendUserFile upload verification failed: store unreachable during HEAD (${e instanceof Error ? e.message : String(e)}) — ${outcome(r, "staged object")}`);
|
|
145
148
|
}
|
|
146
149
|
if (head.status === 404)
|
|
147
150
|
throw new Error("SendUserFile upload verification failed: object not found after upload (HEAD 404)");
|
|
148
151
|
if (!head.ok) {
|
|
149
|
-
const r = await reclaim(stagingBucket, stagingKey);
|
|
152
|
+
const r = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
|
|
150
153
|
throw new Error(`SendUserFile upload verification failed: store error (HEAD ${head.status}) — ${outcome(r, "staged object")}`);
|
|
151
154
|
}
|
|
152
155
|
const staged = Number.parseInt(head.headers.get("content-length") ?? "", 10);
|
|
153
156
|
if (!Number.isFinite(staged) || staged < 0) {
|
|
154
|
-
const r = await reclaim(stagingBucket, stagingKey);
|
|
157
|
+
const r = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
|
|
155
158
|
throw new Error(`SendUserFile upload verification failed: store returned no content-length — ${outcome(r, "staged object")}`);
|
|
156
159
|
}
|
|
157
160
|
if (staged > maxBytes) {
|
|
158
|
-
await reclaim(stagingBucket, stagingKey);
|
|
161
|
+
await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
|
|
159
162
|
throw new Error(`file exceeds the ${maxBytes >= 1024 * 1024 ? `${Math.floor(maxBytes / (1024 * 1024))} MiB` : `${maxBytes} byte`} per-file cap (uploaded object was ${staged} bytes — deleted)`);
|
|
160
163
|
}
|
|
161
164
|
const copy = presignS3CopyUrl({
|
|
165
|
+
op: finalOps.copy,
|
|
162
166
|
endpoint: cfg.endpoint,
|
|
163
167
|
bucket,
|
|
164
168
|
key,
|
|
@@ -167,7 +171,6 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
167
171
|
accessKey: cfg.accessKey,
|
|
168
172
|
secretKey: cfg.secretKey,
|
|
169
173
|
...(cfg.region !== undefined ? { region: cfg.region } : {}),
|
|
170
|
-
expiresSec: 60,
|
|
171
174
|
...(deps?.now !== undefined ? { now: deps.now() } : {}),
|
|
172
175
|
});
|
|
173
176
|
try {
|
|
@@ -177,40 +180,41 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
177
180
|
throw new Error(`copy returned ${res.status}`);
|
|
178
181
|
}
|
|
179
182
|
catch (e) {
|
|
180
|
-
const r1 = await reclaim(stagingBucket, stagingKey);
|
|
181
|
-
const r2 = await reclaim(bucket, key);
|
|
183
|
+
const r1 = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
|
|
184
|
+
const r2 = await reclaim(finalOps.delete, bucket, key);
|
|
182
185
|
throw new Error(`SendUserFile upload verification failed: staging promotion failed (${e instanceof Error ? e.message : String(e)}) — ${outcome(r1 && r2, "uploaded object")}`);
|
|
183
186
|
}
|
|
184
|
-
await reclaim(stagingBucket, stagingKey);
|
|
187
|
+
await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
|
|
185
188
|
let fin;
|
|
186
189
|
try {
|
|
187
|
-
fin = await f(sign("HEAD", bucket, key), { method: "HEAD", ...bounded() });
|
|
190
|
+
fin = await f(sign(finalOps.head, "HEAD", bucket, key), { method: "HEAD", ...bounded() });
|
|
188
191
|
}
|
|
189
192
|
catch (e) {
|
|
190
|
-
const r = await reclaim(bucket, key);
|
|
193
|
+
const r = await reclaim(finalOps.delete, bucket, key);
|
|
191
194
|
throw new Error(`SendUserFile upload verification failed: store unreachable during final HEAD (${e instanceof Error ? e.message : String(e)}) — ${outcome(r, "object")}`);
|
|
192
195
|
}
|
|
193
196
|
if (!fin.ok) {
|
|
194
|
-
const r = await reclaim(bucket, key);
|
|
197
|
+
const r = await reclaim(finalOps.delete, bucket, key);
|
|
195
198
|
throw new Error(`SendUserFile upload verification failed: final object HEAD ${fin.status} — ${outcome(r, "object")}`);
|
|
196
199
|
}
|
|
197
200
|
const size = Number.parseInt(fin.headers.get("content-length") ?? "", 10);
|
|
198
201
|
if (!Number.isFinite(size) || size < 0) {
|
|
199
|
-
const r = await reclaim(bucket, key);
|
|
202
|
+
const r = await reclaim(finalOps.delete, bucket, key);
|
|
200
203
|
throw new Error(`SendUserFile upload verification failed: store returned no content-length — ${outcome(r, "object")}`);
|
|
201
204
|
}
|
|
202
205
|
if (size > maxBytes) {
|
|
203
|
-
await reclaim(bucket, key);
|
|
206
|
+
await reclaim(finalOps.delete, bucket, key);
|
|
204
207
|
throw new Error(`file exceeds the ${maxBytes >= 1024 * 1024 ? `${Math.floor(maxBytes / (1024 * 1024))} MiB` : `${maxBytes} byte`} per-file cap (uploaded object was ${size} bytes — deleted)`);
|
|
205
208
|
}
|
|
206
209
|
return size;
|
|
207
210
|
};
|
|
208
211
|
const finalize = (size) => {
|
|
209
212
|
if (ttl === 0) {
|
|
210
|
-
const url =
|
|
213
|
+
const url = buildPublicObjectUrl({ op: "sendfile/public:GET", endpoint: publicBase, bucket, key });
|
|
211
214
|
return { url, filename: name, size, ttlSec: 0, track: "public", bucket, key, ...scopeField };
|
|
212
215
|
}
|
|
213
216
|
const url = presignS3Url({
|
|
217
|
+
op: "sendfile/private:GET",
|
|
214
218
|
endpoint: publicBase,
|
|
215
219
|
bucket,
|
|
216
220
|
key,
|
|
@@ -225,10 +229,11 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
225
229
|
};
|
|
226
230
|
return { putUrl, verifySize, finalize };
|
|
227
231
|
};
|
|
228
|
-
const deleteObject = async (bucket, key) => {
|
|
232
|
+
const deleteObject = async (bucket, key, track) => {
|
|
229
233
|
if (deps?.remove)
|
|
230
234
|
return deps.remove(bucket, key);
|
|
231
235
|
const url = presignS3Url({
|
|
236
|
+
op: trackOps(track).delete,
|
|
232
237
|
endpoint: cfg.endpoint,
|
|
233
238
|
bucket,
|
|
234
239
|
key,
|
|
@@ -236,7 +241,6 @@ export function createSendUserFileIssuer(cfg, deps) {
|
|
|
236
241
|
accessKey: cfg.accessKey,
|
|
237
242
|
secretKey: cfg.secretKey,
|
|
238
243
|
...(cfg.region !== undefined ? { region: cfg.region } : {}),
|
|
239
|
-
expiresSec: 60,
|
|
240
244
|
...(deps?.now !== undefined ? { now: deps.now() } : {}),
|
|
241
245
|
});
|
|
242
246
|
const res = await f(url, { method: "DELETE", ...bounded() });
|
|
@@ -2,25 +2,30 @@
|
|
|
2
2
|
* **起服准入**(SQL 两方言;常设)—— 「这只库要求每一只服务者有哪些能力」一张表,与本构建的能力名单比:库上记着本构建没有的能力 ⇒ 拒启,本构建不迁移、不服务。
|
|
3
3
|
*
|
|
4
4
|
* ## 律
|
|
5
|
-
* · 一行一能力:`migration_id` = 能力名(= 一次性迁移名,`replica-registration.ts` 的 {@link REPLICA_CAPABILITIES} 同一词表),`state`
|
|
6
|
-
* {@link SERVING_REQUIREMENT_STATES}:`
|
|
5
|
+
* · 一行一能力:`migration_id` = 能力名(= 一次性迁移名,`replica-registration.ts` 的 {@link REPLICA_CAPABILITIES} 同一词表),`state` 一词闭集
|
|
6
|
+
* {@link SERVING_REQUIREMENT_STATES}:`in_force` = 一只带能力 M 的构建在这只库上放行了 M,M 的不兼容写已开始或马上开始。读出词表外的词(含 7.150.0 闭集里
|
|
7
|
+
* 从无写者的 `pending`)⇒ 当作 `in_force`,拒句点名那一行与原词。
|
|
7
8
|
* · 判据 = `knows(本构建, M)`(`knowsCapability`,与停-迁-起证据读法同一只谓词);发布线不进判据(同版本串的构建可以能力不同)。
|
|
8
9
|
* · 两次读,同一判据:SQL 一次性迁移段头、取迁移锁之前读一次(锁外预读);取到迁移锁之后、第一步迁移之前在锁连接上再读一次(锁内复核)——
|
|
9
10
|
* 在本进程等锁时握着锁的那一只若写下了要求,锁内复核读得到。拒 ⇒ 本构建不迁移;库上只留本进程的登记行(段头第一步)。
|
|
10
11
|
* · 表不在(跳过了 `ensureSchema()` 的嵌入方)⇒ 拒:证不出「没有要求」不是「没有要求」。
|
|
11
|
-
* ·
|
|
12
|
+
* · 写者一只({@link recordServingRequirement}):每一条停-迁-起迁移(`stop-then-start.ts`;三类围栏同记)在守卫段放行之后、它**自己的**第一笔不兼容写之前,
|
|
13
|
+
* 在锁连接上落 `(M, in_force)`(缺席才落;已在的行 —— 含词表外的词 —— 一个字节不动)。只在还欠那一条的库上写(起过那一条的库不写)。
|
|
14
|
+
* 表空 ⇒ 准入恒过。7.150.0 起的每一只构建都读它,之后写下的要求挡得住缺这项能力的构建重启开服。
|
|
15
|
+
* · 读法一只({@link unmetServingRequirements}):准入(非空即拒)与起服拒句的升级清单(点名本构建缺的在力能力)同读,不各写一句。
|
|
12
16
|
*
|
|
13
17
|
* ## 射程(如实)
|
|
14
18
|
* 挡的是**起服者**:已在服务的副本不再读本表;准入与开服之间的停顿也不在它的射程里。早于本表那一版的构建不读本表。
|
|
15
19
|
*/
|
|
16
20
|
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
17
|
-
import { type ReplicaRegistration } from "./replica-registration.js";
|
|
21
|
+
import { type ReplicaCapability, type ReplicaRegistration } from "./replica-registration.js";
|
|
18
22
|
import { type UnmetServingRequirement } from "./stop-then-start-sentence.js";
|
|
23
|
+
import type { SchemaProbeRead } from "./schema-live-columns.js";
|
|
19
24
|
import type { SqlDialect, SqlExec } from "./sql-driver.js";
|
|
20
25
|
/** 表名。 */
|
|
21
26
|
export declare const SERVING_REQUIREMENT_TABLE = "serving_requirement";
|
|
22
|
-
/** 要求行的状态词(
|
|
23
|
-
export declare const SERVING_REQUIREMENT_STATES: readonly ["
|
|
27
|
+
/** 要求行的状态词(闭集;一态 —— 本构建没有「认领了、还没写」的中间态:要求紧跟在本条第一笔不兼容写之前落)。 */
|
|
28
|
+
export declare const SERVING_REQUIREMENT_STATES: readonly ["in_force"];
|
|
24
29
|
export type ServingRequirementState = (typeof SERVING_REQUIREMENT_STATES)[number];
|
|
25
30
|
/** 拒启码(确定的拒启表态;继承 {@link StoreBootRefusalError}:开库等待窗不重试、降级臂不吞)。 */
|
|
26
31
|
export declare const SERVING_REQUIREMENT_UNMET_CODE = "config.serving_requirement_unmet";
|
|
@@ -44,6 +49,20 @@ export declare class ServingRequirementUnmetError extends StoreBootRefusalError
|
|
|
44
49
|
}
|
|
45
50
|
/** 准入读的两张表(本表与段头写的登记表)都在库里;不在 ⇒ 拒(`table_absent`),点名公开序列。 */
|
|
46
51
|
export declare function assertReplicaTablesPresent(db: SqlExec, dialect: SqlDialect, me: ReplicaRegistration): Promise<void>;
|
|
47
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* **要求的唯一读法**:库上记着、本构建(`me` 的能力名单)没有的那几行,按能力名排序。纯读;两方言同一句(无占位符,读口 = 列册对账同一只无参读口)。
|
|
54
|
+
* 读者两只:准入({@link assertServingRequirementsMet},非空即拒)与起服拒句的升级清单(`schema-consistency.ts` 的 `describeUpgradeChecklist`,点名这几项能力)。
|
|
55
|
+
*/
|
|
56
|
+
export declare function unmetServingRequirements(read: SchemaProbeRead, me: Pick<ReplicaRegistration, "capabilities">): Promise<readonly UnmetServingRequirement[]>;
|
|
57
|
+
/** 准入:库上每一行要求的能力本构建都有 ⇒ 过;否则拒(`capability_missing`,点名每一行)。读法 = {@link unmetServingRequirements}。 */
|
|
48
58
|
export declare function assertServingRequirementsMet(db: SqlExec, me: ReplicaRegistration): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* **要求的唯一写口**:一条停-迁-起迁移放行之后、它自己的第一笔不兼容写之前,在迁移段的锁连接上落 `(migrationId, in_force)`。缺席才落(MySQL 协议
|
|
61
|
+
* `ON DUPLICATE KEY UPDATE migration_id = migration_id` / PG `ON CONFLICT DO NOTHING`,与副本登记句同形):已在的行 —— 含手写的词表外词 —— 一个字节不动。
|
|
62
|
+
* 记下之后缺这项能力的构建起服即拒(准入);记下之后、那一笔写之前进程崩 ⇒ 行留着(保守:缺能力的构建拒启,带能力的构建下次起服照常续)。
|
|
63
|
+
* 读写权限 7.150.0 起已是部署前提(`docs/DEPLOY-PREREQS.md` 7.150.0 节)。`migrationId` 在型上就要在能力名单里。
|
|
64
|
+
*/
|
|
65
|
+
export declare function recordServingRequirement(db: {
|
|
66
|
+
exec(sql: string, params?: readonly unknown[]): Promise<number>;
|
|
67
|
+
}, dialect: SqlDialect, migrationId: ReplicaCapability, nowMs: number): Promise<void>;
|
|
49
68
|
//# sourceMappingURL=serving-requirement.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { StoreBootRefusalError } from "./store-contracts.js";
|
|
|
2
2
|
import { REPLICA_REGISTRATION_TABLE, knowsCapability } from "./replica-registration.js";
|
|
3
3
|
import { servingRequirementUnmetSentence } from "./stop-then-start-sentence.js";
|
|
4
4
|
export const SERVING_REQUIREMENT_TABLE = "serving_requirement";
|
|
5
|
-
export const SERVING_REQUIREMENT_STATES = ["
|
|
5
|
+
export const SERVING_REQUIREMENT_STATES = ["in_force"];
|
|
6
6
|
export const SERVING_REQUIREMENT_UNMET_CODE = "config.serving_requirement_unmet";
|
|
7
7
|
export function servingRequirementDdl(dialect) {
|
|
8
8
|
return dialect === "mysql"
|
|
@@ -37,17 +37,24 @@ function stateOf(cell) {
|
|
|
37
37
|
const word = String(cell);
|
|
38
38
|
return SERVING_REQUIREMENT_STATES.includes(word) ? { state: word, word } : { state: "in_force", word };
|
|
39
39
|
}
|
|
40
|
-
export async function
|
|
41
|
-
const { rows } = await db.query(`SELECT migration_id, state FROM ${SERVING_REQUIREMENT_TABLE} ORDER BY migration_id`);
|
|
40
|
+
export async function unmetServingRequirements(read, me) {
|
|
42
41
|
const unmet = [];
|
|
43
|
-
for (const r of
|
|
42
|
+
for (const r of await read(`SELECT migration_id, state FROM ${SERVING_REQUIREMENT_TABLE} ORDER BY migration_id`)) {
|
|
44
43
|
const migration = String(r["migration_id"]);
|
|
45
44
|
if (knowsCapability(me.capabilities, migration))
|
|
46
45
|
continue;
|
|
47
46
|
const { state, word } = stateOf(r["state"]);
|
|
48
47
|
unmet.push({ migration, state, word });
|
|
49
48
|
}
|
|
49
|
+
return unmet;
|
|
50
|
+
}
|
|
51
|
+
export async function assertServingRequirementsMet(db, me) {
|
|
52
|
+
const unmet = await unmetServingRequirements(async (sql) => (await db.query(sql)).rows, me);
|
|
50
53
|
if (unmet.length > 0)
|
|
51
54
|
throw new ServingRequirementUnmetError("capability_missing", unmet, [], me.releaseLine);
|
|
52
55
|
}
|
|
56
|
+
export async function recordServingRequirement(db, dialect, migrationId, nowMs) {
|
|
57
|
+
const insert = `INSERT INTO ${SERVING_REQUIREMENT_TABLE} (migration_id, state, required_at_ms) VALUES (${dialect === "mysql" ? "?, 'in_force', ?" : "$1, 'in_force', $2"})`;
|
|
58
|
+
await db.exec(dialect === "mysql" ? `${insert} ON DUPLICATE KEY UPDATE migration_id = migration_id` : `${insert} ON CONFLICT (migration_id) DO NOTHING`, [migrationId, nowMs]);
|
|
59
|
+
}
|
|
53
60
|
//# sourceMappingURL=serving-requirement.js.map
|
|
@@ -14,10 +14,14 @@
|
|
|
14
14
|
* (TiDB 悲观事务里普通 SELECT 读的是事务开始时的快照,看不到等锁期间提交的写;两方言一律 `FOR UPDATE`)。
|
|
15
15
|
*/
|
|
16
16
|
import type { SessionTreeEntry } from "@sema-agent/core";
|
|
17
|
+
import type { SessionKind } from "../session-kind.js";
|
|
17
18
|
import type { RegisterReceipt, SessionBirth, WithdrawOutcome } from "../session-incarnation.js";
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* 出生回执面(会话店的窄面;`register` 的同笔写形)。`kind` = 这只会话的种类(律 `session-kind.ts`):**只在这一笔真建了新行时**写下,写在建行的同一笔里
|
|
21
|
+
* (SQL = 建行事务里的建行钩;local = 建行那一笔格写);已在的行 ⇒ 零写(种类终身不改)。缺席 = 顶层对话,逐字今形。
|
|
22
|
+
*/
|
|
19
23
|
export interface SessionBirthFace {
|
|
20
|
-
registerWithReceipt(sessionId: string, owner: string | null): Promise<RegisterReceipt>;
|
|
24
|
+
registerWithReceipt(sessionId: string, owner: string | null, kind?: SessionKind): Promise<RegisterReceipt>;
|
|
21
25
|
}
|
|
22
26
|
/** 条件撤回面(会话店的窄面;部署装配在店上,一只事务里判完、删完)。`ownTaskId` = 本次提交认领赢的那条 run(没有 ⇒ 缺)。 */
|
|
23
27
|
export interface SessionWithdrawFace {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This database's claim token on the session blob namespace (`session-blob-namespace.ts`): one row, minted once per database (absent ⇒ the first writer's
|
|
3
|
+
* stays; every replica of the database reads the same), never rewritten. The store's open reads it (`StoreBackend.ensureSchema()`) and hands it to the
|
|
4
|
+
* namespace's claim; nothing else reads or writes the table.
|
|
5
|
+
*/
|
|
6
|
+
import type { SqlDialect, SqlExec } from "./sql-driver.js";
|
|
7
|
+
/** The table that keeps this database's claim token (one row, `id = 1`). */
|
|
8
|
+
export declare const SESSION_BLOB_NAMESPACE_CLAIM_TABLE = "blob_namespace_claim";
|
|
9
|
+
/** The create statement (both dialects; idempotent). The central schema statements read it (`tidb-pool.ts` / `pg-pool.ts`). */
|
|
10
|
+
export declare function sessionBlobNamespaceClaimDdl(dialect: SqlDialect): string;
|
|
11
|
+
/** This database's claim token: minted if absent, then read (the store's open; a failure is the open's failure). */
|
|
12
|
+
export declare function ensureSessionBlobClaimToken(db: SqlExec & {
|
|
13
|
+
readonly dialect: SqlDialect;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
//# sourceMappingURL=session-blob-claim-token.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export const SESSION_BLOB_NAMESPACE_CLAIM_TABLE = "blob_namespace_claim";
|
|
3
|
+
export function sessionBlobNamespaceClaimDdl(dialect) {
|
|
4
|
+
return dialect === "mysql"
|
|
5
|
+
? `CREATE TABLE IF NOT EXISTS ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id SMALLINT NOT NULL PRIMARY KEY, claim_token VARCHAR(64) NOT NULL, minted_at_ms BIGINT NOT NULL) COLLATE utf8mb4_bin`
|
|
6
|
+
: `CREATE TABLE IF NOT EXISTS ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id SMALLINT NOT NULL PRIMARY KEY, claim_token VARCHAR(64) COLLATE "C" NOT NULL, minted_at_ms BIGINT NOT NULL)`;
|
|
7
|
+
}
|
|
8
|
+
const MINT_SQL = {
|
|
9
|
+
mysql: `INSERT INTO ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id, claim_token, minted_at_ms) VALUES (1, ?, ?) ON DUPLICATE KEY UPDATE id = id`,
|
|
10
|
+
pg: `INSERT INTO ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id, claim_token, minted_at_ms) VALUES (1, $1, $2) ON CONFLICT (id) DO NOTHING`,
|
|
11
|
+
};
|
|
12
|
+
export async function ensureSessionBlobClaimToken(db) {
|
|
13
|
+
await db.query(MINT_SQL[db.dialect], [randomUUID(), Date.now()]);
|
|
14
|
+
const { rows } = await db.query(`SELECT claim_token FROM ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} WHERE id = 1`);
|
|
15
|
+
const token = rows[0]?.claim_token;
|
|
16
|
+
if (typeof token !== "string" || token === "")
|
|
17
|
+
throw new Error(`${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} holds no claim token after the mint`);
|
|
18
|
+
return token;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=session-blob-claim-token.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type SessionBlobStoreConfig } from "./blob-backend.js";
|
|
2
|
+
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
3
|
+
/** After a create-only write, how long to wait before reading the claim back (once per namespace, on its first claim). */
|
|
4
|
+
export declare const SESSION_BLOB_CLAIM_SETTLE_MS = 1000;
|
|
5
|
+
/** One attempt's reading (closed set). */
|
|
6
|
+
export type SessionBlobClaimReading = {
|
|
7
|
+
readonly state: "mine";
|
|
8
|
+
} | {
|
|
9
|
+
readonly state: "foreign";
|
|
10
|
+
readonly holder: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly state: "unproven";
|
|
13
|
+
readonly detail: string;
|
|
14
|
+
};
|
|
15
|
+
/** The store open's refusal: the namespace is another database's (or the store was opened without binding this database's token). */
|
|
16
|
+
export declare class SessionBlobNamespaceRefusedError extends StoreBootRefusalError {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** The live namespace seat of one database over one bucket × prefix. */
|
|
20
|
+
export interface SessionBlobNamespace {
|
|
21
|
+
/** The bucket config the namespace's ports are built from (`createSessionBlobBackend`). */
|
|
22
|
+
readonly store: SessionBlobStoreConfig;
|
|
23
|
+
/** Bind this database's claim token (`ensureSessionBlobClaimToken`, read by the store's `ensureSchema()`). */
|
|
24
|
+
bind(token: string): void;
|
|
25
|
+
/** One attempt of the claim under `token` (no retry). */
|
|
26
|
+
claimOnce(token: string): Promise<SessionBlobClaimReading>;
|
|
27
|
+
/** Boot, under the bound token, one attempt: `mine` ⇒ resolves; `foreign` ⇒ refuses; `unproven` ⇒ resolves through the registered F-class arm
|
|
28
|
+
* (deletes stay refused until a later reading is `mine`: {@link assertOwned}). */
|
|
29
|
+
establish(): Promise<void>;
|
|
30
|
+
/** Every delete's gate: reads the claim afresh (never remembered) and resolves only when it names this database; rejects otherwise — a namespace with no
|
|
31
|
+
* bound token (the store's `ensureSchema()` never ran) has nothing to prove with. */
|
|
32
|
+
assertOwned(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export interface SessionBlobNamespaceOptions {
|
|
35
|
+
/** Test seam; production = the global fetch (through the egress-redirect rule). */
|
|
36
|
+
readonly fetchImpl?: typeof fetch;
|
|
37
|
+
/** Test seam for the settle wait; production = a timer. */
|
|
38
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
/** The refusal when another database holds the namespace (the one sentence; the boot refusal and a refused delete both say it). */
|
|
41
|
+
export declare function buildSessionBlobNamespaceForeignText(store: SessionBlobStoreConfig, holder: string, mine: string): string;
|
|
42
|
+
/** A delete's refusal when the claim could not be read or written (the delete is retried by its sweep / release on its next call, and goes through once
|
|
43
|
+
* the claim reads as this database's). */
|
|
44
|
+
export declare function buildSessionBlobNamespaceUnprovenText(store: SessionBlobStoreConfig, detail: string): string;
|
|
45
|
+
export declare function createSessionBlobNamespace(store: SessionBlobStoreConfig, opts?: SessionBlobNamespaceOptions): SessionBlobNamespace;
|
|
46
|
+
//# sourceMappingURL=session-blob-namespace.d.ts.map
|
|
@@ -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
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
2
2
|
import { isDupKeyError } from "./sql-errors.js";
|
|
3
|
+
import { sealStoreClass } from "./store-boundary.js";
|
|
3
4
|
export const SESSION_CAPTURE_OPTOUT_TABLE = "session_capture_optout";
|
|
4
5
|
const UNREADABLE_RECORD_REASON = "capture opt-out record present but unreadable (kept fail-closed)";
|
|
5
6
|
export const SESSION_CAPTURE_KEY_WIDTH = 255;
|
|
@@ -81,6 +82,7 @@ export class SqlSessionCaptureRecordStore {
|
|
|
81
82
|
return out;
|
|
82
83
|
}
|
|
83
84
|
}
|
|
85
|
+
sealStoreClass(SqlSessionCaptureRecordStore, "sql-session-capture-record-store", { mark: "exit", read: "exit", list: "exit" });
|
|
84
86
|
export class TiDBSessionCaptureRecordStore extends SqlSessionCaptureRecordStore {
|
|
85
87
|
constructor(pool) {
|
|
86
88
|
super(mysqlDriver(pool), "mysql");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
2
2
|
import type { SqlDialect, SqlDriver } from "./sql-driver.js";
|
|
3
|
+
import type { IndexSpec } from "./ensure-index.js";
|
|
3
4
|
/** 表名(建表句、语句模块、迁移同读)。 */
|
|
4
5
|
export declare const SESSION_EXT_TABLE = "session_ext";
|
|
5
6
|
/** 一列:名字 + 两方言定义。 */
|
|
@@ -27,8 +28,20 @@ export declare const BUMP_CLIENT_WRITES = "client_writes = client_writes + 1";
|
|
|
27
28
|
* 在删对删除门之外的一切读者与写者读作「行不在」(头读 / 列表 / 规则 L 的认领);存量库缺这一列 ⇒ 起服列册对账拒启并给 `ALTER TABLE … ADD COLUMN`。
|
|
28
29
|
*/
|
|
29
30
|
export declare const DELETING_TOKEN_COLUMN: SessionExtColumn;
|
|
30
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* **会话种类**(律 `src/session-kind.ts`):`NULL` = 顶层对话(存量行与老壳建的行恒为 NULL —— 无回填、无一次性迁移);非空 = 闭集里的一个词。
|
|
33
|
+
* 唯一写者 = 建行事务里的建行钩(`plugins/session-store-sql.ts` 的 `birth.record`,本事务刚建下的这一行);此后无写口(终身不改)。宽度 32 = 词表的余量。
|
|
34
|
+
* 存量库缺这一列 ⇒ 起服列册对账拒启并给 `ALTER TABLE … ADD COLUMN`(中央建表句生成)。
|
|
35
|
+
*/
|
|
36
|
+
export declare const SESSION_KIND_COLUMN: SessionExtColumn;
|
|
37
|
+
/** 本表的全部格(主键之外),按建表句的列序。一次性迁移 M41 逐列从 `session_meta` 搬进来的是产品格与执行放置格(计数格、在删令牌与种类不在 `session_meta` 上出现过,不搬)。 */
|
|
31
38
|
export declare const SESSION_EXT_COLUMNS: readonly SessionExtColumn[];
|
|
39
|
+
/**
|
|
40
|
+
* 本表的索引声明(两方言共用一份;MySQL 协议方言另以内联 `KEY` 随建表句到货,存量库缺谁补谁 —— `ensure-index.ts`):种类那一列给老化候选面
|
|
41
|
+
* (`listIdleOfKind`:只读带种类的行,不扫整表)。
|
|
42
|
+
*/
|
|
43
|
+
export declare const SESSION_EXT_KIND_INDEX = "idx_session_ext_kind";
|
|
44
|
+
export declare const SESSION_EXT_INDEXES: readonly IndexSpec[];
|
|
32
45
|
/** M41 从 `session_meta` 搬进本表的那一组(计数格之前的全部格)。 */
|
|
33
46
|
export declare const SESSION_EXT_MOVED_COLUMNS: readonly SessionExtColumn[];
|
|
34
47
|
/** 外键约束名(两方言同名;起服探针与迁移的后置条件按名读)。 */
|