@sema-agent/server 7.146.0 → 7.147.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 +384 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +170 -66
- package/deploy/facts/lanes.json +7 -0
- package/deploy/sema-up/README.md +2 -2
- package/deploy/sema-up/chart/templates/_helpers.tpl +22 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -4
- package/deploy/sema-up/chart/templates/server.yaml +5 -4
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/deploy/sema-up/cluster-up.sh +2 -1
- package/deploy/sema-up/lib/flagshape.sh +4 -0
- package/deploy/sema-up/lib/lanes.json +2 -5
- package/deploy/sema-up/sema-up.sh +12 -9
- package/deploy/sema-up/stack/compose.yaml +5 -4
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -3
- package/dist/approval-card.d.ts +4 -4
- package/dist/approval.d.ts +3 -3
- package/dist/audit.js +2 -2
- package/dist/bench/s1/live-deps.js +3 -1
- package/dist/boot/crash-last.js +2 -1
- package/dist/boot/device-lane.js +5 -5
- package/dist/boot/engine-lease.d.ts +12 -11
- package/dist/boot/execution-env.d.ts +2 -1
- package/dist/boot/execution-env.js +45 -33
- package/dist/boot/execution-lane.d.ts +37 -9
- package/dist/boot/execution-lane.js +74 -20
- package/dist/boot/exit-boundary.d.ts +1 -1
- package/dist/boot/exit-last.js +2 -1
- package/dist/boot/governance-seams.d.ts +2 -2
- package/dist/boot/leader.js +1 -1
- package/dist/boot/memory-boundary.d.ts +11 -11
- package/dist/boot/memory-boundary.js +3 -3
- package/dist/boot/memory-consolidation.d.ts +13 -24
- package/dist/boot/memory-consolidation.js +0 -13
- package/dist/boot/org-memory.js +3 -2
- package/dist/boot/parked-revive-gate.d.ts +1 -1
- package/dist/boot/parked-revive-gate.js +1 -1
- package/dist/boot/reapers.js +1 -1
- package/dist/boot/resolve-spec.js +91 -111
- package/dist/boot/retention-lane.d.ts +4 -42
- package/dist/boot/session-faces.d.ts +1 -0
- package/dist/boot/session-faces.js +8 -2
- package/dist/boot/stage-01-config.d.ts +1 -1
- package/dist/boot/stage-01-config.js +6 -2
- package/dist/boot/stage-02-web-admin-pull.d.ts +1 -1
- package/dist/boot/stage-02-web-admin-pull.js +10 -8
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.js +3 -3
- package/dist/boot/stage-06-runners.d.ts +3 -3
- package/dist/boot/stage-06-runners.js +1 -3
- package/dist/boot/stage-07-capability-layer.d.ts +3 -3
- package/dist/boot/stage-07-capability-layer.js +4 -9
- package/dist/boot/stage-08-reapers.d.ts +3 -3
- package/dist/boot/stage-09-leader.d.ts +4 -3
- package/dist/boot/stage-10-http-server.d.ts +4 -3
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/stores.d.ts +9 -0
- package/dist/boot/stores.js +18 -7
- package/dist/boot/web-admin.js +3 -3
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +6 -2
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/execution-lane.d.ts +7 -7
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +1 -1
- package/dist/capabilities/scenarios.d.ts +32 -18
- package/dist/capabilities/scenarios.js +13 -9
- package/dist/capabilities/settings-contract.d.ts +29 -0
- package/dist/capabilities/settings-contract.js +14 -0
- package/dist/capabilities/skills.d.ts +5 -5
- package/dist/capability-facts.d.ts +38 -0
- package/dist/capability-facts.js +34 -1
- package/dist/center-credential-renewer.js +3 -2
- package/dist/config-catalog.js +6 -5
- package/dist/config-invariants.d.ts +18 -3
- package/dist/config-invariants.js +36 -19
- package/dist/config-lkg.d.ts +1 -1
- package/dist/config-lkg.js +5 -6
- package/dist/config-provider.js +2 -0
- package/dist/config-types.d.ts +190 -186
- package/dist/config.d.ts +68 -5
- package/dist/config.js +80 -54
- package/dist/degenerate-instrument.js +1 -1
- package/dist/deploy-facts.js +3 -2
- package/dist/deployment-shape.d.ts +21 -12
- package/dist/deployment-shape.js +24 -6
- package/dist/env-facts.d.ts +7 -5
- package/dist/env-facts.js +10 -6
- package/dist/execution-config-root.d.ts +5 -5
- package/dist/execution-config-root.js +5 -5
- package/dist/execution-env-purpose.d.ts +14 -0
- package/dist/execution-env-purpose.js +29 -0
- package/dist/execution-lane-caps.d.ts +49 -86
- package/dist/execution-lane-caps.js +52 -62
- package/dist/execution-placement.d.ts +18 -2
- package/dist/execution-placement.js +19 -0
- package/dist/file-history-disclosure.d.ts +2 -2
- package/dist/fleet/drain-entry.d.ts +17 -10
- package/dist/fleet/drain-entry.js +1 -1
- package/dist/fleet/serving-lease.js +3 -4
- package/dist/history-anchor.d.ts +22 -0
- package/dist/history-anchor.js +14 -0
- package/dist/hooks/command-spawn.d.ts +1 -4
- package/dist/hooks/command-spawn.js +0 -11
- package/dist/hooks/hook-runner.d.ts +52 -8
- package/dist/hooks/hook-runner.js +160 -36
- package/dist/http/approval-sweeps.d.ts +2 -5
- package/dist/http/approval-sweeps.js +3 -2
- package/dist/http/checkpoint-ctx.d.ts +17 -0
- package/dist/http/checkpoint-ctx.js +6 -0
- package/dist/http/claim-latency.d.ts +10 -0
- package/dist/http/claim-latency.js +12 -0
- package/dist/http/dispatch.js +1 -1
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/execution-context.d.ts +3 -1
- package/dist/http/execution-context.js +4 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/memory-store-refusal-reply.d.ts +3 -3
- package/dist/http/memory-store-refusal-reply.js +3 -0
- package/dist/http/owner-access.d.ts +11 -0
- package/dist/http/owner-access.js +7 -4
- package/dist/http/principal-gate.d.ts +3 -14
- package/dist/http/principal-gate.js +0 -13
- package/dist/http/request-guards.d.ts +1 -1
- package/dist/http/resume-legs.d.ts +7 -10
- package/dist/http/resume-legs.js +79 -50
- package/dist/http/rewind-files-reply.d.ts +34 -0
- package/dist/http/rewind-files-reply.js +36 -0
- package/dist/http/route-ctx.d.ts +32 -16
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/approvals-assistant.js +5 -25
- package/dist/http/routes/capabilities.js +8 -2
- package/dist/http/routes/devices.js +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/notify-wake.js +2 -1
- package/dist/http/routes/projects.d.ts +70 -0
- package/dist/http/routes/projects.js +86 -0
- package/dist/http/routes/rules.js +30 -2
- package/dist/http/routes/runs.js +8 -8
- package/dist/http/routes/session-sync.js +27 -10
- package/dist/http/routes/sessions.d.ts +7 -1
- package/dist/http/routes/sessions.js +104 -2
- package/dist/http/routes/tasks.js +9 -3
- package/dist/http/server.d.ts +24 -10
- package/dist/http/server.js +17 -6
- package/dist/http/stored-body-replay.d.ts +5 -3
- package/dist/http/stored-body-replay.js +10 -1
- package/dist/http/wire-types.d.ts +19 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/instruction-source-notice.d.ts +1 -1
- package/dist/memory-embedder-reembed.d.ts +38 -0
- package/dist/memory-embedder-reembed.js +20 -0
- package/dist/memory-incident-sink.d.ts +0 -28
- package/dist/memory-incident-sink.js +5 -0
- package/dist/memory-scope.d.ts +2 -2
- package/dist/memory-scope.js +1 -1
- package/dist/memory-store-refusal.d.ts +17 -4
- package/dist/memory-store-refusal.js +1 -1
- package/dist/memory-sync-client.d.ts +2 -0
- package/dist/memory-sync-client.js +13 -11
- package/dist/memory-sync.js +1 -1
- package/dist/model-compat.d.ts +35 -21
- package/dist/model-compat.js +49 -21
- package/dist/model-entry-refusal.d.ts +11 -1
- package/dist/model-entry-refusal.js +7 -0
- package/dist/namespaced-rule-name.d.ts +18 -1
- package/dist/namespaced-rule-name.js +12 -0
- package/dist/observability/fail-open.d.ts +17 -9
- package/dist/observability/fail-open.js +17 -9
- package/dist/observability/metrics.d.ts +2 -0
- package/dist/observability/metrics.js +2 -0
- package/dist/observability/secret-env-scrub.d.ts +30 -4
- package/dist/observability/secret-env-scrub.js +15 -3
- package/dist/orchestration/workflow-agent-session-index.d.ts +7 -12
- package/dist/orchestration/workflow-agent-session-index.js +1 -6
- package/dist/orchestration/workflow-completion-inbox.d.ts +26 -6
- package/dist/orchestration/workflow-completion-inbox.js +63 -45
- package/dist/org-memory-admission.d.ts +5 -2
- package/dist/org-memory-admission.js +25 -10
- package/dist/parked-decide.d.ts +33 -26
- package/dist/parked-decide.js +36 -44
- package/dist/plugins/approval-ask-store-file.d.ts +10 -2
- package/dist/plugins/approval-ask-store-file.js +37 -4
- package/dist/plugins/approval-ask-store-memory.d.ts +5 -3
- package/dist/plugins/approval-ask-store-memory.js +11 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
- package/dist/plugins/approval-ask-store-sql.js +3 -1
- package/dist/plugins/blob-backend.d.ts +38 -195
- package/dist/plugins/blob-backend.js +33 -229
- package/dist/plugins/caching-session-store.d.ts +9 -4
- package/dist/plugins/checkpoint-index-columns.d.ts +1 -1
- package/dist/plugins/checkpoint-index-columns.js +3 -2
- package/dist/plugins/checkpoint-store-face.d.ts +1 -1
- package/dist/plugins/device-store-sql.d.ts +2 -2
- package/dist/plugins/device-store-sql.js +5 -10
- package/dist/plugins/execution-lane-rename-m40.d.ts +20 -0
- package/dist/plugins/execution-lane-rename-m40.js +10 -0
- package/dist/plugins/execution-placement-sql.d.ts +63 -88
- package/dist/plugins/execution-placement-sql.js +94 -54
- package/dist/plugins/file-run-store.d.ts +26 -12
- package/dist/plugins/file-run-store.js +63 -63
- package/dist/plugins/imported-layer-root-retirement.d.ts +17 -8
- package/dist/plugins/imported-layer-root-retirement.js +4 -8
- package/dist/plugins/inbox-payload-column.d.ts +14 -0
- package/dist/plugins/inbox-payload-column.js +13 -0
- package/dist/plugins/local-checkpoint-store.d.ts +5 -0
- package/dist/plugins/local-checkpoint-store.js +11 -7
- package/dist/plugins/local-data-dir-modes-m39.d.ts +48 -0
- package/dist/plugins/local-data-dir-modes-m39.js +182 -0
- package/dist/plugins/local-engine-high-water.d.ts +11 -0
- package/dist/plugins/local-engine-high-water.js +59 -0
- package/dist/plugins/local-session-store.d.ts +91 -25
- package/dist/plugins/local-session-store.js +124 -45
- package/dist/plugins/local-task-attachment-store.d.ts +2 -0
- package/dist/plugins/local-task-attachment-store.js +10 -11
- package/dist/plugins/memory-embedder-fingerprint.d.ts +33 -10
- package/dist/plugins/memory-embedder-fingerprint.js +52 -12
- package/dist/plugins/memory-embedder.d.ts +1 -1
- package/dist/plugins/memory-embedder.js +2 -2
- package/dist/plugins/memory-engine-pg.d.ts +4 -3
- package/dist/plugins/memory-engine-pg.js +4 -4
- package/dist/plugins/memory-engine-sql-apply.d.ts +13 -4
- package/dist/plugins/memory-engine-sql-apply.js +1 -1
- package/dist/plugins/memory-engine-tidb.d.ts +8 -5
- package/dist/plugins/memory-engine-tidb.js +7 -8
- package/dist/plugins/one-time-migrations.d.ts +145 -35
- package/dist/plugins/one-time-migrations.js +272 -120
- package/dist/plugins/owner-only-file-mode.d.ts +29 -0
- package/dist/plugins/owner-only-file-mode.js +141 -0
- package/dist/plugins/permission-rule-store-file.d.ts +8 -8
- package/dist/plugins/permission-rule-store-file.js +14 -2
- package/dist/plugins/pg-pool.d.ts +2 -1
- package/dist/plugins/pg-pool.js +11 -4
- package/dist/plugins/release-version.d.ts +20 -0
- package/dist/plugins/release-version.js +28 -0
- package/dist/plugins/remote-env-adb.d.ts +20 -4
- package/dist/plugins/remote-env-adb.js +39 -11
- package/dist/plugins/remote-env-e2b.d.ts +12 -3
- package/dist/plugins/remote-env-e2b.js +6 -4
- package/dist/plugins/remote-env-host.d.ts +9 -1
- package/dist/plugins/remote-env-host.js +6 -5
- package/dist/plugins/remote-env-k8s.d.ts +5 -2
- package/dist/plugins/remote-env-k8s.js +15 -4
- package/dist/plugins/remote-env-local-docker.d.ts +4 -1
- package/dist/plugins/remote-env-local-docker.js +3 -3
- package/dist/plugins/remote-env-ssh.d.ts +8 -2
- package/dist/plugins/remote-env-ssh.js +26 -24
- package/dist/plugins/remote-shell.d.ts +59 -30
- package/dist/plugins/remote-shell.js +175 -62
- package/dist/plugins/retention-declaration.d.ts +37 -0
- package/dist/plugins/retention-declaration.js +9 -0
- package/dist/plugins/retention-deployment.d.ts +126 -0
- package/dist/plugins/retention-deployment.js +88 -0
- package/dist/plugins/retention-lane-store-sql.d.ts +74 -4
- package/dist/plugins/retention-lane-store-sql.js +103 -1
- package/dist/plugins/retention-sql.d.ts +29 -0
- package/dist/plugins/retention-sql.js +8 -0
- package/dist/plugins/schema-consistency.d.ts +0 -6
- package/dist/plugins/schema-consistency.js +1 -2
- package/dist/plugins/schema-ensure-legs.d.ts +1 -1
- package/dist/plugins/schema-ensure-legs.js +11 -1
- package/dist/plugins/session-ext-table.d.ts +74 -0
- package/dist/plugins/session-ext-table.js +121 -0
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-row-sql.d.ts +8 -7
- package/dist/plugins/session-row-sql.js +15 -7
- package/dist/plugins/session-store-sql.d.ts +131 -0
- package/dist/plugins/session-store-sql.js +298 -0
- package/dist/plugins/session-workspace-cell.d.ts +2 -2
- package/dist/plugins/session-workspace-cell.js +2 -2
- package/dist/plugins/shared-memory-store-sql.d.ts +10 -0
- package/dist/plugins/shared-memory-store-sql.js +13 -13
- package/dist/plugins/snapshot-bucket-presence.js +1 -1
- package/dist/plugins/sql-driver.d.ts +23 -7
- package/dist/plugins/sql-driver.js +41 -8
- package/dist/plugins/sql-errors.d.ts +5 -2
- package/dist/plugins/sql-errors.js +4 -0
- package/dist/plugins/store-backend.d.ts +35 -8
- package/dist/plugins/store-backend.js +184 -62
- package/dist/plugins/store-contracts.d.ts +9 -2
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/task-attachment-store.d.ts +6 -8
- package/dist/plugins/task-attachment-store.js +15 -10
- package/dist/plugins/task-list-store-sql.d.ts +1 -1
- package/dist/plugins/text-column-capacity.d.ts +21 -0
- package/dist/plugins/text-column-capacity.js +5 -0
- package/dist/plugins/tidb-pool.d.ts +3 -2
- package/dist/plugins/tidb-pool.js +19 -31
- package/dist/plugins/user-input-ledger-sql.d.ts +1 -1
- package/dist/read-face-posture.d.ts +6 -6
- package/dist/read-face-posture.js +2 -2
- package/dist/request-value-closure.d.ts +5 -5
- package/dist/request-value-closure.js +1 -1
- package/dist/rules-consent.d.ts +41 -13
- package/dist/rules-consent.js +101 -53
- package/dist/run-local.js +17 -13
- package/dist/runs.d.ts +48 -30
- package/dist/runs.js +26 -16
- package/dist/runtime-caps-resolver.d.ts +15 -8
- package/dist/runtime-caps-resolver.js +14 -10
- package/dist/sealed-key-layout.d.ts +2 -3
- package/dist/sealed-key-layout.js +0 -1
- package/dist/sealed-key.js +7 -5
- package/dist/security.d.ts +5 -3
- package/dist/sema-config-dir.js +2 -2
- package/dist/server-secret-env.d.ts +2 -0
- package/dist/service-version.d.ts +2 -1
- package/dist/service-version.js +2 -2
- package/dist/session-error-wire.d.ts +1 -1
- package/dist/session-incarnation.d.ts +8 -5
- package/dist/session-resident-background.d.ts +85 -32
- package/dist/session-resident-background.js +75 -7
- package/dist/session-row.d.ts +1 -1
- package/dist/session-sync-kernel.d.ts +1 -1
- package/dist/session-workspace.d.ts +11 -2
- package/dist/session-workspace.js +5 -0
- package/dist/sighup-idle.d.ts +1 -1
- package/dist/store-posture.d.ts +2 -1
- package/dist/stored-request-hooks-m20.d.ts +3 -2
- package/dist/stored-request-hooks-m20.js +19 -14
- package/dist/stores-sql/blob-index-sql.d.ts +148 -0
- package/dist/stores-sql/blob-index-sql.js +154 -0
- package/dist/stores-sql/checkpoint-store-sql.d.ts +1 -1
- package/dist/stores-sql/checkpoint-store-sql.js +11 -27
- package/dist/{plugins → stores-sql}/file-history-store-sql.d.ts +23 -11
- package/dist/{plugins → stores-sql}/file-history-store-sql.js +23 -18
- package/dist/{plugins → stores-sql}/pg-session-storage.d.ts +23 -76
- package/dist/{plugins → stores-sql}/pg-session-storage.js +80 -209
- package/dist/{plugins → stores-sql}/retention-store-sql.d.ts +123 -217
- package/dist/{plugins → stores-sql}/retention-store-sql.js +66 -190
- package/dist/{plugins → stores-sql}/session-event-payload.d.ts +1 -1
- package/dist/{plugins → stores-sql}/session-event-payload.js +1 -1
- package/dist/stores-sql/session-policy-store-sql.d.ts +1 -1
- package/dist/stores-sql/session-write-hooks.d.ts +59 -0
- package/dist/stores-sql/session-write-hooks.js +2 -0
- package/dist/{plugins → stores-sql}/tidb-session-storage.d.ts +10 -11
- package/dist/{plugins → stores-sql}/tidb-session-storage.js +10 -21
- package/dist/{plugins → stores-sql}/tidb-session-store.d.ts +32 -122
- package/dist/{plugins → stores-sql}/tidb-session-store.js +71 -171
- package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
- package/dist/stores-sql/tool-result-store-sql.js +1 -1
- package/dist/stores-sql/windowed-session-storage.d.ts +56 -0
- package/dist/stores-sql/windowed-session-storage.js +61 -0
- package/dist/task-a2a.d.ts +7 -1
- package/dist/task-a2a.js +15 -14
- package/dist/task-cwd.d.ts +11 -3
- package/dist/task-cwd.js +17 -1
- package/dist/task-mcp.d.ts +10 -2
- package/dist/task-mcp.js +13 -16
- package/dist/task-settings.d.ts +9 -4
- package/dist/task-settings.js +17 -6
- package/dist/tool-approval.d.ts +21 -8
- package/dist/tool-approval.js +10 -6
- package/dist/trace/core-keyset-guard.d.ts +9 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +23 -1
- package/dist/trace/exit-seat.js +3 -0
- package/dist/trace/project.d.ts +33 -16
- package/dist/trace/project.js +12 -9
- package/dist/trace/redact.d.ts +1 -1
- package/dist/trace/redact.js +61 -73
- package/dist/trace/self-minted-wire.d.ts +263 -10
- package/dist/trace/self-minted-wire.js +90 -2
- package/dist/trace/sema-provenance.d.ts +45 -41
- package/dist/trace/sema-provenance.js +81 -10
- package/dist/trace/task-notification-facets.d.ts +70 -2
- package/dist/trace/task-notification-facets.js +71 -1
- package/dist/trace/wire-projection-faces.d.ts +38 -2
- package/dist/trace/wire-projection-faces.js +26 -1
- package/dist/unread-request-members.d.ts +66 -0
- package/dist/unread-request-members.js +41 -0
- package/dist/user-input-ledger.d.ts +2 -2
- package/dist/web-admin-client/apply-effective.js +22 -26
- package/dist/web-admin-client/candidate-gate.d.ts +35 -3
- package/dist/web-admin-client/candidate-gate.js +53 -5
- package/dist/web-admin-client/center-request.d.ts +4 -1
- package/dist/web-admin-client/center-request.js +2 -1
- package/dist/web-admin-client/effective-keys.js +0 -1
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/http-client.d.ts +1 -0
- package/dist/web-admin-client/http-client.js +17 -9
- package/dist/web-admin-client/restart-signal.js +5 -2
- package/dist/web-admin-client/skills-mcp.d.ts +20 -6
- package/dist/web-admin-client/skills-mcp.js +26 -21
- package/dist/web-admin-client/types.d.ts +34 -49
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +2 -2
- package/package.json +13 -12
- package/scripts/drain-check-local.mjs +2 -28
- package/scripts/drain-check.mysql.sql +2 -18
- package/scripts/drain-check.pg.sql +2 -18
- package/wire-changes/server@7.147.0.json +813 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ExecutionError, FileError, SYMLINK_CHAIN_MAX_HOPS } from "@sema-agent/core";
|
|
2
2
|
import { NAME_MAX_BYTES, classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
|
|
3
3
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
4
|
+
import { createLogger } from "../observability/logger.js";
|
|
5
|
+
import { seatText } from "../trace/exit-seat.js";
|
|
4
6
|
import { randomBytes } from "node:crypto";
|
|
5
7
|
export const EXEC_FORCE_SETTLE_GRACE_MS = 3000;
|
|
6
8
|
export function armPipeDestroyGrace(child) {
|
|
@@ -157,8 +159,11 @@ export async function runSymlinkChain(absPath, run) {
|
|
|
157
159
|
const STAGED_FILE = "data";
|
|
158
160
|
const STAGE_DIR_REFUSED_EXIT = 3;
|
|
159
161
|
const STAGE_DIR_UNSEALED_EXIT = 4;
|
|
160
|
-
const
|
|
162
|
+
const STAGE_DIR_UNSEALED_REASON = "chmod 700 failed or did not take effect, the mode or owner read back differently, or an inherited ACL entry could not be removed or read on this platform — the filesystem ignores permission bits or the parent directory carries a default or inheritable ACL";
|
|
163
|
+
const STAGE_DIR_UNSEALED_MESSAGE = `sema: the private staging directory could not be made private (${STAGE_DIR_UNSEALED_REASON}); nothing was staged`;
|
|
164
|
+
const RESTAGE_DIR_UNSEALED_MESSAGE = `sema: the private staging directory beside the link's target could not be made private (${STAGE_DIR_UNSEALED_REASON}); the target was not touched`;
|
|
161
165
|
export const STAGE_DIR_LEFT_MARK = "sema: staging directory left behind:";
|
|
166
|
+
export const STAGE_MODE_NOT_CARRIED_MARK = "sema: file mode not carried:";
|
|
162
167
|
export function buildStagingPaths(abs, nonce = randomBytes(6).toString("hex")) {
|
|
163
168
|
const cut = abs.lastIndexOf("/");
|
|
164
169
|
const parent = abs.slice(0, cut + 1);
|
|
@@ -172,9 +177,11 @@ export function buildStagingPaths(abs, nonce = randomBytes(6).toString("hex")) {
|
|
|
172
177
|
function privateMkdir(dir) {
|
|
173
178
|
return `(umask 077 && mkdir -- ${dir})`;
|
|
174
179
|
}
|
|
180
|
+
function readModeOwner(path) {
|
|
181
|
+
return `l=$(ls -ldn -- ${path} 2>/dev/null) && set -f && set -- $l && [ -n "$3" ]`;
|
|
182
|
+
}
|
|
175
183
|
function hardenPrivate(dir) {
|
|
176
|
-
|
|
177
|
-
return `( chmod -- 700 ${dir} 2>/dev/null && m=${statOf("%a", "%Lp")} && u=${statOf("%u", "%u")} && case "$m" in 700|[1-7]700) [ "$u" = "$(id -u)" ];; *) false;; esac && ${aclCleared(dir)} )`;
|
|
184
|
+
return `( chmod -- 700 ${dir} 2>/dev/null && ${readModeOwner(dir)} && case "$1" in drw[xs]--[-S]--[-T]*) [ "$3" = "$(id -u)" ];; *) false;; esac && ${aclCleared(dir)} )`;
|
|
178
185
|
}
|
|
179
186
|
function aclCleared(dir) {
|
|
180
187
|
const marker = `ls_out=$(cd ${dir} && ls -ld . 2>&1) && [ -z "$(printf '%s\\n' "$ls_out" | sed -n 2p)" ] && mode_word=\${ls_out%% *} && ` +
|
|
@@ -189,9 +196,6 @@ function aclCleared(dir) {
|
|
|
189
196
|
function emptyPrivate(dir) {
|
|
190
197
|
return `[ ! -e ${dir}/${STAGED_FILE} ] && [ ! -L ${dir}/${STAGED_FILE} ]`;
|
|
191
198
|
}
|
|
192
|
-
function sealPrivate(dir) {
|
|
193
|
-
return `${hardenPrivate(dir)} && ${emptyPrivate(dir)}`;
|
|
194
|
-
}
|
|
195
199
|
function removalOf(dir) {
|
|
196
200
|
return `{ [ ! -L ${dir} ] && rm -f -- ${dir}/${STAGED_FILE} && { rmdir -- ${dir} 2>/dev/null || [ ! -e ${dir} ]; }; }`;
|
|
197
201
|
}
|
|
@@ -213,21 +217,31 @@ export function buildStagingPrepareCommand(stage) {
|
|
|
213
217
|
}
|
|
214
218
|
function takeAndWrite(redirect) {
|
|
215
219
|
return (`[ -r "$s" ] || chmod -- u+r "$s" 2>/dev/null; exec 3< "$s" || exit 1; { [ ! -L "$d" ] && rm -f -- "$s"; } || { ${leftMark(`"$d"`)}; exit 1; }; ` +
|
|
216
|
-
`cat <&3 ${redirect} "$t"; w=$?; exec 3<&-; ${removePrivate(`"$d"`)}; exit $w; `);
|
|
220
|
+
`cat <&3 ${redirect} "$t"; w=$?; [ "$w" -eq 0 ] || w=1; exec 3<&-; ${removePrivate(`"$d"`)}; exit $w; `);
|
|
221
|
+
}
|
|
222
|
+
function carryMode(src, dst) {
|
|
223
|
+
const digit = `perm_digit() { case "$1" in ---|--S|--T) echo 0;; --x|--s|--t) echo 1;; -w-|-wS|-wT) echo 2;; -wx|-ws|-wt) echo 3;; ` +
|
|
224
|
+
`r--|r-S|r-T) echo 4;; r-x|r-s|r-t) echo 5;; rw-|rwS|rwT) echo 6;; rwx|rws|rwt) echo 7;; *) return 1;; esac; }`;
|
|
225
|
+
const triples = `m=\${1#?} && pu=\${m%"\${m#???}"} && m=\${m#???} && pg=\${m%"\${m#???}"} && m=\${m#???} && po=\${m%"\${m#???}"}`;
|
|
226
|
+
const special = `sp=0; case "$pu" in ??[sS]) sp=4;; esac; case "$pg" in ??[sS]) sp=$((sp+2));; esac; case "$po" in ??[tT]) sp=$((sp+1));; esac`;
|
|
227
|
+
return (`( if ${readModeOwner(src)}; then ${digit} && ${triples} && du=$(perm_digit "$pu") && dg=$(perm_digit "$pg") && do_=$(perm_digit "$po") && ` +
|
|
228
|
+
`{ ${special}; chmod "$sp$du$dg$do_" ${dst}; } && exit 0; printf '%s' "$1"; fi; exit 1 ) 2>/dev/null`);
|
|
217
229
|
}
|
|
218
230
|
export function buildStagedCommitCommand(stage, target) {
|
|
219
231
|
return (`{ d=${shellQuote(stage.dir)}; s="$d/${STAGED_FILE}"; t=${shellQuote(target)}; ` +
|
|
220
|
-
`if [ -L "$t" ]; then h=$(readlink
|
|
232
|
+
`if [ -L "$t" ]; then nl=$(printf '\\n.'); nl=\${nl%.}; h=$(readlink -- "$t" && printf .) || h=; h=\${h%.}; h=\${h%"$nl"}; ` +
|
|
221
233
|
`case "$h" in "") ;; /*) ;; *) l="\${t%/*}"; case "$l" in "") h="/$h" ;; */) h="$l$h" ;; *) h="$l/$h" ;; esac ;; esac; ` +
|
|
222
|
-
`if [ -n "$h" ] && [ ! -L "$h" ] &&
|
|
234
|
+
`if [ -n "$h" ] && [ ! -L "$h" ] && [ -f "$h" ] && [ ! -e "$h$nl" ] && [ ! -L "$h$nl" ] && [ "$h" -ef "$t" ]; then t="$h"; fi; fi; ` +
|
|
223
235
|
`if [ ! -L "$t" ] && { [ ! -e "$t" ] || [ -f "$t" ]; }; then ` +
|
|
224
236
|
`if [ "\${t%/*}" != "\${d%/*}" ]; then e="\${t%/*}/\${d##*/}"; ` +
|
|
225
237
|
`if { ${privateMkdir(`"$e"`)}; } 2>/dev/null; then ` +
|
|
226
|
-
`if { ${
|
|
238
|
+
`if { ${hardenPrivate(`"$e"`)}; } 2>/dev/null; then ` +
|
|
239
|
+
`if { ${emptyPrivate(`"$e"`)}; } 2>/dev/null; then ` +
|
|
227
240
|
`if cat -- "$s" > "$e/${STAGED_FILE}" 2>/dev/null; then o="$d"; d="$e"; s="$e/${STAGED_FILE}"; ${removePrivate(`"$o"`)}; else ${removePrivate(`"$e"`)}; fi; ` +
|
|
228
|
-
`else ${abandonPrivate(`"$e"`)}; fi;
|
|
229
|
-
`
|
|
230
|
-
`
|
|
241
|
+
`else ${abandonPrivate(`"$e"`)}; fi; ` +
|
|
242
|
+
`else ${abandonPrivate(`"$e"`)}; printf '%s\\n' ${shellQuote(RESTAGE_DIR_UNSEALED_MESSAGE)} >&2; exit ${STAGE_DIR_UNSEALED_EXIT}; fi; fi; fi; ` +
|
|
243
|
+
`carry_mode() { ${carryMode('"$t"', '"$s"')}; }; cm=-; if [ -f "$t" ]; then cm=$(carry_mode) && cm=-; fi; ` +
|
|
244
|
+
`if mv -f -- "$s" "$t" 2>/dev/null; then [ "$cm" = - ] || printf '%s %s\\0' ${shellQuote(STAGE_MODE_NOT_CARRIED_MARK)} "$cm"; ${removePrivate(`"$d"`)}; exit 0; fi; fi; ` +
|
|
231
245
|
`${takeAndWrite(">")}}`);
|
|
232
246
|
}
|
|
233
247
|
export function buildStagedAppendCommand(stage, target) {
|
|
@@ -236,14 +250,77 @@ export function buildStagedAppendCommand(stage, target) {
|
|
|
236
250
|
export function buildStagingDiscardCommand(stage) {
|
|
237
251
|
return removalOf(shellQuote(stage.dir));
|
|
238
252
|
}
|
|
239
|
-
|
|
240
|
-
|
|
253
|
+
export function readyUpload(upload) {
|
|
254
|
+
return async () => ({ ok: true, value: { upload } });
|
|
255
|
+
}
|
|
256
|
+
async function cleanOnce(lane, cmd, fallbackWhy) {
|
|
257
|
+
const r = await lane.run(cmd);
|
|
241
258
|
if (!r.ok)
|
|
242
259
|
return r.error.message;
|
|
243
|
-
return r.value.exitCode === 0 ? undefined : r.value.stderr.trim() ||
|
|
260
|
+
return r.value.exitCode === 0 ? undefined : r.value.stderr.trim() || fallbackWhy;
|
|
261
|
+
}
|
|
262
|
+
function scriptRecordsOf(stdout) {
|
|
263
|
+
const heads = [
|
|
264
|
+
{ kind: "left", head: `${STAGE_DIR_LEFT_MARK} ` },
|
|
265
|
+
{ kind: "mode", head: `${STAGE_MODE_NOT_CARRIED_MARK} ` },
|
|
266
|
+
];
|
|
267
|
+
const leftDirs = [];
|
|
268
|
+
let modeNotCarried;
|
|
269
|
+
for (const rec of stdout.split("\0")) {
|
|
270
|
+
let first;
|
|
271
|
+
for (const { kind, head } of heads) {
|
|
272
|
+
const at = rec.indexOf(head);
|
|
273
|
+
if (at >= 0 && (first === undefined || at < first.at))
|
|
274
|
+
first = { kind, at, head };
|
|
275
|
+
}
|
|
276
|
+
if (first === undefined)
|
|
277
|
+
continue;
|
|
278
|
+
const value = rec.slice(first.at + first.head.length);
|
|
279
|
+
switch (first.kind) {
|
|
280
|
+
case "left":
|
|
281
|
+
leftDirs.push(value);
|
|
282
|
+
break;
|
|
283
|
+
case "mode":
|
|
284
|
+
modeNotCarried = value;
|
|
285
|
+
break;
|
|
286
|
+
default: {
|
|
287
|
+
const unreachable = first.kind;
|
|
288
|
+
throw new Error(`unhandled script record kind ${String(unreachable)}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { leftDirs, modeNotCarried };
|
|
293
|
+
}
|
|
294
|
+
async function settleStaging(lane, stage, named, discardOriginal) {
|
|
295
|
+
const todo = new Map();
|
|
296
|
+
if (discardOriginal)
|
|
297
|
+
todo.set(stage.dir, { cmd: buildStagingDiscardCommand(stage), why: "it holds something this write did not put there, or it was replaced by a link" });
|
|
298
|
+
const name = stage.dir.slice(stage.dir.lastIndexOf("/") + 1);
|
|
299
|
+
for (const dir of named) {
|
|
300
|
+
if (todo.has(dir))
|
|
301
|
+
continue;
|
|
302
|
+
const faithful = dir.endsWith(`/${name}`) && !dir.includes("\uFFFD");
|
|
303
|
+
todo.set(dir, faithful ? { cmd: reapCommandOf(dir), why: "it still holds an entry, or it was replaced by a link" } : { cmd: undefined, why: "the script named a directory that is not this write's staging directory under a path that can be trusted; left alone" });
|
|
304
|
+
}
|
|
305
|
+
const left = [];
|
|
306
|
+
for (const [dir, { cmd, why }] of todo) {
|
|
307
|
+
const still = cmd === undefined ? why : await cleanOnce(lane, cmd, why);
|
|
308
|
+
if (still === undefined)
|
|
309
|
+
continue;
|
|
310
|
+
left.push({ dir, why: still });
|
|
311
|
+
recordFailOpen("server.remote-write.staging-dir-left", dir);
|
|
312
|
+
}
|
|
313
|
+
return left;
|
|
244
314
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
315
|
+
function reapCommandOf(dir) {
|
|
316
|
+
const d = shellQuote(dir);
|
|
317
|
+
return `{ rmdir -- ${d} 2>/dev/null || { [ ! -e ${d} ] && [ ! -L ${d} ]; }; }`;
|
|
318
|
+
}
|
|
319
|
+
function withResidue(e, left) {
|
|
320
|
+
if (left.length === 0)
|
|
321
|
+
return e;
|
|
322
|
+
const named = left.length === 1 ? `the staging directory ${left[0].dir}` : `the staging directories ${left.map((l) => l.dir).join(", ")}`;
|
|
323
|
+
return new FileError(e.code, `${e.message} (${named} could not be removed: ${left.map((l) => l.why).join("; ")})`, e.path, e);
|
|
247
324
|
}
|
|
248
325
|
async function landStaged(lane, abs, abortSignal, commitOf, direct) {
|
|
249
326
|
if (abortSignal?.aborted)
|
|
@@ -251,78 +328,114 @@ async function landStaged(lane, abs, abortSignal, commitOf, direct) {
|
|
|
251
328
|
const stage = buildStagingPaths(abs);
|
|
252
329
|
const prep = await lane.run(buildStagingPrepareCommand(stage), abortSignal);
|
|
253
330
|
if (!prep.ok) {
|
|
254
|
-
const left = await
|
|
255
|
-
return { ok: false, error: withResidue(fileErrorFromExec(prep.error, abs),
|
|
331
|
+
const left = await settleStaging(lane, stage, [], true);
|
|
332
|
+
return { ok: false, error: withResidue(fileErrorFromExec(prep.error, abs), left) };
|
|
256
333
|
}
|
|
257
334
|
if (prep.value.exitCode !== 0) {
|
|
258
|
-
|
|
335
|
+
const left = await settleStaging(lane, stage, scriptRecordsOf(prep.value.stdout).leftDirs, false);
|
|
259
336
|
if (abortSignal?.aborted)
|
|
260
|
-
return { ok: false, error: new FileError("aborted", "aborted before the write started; the target was not touched", abs) };
|
|
337
|
+
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the write started; the target was not touched", abs), left) };
|
|
261
338
|
const e = classifyFsStderr(prep.value.stderr, abs, "unknown");
|
|
262
339
|
if (prep.value.exitCode === STAGE_DIR_REFUSED_EXIT && e.code === "permission_denied")
|
|
263
|
-
return direct
|
|
340
|
+
return viaDirect(direct, abs, abortSignal, "the directory does not accept new names", left);
|
|
264
341
|
if (prep.value.exitCode === STAGE_DIR_UNSEALED_EXIT) {
|
|
265
342
|
recordFailOpen("server.remote-write.stage-seal-fallback", `${abs}: ${prep.value.stderr.trim()}`);
|
|
266
|
-
return direct
|
|
343
|
+
return viaDirect(direct, abs, abortSignal, "the private staging directory could not be made private", left);
|
|
267
344
|
}
|
|
268
|
-
return { ok: false, error: e };
|
|
345
|
+
return { ok: false, error: withResidue(e, left) };
|
|
269
346
|
}
|
|
270
347
|
if (abortSignal?.aborted) {
|
|
271
|
-
const left = await
|
|
272
|
-
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the write started; the target was not touched", abs),
|
|
348
|
+
const left = await settleStaging(lane, stage, [], true);
|
|
349
|
+
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the write started; the target was not touched", abs), left) };
|
|
350
|
+
}
|
|
351
|
+
const piece = await lane.prepare(abortSignal);
|
|
352
|
+
if (!piece.ok) {
|
|
353
|
+
const left = await settleStaging(lane, stage, [], true);
|
|
354
|
+
return { ok: false, error: withResidue(piece.error, left) };
|
|
355
|
+
}
|
|
356
|
+
let up;
|
|
357
|
+
try {
|
|
358
|
+
up = abortSignal?.aborted ? { ok: false, error: new FileError("aborted", "aborted before the write started; the target was not touched", abs) } : await piece.value.upload(stage.file, abortSignal);
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
await piece.value.release?.();
|
|
273
362
|
}
|
|
274
|
-
const up = await lane.upload(stage.file, abortSignal);
|
|
275
363
|
if (!up.ok) {
|
|
276
|
-
const left = await
|
|
277
|
-
return { ok: false, error: withResidue(up.error,
|
|
364
|
+
const left = await settleStaging(lane, stage, [], true);
|
|
365
|
+
return { ok: false, error: withResidue(up.error, left) };
|
|
278
366
|
}
|
|
279
367
|
if (abortSignal?.aborted) {
|
|
280
|
-
const left = await
|
|
281
|
-
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the commit; the target was not touched", abs),
|
|
368
|
+
const left = await settleStaging(lane, stage, [], true);
|
|
369
|
+
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the commit; the target was not touched", abs), left) };
|
|
282
370
|
}
|
|
283
371
|
const c = await lane.run(commitOf(stage));
|
|
284
372
|
if (!c.ok) {
|
|
285
|
-
const left = await
|
|
373
|
+
const left = await settleStaging(lane, stage, [], true);
|
|
286
374
|
const e = fileErrorFromExec(c.error, abs);
|
|
287
|
-
return { ok: false, error: withResidue(new FileError("unknown", `${e.message} (the commit's round trip failed, so whether it ran is unknown — re-read the target before retrying)`, abs, e),
|
|
375
|
+
return { ok: false, error: withResidue(new FileError("unknown", `${e.message} (the commit's round trip failed, so whether it ran is unknown — re-read the target before retrying)`, abs, e), left) };
|
|
376
|
+
}
|
|
377
|
+
if (c.value.exitCode === STAGE_DIR_UNSEALED_EXIT) {
|
|
378
|
+
const left = await settleStaging(lane, stage, scriptRecordsOf(c.value.stdout).leftDirs, true);
|
|
379
|
+
if (abortSignal?.aborted)
|
|
380
|
+
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the write started; the target was not touched", abs), left) };
|
|
381
|
+
recordFailOpen("server.remote-write.stage-seal-fallback", `${abs}: ${c.value.stderr.trim()}`);
|
|
382
|
+
return viaDirect(direct, abs, abortSignal, "the private staging directory could not be made private", left);
|
|
288
383
|
}
|
|
289
|
-
noteLeftDirs(c.value.stdout);
|
|
290
384
|
if (c.value.exitCode !== 0) {
|
|
291
|
-
const left = await
|
|
292
|
-
return { ok: false, error: withResidue(classifyFsStderr(c.value.stderr, abs, "unknown"),
|
|
385
|
+
const left = await settleStaging(lane, stage, scriptRecordsOf(c.value.stdout).leftDirs, true);
|
|
386
|
+
return { ok: false, error: withResidue(classifyFsStderr(c.value.stderr, abs, "unknown"), left) };
|
|
387
|
+
}
|
|
388
|
+
const records = scriptRecordsOf(c.value.stdout);
|
|
389
|
+
await settleStaging(lane, stage, records.leftDirs, false);
|
|
390
|
+
if (records.modeNotCarried !== undefined) {
|
|
391
|
+
(lane.logger ?? fallbackLaneLogger()).warn("remote_env_file_mode_failed", {
|
|
392
|
+
provider: lane.provider,
|
|
393
|
+
path: seatText("operator-log-arg", abs),
|
|
394
|
+
...(records.modeNotCarried !== "" ? { mode: seatText("operator-log-text", records.modeNotCarried) } : {}),
|
|
395
|
+
error: records.modeNotCarried !== "" ? "the target machine refused to set the target's mode on the replacement" : "the target's mode could not be read",
|
|
396
|
+
effect: "the new bytes are in place; the target's previous mode bits were not carried over, so it now has the mode the replacement was created with (setting the mode is best effort)",
|
|
397
|
+
});
|
|
293
398
|
}
|
|
294
399
|
return { ok: true, value: undefined };
|
|
295
400
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
401
|
+
let fallbackLogger;
|
|
402
|
+
function fallbackLaneLogger() {
|
|
403
|
+
return (fallbackLogger ??= createLogger());
|
|
404
|
+
}
|
|
405
|
+
async function viaDirect(direct, abs, abortSignal, why, left) {
|
|
406
|
+
const r = await writeInPlace(direct, abs, abortSignal, why);
|
|
407
|
+
return r.ok ? r : { ok: false, error: withResidue(r.error, left) };
|
|
408
|
+
}
|
|
409
|
+
async function writeInPlace(direct, abs, abortSignal, why) {
|
|
410
|
+
const abortedBefore = () => ({ ok: false, error: new FileError("aborted", `aborted before the in-place write started (${why}); the target was not touched`, abs) });
|
|
411
|
+
if (abortSignal?.aborted)
|
|
412
|
+
return abortedBefore();
|
|
413
|
+
const piece = await direct(abortSignal);
|
|
414
|
+
if (!piece.ok)
|
|
415
|
+
return piece;
|
|
416
|
+
try {
|
|
417
|
+
if (abortSignal?.aborted)
|
|
418
|
+
return abortedBefore();
|
|
419
|
+
const r = await piece.value.upload(abs, abortSignal);
|
|
420
|
+
if (r.ok || r.error.code !== "aborted")
|
|
421
|
+
return r;
|
|
422
|
+
return { ok: false, error: new FileError("unknown", `cancelled during an in-place write (${why}, so the bytes went straight to the target); the target may be partially written — re-read it before retrying`, abs, r.error) };
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
await piece.value.release?.();
|
|
302
426
|
}
|
|
303
427
|
}
|
|
304
|
-
function
|
|
305
|
-
return {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (r.ok || r.error.code !== "aborted")
|
|
312
|
-
return r;
|
|
313
|
-
return { ok: false, error: new FileError("unknown", `cancelled during an in-place write (${why}, so the bytes went straight to the target); the target may be partially written — re-read it before retrying`, abs, r.error) };
|
|
314
|
-
},
|
|
428
|
+
export function rewriteAppend(read, prepareOf, add) {
|
|
429
|
+
return async (abortSignal) => {
|
|
430
|
+
const existing = await read(abortSignal);
|
|
431
|
+
if (!existing.ok && existing.error.code !== "not_found")
|
|
432
|
+
return existing;
|
|
433
|
+
const merged = Buffer.concat([existing.ok ? existing.value : new Uint8Array(0), typeof add === "string" ? Buffer.from(add, "utf8") : add]);
|
|
434
|
+
return prepareOf(merged)(abortSignal);
|
|
315
435
|
};
|
|
316
436
|
}
|
|
317
|
-
export async function rewriteAppend(read, write, add, ctx) {
|
|
318
|
-
const existing = await read(ctx.signal);
|
|
319
|
-
if (!existing.ok && existing.error.code !== "not_found")
|
|
320
|
-
return existing;
|
|
321
|
-
const merged = Buffer.concat([existing.ok ? existing.value : new Uint8Array(0), typeof add === "string" ? Buffer.from(add, "utf8") : add]);
|
|
322
|
-
return ctx.inPlace((signal) => write(merged, signal));
|
|
323
|
-
}
|
|
324
437
|
export function writeFileStaged(lane, abs, abortSignal) {
|
|
325
|
-
return landStaged(lane, abs, abortSignal, (stage) => buildStagedCommitCommand(stage, abs),
|
|
438
|
+
return landStaged(lane, abs, abortSignal, (stage) => buildStagedCommitCommand(stage, abs), lane.prepare);
|
|
326
439
|
}
|
|
327
440
|
export function appendFileStaged(lane, abs, abortSignal, direct) {
|
|
328
441
|
return landStaged(lane, abs, abortSignal, (stage) => buildStagedAppendCommand(stage, abs), direct);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 托管留存的**声明词**(部署说的话):三只被托管的 SQL 店交给引擎的 `retention` 词,与「本部署跑不跑扫车道」这一个事实。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不住在执行器文件里:执行器(`stores-sql/retention-store-sql.ts`)是阶段三逐字搬进引擎包的那一半;「这只店的行有没有人按期删」是**部署**知道的事
|
|
5
|
+
* (扫车道开不开),声明词由装配它的部署给 —— 引擎包里同一个事实读 `retention` 选项(必填,部署表态),不读店类常量。阶段三这两只常量随店类常量一起退役。
|
|
6
|
+
*/
|
|
7
|
+
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
8
|
+
/**
|
|
9
|
+
* 三只**被托管**的 SQL 店(session / checkpoint / tool-result)在扫车道开着时的 `retention` 声明常量。
|
|
10
|
+
*
|
|
11
|
+
* 🔴 **谁说这个词**(引擎 7.58.0 起:店说 `"managed"` 而部署不跑执行器 ⇒ 锁不锁都拒启):「这只店的行有没有人按期删」不是店类自己知道的事,
|
|
12
|
+
* 是装配它的部署知道的事。唯一事实 = 扫车道开不开({@link retentionSweepRuns});后端铸店时按它给词({@link sqlStoreRetention}),
|
|
13
|
+
* 交给引擎启动检查的执行器读同一个事实(开 ⇒ 执行器,关 ⇒ `null`)。店构造不给词 ⇒ `"none"`(引擎对缺席的同一读法)。
|
|
14
|
+
*
|
|
15
|
+
* 🔴 为什么是一个共享常量而不是各写各的字面量:这一格是 core `assertRetentionCapability` 的**唯一**读点,
|
|
16
|
+
* 三只店的答案必须同进同退 —— 哪天本能力从某只店的行上撤了(比如 tool_result 改由别处清),漏改一处
|
|
17
|
+
* 就是一句谎,而谎的方向是「locked policy 放行了一只其实不删的店」。
|
|
18
|
+
*
|
|
19
|
+
* 🔴 声明的**读法**(与 core 契约的字面对齐,别读大也别读小):`"managed"` 说的是「**这只店的行**由本
|
|
20
|
+
* 部署的托管留存按期删除」,不是「本对象自己实现了三个方法」。三方法的实现体是执行器
|
|
21
|
+
* `SqlRetentionStore`(`stores-sql/retention-store-sql.ts`)—— 它是 E21 purge 协调器的事务化孪生,横跨十余张表(部署那几张经它的事务内钩)。
|
|
22
|
+
* core 的门读的是「这只店的数据会不会被按期删掉」(拒启文案逐字:「a locked retention policy over stores
|
|
23
|
+
* that cannot delete would be *policy locked, data immortal*」),SQL 三店的诚实答案就是 `"managed"`。
|
|
24
|
+
* file/in-memory 形没有任何东西会按期删它们的行 ⇒ 显式 `"none"`(缺席也读 none,但显式是文档义务)。
|
|
25
|
+
*/
|
|
26
|
+
export declare const MANAGED_RETENTION: RetentionDeclaration;
|
|
27
|
+
/** file/in-memory 形、以及扫车道关着的 SQL 店的诚实声明(见 {@link MANAGED_RETENTION} 的读法说明)。 */
|
|
28
|
+
export declare const UNMANAGED_RETENTION: RetentionDeclaration;
|
|
29
|
+
/** 本部署跑不跑托管留存的扫车道(`RETENTION_SWEEP_INTERVAL_SEC > 0`)—— 三只 SQL 店的声明与交给引擎启动检查的执行器只读这一处。 */
|
|
30
|
+
export declare function retentionSweepRuns(sweep: {
|
|
31
|
+
readonly intervalSec: number;
|
|
32
|
+
}): boolean;
|
|
33
|
+
/** 后端铸三只被托管的 SQL 店时交给店的词:扫车道开 ⇒ `"managed"`,关 ⇒ `"none"`。 */
|
|
34
|
+
export declare function sqlStoreRetention(sweep: {
|
|
35
|
+
readonly intervalSec: number;
|
|
36
|
+
}): RetentionDeclaration;
|
|
37
|
+
//# sourceMappingURL=retention-declaration.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const MANAGED_RETENTION = "managed";
|
|
2
|
+
export const UNMANAGED_RETENTION = "none";
|
|
3
|
+
export function retentionSweepRuns(sweep) {
|
|
4
|
+
return sweep.intervalSec > 0;
|
|
5
|
+
}
|
|
6
|
+
export function sqlStoreRetention(sweep) {
|
|
7
|
+
return retentionSweepRuns(sweep) ? MANAGED_RETENTION : UNMANAGED_RETENTION;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=retention-declaration.js.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 托管留存的**部署那一半** —— 本仓的表经执行器(`stores-sql/retention-store-sql.ts`)的事务内钩表(K4)参与同一个破坏性事务。
|
|
3
|
+
* 执行器是事务属主(开 / 提交 / 回滚),只认 core 的 `{domain, cutoffMs}`;部署的事实(hold、审计行的判定时点三格、认领、十一张部署表、
|
|
4
|
+
* 结束了的空间)全在这里,经钩出场:
|
|
5
|
+
* · `holdLock`(首句) —— hold 哨兵行锁读(`retention-lane-store-sql.ts` 的 {@link lockHoldSentinel});
|
|
6
|
+
* · `beforeDelete`(终集)—— 删十一张部署表里这批会话的行(下面 {@link deleteDeploymentSessionRows}),并按收到的
|
|
7
|
+
* `{sessionId, incarnation}` 自算结束了的空间(一份来源:执行器交来的那批行,不再读第二份);
|
|
8
|
+
* · `audit`(提交前末句)—— 破坏性审计行({@link appendDestructiveAuditRow}),判定时点三格是这一次调用的。
|
|
9
|
+
* 认领判(每只候选根的 `session.beforeCommit`)不在这里:它是会话座的钩(`plugins/session-store-sql.ts` 的 `createSqlSessionWriteHooks`,
|
|
10
|
+
* `retention-delete` 臂),组合座把同一只交给执行器 —— 一处实现。
|
|
11
|
+
* 钩只拿语句面(`query`),从不碰事务本身;库外副作用(回收空间)不在钩里做 —— 执行器提交成功返回之后,组合座把结束了的空间交给 lane。
|
|
12
|
+
*
|
|
13
|
+
* 钩表为什么**每次破坏性调用**铸一份(组合座 `plugins/store-backend.ts` 的 `createSqlRetentionExecutor`):审计行的 `{mode, policyDays,
|
|
14
|
+
* fencingToken}` 与 `checkpoint_ctx` 属主门要的域是**这一次调用**的事实,而 core 的钩签名不带它们(`audit` 收 `{domain, op}` 与回执,
|
|
15
|
+
* `beforeDelete` 只收行)。
|
|
16
|
+
*
|
|
17
|
+
* ── 方言差异(只列本文件独有的;其余见执行器顶注)──────────────────────────────────────────────────
|
|
18
|
+
* - 级联 DELETE 的 JOIN 形:`DELETE te FROM a te JOIN b …` vs `DELETE FROM a te USING b …`
|
|
19
|
+
*/
|
|
20
|
+
import type { ManagedRetentionCapability, RetentionReceipt } from "@sema-agent/core";
|
|
21
|
+
import type { SqlDialect, SqlExec } from "./sql-driver.js";
|
|
22
|
+
import { type RetentionAuditContext } from "./retention-lane-store-sql.js";
|
|
23
|
+
import type { EndedSpace } from "../workspace-space.js";
|
|
24
|
+
/**
|
|
25
|
+
* lane 消费的**执行器**面 —— core `ManagedRetentionCapability` 的三方法 + 非契约窄读口 `previewRetention`
|
|
26
|
+
* (audit-only 档的读数源;core 契约无 count 面)+ 声明位。真身 = 组合座 `createSqlRetentionExecutor`。
|
|
27
|
+
*
|
|
28
|
+
* 🔴 三方法的入参是 core 契约形的**超集**(多一个必填的 `audit`):判定时点三格是部署的事实,组合座把它绑进这一次调用的 audit 钩
|
|
29
|
+
* —— 写进类型,让「忘了传」是编译错。
|
|
30
|
+
*/
|
|
31
|
+
export interface RetentionExecutor {
|
|
32
|
+
readonly retention?: "managed" | "none";
|
|
33
|
+
listRetentionDomains(): Promise<readonly string[]>;
|
|
34
|
+
previewRetention(input: {
|
|
35
|
+
domain: string;
|
|
36
|
+
cutoffMs: number;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
checkpoints: number;
|
|
39
|
+
sessions: number;
|
|
40
|
+
toolResults: number;
|
|
41
|
+
}>;
|
|
42
|
+
expireCheckpoints(input: RetentionDestructiveInput): Promise<RetentionReceipt>;
|
|
43
|
+
deleteExpiredSessions(input: RetentionDestructiveInput): Promise<RetentionSessionsReceipt>;
|
|
44
|
+
deleteOrphanToolResults(input: RetentionDestructiveInput): Promise<RetentionReceipt>;
|
|
45
|
+
}
|
|
46
|
+
/** 破坏性调用的入参(判定时点的三格随行 —— 理由逐字见 {@link RetentionAuditContext} 的头注)。 */
|
|
47
|
+
export interface RetentionDestructiveInput {
|
|
48
|
+
domain: string;
|
|
49
|
+
cutoffMs: number;
|
|
50
|
+
audit: RetentionAuditContext;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 会话树 sweep 的回执 = core 契约的 `RetentionReceipt` + 本仓一位:`endedSpaces` = 本次删掉的会话树里**结束了的空间**(会话 id + 住客空间键 = 会话化身;
|
|
54
|
+
* 读不出住客的空化身不算),present-iff 非空。lane 把它交给空间的回收座(与会话删除协调器同一只),缓存卷与快照随空间的终结回收。
|
|
55
|
+
*/
|
|
56
|
+
export type RetentionSessionsReceipt = RetentionReceipt & {
|
|
57
|
+
readonly endedSpaces?: readonly EndedSpace[];
|
|
58
|
+
};
|
|
59
|
+
/** 装配面必须如实告知的**可选部署表在场性**(+ 执行器的诊断日志座)。 */
|
|
60
|
+
export interface RetentionExecutorOptions {
|
|
61
|
+
/**
|
|
62
|
+
* `task_attachment` 表是否在场。**必填,没有默认值**:boot 只在配了对象存储时才 ensure 这张表
|
|
63
|
+
* (`boot/stores.ts` 的附件段:无 MinIO ⇒ 整段不接线、表不建),于是一条盲发的 DELETE 在无对象存储的
|
|
64
|
+
* 部署上会以 unknown-table 报错、把整轮 sweep 打红。让装配层如实回答,比让钩去吞一个错误好 —— 吞错误
|
|
65
|
+
* 就是静默 fail-open(附件行从此永不被留存清理,而没有任何人知道)。
|
|
66
|
+
*/
|
|
67
|
+
taskAttachmentTable: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* [ref] `task_list_meta` / `task_list_item` 两表是否在场。**必填,没有默认值**(与
|
|
70
|
+
* `taskAttachmentTable` 同一条理由:盲发 DELETE 会在没建表的库上把整轮 sweep 打红)。
|
|
71
|
+
* 生产上 `boot/task-list-lane.ts` 在**任何 SQL 后端**都无条件 ensure 这两张表 ⇒ 装配层照实传 true;
|
|
72
|
+
* 纯内存/local 车道压根没有 SQL 留存腿。
|
|
73
|
+
*/
|
|
74
|
+
taskListTables: boolean;
|
|
75
|
+
/** 诊断日志座(可选;交给执行器)。 */
|
|
76
|
+
logger?: {
|
|
77
|
+
info?(msg: string, meta?: unknown): void;
|
|
78
|
+
warn?(msg: string, meta?: unknown): void;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** 执行器会删东西的那几只成员(钩被告知的 `op`;与执行器钩表的同名型同读 core 的键集)。 */
|
|
82
|
+
type RetentionOp = Exclude<keyof ManagedRetentionCapability, "listRetentionDomains">;
|
|
83
|
+
/**
|
|
84
|
+
* 一次破坏性调用的钩表 + 它收集到的结束了的空间。`call` = 这一次调用(域与判定时点三格)。
|
|
85
|
+
* 钩的形对齐执行器的 `SqlTransactionHooks`(组合座把 `hooks` 交给执行器时由类型系统对表,形一分叉就编译红)。
|
|
86
|
+
*/
|
|
87
|
+
export declare function createRetentionDeploymentHooks(dialect: SqlDialect, opts: RetentionExecutorOptions, call: RetentionDestructiveInput): {
|
|
88
|
+
hooks: {
|
|
89
|
+
retention: {
|
|
90
|
+
holdLock: (txn: SqlExec, at: {
|
|
91
|
+
readonly domain: string;
|
|
92
|
+
readonly op: RetentionOp;
|
|
93
|
+
}) => Promise<"proceed" | "held">;
|
|
94
|
+
beforeDelete: (txn: SqlExec, rows: readonly {
|
|
95
|
+
readonly sessionId: string;
|
|
96
|
+
readonly incarnation: string | undefined;
|
|
97
|
+
}[]) => Promise<void>;
|
|
98
|
+
audit: (txn: SqlExec, at: {
|
|
99
|
+
readonly domain: string;
|
|
100
|
+
readonly op: RetentionOp;
|
|
101
|
+
}, receipt: RetentionReceipt) => Promise<void>;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
endedSpaces: () => readonly EndedSpace[];
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* 会话树里**部署表**这一半的删除(`beforeDelete` 钩的身体):十一张部署表,发在执行器事务的连接上,先于执行器删自己的表(K4 次序)。
|
|
108
|
+
* 表与相对次序与在线协调器(`boot/session-faces.ts` 的 `purgeSession`)同一条顺序契约;等价性由
|
|
109
|
+
* `retention-e21-equivalence-db-integration.test.ts` 的同种子剩余行集断言钉住(逐表比剩余行,与发语句的先后无关)。
|
|
110
|
+
* 次序的承重理由:
|
|
111
|
+
* · `task_event` / `task_active` 经 `task_run` 连删 ⇒ 必须排在 `task_run` 删除**之前**;
|
|
112
|
+
* · `checkpoint_ctx` 的属主门是 `session_meta.owner` 的 EXISTS 子查询 ⇒ 必须在 meta 删除之前(执行器的 meta 删除在本钩之后,天然满足);
|
|
113
|
+
* · 任务清单:meta 行锁 → item → meta(与 `mutate` 同向的锁序,见该腿注)。
|
|
114
|
+
*
|
|
115
|
+
* 🔴 运行账本(task_event / task_active / task_run)**没有**属主门,按 `session_id` 单键删(F-A,2026-08-17
|
|
116
|
+
* 合并码扫描;与在线协调器同刀改):`task_run.owner` 是**每次提交的 principal**,与会话主会分叉(无主存量
|
|
117
|
+
* 会话被带 principal 的壳续聊 ⇒ 新行 owner="default")。行级属主门于是只删得掉半棵树,而 `session_meta`
|
|
118
|
+
* 在同一事务里随后就删掉 ⇒ 执行器的候选读从此**永远枚举不到**这些行:它们不是
|
|
119
|
+
* "下一轮再清",是**没有任何清理路径**的孤儿。域隔离不靠这道门承担 —— `ids` 已经是**本域** session_meta
|
|
120
|
+
* 里选出来并 `FOR UPDATE` 锁住的终集,行级门去掉之后越域仍然结构上不可能。
|
|
121
|
+
*
|
|
122
|
+
* `checkpoint_ctx` 的属主门用**域键**(`domain`,`""` = 无主桶)而不是会话的 owner 值 —— 理由逐字见执行器 `purgeSessionTrees` 头注「有意改写」段。
|
|
123
|
+
*/
|
|
124
|
+
export declare function deleteDeploymentSessionRows(txn: SqlExec, dialect: SqlDialect, domain: string, ids: readonly string[], opts: RetentionExecutorOptions): Promise<void>;
|
|
125
|
+
export {};
|
|
126
|
+
//# sourceMappingURL=retention-deployment.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { TASK_LIST_ITEM_TABLE, TASK_LIST_META_TABLE, taskListKeyFor } from "./task-list-store-sql.js";
|
|
2
|
+
import { USER_INPUT_ACCEPTANCE_TABLE } from "./user-input-ledger-sql.js";
|
|
3
|
+
import { retentionDomainWhere, retentionIdSet } from "./retention-sql.js";
|
|
4
|
+
import { appendDestructiveAuditRow, lockHoldSentinel } from "./retention-lane-store-sql.js";
|
|
5
|
+
function auditActionOf(op) {
|
|
6
|
+
switch (op) {
|
|
7
|
+
case "expireCheckpoints":
|
|
8
|
+
return "expired_checkpoints";
|
|
9
|
+
case "deleteExpiredSessions":
|
|
10
|
+
return "deleted_sessions";
|
|
11
|
+
case "deleteOrphanToolResults":
|
|
12
|
+
return "deleted_tool_results";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function endedSpacesOf(rows) {
|
|
16
|
+
return rows.flatMap((r) => (r.incarnation === undefined ? [] : [{ sessionId: r.sessionId, spaceKey: r.incarnation }]));
|
|
17
|
+
}
|
|
18
|
+
export function createRetentionDeploymentHooks(dialect, opts, call) {
|
|
19
|
+
if (!call.audit) {
|
|
20
|
+
throw new Error(`retention: refusing a destructive call in domain ${JSON.stringify(call.domain)} with no audit context — every destructive retention ` +
|
|
21
|
+
`transaction must persist its {mode, policyDays, fencingToken} alongside the deletion. Pass \`audit\` from the sweep lane's current round.`);
|
|
22
|
+
}
|
|
23
|
+
const ended = [];
|
|
24
|
+
const hooks = {
|
|
25
|
+
retention: {
|
|
26
|
+
holdLock: async (txn, at) => (await lockHoldSentinel(txn, dialect, at.domain)) ? "held" : "proceed",
|
|
27
|
+
beforeDelete: async (txn, rows) => {
|
|
28
|
+
await deleteDeploymentSessionRows(txn, dialect, call.domain, rows.map((r) => r.sessionId), opts);
|
|
29
|
+
ended.push(...endedSpacesOf(rows));
|
|
30
|
+
},
|
|
31
|
+
audit: async (txn, at, receipt) => appendDestructiveAuditRow(txn, dialect, {
|
|
32
|
+
domain: at.domain,
|
|
33
|
+
action: auditActionOf(at.op),
|
|
34
|
+
audit: call.audit,
|
|
35
|
+
deleted: receipt.deleted,
|
|
36
|
+
skipped: receipt.skipped,
|
|
37
|
+
tombstones: receipt.tombstones,
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return { hooks, endedSpaces: () => ended };
|
|
42
|
+
}
|
|
43
|
+
export async function deleteDeploymentSessionRows(txn, dialect, domain, ids, opts) {
|
|
44
|
+
const q = (mysql, pg) => (dialect === "mysql" ? mysql : pg);
|
|
45
|
+
const set = (col, pgIndex) => retentionIdSet(dialect, col, ids, pgIndex);
|
|
46
|
+
{
|
|
47
|
+
const s = set("tr.session_id", 1);
|
|
48
|
+
await txn.query(q(`DELETE te FROM task_event te JOIN task_run tr ON te.task_id = tr.task_id WHERE ${s.sql}`, `DELETE FROM task_event te USING task_run tr WHERE te.task_id = tr.task_id AND ${s.sql}`), [...s.params]);
|
|
49
|
+
}
|
|
50
|
+
{
|
|
51
|
+
const s = set("tr.session_id", 1);
|
|
52
|
+
await txn.query(q(`DELETE ta FROM task_active ta JOIN task_run tr ON ta.task_id = tr.task_id WHERE ${s.sql}`, `DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND ${s.sql}`), [...s.params]);
|
|
53
|
+
}
|
|
54
|
+
{
|
|
55
|
+
const s = set("session_id", 1);
|
|
56
|
+
await txn.query(`DELETE FROM task_run WHERE ${s.sql}`, [...s.params]);
|
|
57
|
+
}
|
|
58
|
+
{
|
|
59
|
+
const s = set("checkpoint_ctx.session_id", 1);
|
|
60
|
+
const w = retentionDomainWhere(dialect, "sm.owner", domain, 2);
|
|
61
|
+
await txn.query(`DELETE FROM checkpoint_ctx WHERE ${s.sql} AND EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = checkpoint_ctx.session_id AND ${w.sql})`, [...s.params, ...w.params]);
|
|
62
|
+
}
|
|
63
|
+
for (const [table, col] of [
|
|
64
|
+
["resume_anchor", "session_id"],
|
|
65
|
+
["approval_exemption", "session_id"],
|
|
66
|
+
...(opts.taskAttachmentTable ? [["task_attachment", "session_id"]] : []),
|
|
67
|
+
[USER_INPUT_ACCEPTANCE_TABLE, "session_id"],
|
|
68
|
+
]) {
|
|
69
|
+
const s = set(col, 1);
|
|
70
|
+
await txn.query(`DELETE FROM ${table} WHERE ${s.sql}`, s.params);
|
|
71
|
+
}
|
|
72
|
+
if (opts.taskListTables) {
|
|
73
|
+
const listKeys = ids.map((id) => taskListKeyFor(id)).sort();
|
|
74
|
+
{
|
|
75
|
+
const s = retentionIdSet(dialect, "list_key", listKeys, 1);
|
|
76
|
+
await txn.query(`SELECT list_key FROM ${TASK_LIST_META_TABLE} WHERE ${s.sql} ORDER BY list_key FOR UPDATE`, s.params);
|
|
77
|
+
}
|
|
78
|
+
for (const table of [TASK_LIST_ITEM_TABLE, TASK_LIST_META_TABLE]) {
|
|
79
|
+
const s = retentionIdSet(dialect, "list_key", listKeys, 1);
|
|
80
|
+
await txn.query(`DELETE FROM ${table} WHERE ${s.sql}`, s.params);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
{
|
|
84
|
+
const s = set("session_id", 1);
|
|
85
|
+
await txn.query(`DELETE FROM workflow_completion_inbox WHERE ${s.sql}`, s.params);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=retention-deployment.js.map
|