@sema-agent/server 7.139.0 → 7.141.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 +471 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +94 -26
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +37 -22
- package/dist/approval-card.js +6 -3
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +10 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +2 -2
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/device-lane.d.ts +11 -0
- package/dist/boot/device-lane.js +16 -0
- package/dist/boot/engine-lease.d.ts +24 -3
- package/dist/boot/engine-lease.js +9 -2
- package/dist/boot/execution-env.js +18 -12
- package/dist/boot/execution-lane.d.ts +8 -0
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/memory-boundary.d.ts +16 -53
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +16 -12
- package/dist/boot/memory-consolidation.js +24 -13
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/resolve-spec.d.ts +3 -5
- package/dist/boot/resolve-spec.js +9 -8
- package/dist/boot/runner-deps.d.ts +3 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +4 -3
- package/dist/boot/shutdown.js +5 -0
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-05-execution-env.js +3 -1
- package/dist/boot/stage-06-runners.d.ts +2 -8
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -7
- package/dist/boot/stage-07-capability-layer.js +8 -10
- package/dist/boot/stage-08-reapers.d.ts +2 -7
- package/dist/boot/stage-09-leader.d.ts +2 -7
- package/dist/boot/stage-10-http-server.d.ts +2 -7
- package/dist/boot/stage-10-http-server.js +6 -4
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +37 -44
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +36 -9
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +9 -0
- package/dist/capability-facts.js +9 -0
- package/dist/card-void.d.ts +38 -0
- package/dist/card-void.js +23 -0
- package/dist/config-catalog.js +12 -8
- package/dist/config-provider.d.ts +18 -17
- package/dist/config-provider.js +28 -36
- package/dist/config-types.d.ts +36 -9
- package/dist/config.js +56 -46
- package/dist/delegated-session-owner.d.ts +23 -20
- package/dist/delegated-session-owner.js +2 -2
- package/dist/deployment-shape.d.ts +9 -8
- package/dist/deployment-shape.js +4 -4
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +3 -0
- package/dist/execution-config-root.d.ts +22 -0
- package/dist/execution-config-root.js +35 -0
- package/dist/execution-lane-caps.d.ts +145 -107
- package/dist/execution-lane-caps.js +144 -83
- package/dist/execution-placement.d.ts +3 -0
- package/dist/execution-placement.js +7 -0
- package/dist/fleet/drain-entry.d.ts +3 -0
- package/dist/hosted-posture.d.ts +8 -0
- package/dist/hosted-posture.js +4 -0
- package/dist/http/admission.js +3 -3
- package/dist/http/caller-trust.d.ts +3 -2
- package/dist/http/caller-trust.js +2 -1
- package/dist/http/dispatch.js +4 -0
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +49 -35
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/capabilities.js +5 -4
- package/dist/http/routes/diagnostics.d.ts +0 -19
- package/dist/http/routes/diagnostics.js +16 -2
- package/dist/http/routes/memory-bundle.d.ts +2 -2
- package/dist/http/routes/memory-bundle.js +4 -1
- package/dist/http/routes/memory-compliance.js +9 -2
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +7 -2
- package/dist/http/routes/run-permission-mode.d.ts +75 -0
- package/dist/http/routes/run-permission-mode.js +170 -0
- package/dist/http/routes/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions.js +33 -15
- package/dist/http/routes/tasks.js +67 -68
- package/dist/http/run-read-face.js +3 -3
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +2 -2
- package/dist/http/server.d.ts +14 -27
- package/dist/http/server.js +2 -2
- package/dist/http/sse-log.d.ts +3 -8
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +3 -1
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +33 -9
- package/dist/memory-scope.js +17 -5
- package/dist/memory-sync-client.d.ts +34 -9
- package/dist/memory-sync-client.js +108 -28
- package/dist/observability/fail-open.d.ts +30 -2
- package/dist/observability/fail-open.js +30 -2
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +6 -2
- package/dist/observability/run-terminal.js +25 -26
- package/dist/observability/secret-env-scrub.d.ts +20 -1
- package/dist/observability/secret-env-scrub.js +14 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
- package/dist/orchestration/workflow-completion-inbox.js +13 -5
- package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -1
- package/dist/permission-mode-admission.d.ts +68 -0
- package/dist/permission-mode-admission.js +23 -0
- package/dist/permission-rule-list.d.ts +55 -6
- package/dist/permission-rule-list.js +52 -5
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-sql.js +14 -14
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +90 -19
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +23 -20
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +19 -9
- package/dist/plugins/execution-placement-sql.js +20 -9
- package/dist/plugins/file-history-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.js +4 -4
- package/dist/plugins/file-run-store.d.ts +2 -2
- package/dist/plugins/file-run-store.js +1 -1
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/local-checkpoint-store.d.ts +10 -1
- package/dist/plugins/local-checkpoint-store.js +22 -0
- package/dist/plugins/local-session-store.d.ts +227 -35
- package/dist/plugins/local-session-store.js +518 -37
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +2 -2
- package/dist/plugins/memory-run-store.js +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +52 -11
- package/dist/plugins/one-time-migrations.js +101 -39
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
- package/dist/plugins/permission-rule-store-sql.js +16 -16
- package/dist/plugins/pg-pool.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +3 -2
- package/dist/plugins/remote-env-adb.d.ts +7 -24
- package/dist/plugins/remote-env-adb.js +22 -5
- package/dist/plugins/remote-env-device.d.ts +2 -28
- package/dist/plugins/remote-env-device.js +2 -1
- package/dist/plugins/remote-env-e2b.d.ts +40 -27
- package/dist/plugins/remote-env-e2b.js +180 -20
- package/dist/plugins/remote-env-file-error.d.ts +1 -1
- package/dist/plugins/remote-env-file-error.js +8 -2
- package/dist/plugins/remote-env-host.d.ts +19 -35
- package/dist/plugins/remote-env-host.js +26 -3
- package/dist/plugins/remote-env-k8s.d.ts +22 -14
- package/dist/plugins/remote-env-k8s.js +150 -26
- package/dist/plugins/remote-env-local-docker.d.ts +27 -24
- package/dist/plugins/remote-env-local-docker.js +91 -24
- package/dist/plugins/remote-env-ssh.d.ts +4 -24
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +6 -6
- package/dist/plugins/run-store-sql.d.ts +3 -3
- package/dist/plugins/run-store-sql.js +19 -18
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/schema-consistency.d.ts +6 -6
- package/dist/plugins/schema-consistency.js +1 -1
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-ownership.d.ts +2 -1
- package/dist/plugins/session-placement.d.ts +17 -4
- package/dist/plugins/session-placement.js +2 -1
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +35 -6
- package/dist/plugins/store-backend.js +27 -10
- package/dist/plugins/store-contracts.d.ts +9 -2
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.js +7 -6
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +16 -15
- package/dist/plugins/tool-result-store-sql.d.ts +2 -2
- package/dist/plugins/tool-result-store-sql.js +3 -3
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/run-local.js +14 -19
- package/dist/runs.d.ts +47 -16
- package/dist/runs.js +112 -113
- package/dist/security.d.ts +2 -2
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +22 -4
- package/dist/session-error-wire.js +16 -0
- package/dist/session-incarnation.d.ts +10 -4
- package/dist/session-incarnation.js +1 -1
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-sync-kernel.d.ts +42 -1
- package/dist/session-sync-kernel.js +17 -0
- package/dist/session-sync.js +16 -4
- package/dist/sql-engine-coordinates.d.ts +49 -0
- package/dist/sql-engine-coordinates.js +27 -0
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/standing-answer-grant.d.ts +31 -2
- package/dist/standing-answer-grant.js +14 -2
- package/dist/store-posture.d.ts +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-a2a.js +5 -6
- package/dist/task-mcp.d.ts +21 -8
- package/dist/task-mcp.js +17 -9
- package/dist/task-settings.d.ts +52 -25
- package/dist/task-settings.js +91 -28
- package/dist/terminal.d.ts +103 -19
- package/dist/terminal.js +72 -20
- package/dist/trace/core-keyset-guard.d.ts +79 -2
- package/dist/trace/exit-seat.d.ts +7 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +395 -110
- package/dist/trace/self-minted-wire.d.ts +226 -2
- package/dist/trace/self-minted-wire.js +79 -0
- package/dist/verifier-pause-card.d.ts +8 -0
- package/dist/verifier-pause-card.js +19 -0
- package/dist/web-admin-client/apply-effective.d.ts +5 -7
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.140.0.json +184 -0
- package/wire-changes/server@7.141.0.json +367 -0
|
@@ -55,6 +55,11 @@ export function buildMigrationReceipts() {
|
|
|
55
55
|
executionPlacementsBackfilled: 0,
|
|
56
56
|
askExecutionLanesBackfilled: 0,
|
|
57
57
|
executionPlacementSessionsUnreadable: [],
|
|
58
|
+
localArchiveRowFactsBackfilled: 0,
|
|
59
|
+
localArchivesAnonymized: 0,
|
|
60
|
+
localArchivesKeptStamped: [],
|
|
61
|
+
localArchivesUnreadable: [],
|
|
62
|
+
localArchiveSnapshotsRecovered: 0,
|
|
58
63
|
};
|
|
59
64
|
}
|
|
60
65
|
function completedReport(r) {
|
|
@@ -100,7 +105,12 @@ function changedAnything(r) {
|
|
|
100
105
|
r.deviceBindingTablesDropped > 0 ||
|
|
101
106
|
r.executionPlacementsBackfilled > 0 ||
|
|
102
107
|
r.askExecutionLanesBackfilled > 0 ||
|
|
103
|
-
r.executionPlacementSessionsUnreadable.length > 0
|
|
108
|
+
r.executionPlacementSessionsUnreadable.length > 0 ||
|
|
109
|
+
r.localArchiveRowFactsBackfilled > 0 ||
|
|
110
|
+
r.localArchivesAnonymized > 0 ||
|
|
111
|
+
r.localArchivesKeptStamped.length > 0 ||
|
|
112
|
+
r.localArchivesUnreadable.length > 0 ||
|
|
113
|
+
r.localArchiveSnapshotsRecovered > 0);
|
|
104
114
|
}
|
|
105
115
|
function readingsSnapshot(r) {
|
|
106
116
|
return {
|
|
@@ -113,6 +123,8 @@ function readingsSnapshot(r) {
|
|
|
113
123
|
parkedApprovalCardRowsUnreadable: [...r.parkedApprovalCardRowsUnreadable],
|
|
114
124
|
parkedApprovalCardRowsTooLong: [...r.parkedApprovalCardRowsTooLong],
|
|
115
125
|
executionPlacementSessionsUnreadable: [...r.executionPlacementSessionsUnreadable],
|
|
126
|
+
localArchivesKeptStamped: [...r.localArchivesKeptStamped],
|
|
127
|
+
localArchivesUnreadable: [...r.localArchivesUnreadable],
|
|
116
128
|
};
|
|
117
129
|
}
|
|
118
130
|
function receiptSentence(r) {
|
|
@@ -381,7 +393,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
381
393
|
removal: "删 migrateOn 的 M23 步(`inheritPlacedSessionOwners`:placed ∧ 属主 NULL 的行按 session_id 键集分页单趟扫描 + 同一条语句连读根 / 父两行 + 按认领句回写属主)与 `PLACED_OWNER_SCAN_PAGE` / `placedOwnerScanSql` / `scannedPlacement` / `scannedFaces` / `scannedPlacedSources`(`isOrphanSessionOwner` 若再无别的读者一并删);" +
|
|
382
394
|
"**同窗同删的另一半**(认领:按律给升级前写下的无主子代行写属主 —— 停驻挂点对宿主无主行做的就是本条对那一行的单行步):`delegated-session-owner.ts` 的 `CLAIM_OWNERLESS_PLACED_SQL` / `claimOwnerlessParams` / `OwnerlessClaim` / " +
|
|
383
395
|
"`settleDelegatedOwner` 的认领一步(删后它只剩树比对与属主比对)/ `DelegationTreeStore.claimOwnerless`," +
|
|
384
|
-
"两只 SQL 店(`tidb-session-store.ts` / `pg-session-storage.ts`)的 `claimOwnerless`、`CachingSessionStore` 的转发、`OwnerAwareSessionStore.claimOwnerless`、`delegatedOwnerHostOf` 对它的「与 `placementOf` 同在或同缺」;" +
|
|
396
|
+
"两只 SQL 店(`tidb-session-store.ts` / `pg-session-storage.ts`)与 local 店(`local-session-store.ts` 的 `claimOwnerless` 字段与 `claimOwnerlessPlaced`)的 `claimOwnerless`、`CachingSessionStore` 的转发、`OwnerAwareSessionStore.claimOwnerless`、`delegatedOwnerHostOf` 对它的「与 `placementOf` 同在或同缺」;" +
|
|
385
397
|
"报告、日志行与 `NO_SQL_MIGRATIONS` 的 `placedSessionOwnersInherited` / `placedSessionOwnersOrphaned` / `placedSessionOwnersRemaining` 与 `placed_session_owners_orphaned` 那一行 warn;" +
|
|
386
398
|
"`test/one-time-migrations.test.ts` 的 M23 组与 `test/one-time-migrations-db-integration.test.ts` 的 M23 格。" +
|
|
387
399
|
"**不随本条删**:属主为 NULL 的 placed 行只对 fleet 调用方放行(`security.ts` 的 `ownerlessRowOpen` / `sessionOwnerCellOf` 与各门读 `placementOf` 的那一次)—— 那是常设的读面律",
|
|
@@ -531,6 +543,24 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
531
543
|
"跳过了带本条的版本直接升的库响亮拒启,出路 = 先在带本条的版本起服一次让切换走完,再升;缺执行放置列的库由列集对账缺列拒启(`config.schema_mismatch`,拒句给 ALTER),同一出路",
|
|
532
544
|
reportKeys: ["executionPlacementColumnsAdded", "deviceBindingsMoved", "deviceBindingsOrphaned", "deviceBindingTablesDropped", "executionPlacementsBackfilled", "askExecutionLanesBackfilled", "executionPlacementSessionsUnreadable"],
|
|
533
545
|
},
|
|
546
|
+
{
|
|
547
|
+
id: "M33",
|
|
548
|
+
introducedIn: "7.141.0",
|
|
549
|
+
releasedOn: "2026-10-30",
|
|
550
|
+
removableAfter: "2026-11-29",
|
|
551
|
+
removal: "删 `LocalSessionStore.migrateLocalArchives` 与它的私有助手 `anonymizeArchiveHeld` / `fillRowFacts`、`settleArchiveSnapshots` 里除最后一句常设臂 `assertNoArchiveSnapshots(dir)` 以外的一切(逐份消化的循环与 `settleArchiveSnapshot`)、" +
|
|
552
|
+
"`archiveSnapshotsSettled` 字段、`syncArchiveDirectory`、`rowFactsFilled` / `ArchivedRowFacts` / `archivedRowFactsOf` / `ARCHIVE_SNAPSHOT_RECORD` / `ArchiveSnapshotRecord` / `readArchiveSnapshotRecord` / `sameLog` / " +
|
|
553
|
+
"`ArchiveRewriteStep` / `LocalArchiveMigrationReading` / `errnoOf`、" +
|
|
554
|
+
"`archiveRewriteReadScope` / `noteReplayInArchiveRewriteRead` 与 `createLocalSessionRepo` 座里调它的那一腿、`SidecarMap.syncToDisk`(若再无别的调用方)与 `fsyncPaths`(同);" +
|
|
555
|
+
"`store-backend.ts` 的 `LocalBackend.runOneTimeMigrations` 里对 `migrateLocalArchives` 的调用与报告五键的那五行;本文件的 `LocalArchiveKeptReason` / `LocalArchiveKeptStamped` / `LocalArchiveUnreadable`、" +
|
|
556
|
+
"报告 / 收据 / `changedAnything` / `readingsSnapshot` / `readingFields` 里的 `localArchiveRowFactsBackfilled` / `localArchivesAnonymized` / `localArchivesKeptStamped` / `localArchivesUnreadable` / `localArchiveSnapshotsRecovered`、" +
|
|
557
|
+
"`local_archive_kept_stamped` / `local_archive_unreadable` 两行 warn、读数行 note 里的 M33 句与头注 M33 段;`test/local-archive-owner.test.ts` 里标题含 `P3` / `P4` / `P5` / `P6` / `F1` / `F2` / `F3` 的格与 `legacyRoot` / `writeArchive` / `boot` / `M33_ZERO` / `m33Of` 助手。" +
|
|
558
|
+
"**不随本条删**:行格的 `createdAt` / `forkedFrom` 两键(常设的数据形)、`LocalArchiveSnapshotsPresentError` / `assertNoArchiveSnapshots` / `LOCAL_ARCHIVE_SNAPSHOT_DIR` 与 `LocalBackend.ensureSchema` 里调它的那一句、" +
|
|
559
|
+
"`session-error-wire.ts` 的 `StampedSessionArchiveError` / `STAMPED_SESSION_ARCHIVE_SENTENCE` / `sessionErrorWireOf` 与本店两处带戳判(fork 的源、改日志推送的落点)、行上缺建档时刻的 F 类读法(`server.local-session-row.created-at-absent`)",
|
|
560
|
+
afterRemoval: "三只常设臂接住:① 数据根下的快照目录非空 ⇒ 拒启(`config.local_archive_snapshot_present`,点名文件与两条出路);② 跳过了带本条的版本直接升的数据根(或本条留着的存档),带属主戳的存档被 fork 与改日志的推送同一只具名拒(409 `conflict` + 固定句,零写 / 行格净零写)," +
|
|
561
|
+
"读 / 续跑 / 拉取照常 —— 出路 = 删掉会话后重推,或升级时先过一次带本条的版本并在窗内起服;③ 行上没有建档时刻的格(同一类数据根)⇒ 列表 / 会话视图 / 交出的元数据读纪元(F 类 tag 计数)",
|
|
562
|
+
reportKeys: ["localArchiveRowFactsBackfilled", "localArchivesAnonymized", "localArchivesKeptStamped", "localArchivesUnreadable", "localArchiveSnapshotsRecovered"],
|
|
563
|
+
},
|
|
534
564
|
];
|
|
535
565
|
export function migrationWindowOpen(window, today) {
|
|
536
566
|
return window.removableAfter === null || today <= window.removableAfter;
|
|
@@ -602,7 +632,7 @@ function pgDb(client) {
|
|
|
602
632
|
};
|
|
603
633
|
}
|
|
604
634
|
async function tableExists(db, dialect, table) {
|
|
605
|
-
const sql = dialect === "
|
|
635
|
+
const sql = dialect === "mysql"
|
|
606
636
|
? `SELECT 1 AS present FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '${table}'`
|
|
607
637
|
: `SELECT 1 AS present FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = '${table}'`;
|
|
608
638
|
return (await db.rows(sql)).length > 0;
|
|
@@ -617,12 +647,12 @@ async function execUntil(db, sql, postcondition) {
|
|
|
617
647
|
}
|
|
618
648
|
}
|
|
619
649
|
async function columnExists(db, dialect, table, column) {
|
|
620
|
-
const scope = dialect === "
|
|
650
|
+
const scope = dialect === "mysql" ? "DATABASE()" : "current_schema()";
|
|
621
651
|
const sql = `SELECT 1 AS present FROM information_schema.columns WHERE table_schema = ${scope} AND table_name = '${table}' AND column_name = '${column}'`;
|
|
622
652
|
return (await db.rows(sql)).length > 0;
|
|
623
653
|
}
|
|
624
654
|
export async function runSqlOneTimeMigrations(dialect, pool, input) {
|
|
625
|
-
return dialect === "
|
|
655
|
+
return dialect === "mysql" ? withMysqlLock(pool, (db) => migrateOn(db, "mysql", input)) : withPgLock(pool, (db) => migrateOn(db, "pg", input));
|
|
626
656
|
}
|
|
627
657
|
async function withMysqlLock(pool, run) {
|
|
628
658
|
const conn = await pool.getConnection();
|
|
@@ -663,10 +693,10 @@ async function migrateOn(db, dialect, input) {
|
|
|
663
693
|
return completedReport(receipts);
|
|
664
694
|
}
|
|
665
695
|
async function migrateStepsOn(db, dialect, receipts, input) {
|
|
666
|
-
receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "
|
|
696
|
+
receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "mysql"
|
|
667
697
|
? "UPDATE task_run SET error_code = ? WHERE status = 'failed' AND error_code IS NULL AND error = ?"
|
|
668
698
|
: "UPDATE task_run SET error_code = $1 WHERE status = 'failed' AND error_code IS NULL AND error = $2", [RUN_HOLDER_DEATH_CODES.stalled, RUN_STALLED_ERROR]);
|
|
669
|
-
receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "
|
|
699
|
+
receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "mysql"
|
|
670
700
|
? "UPDATE task_run SET error_code = ? WHERE status = 'failed' AND error_code IS NULL AND (error = ? OR error LIKE ?)"
|
|
671
701
|
: "UPDATE task_run SET error_code = $1 WHERE status = 'failed' AND error_code IS NULL AND (error = $2 OR error LIKE $3)", [RUN_HOLDER_DEATH_CODES.orphaned, RUN_ORPHANED_ERROR, `${RUN_ORPHANED_ERROR} (%`]);
|
|
672
702
|
await migrateShadowedRuleKey(db, dialect, receipts);
|
|
@@ -727,7 +757,7 @@ function switchComplete(s) {
|
|
|
727
757
|
return s.placementColumns.size === SESSION_META_PLACEMENT_COLUMNS.length && (!s.askTable || s.askLaneColumn) && !s.bindingTable && !s.parkedTable;
|
|
728
758
|
}
|
|
729
759
|
function askLaneBackfillSql(dialect) {
|
|
730
|
-
return dialect === "
|
|
760
|
+
return dialect === "mysql"
|
|
731
761
|
? `UPDATE ${APPROVAL_ASK_TABLE} a LEFT JOIN session_meta sm ON sm.session_id = a.session_id AND sm.placement_kind IS NULL SET a.execution_lane = COALESCE(sm.execution_lane, ?) WHERE a.execution_lane = ''`
|
|
732
762
|
: `UPDATE ${APPROVAL_ASK_TABLE} a SET execution_lane = COALESCE((SELECT sm.execution_lane FROM session_meta sm WHERE sm.session_id = a.session_id AND sm.placement_kind IS NULL), $1) WHERE a.execution_lane = ''`;
|
|
733
763
|
}
|
|
@@ -735,7 +765,7 @@ async function parkedRelationNamesWithRetiredPrefix(db) {
|
|
|
735
765
|
const rows = await db.rows(`SELECT i.relname AS name FROM pg_index x JOIN pg_class i ON i.oid = x.indexrelid JOIN pg_class t ON t.oid = x.indrelid WHERE t.relname = '${PARKED_DEVICE_BINDING_TABLE}' AND t.relnamespace = current_schema()::regnamespace AND i.relname LIKE '${RETIRED_DEVICE_BINDING_TABLE}\\_%'`);
|
|
736
766
|
return rows.map((r) => String(r["name"])).filter((name) => name.startsWith(`${RETIRED_DEVICE_BINDING_TABLE}_`) && !name.startsWith(PARKED_DEVICE_BINDING_TABLE));
|
|
737
767
|
}
|
|
738
|
-
const bindingRootKey = (dialect, alias) => (dialect === "
|
|
768
|
+
const bindingRootKey = (dialect, alias) => (dialect === "mysql" ? `CONVERT(${alias}.root_session_id USING utf8mb4) COLLATE utf8mb4_bin` : `${alias}.root_session_id`);
|
|
739
769
|
async function refuseOnSwitchFence(read, dialect, shape) {
|
|
740
770
|
if (!switchStarted(shape) || !shape.bindingTable)
|
|
741
771
|
return;
|
|
@@ -744,7 +774,7 @@ async function refuseOnSwitchFence(read, dialect, shape) {
|
|
|
744
774
|
return;
|
|
745
775
|
const roots = rows.slice(0, FENCE_ROOTS_SHOWN).map((r) => (Buffer.isBuffer(r["root"]) ? r["root"].toString("utf8") : String(r["root"])));
|
|
746
776
|
const more = rows.length > FENCE_ROOTS_SHOWN ? ` (and more — the table holds over ${FENCE_ROOTS_SHOWN} rows)` : "";
|
|
747
|
-
const rename = dialect === "
|
|
777
|
+
const rename = dialect === "mysql" ? `RENAME TABLE ${RETIRED_DEVICE_BINDING_TABLE} TO ${PARKED_DEVICE_BINDING_TABLE};` : `ALTER TABLE ${RETIRED_DEVICE_BINDING_TABLE} RENAME TO ${PARKED_DEVICE_BINDING_TABLE};`;
|
|
748
778
|
const adopt = shape.parkedTable
|
|
749
779
|
? `DELETE FROM ${PARKED_DEVICE_BINDING_TABLE} WHERE root_session_id IN (SELECT root_session_id FROM ${RETIRED_DEVICE_BINDING_TABLE}); INSERT INTO ${PARKED_DEVICE_BINDING_TABLE} (${BINDING_COLUMNS}) SELECT ${BINDING_COLUMNS} FROM ${RETIRED_DEVICE_BINDING_TABLE}; DELETE FROM ${RETIRED_DEVICE_BINDING_TABLE};`
|
|
750
780
|
: rename;
|
|
@@ -795,10 +825,10 @@ export async function assertExecutionPlacementSwitchAdmissible(read, dialect, in
|
|
|
795
825
|
await refuseOnAmbiguousBackfill(read, dialect, shape, input.lanes);
|
|
796
826
|
}
|
|
797
827
|
export async function assertExecutionPlacementSwitchAdmissibleOnPool(dialect, pool, input) {
|
|
798
|
-
if (dialect === "
|
|
828
|
+
if (dialect === "mysql") {
|
|
799
829
|
const conn = await pool.getConnection();
|
|
800
830
|
try {
|
|
801
|
-
await assertExecutionPlacementSwitchAdmissible(async (sql) => (await conn.query(sql))[0], "
|
|
831
|
+
await assertExecutionPlacementSwitchAdmissible(async (sql) => (await conn.query(sql))[0], "mysql", input);
|
|
802
832
|
}
|
|
803
833
|
finally {
|
|
804
834
|
conn.release();
|
|
@@ -830,7 +860,7 @@ const EXECUTION_PLACEMENT_SWITCH_STEPS = [
|
|
|
830
860
|
const s = await shape();
|
|
831
861
|
if (!s.bindingTable || s.placementColumns.has("execution_role"))
|
|
832
862
|
return;
|
|
833
|
-
const sql = dialect === "
|
|
863
|
+
const sql = dialect === "mysql" ? `RENAME TABLE ${RETIRED_DEVICE_BINDING_TABLE} TO ${PARKED_DEVICE_BINDING_TABLE}` : `ALTER TABLE ${RETIRED_DEVICE_BINDING_TABLE} RENAME TO ${PARKED_DEVICE_BINDING_TABLE}`;
|
|
834
864
|
await execUntil(db, sql, async () => {
|
|
835
865
|
const after = await shape();
|
|
836
866
|
return after.parkedTable && !after.bindingTable;
|
|
@@ -862,7 +892,7 @@ const EXECUTION_PLACEMENT_SWITCH_STEPS = [
|
|
|
862
892
|
}
|
|
863
893
|
const ask = await shape();
|
|
864
894
|
if (ask.askTable && !ask.askLaneColumn) {
|
|
865
|
-
await execUntil(db, `ALTER TABLE ${APPROVAL_ASK_TABLE} ADD COLUMN ${APPROVAL_ASK_EXECUTION_LANE_COLUMN[dialect
|
|
895
|
+
await execUntil(db, `ALTER TABLE ${APPROVAL_ASK_TABLE} ADD COLUMN ${APPROVAL_ASK_EXECUTION_LANE_COLUMN[dialect]}`, async () => (await shape()).askLaneColumn);
|
|
866
896
|
receipts.executionPlacementColumnsAdded += 1;
|
|
867
897
|
}
|
|
868
898
|
},
|
|
@@ -973,7 +1003,7 @@ async function dropRejectedMailboxKeys(db, dialect, receipts) {
|
|
|
973
1003
|
for (const table of MAILBOX_KEYED_TABLES)
|
|
974
1004
|
if (await tableExists(db, dialect, table))
|
|
975
1005
|
present.push(table);
|
|
976
|
-
const p = (i) => (dialect === "
|
|
1006
|
+
const p = (i) => (dialect === "mysql" ? "?" : `$${i}`);
|
|
977
1007
|
for (const table of present) {
|
|
978
1008
|
let after;
|
|
979
1009
|
for (;;) {
|
|
@@ -1007,14 +1037,14 @@ const M24_OWNER_INCARNATION = "m24-backfill";
|
|
|
1007
1037
|
const M24_COPY_BATCH_BYTES = 32 * 1024 * 1024;
|
|
1008
1038
|
const M24_COPY_SCAN_PAGE = 1000;
|
|
1009
1039
|
function journalKeyBound(dialect, op, first) {
|
|
1010
|
-
const p = (i) => (dialect === "
|
|
1040
|
+
const p = (i) => (dialect === "mysql" ? "?" : `$${first + i}`);
|
|
1011
1041
|
return op === "after"
|
|
1012
1042
|
? `(${WORKFLOW_JOURNAL_TABLE}.run_id > ${p(0)} OR (${WORKFLOW_JOURNAL_TABLE}.run_id = ${p(1)} AND ordinal > ${p(2)}))`
|
|
1013
1043
|
: `(${WORKFLOW_JOURNAL_TABLE}.run_id < ${p(0)} OR (${WORKFLOW_JOURNAL_TABLE}.run_id = ${p(1)} AND ordinal <= ${p(2)}))`;
|
|
1014
1044
|
}
|
|
1015
1045
|
async function copyJournalInBatches(db, dialect) {
|
|
1016
1046
|
const T = WORKFLOW_JOURNAL_TABLE;
|
|
1017
|
-
const bytes = dialect === "
|
|
1047
|
+
const bytes = dialect === "mysql" ? "LENGTH(result)" : "OCTET_LENGTH(result)";
|
|
1018
1048
|
const copyHead = `INSERT INTO ${WORKFLOW_JOURNAL_STAGING} (run_id, incarnation, ordinal, call_key, result, created_at_ms) ` +
|
|
1019
1049
|
`SELECT ${T}.run_id, o.incarnation, ordinal, call_key, ${jsonColumnText(dialect, "result")}, created_at_ms FROM ${T} ` +
|
|
1020
1050
|
`JOIN ${WORKFLOW_JOURNAL_OWNER_TABLE} o ON o.run_id = ${T}.run_id AND o.scope = ${T}.${RETIRED_JOURNAL_SCOPE_COLUMN} WHERE `;
|
|
@@ -1053,7 +1083,7 @@ export async function moveWorkflowJournalScope(db, dialect, receipts) {
|
|
|
1053
1083
|
receipts.workflowJournalScopeColumn = "current";
|
|
1054
1084
|
return;
|
|
1055
1085
|
}
|
|
1056
|
-
const ddl = (dialect === "
|
|
1086
|
+
const ddl = (dialect === "mysql" ? SCHEMA_STATEMENTS : PG_SCHEMA_STATEMENTS).find((stmt) => stmt.includes(`CREATE TABLE IF NOT EXISTS ${T} (`));
|
|
1057
1087
|
if (ddl === undefined)
|
|
1058
1088
|
throw new Error(`one-time migration M24: the central schema carries no definition of ${T} — refusing to rebuild a table it cannot define`);
|
|
1059
1089
|
if (await exists(S))
|
|
@@ -1067,7 +1097,7 @@ export async function moveWorkflowJournalScope(db, dialect, receipts) {
|
|
|
1067
1097
|
await db.exec(ddl.replace(`CREATE TABLE IF NOT EXISTS ${T} (`, `CREATE TABLE IF NOT EXISTS ${S} (`));
|
|
1068
1098
|
await copyJournalInBatches(db, dialect);
|
|
1069
1099
|
const swapped = async () => !(await exists(S)) && (await exists(T)) && !(await legacy());
|
|
1070
|
-
if (dialect === "
|
|
1100
|
+
if (dialect === "mysql") {
|
|
1071
1101
|
await execUntil(db, `RENAME TABLE ${T} TO ${O}, ${S} TO ${T}`, swapped);
|
|
1072
1102
|
receipts.workflowJournalForeignScopeRowsRemoved += foreign;
|
|
1073
1103
|
await execUntil(db, `DROP TABLE IF EXISTS ${O}`, async () => !(await exists(O)));
|
|
@@ -1110,9 +1140,9 @@ const M17_PROVEN_GRAMMAR = 1;
|
|
|
1110
1140
|
const RETIRED_BUCKET_PATTERNS = ['%"commandDeny"%', '%"commandAllow"%'];
|
|
1111
1141
|
async function scanSessionRuleRows(db, dialect, column, patterns, visit) {
|
|
1112
1142
|
const text = jsonColumnTextExpr(dialect, column);
|
|
1113
|
-
const prefilter = patterns.length === 0 ? "" : ` AND (${patterns.map((_, i) => `${text} LIKE ${dialect === "
|
|
1114
|
-
const scan = dialect === "
|
|
1115
|
-
? `SELECT policy_key, session_id, rev, ${jsonColumnText("
|
|
1143
|
+
const prefilter = patterns.length === 0 ? "" : ` AND (${patterns.map((_, i) => `${text} LIKE ${dialect === "mysql" ? "?" : `$${i + 2}`}`).join(" OR ")})`;
|
|
1144
|
+
const scan = dialect === "mysql"
|
|
1145
|
+
? `SELECT policy_key, session_id, rev, ${jsonColumnText("mysql", column)} FROM ${SESSION_POLICY_TABLE} WHERE policy_key > ?${prefilter} ORDER BY policy_key LIMIT ${SESSION_RULES_SCAN_PAGE}`
|
|
1116
1146
|
: `SELECT policy_key, session_id, rev, ${jsonColumnText("pg", column)} FROM ${SESSION_POLICY_TABLE} WHERE policy_key > $1${prefilter} ORDER BY policy_key LIMIT ${SESSION_RULES_SCAN_PAGE}`;
|
|
1117
1147
|
let cursor = "";
|
|
1118
1148
|
for (;;) {
|
|
@@ -1137,7 +1167,7 @@ async function scanSessionRuleRows(db, dialect, column, patterns, visit) {
|
|
|
1137
1167
|
async function judgeOldGrammarRows(db, dialect, receipts) {
|
|
1138
1168
|
const column = OLD_GRAMMAR_SESSION_RULES_COLUMN;
|
|
1139
1169
|
const text = jsonColumnTextExpr(dialect, column);
|
|
1140
|
-
const cas = dialect === "
|
|
1170
|
+
const cas = dialect === "mysql"
|
|
1141
1171
|
? `UPDATE ${SESSION_POLICY_TABLE} SET ${column} = ? WHERE policy_key = ? AND CAST(${text} AS BINARY) = CAST(? AS BINARY)`
|
|
1142
1172
|
: `UPDATE ${SESSION_POLICY_TABLE} SET ${column} = $1::jsonb WHERE policy_key = $2 AND ${text} = $3`;
|
|
1143
1173
|
await scanSessionRuleRows(db, dialect, column, [], async (row) => {
|
|
@@ -1188,12 +1218,12 @@ export async function migrateSessionRuleGrammar(db, dialect, receipts) {
|
|
|
1188
1218
|
});
|
|
1189
1219
|
}
|
|
1190
1220
|
function renameSessionRulesColumnSql(dialect, from, to) {
|
|
1191
|
-
const definition = SESSION_RULES_COLUMN.
|
|
1192
|
-
return dialect === "
|
|
1221
|
+
const definition = SESSION_RULES_COLUMN.mysql.slice(SESSION_RULES_COLUMN.name.length);
|
|
1222
|
+
return dialect === "mysql" ? `ALTER TABLE ${SESSION_POLICY_TABLE} CHANGE COLUMN ${from} ${to}${definition}` : `ALTER TABLE ${SESSION_POLICY_TABLE} RENAME COLUMN ${from} TO ${to}`;
|
|
1193
1223
|
}
|
|
1194
1224
|
const WORKFLOW_PARK_INDEX_TABLE = "workflow_agent_session";
|
|
1195
1225
|
async function primaryKeyWidth(db, dialect, table) {
|
|
1196
|
-
const sql = dialect === "
|
|
1226
|
+
const sql = dialect === "mysql"
|
|
1197
1227
|
? `SELECT COUNT(*) AS n FROM information_schema.key_column_usage WHERE table_schema = DATABASE() AND table_name = '${table}' AND constraint_name = 'PRIMARY'`
|
|
1198
1228
|
: `SELECT COUNT(*) AS n FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ` +
|
|
1199
1229
|
`ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema AND kcu.table_name = tc.table_name ` +
|
|
@@ -1207,10 +1237,10 @@ async function reshapeWorkflowParkIndex(db, dialect, receipts) {
|
|
|
1207
1237
|
const S = WORKFLOW_PARK_INDEX_STAGING;
|
|
1208
1238
|
const O = WORKFLOW_PARK_INDEX_RETIRED;
|
|
1209
1239
|
const exists = (t) => tableExists(db, dialect, t);
|
|
1210
|
-
const ddl = (dialect === "
|
|
1240
|
+
const ddl = (dialect === "mysql" ? SCHEMA_STATEMENTS : PG_SCHEMA_STATEMENTS).find((stmt) => stmt.includes(`CREATE TABLE IF NOT EXISTS ${T} (`));
|
|
1211
1241
|
if (ddl === undefined)
|
|
1212
1242
|
throw new Error(`one-time migration M9: the central schema carries no definition of ${T} — refusing to reshape a table it cannot rebuild`);
|
|
1213
|
-
const copyPointers = (to, from) => db.exec(dialect === "
|
|
1243
|
+
const copyPointers = (to, from) => db.exec(dialect === "mysql"
|
|
1214
1244
|
? `INSERT IGNORE INTO ${to} (session_id, run_id, agent_key, scope) SELECT session_id, run_id, agent_key, scope FROM ${from}`
|
|
1215
1245
|
: `INSERT INTO ${to} (session_id, run_id, agent_key, scope) SELECT session_id, run_id, agent_key, scope FROM ${from} ON CONFLICT DO NOTHING`);
|
|
1216
1246
|
const resumed = await exists(S);
|
|
@@ -1230,7 +1260,7 @@ async function reshapeWorkflowParkIndex(db, dialect, receipts) {
|
|
|
1230
1260
|
await db.exec(ddl.replace(`CREATE TABLE IF NOT EXISTS ${T} (`, `CREATE TABLE IF NOT EXISTS ${S} (`));
|
|
1231
1261
|
receipts.workflowParkIndexRowsCarried += await copyPointers(S, T);
|
|
1232
1262
|
const swapped = async () => !(await exists(S)) && (await exists(T)) && (await primaryKeyWidth(db, dialect, T)) !== 1;
|
|
1233
|
-
if (dialect === "
|
|
1263
|
+
if (dialect === "mysql") {
|
|
1234
1264
|
await execUntil(db, `RENAME TABLE ${T} TO ${O}, ${S} TO ${T}`, swapped);
|
|
1235
1265
|
await execUntil(db, `DROP TABLE IF EXISTS ${O}`, async () => !(await exists(O)));
|
|
1236
1266
|
}
|
|
@@ -1260,7 +1290,7 @@ async function execAtomically(db, statements, postcondition) {
|
|
|
1260
1290
|
}
|
|
1261
1291
|
}
|
|
1262
1292
|
async function finishParkIndexShape(db, dialect) {
|
|
1263
|
-
await ensureIndexes(async (sql, params) => (dialect === "
|
|
1293
|
+
await ensureIndexes(async (sql, params) => (dialect === "mysql" ? [await db.rows(sql, params)] : { rows: await db.rows(sql, params) }), dialect, CENTRAL_SCHEMA_INDEXES.filter((spec) => spec.table === WORKFLOW_PARK_INDEX_TABLE));
|
|
1264
1294
|
if (dialect !== "pg")
|
|
1265
1295
|
return;
|
|
1266
1296
|
const conventional = `${WORKFLOW_PARK_INDEX_TABLE}_pkey`;
|
|
@@ -1293,7 +1323,7 @@ async function migrateShadowedRuleKey(db, dialect, receipts) {
|
|
|
1293
1323
|
const like = `%"${RETIRED_SHADOWED_RULE_KEY}"%`;
|
|
1294
1324
|
let after = "";
|
|
1295
1325
|
for (;;) {
|
|
1296
|
-
const rows = await db.rows(dialect === "
|
|
1326
|
+
const rows = await db.rows(dialect === "mysql"
|
|
1297
1327
|
? `SELECT token, ${jsonColumnText(dialect, "risk_descriptor")} FROM checkpoint WHERE ${jsonColumnTextExpr(dialect, "risk_descriptor")} LIKE ? AND token > ? ORDER BY token LIMIT ${SHADOWED_RULE_BATCH}`
|
|
1298
1328
|
: `SELECT token, ${jsonColumnText(dialect, "risk_descriptor")} FROM checkpoint WHERE ${jsonColumnTextExpr(dialect, "risk_descriptor")} LIKE $1 AND token > $2 ORDER BY token LIMIT ${SHADOWED_RULE_BATCH}`, [like, after]);
|
|
1299
1329
|
for (const r of rows) {
|
|
@@ -1309,7 +1339,7 @@ async function migrateShadowedRuleKey(db, dialect, receipts) {
|
|
|
1309
1339
|
}
|
|
1310
1340
|
if (next.kind === "untouched")
|
|
1311
1341
|
continue;
|
|
1312
|
-
receipts.allowRuleShadowRowsMigrated += await db.exec(dialect === "
|
|
1342
|
+
receipts.allowRuleShadowRowsMigrated += await db.exec(dialect === "mysql"
|
|
1313
1343
|
? `UPDATE checkpoint SET risk_descriptor = ? WHERE token = ? AND ${jsonColumnTextExpr(dialect, "risk_descriptor")} = ?`
|
|
1314
1344
|
: `UPDATE checkpoint SET risk_descriptor = $1 WHERE token = $2 AND ${jsonColumnTextExpr(dialect, "risk_descriptor")} = $3`, [next.text, token, text]);
|
|
1315
1345
|
}
|
|
@@ -1342,7 +1372,7 @@ async function migrateRetiredCardWords(db, dialect, receipts) {
|
|
|
1342
1372
|
const cardText = jsonColumnTextExpr(dialect, "card_json");
|
|
1343
1373
|
let after = "";
|
|
1344
1374
|
for (;;) {
|
|
1345
|
-
const rows = await db.rows(dialect === "
|
|
1375
|
+
const rows = await db.rows(dialect === "mysql"
|
|
1346
1376
|
? `SELECT ask_id, ${jsonColumnText(dialect, "card_json")} FROM approval_ask WHERE (${cardText} LIKE ? OR ${cardText} LIKE ?) AND ask_id > ? ORDER BY ask_id LIMIT ${RETIRED_CARD_WORD_BATCH}`
|
|
1347
1377
|
: `SELECT ask_id, ${jsonColumnText(dialect, "card_json")} FROM approval_ask WHERE (${cardText} LIKE $1 OR ${cardText} LIKE $2) AND ask_id > $3 ORDER BY ask_id LIMIT ${RETIRED_CARD_WORD_BATCH}`, [...RETIRED_CARD_WORD_PATTERNS, after]);
|
|
1348
1378
|
for (const r of rows) {
|
|
@@ -1358,7 +1388,7 @@ async function migrateRetiredCardWords(db, dialect, receipts) {
|
|
|
1358
1388
|
}
|
|
1359
1389
|
if (next.kind === "untouched")
|
|
1360
1390
|
continue;
|
|
1361
|
-
receipts.retiredCardWordAskRowsRewritten += await db.exec(dialect === "
|
|
1391
|
+
receipts.retiredCardWordAskRowsRewritten += await db.exec(dialect === "mysql"
|
|
1362
1392
|
? `UPDATE approval_ask SET card_json = ? WHERE ask_id = ? AND CAST(${cardText} AS BINARY) = CAST(? AS BINARY)`
|
|
1363
1393
|
: `UPDATE approval_ask SET card_json = $1 WHERE ask_id = $2 AND ${cardText} = $3`, [next.text, askId, text]);
|
|
1364
1394
|
}
|
|
@@ -1398,7 +1428,7 @@ function m21Subtree(cell, where) {
|
|
|
1398
1428
|
}
|
|
1399
1429
|
}
|
|
1400
1430
|
export async function repairParkedRows(db, dialect, receipts) {
|
|
1401
|
-
const ph = (n) => (dialect === "
|
|
1431
|
+
const ph = (n) => (dialect === "mysql" ? "?" : `$${n}`);
|
|
1402
1432
|
const kinds = APPROVAL_GATE_KINDS;
|
|
1403
1433
|
const kindSlots = kinds.map((_, i) => ph(i + 1)).join(", ");
|
|
1404
1434
|
const scan = `SELECT token, session_id, gate_kind, ${jsonColumnText(dialect, "checkpoint", M21_GATE)}, ${jsonColumnText(dialect, "checkpoint", M21_PENDING_ACTION)} FROM checkpoint ` +
|
|
@@ -1441,7 +1471,7 @@ export async function repairParkedRows(db, dialect, receipts) {
|
|
|
1441
1471
|
continue;
|
|
1442
1472
|
}
|
|
1443
1473
|
const next = encode({ ...doc, gate: { ...gate, approvalCard: QUESTION_CARD } }, "checkpoint (M21 question-card repair)");
|
|
1444
|
-
receipts.parkedQuestionCardsWritten += await db.exec(dialect === "
|
|
1474
|
+
receipts.parkedQuestionCardsWritten += await db.exec(dialect === "mysql"
|
|
1445
1475
|
? `UPDATE checkpoint SET checkpoint = ? WHERE token = ? AND status = 'pending' AND CAST(${docText} AS BINARY) = CAST(? AS BINARY)`
|
|
1446
1476
|
: `UPDATE checkpoint SET checkpoint = $1::jsonb WHERE token = $2 AND status = 'pending' AND ${docText} = $3`, [next, token, String(full.checkpoint)]);
|
|
1447
1477
|
}
|
|
@@ -1454,7 +1484,7 @@ const readM29Document = createObjectColumnReader();
|
|
|
1454
1484
|
export async function backfillParkedApprovalCards(db, dialect, receipts) {
|
|
1455
1485
|
if (!(await columnExists(db, dialect, "checkpoint", "approval_card")))
|
|
1456
1486
|
return;
|
|
1457
|
-
const ph = (n) => (dialect === "
|
|
1487
|
+
const ph = (n) => (dialect === "mysql" ? "?" : `$${n}`);
|
|
1458
1488
|
const kinds = APPROVAL_GATE_KINDS;
|
|
1459
1489
|
const scan = `SELECT token FROM checkpoint WHERE status = 'pending' AND approval_card IS NULL AND gate_kind IN (${kinds.map((_, i) => ph(i + 1)).join(", ")}) ` +
|
|
1460
1490
|
`AND token > ${ph(kinds.length + 1)} ORDER BY token LIMIT ${PARKED_CARD_BACKFILL_PAGE}`;
|
|
@@ -1510,7 +1540,7 @@ function placedOwnerScanSql(dialect) {
|
|
|
1510
1540
|
"p.session_id AS parent_row, p.owner AS parent_owner, p.incarnation AS parent_incarnation, p.placement_kind AS parent_kind, " +
|
|
1511
1541
|
"c.placement_root_session_id AS root_id, c.placement_parent_session_id AS parent_id " +
|
|
1512
1542
|
"FROM session_meta c LEFT JOIN session_meta r ON r.session_id = NULLIF(c.placement_root_session_id, '') LEFT JOIN session_meta p ON p.session_id = NULLIF(c.placement_parent_session_id, '') " +
|
|
1513
|
-
`WHERE c.placement_kind IS NOT NULL AND c.owner IS NULL AND c.session_id > ${dialect === "
|
|
1543
|
+
`WHERE c.placement_kind IS NOT NULL AND c.owner IS NULL AND c.session_id > ${dialect === "mysql" ? "?" : "$1"} ORDER BY c.session_id LIMIT ${PLACED_OWNER_SCAN_PAGE}`);
|
|
1514
1544
|
}
|
|
1515
1545
|
function scannedPlacement(r) {
|
|
1516
1546
|
return {
|
|
@@ -1602,6 +1632,11 @@ function readingFields(r) {
|
|
|
1602
1632
|
executionPlacementsBackfilled: r.executionPlacementsBackfilled,
|
|
1603
1633
|
askExecutionLanesBackfilled: r.askExecutionLanesBackfilled,
|
|
1604
1634
|
executionPlacementSessionsUnreadable: r.executionPlacementSessionsUnreadable.length,
|
|
1635
|
+
localArchiveRowFactsBackfilled: r.localArchiveRowFactsBackfilled,
|
|
1636
|
+
localArchivesAnonymized: r.localArchivesAnonymized,
|
|
1637
|
+
localArchivesKeptStamped: r.localArchivesKeptStamped.length,
|
|
1638
|
+
localArchivesUnreadable: r.localArchivesUnreadable.length,
|
|
1639
|
+
localArchiveSnapshotsRecovered: r.localArchiveSnapshotsRecovered,
|
|
1605
1640
|
};
|
|
1606
1641
|
}
|
|
1607
1642
|
export function logOneTimeMigrations(log, backend, report) {
|
|
@@ -1650,7 +1685,12 @@ export function logOneTimeMigrations(log, backend, report) {
|
|
|
1650
1685
|
"M32: session↔device bindings moved from the retired table device_session onto the root session rows (the table is first renamed device_session_retired, " +
|
|
1651
1686
|
"the eight execution placement columns are added to session_meta, every binding overwrites the device placement of its root session row — a binding whose " +
|
|
1652
1687
|
"root session row is gone is dropped and counted, named by a device_bindings_orphaned line — and the renamed table is dropped), and session rows that have " +
|
|
1653
|
-
"runs but no execution placement placed on the only declared lane; once the switch is complete this step does nothing
|
|
1688
|
+
"runs but no execution placement placed on the only declared lane; once the switch is complete this step does nothing; " +
|
|
1689
|
+
"M33 (local backend only): each local session's creation time and fork lineage were copied from its stored archive into its session-metadata cell, and archives " +
|
|
1690
|
+
"that still carried an importer's owner stamp were rewritten as anonymous with every entry kept as it was (a snapshot of the log is written before each rewrite " +
|
|
1691
|
+
"and removed after it; an interrupted rewrite is finished from the snapshot on the next boot); an archive whose log the import check refuses, whose re-import would " +
|
|
1692
|
+
"change its content, or whose read skipped unreadable records is left byte for byte as it was and named by a local_archive_kept_stamped line, and a session whose " +
|
|
1693
|
+
"archive header cannot be read is named by a local_archive_unreadable line). Each step " +
|
|
1654
1694
|
"runs on every boot until its window closes (the release that introduced it + 30 days; the per-step date is in " +
|
|
1655
1695
|
"removableAfter, null while that release is not published yet) and is deleted in the first minor after that — start a " +
|
|
1656
1696
|
"version that still carries a step at least once before upgrading past its deletion.",
|
|
@@ -1658,6 +1698,28 @@ export function logOneTimeMigrations(log, backend, report) {
|
|
|
1658
1698
|
logReceiptRows(log, backend, report);
|
|
1659
1699
|
}
|
|
1660
1700
|
function logReceiptRows(log, backend, r) {
|
|
1701
|
+
for (const row of r.localArchivesKeptStamped) {
|
|
1702
|
+
log.warn("local_archive_kept_stamped", {
|
|
1703
|
+
backend,
|
|
1704
|
+
sessionId: row.sessionId,
|
|
1705
|
+
reason: row.reason,
|
|
1706
|
+
...(row.detail !== undefined ? { detail: row.detail } : {}),
|
|
1707
|
+
note: "this local session's stored archive still carries the owner stamp an earlier version wrote, and the one-time migration that rewrites such archives as anonymous left it byte for byte as it was " +
|
|
1708
|
+
"(reason invalid = the import check refuses its log; normalizes = re-importing would change its content; records_skipped = reading it skipped unreadable records, which a rewrite would erase). " +
|
|
1709
|
+
"Reading, resuming and pulling the session work as before; forking it is refused and a push that changes its log answers 409 conflict. Way out: delete the session and push it again " +
|
|
1710
|
+
"(a push onto an absent session creates it anew), or repair the archive under the data root's sessions directory. Named on every boot until then",
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
for (const row of r.localArchivesUnreadable) {
|
|
1714
|
+
log.warn("local_archive_unreadable", {
|
|
1715
|
+
backend,
|
|
1716
|
+
sessionId: row.sessionId,
|
|
1717
|
+
...(row.errno !== undefined ? { errno: row.errno } : {}),
|
|
1718
|
+
note: "this local session's stored archive cannot be read (its header, or the whole archive), so the one-time migration could neither copy its creation time into its session-metadata cell nor rewrite it; " +
|
|
1719
|
+
"nothing was written. Every request that needs the session keeps answering the archive's own fault until the archive under the data root's sessions directory is restored from a kept copy " +
|
|
1720
|
+
"(the next boot then migrates it) or the session is deleted. Named on every boot until then",
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1661
1723
|
if (r.deviceBindingsOrphaned > 0) {
|
|
1662
1724
|
log.warn("device_bindings_orphaned", {
|
|
1663
1725
|
backend,
|
|
@@ -128,8 +128,8 @@ export class SqlOrgDeliveryQuotaStore {
|
|
|
128
128
|
constructor(db) {
|
|
129
129
|
this.db = db;
|
|
130
130
|
}
|
|
131
|
-
q(
|
|
132
|
-
return this.db.dialect === "
|
|
131
|
+
q(mysql, pg) {
|
|
132
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
133
133
|
}
|
|
134
134
|
async admitAttempt(fromScope, policy) {
|
|
135
135
|
return this.inTx(ORG_DELIVERY_SENDER_QUOTA_TABLE, policy, (conn, at) => this.admitIn(conn, fromScope, at, policy));
|
|
@@ -87,8 +87,8 @@ export class SqlOutcomeLedger {
|
|
|
87
87
|
constructor(db) {
|
|
88
88
|
this.db = db;
|
|
89
89
|
}
|
|
90
|
-
q(
|
|
91
|
-
return this.db.dialect === "
|
|
90
|
+
q(mysql, pg) {
|
|
91
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
92
92
|
}
|
|
93
93
|
json(value) {
|
|
94
94
|
return dialectJsonEncoder(this.db.dialect)(value);
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { type PermissionRuleWriter, type DurableRulePartition, type DurableRulePartitionProvider, type RuleScope, type RuleSyncFrontier, type RuleBehavior, type RuleCandidate, type RuleApprovalRecord, type StaleRuleApprovalRecord, type RuleApprovalRecordStore, type RuleOwner } from "@sema-agent/core";
|
|
3
3
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
4
4
|
import type { PgQueryFn } from "./pg-query.js";
|
|
5
|
-
import { type SqlDriver } from "./sql-driver.js";
|
|
5
|
+
import { type SqlDriver, type SqlDialect } from "./sql-driver.js";
|
|
6
6
|
import { type IndexSpec } from "./ensure-index.js";
|
|
7
7
|
/**
|
|
8
8
|
* 一只 durable 分区的写面,或 `undefined`(该 backend 从引擎侧看是只读的)。
|
|
@@ -105,7 +105,7 @@ export declare function ensureTiDBPermissionRuleSchema(pool: MySqlPool): Promise
|
|
|
105
105
|
*/
|
|
106
106
|
export declare function assertPermissionRuleApprovalSchema(query: (sql: string) => Promise<{
|
|
107
107
|
rows: Record<string, unknown>[];
|
|
108
|
-
}>, dialect:
|
|
108
|
+
}>, dialect: SqlDialect): Promise<void>;
|
|
109
109
|
/**
|
|
110
110
|
* [ref] A7 升级前置断言 —— **拒启**(形与理由逐字照 {@link assertPermissionRuleApprovalSchema} 的配方,
|
|
111
111
|
* 那条头注写了这道门为什么必须是拒启而不是 warn:本仓不发 `ALTER TABLE`,`CREATE TABLE IF NOT EXISTS`
|
|
@@ -127,7 +127,7 @@ export declare function assertPermissionRuleApprovalSchema(query: (sql: string)
|
|
|
127
127
|
*/
|
|
128
128
|
export declare function assertPermissionRuleTicketSchema(query: (sql: string) => Promise<{
|
|
129
129
|
rows: Record<string, unknown>[];
|
|
130
|
-
}>, dialect:
|
|
130
|
+
}>, dialect: SqlDialect): Promise<void>;
|
|
131
131
|
/** [ref]:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgPermissionRuleSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
132
132
|
export declare const PERMISSION_RULE_INDEXES: readonly IndexSpec[];
|
|
133
133
|
export declare function ensurePgPermissionRuleSchema(q: PgQueryFn): Promise<void>;
|
|
@@ -502,7 +502,7 @@ export interface UndurableScopePurgeDb {
|
|
|
502
502
|
* 读数写进调用方递进来的账(`into`,{@link UndurableScopePurgeReadings}):删掉的行在那只桶的回写提交之后追加 ⇒ 本函数半途抛出时,已删的行已经在账上;
|
|
503
503
|
* 错误**原样**上抛(不加工、不自铸拒因句 —— 收据由迁移段那一只收据账随拒因带走,[ref])。
|
|
504
504
|
*/
|
|
505
|
-
export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect:
|
|
505
|
+
export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect: SqlDialect, into: UndurableScopePurgeReadings, now?: () => number): Promise<void>;
|
|
506
506
|
/** 三个 SQL 面的一次性装配束(one driver, three faces)。`durable` = [ref] 的 durable 分区后端;
|
|
507
507
|
* 引擎接缝上的统一店由 `main.ts` 用 `createPermissionRuleStoreProvider({ durable })` 合成一次。 */
|
|
508
508
|
export interface PermissionRuleStores {
|
|
@@ -410,7 +410,7 @@ function refuseRowsTheValidatorRefuses(ownerKey, rules, tombstones) {
|
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
function fencedCasWhere(dialect, firstPlaceholder, ownerKey, cas) {
|
|
413
|
-
const ph = (offset) => (dialect === "
|
|
413
|
+
const ph = (offset) => (dialect === "mysql" ? "?" : `$${firstPlaceholder + offset}`);
|
|
414
414
|
return {
|
|
415
415
|
sql: `owner_key = ${ph(0)} AND ${cas.column} = ${ph(1)} AND (grammar IS NULL OR grammar <= ${ph(2)})`,
|
|
416
416
|
params: [ownerKey, cas.expected, SESSION_RULES_GRAMMAR],
|
|
@@ -430,7 +430,7 @@ async function writeBucket(exec, dialect, ownerKey, cur, next, nowMs) {
|
|
|
430
430
|
const body = { rules: next.rules, tombstones: next.tombstones, quarantined: cur.quarantined, ...(cur.observedVector !== undefined ? { observedVector: cur.observedVector } : {}) };
|
|
431
431
|
const enc = dialectProtocolJsonEncoder(dialect);
|
|
432
432
|
const where = fencedCasWhere(dialect, 7, ownerKey, { column: "rev", expected: cur.rev });
|
|
433
|
-
const affected = await exec(dialect === "
|
|
433
|
+
const affected = await exec(dialect === "mysql"
|
|
434
434
|
? `UPDATE ${PERMISSION_RULE_TABLE} SET rules_json = ?, tombstones_json = ?, quarantined_json = ?, checksum = ?, grammar = ?, rev = rev + 1, updated_at_ms = ? WHERE ${where.sql}`
|
|
435
435
|
: `UPDATE ${PERMISSION_RULE_TABLE} SET rules_json = $1, tombstones_json = $2, quarantined_json = $3, checksum = $4, grammar = $5, rev = rev + 1, updated_at_ms = $6 WHERE ${where.sql}`, [
|
|
436
436
|
enc(next.rules, "permission_rule.rules_json"),
|
|
@@ -460,13 +460,13 @@ class SqlDurableRulePartition {
|
|
|
460
460
|
return dialectProtocolJsonEncoder(this.db.dialect)(v, label);
|
|
461
461
|
}
|
|
462
462
|
q(tidbSql, pgSql) {
|
|
463
|
-
return this.db.dialect === "
|
|
463
|
+
return this.db.dialect === "mysql" ? tidbSql : pgSql;
|
|
464
464
|
}
|
|
465
465
|
async read() {
|
|
466
466
|
return refusingUnreadableColumns(`bucket ${this.ownerKey}`, () => this.readBucketRow());
|
|
467
467
|
}
|
|
468
468
|
async readBucketRow() {
|
|
469
|
-
const { rows } = await this.db.query(this.q(`SELECT ${bucketRowColumns("
|
|
469
|
+
const { rows } = await this.db.query(this.q(`SELECT ${bucketRowColumns("mysql")} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?`, `SELECT ${bucketRowColumns("pg")} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`), [this.ownerKey]);
|
|
470
470
|
const row = rows[0];
|
|
471
471
|
if (row === undefined)
|
|
472
472
|
return { ...EMPTY_BUCKET, rules: [], tombstones: [], quarantined: [] };
|
|
@@ -604,7 +604,7 @@ export class SqlRuleApprovalRecordStore {
|
|
|
604
604
|
this.now = now;
|
|
605
605
|
}
|
|
606
606
|
q(tidbSql, pgSql) {
|
|
607
|
-
return this.db.dialect === "
|
|
607
|
+
return this.db.dialect === "mysql" ? tidbSql : pgSql;
|
|
608
608
|
}
|
|
609
609
|
enc(v, label) {
|
|
610
610
|
return dialectProtocolJsonEncoder(this.db.dialect)(v, label);
|
|
@@ -613,7 +613,7 @@ export class SqlRuleApprovalRecordStore {
|
|
|
613
613
|
return refusingUnreadableColumns(`approval record ${id}`, () => this.readApprovalRow(id));
|
|
614
614
|
}
|
|
615
615
|
async readApprovalRow(id) {
|
|
616
|
-
const { rows } = await this.db.query(this.q(`SELECT record_id, owner_kind, principal, kind, state, version, rev, ${jsonColumnText("
|
|
616
|
+
const { rows } = await this.db.query(this.q(`SELECT record_id, owner_kind, principal, kind, state, version, rev, ${jsonColumnText("mysql", "candidates_json")}, ${jsonColumnText("mysql", "offers_json")}, selected_offer, ${jsonColumnText("mysql", "redeemed_dots_json")}, tool_call_id, bound_input_hash, command, ${jsonColumnText("mysql", "edited_json")}, created_at_iso FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE record_id = ?`, `SELECT record_id, owner_kind, principal, kind, state, version, rev, ${jsonColumnText("pg", "candidates_json")}, ${jsonColumnText("pg", "offers_json")}, selected_offer, ${jsonColumnText("pg", "redeemed_dots_json")}, tool_call_id, bound_input_hash, command, ${jsonColumnText("pg", "edited_json")}, created_at_iso FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE record_id = $1`), [id]);
|
|
617
617
|
const row = rows[0];
|
|
618
618
|
if (row === undefined)
|
|
619
619
|
return undefined;
|
|
@@ -717,7 +717,7 @@ export class SqlRuleImportTicketStore {
|
|
|
717
717
|
this.db = db;
|
|
718
718
|
}
|
|
719
719
|
q(tidbSql, pgSql) {
|
|
720
|
-
return this.db.dialect === "
|
|
720
|
+
return this.db.dialect === "mysql" ? tidbSql : pgSql;
|
|
721
721
|
}
|
|
722
722
|
async dbNowMs() {
|
|
723
723
|
return sqlDbNowMs(this.db, PERMISSION_RULE_TICKET_TABLE);
|
|
@@ -755,7 +755,7 @@ export class SqlRuleImportTicketStore {
|
|
|
755
755
|
if (row.expiresAtMs <= row.dbNowMs)
|
|
756
756
|
return { ok: false, reason: "expired" };
|
|
757
757
|
const claimId = randomUUID();
|
|
758
|
-
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.
|
|
758
|
+
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.mysql}, claim_id = ? WHERE ticket_id = ? AND owner_key = ? AND purpose = ? AND consumed_at_ms IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.mysql}`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.pg}, claim_id = $1 WHERE ticket_id = $2 AND owner_key = $3 AND purpose = $4 AND consumed_at_ms IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.pg}`), [claimId, ticketId, ownerKey, purpose]);
|
|
759
759
|
if (affected === 1)
|
|
760
760
|
return { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId };
|
|
761
761
|
const after = await this.readRow(ticketId);
|
|
@@ -768,11 +768,11 @@ export class SqlRuleImportTicketStore {
|
|
|
768
768
|
return { ok: false, reason: "consumed" };
|
|
769
769
|
}
|
|
770
770
|
async release(ticketId, principal, claimId, mustRemainValidMs = 0) {
|
|
771
|
-
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = NULL, claim_id = NULL WHERE ticket_id = ? AND owner_key = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.
|
|
771
|
+
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = NULL, claim_id = NULL WHERE ticket_id = ? AND owner_key = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.mysql} + ?`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = NULL, claim_id = NULL WHERE ticket_id = $1 AND owner_key = $2 AND claim_id = $3 AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.pg} + $4`), [ticketId, buildRuleOwnerKey({ kind: "principal", principal }), claimId, mustRemainValidMs]);
|
|
772
772
|
return affected === 1;
|
|
773
773
|
}
|
|
774
774
|
async settle(ticketId, principal, claimId, outcome) {
|
|
775
|
-
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET redeemed_outcome = ?, settled_at_ms = ${SQL_DB_NOW_MS.
|
|
775
|
+
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET redeemed_outcome = ?, settled_at_ms = ${SQL_DB_NOW_MS.mysql} WHERE ticket_id = ? AND owner_key = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET redeemed_outcome = $1, settled_at_ms = ${SQL_DB_NOW_MS.pg} WHERE ticket_id = $2 AND owner_key = $3 AND claim_id = $4 AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL`), [
|
|
776
776
|
dialectProtocolJsonEncoder(this.db.dialect)(outcome, "permission_rule_ticket.redeemed_outcome"),
|
|
777
777
|
ticketId,
|
|
778
778
|
buildRuleOwnerKey({ kind: "principal", principal }),
|
|
@@ -805,11 +805,11 @@ export class SqlRuleImportTicketStore {
|
|
|
805
805
|
if (row.claimId === undefined)
|
|
806
806
|
return { ok: false };
|
|
807
807
|
const claimId = randomUUID();
|
|
808
|
-
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.
|
|
808
|
+
const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.mysql}, claim_id = ? WHERE ticket_id = ? AND owner_key = ? AND purpose = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND consumed_at_ms <= ${SQL_DB_NOW_MS.mysql} - ? AND redeemed_outcome IS NULL`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.pg}, claim_id = $1 WHERE ticket_id = $2 AND owner_key = $3 AND purpose = $4 AND claim_id = $5 AND consumed_at_ms IS NOT NULL AND consumed_at_ms <= ${SQL_DB_NOW_MS.pg} - $6 AND redeemed_outcome IS NULL`), [claimId, ticketId, buildRuleOwnerKey({ kind: "principal", principal }), purpose, row.claimId, minClaimAgeMs]);
|
|
809
809
|
return affected === 1 ? { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId } : { ok: false };
|
|
810
810
|
}
|
|
811
811
|
async readRow(ticketId) {
|
|
812
|
-
const { rows } = await this.db.query(this.q(`SELECT owner_key, approval_id, payload_hash, purpose, expires_at_ms, consumed_at_ms, claim_id, ${jsonColumnText("
|
|
812
|
+
const { rows } = await this.db.query(this.q(`SELECT owner_key, approval_id, payload_hash, purpose, expires_at_ms, consumed_at_ms, claim_id, ${jsonColumnText("mysql", "redeemed_outcome")}, ${SQL_DB_NOW_MS.mysql} AS db_now_ms FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ticket_id = ?`, `SELECT owner_key, approval_id, payload_hash, purpose, expires_at_ms, consumed_at_ms, claim_id, ${jsonColumnText("pg", "redeemed_outcome")}, ${SQL_DB_NOW_MS.pg} AS db_now_ms FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ticket_id = $1`), [ticketId]);
|
|
813
813
|
const row = rows[0];
|
|
814
814
|
if (row === undefined)
|
|
815
815
|
return undefined;
|
|
@@ -834,7 +834,7 @@ export async function purgeUndurableScopeRows(db, dialect, into, now = Date.now)
|
|
|
834
834
|
const cols = `owner_key, ${bucketRowColumns(dialect)}`;
|
|
835
835
|
let after = "";
|
|
836
836
|
for (;;) {
|
|
837
|
-
const page = await db.rows(dialect === "
|
|
837
|
+
const page = await db.rows(dialect === "mysql"
|
|
838
838
|
? `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > ? ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`
|
|
839
839
|
: `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > $1 ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`, [after]);
|
|
840
840
|
for (const first of page) {
|
|
@@ -857,7 +857,7 @@ export async function purgeUndurableScopeRows(db, dialect, into, now = Date.now)
|
|
|
857
857
|
settled = true;
|
|
858
858
|
break;
|
|
859
859
|
case "conflict":
|
|
860
|
-
row = (await db.rows(dialect === "
|
|
860
|
+
row = (await db.rows(dialect === "mysql" ? `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?` : `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`, [ownerKey]))[0];
|
|
861
861
|
if (row === undefined)
|
|
862
862
|
settled = true;
|
|
863
863
|
break;
|
|
@@ -907,13 +907,13 @@ function contentFreeRefusal(ownerKey, e) {
|
|
|
907
907
|
return `permission_rule bucket ${ownerKey}: the bucket judge threw ${e instanceof Error ? e.name : typeof e} (its text is withheld here: it may quote the row)`;
|
|
908
908
|
}
|
|
909
909
|
export function createSqlPermissionRuleStores(db, now) {
|
|
910
|
-
const q = (
|
|
910
|
+
const q = (mysql, pg) => (db.dialect === "mysql" ? mysql : pg);
|
|
911
911
|
return {
|
|
912
912
|
durable: new SqlDurableRulePartitionProvider(db, now),
|
|
913
913
|
approvals: new SqlRuleApprovalRecordStore(db, now),
|
|
914
914
|
tickets: new SqlRuleImportTicketStore(db),
|
|
915
915
|
reapExpired: async (nowMs) => {
|
|
916
|
-
const deadTickets = await db.query(q(`DELETE FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE (consumed_at_ms IS NULL AND expires_at_ms <= ${SQL_DB_NOW_MS.
|
|
916
|
+
const deadTickets = await db.query(q(`DELETE FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE (consumed_at_ms IS NULL AND expires_at_ms <= ${SQL_DB_NOW_MS.mysql} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (redeemed_outcome IS NOT NULL AND settled_at_ms IS NOT NULL AND settled_at_ms <= ${SQL_DB_NOW_MS.mysql} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND consumed_at_ms < ${SQL_DB_NOW_MS.mysql} - ${RULE_PENDING_APPROVAL_RETENTION_MS}) LIMIT ${RULE_REAP_BATCH}`, `DELETE FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ctid IN (SELECT ctid FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE (consumed_at_ms IS NULL AND expires_at_ms <= ${SQL_DB_NOW_MS.pg} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (redeemed_outcome IS NOT NULL AND settled_at_ms IS NOT NULL AND settled_at_ms <= ${SQL_DB_NOW_MS.pg} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND consumed_at_ms < ${SQL_DB_NOW_MS.pg} - ${RULE_PENDING_APPROVAL_RETENTION_MS}) LIMIT ${RULE_REAP_BATCH})`), []);
|
|
917
917
|
const orphanPending = await db.query(q(`DELETE FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE state = 'pending' AND created_at_ms < ? AND NOT EXISTS (SELECT 1 FROM ${PERMISSION_RULE_TICKET_TABLE} t WHERE t.approval_id = ${PERMISSION_RULE_APPROVAL_TABLE}.record_id) LIMIT ${RULE_REAP_BATCH}`, `DELETE FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE ctid IN (SELECT a.ctid FROM ${PERMISSION_RULE_APPROVAL_TABLE} a WHERE a.state = 'pending' AND a.created_at_ms < $1 AND NOT EXISTS (SELECT 1 FROM ${PERMISSION_RULE_TICKET_TABLE} t WHERE t.approval_id = a.record_id) LIMIT ${RULE_REAP_BATCH})`), [nowMs - RULE_PENDING_APPROVAL_RETENTION_MS]);
|
|
918
918
|
return deadTickets.affected + orphanPending.affected;
|
|
919
919
|
},
|