@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
|
@@ -2,7 +2,10 @@ import type { Pool as MySqlPool } from "mysql2/promise";
|
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
3
|
import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity, type ToolApprovalCard } from "@sema-agent/core";
|
|
4
4
|
import { type ParkRowFacts, type RuleOfferProjection, type SkillCardFacts } from "../approval-card.js";
|
|
5
|
+
import type { KindlessBatchRowSource, KindlessBatchScan } from "../approval-batch-census.js";
|
|
5
6
|
import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
|
|
7
|
+
import type { SqlRunStore } from "./run-store-sql.js";
|
|
8
|
+
import type { FencedRun } from "./store-contracts.js";
|
|
6
9
|
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
7
10
|
import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
|
|
8
11
|
/** ctx 行改写店腿的读数(两孪生同形):扫到的可读行 · 回写成的行 · 回写成的行里改写函数报的改动数 · 读不出的行 · 改写之后一行装不下的行。 */
|
|
@@ -59,7 +62,8 @@ export declare function tokenFingerprint(token: string): string;
|
|
|
59
62
|
* 型与值都只有一个来源:`approval-card.ts` 的 {@link parkRowFacts}(两只店各读自己手里的那只寄存行 `pendingAction`,
|
|
60
63
|
* 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。
|
|
61
64
|
*
|
|
62
|
-
*
|
|
65
|
+
* [ref]:授予绑定那一臂(`boundGrantDigest` / `boundGrantAbsence` 恰带其一)是 {@link ParkRowFacts} 的一部分,同样读自寄存行。
|
|
66
|
+
* 技能卡两位({@link SkillCardFacts}:`boundGrants` / `classifierNoRuling`)另有一只投影 {@link skillCardFacts},输入是 core 的检查点摘要而不是
|
|
63
67
|
* 寄存行原值(清单只在哈希等于摘要时才配上 wire,校验点单点在 core —— 见 {@link skillCardFacts} 顶注);同一张行上与收件箱行逐字同值,在场才带。 */
|
|
64
68
|
export interface PendingCheckpoint extends ParkRowFacts, SkillCardFacts {
|
|
65
69
|
sessionId: string;
|
|
@@ -271,6 +275,12 @@ export declare const CHECKPOINT_ADDED_COLUMNS: ReadonlyArray<{
|
|
|
271
275
|
export declare function assertCheckpointColumnSchema(query: (sql: string) => Promise<{
|
|
272
276
|
rows: Record<string, unknown>[];
|
|
273
277
|
}>, dialect: SqlDialect): Promise<void>;
|
|
278
|
+
/**
|
|
279
|
+
* 本店读者({@link boundedRuleOffers})**交给 core 读者的那份输入**:offer 扫描窗 {@link MAX_RULE_OFFER_SCAN} 与成员前缀帽({@link preboundBatchMembers})都扣在读之前,
|
|
280
|
+
* 不是列表的值原样透传。**唯一写法**:回读 / 铸行 / LOCAL 孪生三条腿与起服普查({@link SqlCheckpointStore.scanKindlessBatchRows} 的 `rule_suggestions` 载体)读同一份输入 ——
|
|
281
|
+
* 普查若对整只数组另判,「前 8 个成员合法、第 9 个是垃圾」的行本店读者读得出(走存量臂)而普查读不出,会把仍靠存量臂的行报成零。
|
|
282
|
+
*/
|
|
283
|
+
export declare function boundedReaderInput(raw: unknown): unknown;
|
|
274
284
|
/**
|
|
275
285
|
* `pendingAction.ruleOffers` 的**铸行侧**投影(mint 面,与 {@link boundedToolInput} 同位同待遇)。
|
|
276
286
|
*
|
|
@@ -460,8 +470,17 @@ interface SteerColumnRead {
|
|
|
460
470
|
* 列有内容却读不出 ⇒ `lossy`(两个调用点对它的处置见 {@link SteerColumnRead})。
|
|
461
471
|
*/
|
|
462
472
|
export declare function readPendingSteerQueueColumn(queueRaw: unknown): SteerColumnRead;
|
|
473
|
+
/** 认领栅栏下改写一行 ctx 的结局(闭集;SQL / local 两孪生同词):`written` 写了;`claim_released` 接下改写的那条腿此刻已不持有这条 run(认领放了 / 不在跑 / 腿判据答否)⇒ 零写;
|
|
474
|
+
* `context_absent` 持有、但会话没有 ctx 行 ⇒ 零写(不建行)。 */
|
|
475
|
+
export declare const CTX_CLAIM_REWRITES: readonly ["written", "claim_released", "context_absent"];
|
|
476
|
+
export type CtxClaimRewrite = (typeof CTX_CLAIM_REWRITES)[number];
|
|
477
|
+
/** SQL checkpoint 店的装配选项:托管留存声明 + ctx 改写的认领栅栏(同一只池上的 run 店)。 */
|
|
478
|
+
export interface SqlCheckpointStoreOptions {
|
|
479
|
+
readonly retention?: RetentionDeclaration;
|
|
480
|
+
readonly claims?: SqlRunStore;
|
|
481
|
+
}
|
|
463
482
|
/** Dual-dialect durable CheckpointStore. See the file header for the dialect-delta ledger. */
|
|
464
|
-
export declare class SqlCheckpointStore implements CheckpointStore {
|
|
483
|
+
export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatchRowSource {
|
|
465
484
|
protected readonly db: SqlDriver;
|
|
466
485
|
protected readonly logger?: {
|
|
467
486
|
info?(msg: string, meta?: unknown): void;
|
|
@@ -514,11 +533,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
514
533
|
readonly execution: {
|
|
515
534
|
readonly outcome: true;
|
|
516
535
|
};
|
|
536
|
+
/** ctx 改写的认领栅栏({@link SqlCheckpointStore.rewriteCtxWhileClaimed}):后端装配绑**同一只池**上的 run 店(`plugins/store-backend.ts` 的 `checkpoint()`)。
|
|
537
|
+
* 缺席 ⇒ 那一口响亮抛(装配缺陷),其余口不读它。 */
|
|
538
|
+
private readonly claims;
|
|
517
539
|
constructor(db: SqlDriver, logger?: {
|
|
518
540
|
info?(msg: string, meta?: unknown): void;
|
|
519
|
-
} | undefined, opts?:
|
|
520
|
-
readonly retention?: RetentionDeclaration;
|
|
521
|
-
});
|
|
541
|
+
} | undefined, opts?: SqlCheckpointStoreOptions);
|
|
522
542
|
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
523
543
|
private q;
|
|
524
544
|
/** JSON column binding. TiDB: plain `JSON.stringify`, stored verbatim. PG: `pgProtocolJsonStringify` —
|
|
@@ -691,18 +711,19 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
691
711
|
*/
|
|
692
712
|
listPending(scope?: string): Promise<PendingCheckpoint[]>;
|
|
693
713
|
/**
|
|
694
|
-
* 🔴 [ref] ——
|
|
714
|
+
* 🔴 [ref] —— 运维队列行的**技能卡两位**({@link SkillCardFacts}:清单 / 成因词)。值与收件箱行同源:core `summarizeCheckpoint` 对同一行投的摘要,经 {@link skillCardFacts} 这一只投影。
|
|
715
|
+
* (授予绑定那一臂 —— 摘要 / 缺席词 —— 不在这里:[ref] 起由 {@link parkRowFacts} 读子树投出,本读口失败时它照在。)
|
|
695
716
|
*
|
|
696
|
-
* 为什么不像 {@link parkRowFacts} 那几位一样直接读 `pendingAction`
|
|
717
|
+
* 为什么不像 {@link parkRowFacts} 那几位一样直接读 `pendingAction` 子树原值:**清单** `boundGrants` 在寄存行上是未经哈希校验的原值,core 只在摘要里「哈希等于摘要」
|
|
697
718
|
* 才发布它(「never a list beside a digest that binds another」);校验读口与哈希函数都不在 core 的公开导出面上,本仓既算不出也不该再写一份 ——
|
|
698
719
|
* 于是要拿到校验过的清单,只有对这一行跑 core 的 `summarizeCheckpoint`,而它吃的是整只 {@link Checkpoint}(不是子树)。
|
|
699
720
|
*
|
|
700
|
-
* 读放大(如实)
|
|
701
|
-
* 主 SELECT 仍只取 `pendingAction` 子树(NP-3 不退)
|
|
702
|
-
* 🚧 清单校验读口成为 core
|
|
721
|
+
* 读放大(如实):**只对行上可能带两位之一的行**(摘要是串,或成因词键在场 —— {@link parkRowCarriesSkillCardFacts})多读该行一次整只快照;普通卡(绝大多数行)零额外读,
|
|
722
|
+
* 主 SELECT 仍只取 `pendingAction` 子树(NP-3 不退)。这是一个**只增不改的代价**:带两位之一的待决行数 × 读次数(`/stream` 每连接每 3s 一拍)。
|
|
723
|
+
* 🚧 清单校验读口成为 core 公开导出的那一版,本读口与门一并退役,两位改走 `parkRowFacts` 读原值经 core 的读口 —— 零额外读。
|
|
703
724
|
*
|
|
704
725
|
* 失败方向与 {@link readParkRowActionCell} 逐字同一条判据(展示/分诊材料、行级容忍 + 响亮、同一个 tag):整只快照在场却不是一只合法的检查点(读回的体坏了,或 core 的摘要读不动它)
|
|
705
|
-
* ⇒
|
|
726
|
+
* ⇒ 只丢**这一行**的两位(授予绑定那一臂照在);行已不在待决(两次读之间被决/过期)⇒ 两位缺席;`not-text`(读腿漏了投影)原样抛。
|
|
706
727
|
*/
|
|
707
728
|
private readParkedSkillCardFacts;
|
|
708
729
|
/**
|
|
@@ -836,6 +857,23 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
836
857
|
* reader the read leg uses — BEFORE the statement runs: refuse loudly, leave the row on disk untouched. */
|
|
837
858
|
putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
|
|
838
859
|
getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
|
|
860
|
+
/**
|
|
861
|
+
* **认领栅栏下改写既有 ctx 行** —— 在飞 run 的切档口(`http/routes/run-permission-mode.ts`)把准入的模式词写回存储体的唯一写口。
|
|
862
|
+
*
|
|
863
|
+
* 规则:只有**接下这笔改写的那条腿**此刻仍持有这条 run 时才写 —— `taskId` 持有 `sessionId` 的会话认领、这条 run 在跑、且调用方的腿判据 `legHolds`
|
|
864
|
+
* 对锁住那一行的 {@link FencedRun} 答是(认领与 `running` 只认 taskId:续跑腿让同一个 taskId 停驻之后回到 `running`、认领仍在,旧腿迟到的改写不许盖掉续跑的新腿
|
|
865
|
+
* 写下的体 —— 那是 ABA,只有腿判据分得开;今天的腿身份 = 副本 `instanceId`(续跑的 CAS 赢点改写它)∧ 调用方进程里这条 run 的活流仍是接下改写的那一只)。
|
|
866
|
+
* 写在所绑 run 店的认领栅栏事务里
|
|
867
|
+
* (`SqlRunStore.whileHoldingClaim`:先锁 run 行、再锁认领行 —— 与终局 / 收割同序 —— 刷新活性,本口的锁读 + UPDATE 在同一个事务连接上)。
|
|
868
|
+
* 于是「这条 run 终局放掉认领 → 同会话新 run 写下它自己的 ctx → 本口迟到的改写」这一序**结构上不可能**盖掉新 run 的行:终局要等本事务提交才拿得到
|
|
869
|
+
* run 行的锁,拿到之后本口再来就判出不持有、一个字节都不写。`putCtx` 的无条件 upsert 只给持有认领的那几条腿自己的写点(提交 / 续跑 CAS 赢点)用;
|
|
870
|
+
* 不在腿里的写者走这一口。
|
|
871
|
+
* 只改**既有**行(锁读零行 ⇒ `context_absent`,不建行):切档改的是这条 run 提交时写下的那一行,没有那一行就没有要改的存储体。
|
|
872
|
+
* 结局闭集 {@link CtxClaimRewrite}:`written` / `claim_released`(接下改写的那条腿已不持有这条 run —— 认领放了 / 不在跑 / 腿判据答否:零写)/ `context_absent`(持有、无行:零写)。
|
|
873
|
+
* 腿判据在栅栏里同步问一次(锁已持有、写之前);它只读、不许 await。
|
|
874
|
+
* 编码与形闸同 {@link SqlCheckpointStore.putCtx}(写之前判,坏形响亮拒、盘上不动);一行装不下 ⇒ `StoreRowTooLargeError`。
|
|
875
|
+
*/
|
|
876
|
+
rewriteCtxWhileClaimed(sessionId: string, taskId: string, ctx: SealedCheckpointCtx, legHolds: (run: FencedRun) => boolean): Promise<CtxClaimRewrite>;
|
|
839
877
|
/**
|
|
840
878
|
* 🔴 **一次性迁移的 ctx 行改写店腿**({@link CheckpointCtxRowsRewriter};用它的迁移与各自的行改写见那里,窗与删法见 `plugins/one-time-migrations.ts` 登记表;
|
|
841
879
|
* 本店不知道改写是什么,只负责扫与回写):按 `session_id` 键集分页扫 `checkpoint_ctx`
|
|
@@ -845,6 +883,20 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
845
883
|
* ⇒ 不计、不重试(新写者是本版写下的、已是新形;是旧版副本就由之后的起服再扫到)。一行装不下(如封签比明文长)⇒ 计 `tooLarge`、那一行留原样,其余照扫。
|
|
846
884
|
*/
|
|
847
885
|
rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
|
|
886
|
+
/**
|
|
887
|
+
* 起服普查的读口({@link KindlessBatchRowSource};用法、范围与『为什么只数不改』见 `approval-batch-census.ts` 头注)—— 两个 SQL 方言**同一份实现**:
|
|
888
|
+
* 按 `token` 键集分页扫 `checkpoint`(所有状态的行),取两个载体,**各按它的读者判**(判官都是 core `readRuleOffers` 给读出的 batch 打的 `kindlessForm` 记号,
|
|
889
|
+
* {@link countKindlessBatchOffers};一行里任一载体带旧形成员即算一行):
|
|
890
|
+
* · `rule_suggestions` 列(审批候选的落列)—— 读者是本店的回读口,吃的是 {@link boundedReaderInput}(扫描窗 + 成员前缀帽);普查读同一份输入;
|
|
891
|
+
* · blob 里 `pendingAction.ruleOffers`(引擎铸、本店原样存、`put` 不校验)—— 读者是引擎(整只数组,没有本店的窗),普查读整只数组;在场却不是列表 ⇒ 数进 `unreadableCells`。
|
|
892
|
+
* 预筛(只为省传输,**不是判官**):`rule_suggestions` 是原样存的 TEXT(转义写法可以不同于 `JSON.stringify`),所以只问 `IS NOT NULL`,不做字符串匹配;
|
|
893
|
+
* blob 是 JSON 承载列(MySQL 协议 JSON / PG jsonb 入库即规范化),它的 `pendingAction` 子树文本里一个字符串值 `batch` 恒以 `"batch"` 字面出现 ⇒ `LIKE '%"batch"%'` 对它是完备的
|
|
894
|
+
* (空白不同无妨:MySQL 协议 `"kind": "batch"`)。
|
|
895
|
+
* 读不出形的 cell(坏 JSON / 不是列该有的形;读口是 {@link decodeNullableJsonColumn} 同一只,只容忍「体」的问题,读腿漏投影的 `not-text` 原样抛)不计入行,
|
|
896
|
+
* 数进 `unreadableCells`(到不了读者,但不静默:`rule_suggestions` 坏 cell 在队列读面有 `server.approvals.rule-suggestions-cell-unreadable`,普查另出 warn)。
|
|
897
|
+
* 行指纹 = {@link tokenFingerprint}(token 是机密,永不进遥测)。**只读**:不改任何一行;逐页读、**不是原子快照**(页间别的副本的写入可以让一行被漏数或多数一行)。
|
|
898
|
+
*/
|
|
899
|
+
scanKindlessBatchRows(): Promise<KindlessBatchScan>;
|
|
848
900
|
/**
|
|
849
901
|
* GC ctx rows whose lifecycle is over (bound to the checkpoint, don't leak):
|
|
850
902
|
* a session with NO pending checkpoint, NO active run, AND not touched since `cutoff`.
|
|
@@ -883,16 +935,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
883
935
|
export declare class TiDBCheckpointStore extends SqlCheckpointStore {
|
|
884
936
|
constructor(pool: MySqlPool, logger?: {
|
|
885
937
|
info?(msg: string, meta?: unknown): void;
|
|
886
|
-
}, opts?:
|
|
887
|
-
readonly retention?: RetentionDeclaration;
|
|
888
|
-
});
|
|
938
|
+
}, opts?: SqlCheckpointStoreOptions);
|
|
889
939
|
}
|
|
890
940
|
/** PostgreSQL binding — historical class name + ctor shape preserved. */
|
|
891
941
|
export declare class PgCheckpointStore extends SqlCheckpointStore {
|
|
892
942
|
constructor(pool: PgPool, logger?: {
|
|
893
943
|
info?(msg: string, meta?: unknown): void;
|
|
894
|
-
}, opts?:
|
|
895
|
-
readonly retention?: RetentionDeclaration;
|
|
896
|
-
});
|
|
944
|
+
}, opts?: SqlCheckpointStoreOptions);
|
|
897
945
|
}
|
|
898
946
|
//# sourceMappingURL=checkpoint-store-sql.d.ts.map
|
|
@@ -3,8 +3,8 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, judgeCheckpointIndexCells } from "./checkpoint-index-columns.js";
|
|
4
4
|
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
5
5
|
import { redactDeep, redactSecrets } from "../trace/redact.js";
|
|
6
|
-
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, parkRowCarriesSkillCardFacts, parkRowFacts, readRuleOfferList, skillCardFacts } from "../approval-card.js";
|
|
7
|
-
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
6
|
+
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, countKindlessBatchOffers, parkRowCarriesSkillCardFacts, parkRowFacts, readRuleOfferList, skillCardFacts } from "../approval-card.js";
|
|
7
|
+
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
8
8
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
9
|
import { durableRowIdentity, withoutCheckpointToken } from "../checkpoint-identity.js";
|
|
10
10
|
import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
|
|
@@ -13,6 +13,7 @@ import { describeProbedTableDrift } from "./schema-drift-sentence.js";
|
|
|
13
13
|
import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
|
|
14
14
|
import { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
|
|
15
15
|
const CTX_REWRITE_PAGE = 500;
|
|
16
|
+
const KINDLESS_SCAN_PAGE = 200;
|
|
16
17
|
export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
|
|
17
18
|
const MAX_TOOL_INPUT_CHARS = 8192;
|
|
18
19
|
export const TERMINAL_BACKSTOP_MS = Math.max(60_000, (Number.isFinite(Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS)) ? Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS) : 0) || 30 * 86_400_000);
|
|
@@ -78,10 +79,13 @@ function preboundBatchMembers(item) {
|
|
|
78
79
|
return item;
|
|
79
80
|
return { ...candidate, rules: candidate.rules.slice(0, MAX_RULE_OFFER_BATCH_MEMBERS) };
|
|
80
81
|
}
|
|
82
|
+
export function boundedReaderInput(raw) {
|
|
83
|
+
return Array.isArray(raw) ? raw.slice(0, MAX_RULE_OFFER_SCAN).map(preboundBatchMembers) : raw;
|
|
84
|
+
}
|
|
81
85
|
export function boundedRuleOffers(raw, opts = {}) {
|
|
82
86
|
const observe = opts.countRedactions ?? true;
|
|
83
87
|
const scrub = (text) => redactSecrets(text, { observe }).slice(0, MAX_RULE_TEXT_CHARS);
|
|
84
|
-
const read = readRuleOfferList(
|
|
88
|
+
const read = readRuleOfferList(boundedReaderInput(raw));
|
|
85
89
|
const out = [];
|
|
86
90
|
for (const { offer } of read) {
|
|
87
91
|
if (out.length >= MAX_RULE_OFFERS)
|
|
@@ -331,6 +335,7 @@ export function readPendingSteerQueueColumn(queueRaw) {
|
|
|
331
335
|
}
|
|
332
336
|
const STEER_CAS_ATTEMPTS = 8;
|
|
333
337
|
const EXECUTION_RECORD_CAS_ATTEMPTS = 8;
|
|
338
|
+
export const CTX_CLAIM_REWRITES = ["written", "claim_released", "context_absent"];
|
|
334
339
|
export class SqlCheckpointStore {
|
|
335
340
|
db;
|
|
336
341
|
logger;
|
|
@@ -338,13 +343,15 @@ export class SqlCheckpointStore {
|
|
|
338
343
|
durability = "durable";
|
|
339
344
|
fidelity = "json";
|
|
340
345
|
execution = { outcome: true };
|
|
346
|
+
claims;
|
|
341
347
|
constructor(db, logger, opts = {}) {
|
|
342
348
|
this.db = db;
|
|
343
349
|
this.logger = logger;
|
|
344
350
|
this.retention = opts.retention ?? UNMANAGED_RETENTION;
|
|
351
|
+
this.claims = opts.claims;
|
|
345
352
|
}
|
|
346
|
-
q(
|
|
347
|
-
return this.db.dialect === "
|
|
353
|
+
q(mysql, pg) {
|
|
354
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
348
355
|
}
|
|
349
356
|
json(value, label) {
|
|
350
357
|
return dialectProtocolJsonEncoder(this.db.dialect)(value, label);
|
|
@@ -353,7 +360,7 @@ export class SqlCheckpointStore {
|
|
|
353
360
|
return isDupKeyError(this.db.dialect, e);
|
|
354
361
|
}
|
|
355
362
|
ph(n) {
|
|
356
|
-
return this.db.dialect === "
|
|
363
|
+
return this.db.dialect === "mysql" ? "?" : `$${n}`;
|
|
357
364
|
}
|
|
358
365
|
pendingRowQuery(select, sessionId, scope, binding) {
|
|
359
366
|
const params = [sessionId];
|
|
@@ -424,8 +431,8 @@ export class SqlCheckpointStore {
|
|
|
424
431
|
}
|
|
425
432
|
}
|
|
426
433
|
async get(token) {
|
|
427
|
-
const { rows } = await this.db.query(this.q(`SELECT version, status, ${jsonColumnText("
|
|
428
|
-
`${jsonColumnText("
|
|
434
|
+
const { rows } = await this.db.query(this.q(`SELECT version, status, ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnText("mysql", "outcome")}, reopen_reason, rev, ` +
|
|
435
|
+
`${jsonColumnText("mysql", "pending_steer_queue")}, ${jsonColumnText("mysql", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT version, status, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnText("pg", "outcome")}, reopen_reason, rev, ` +
|
|
429
436
|
`${jsonColumnText("pg", "pending_steer_queue")}, ${jsonColumnText("pg", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = $1`), [token]);
|
|
430
437
|
const r = rows[0];
|
|
431
438
|
if (!r)
|
|
@@ -480,7 +487,7 @@ export class SqlCheckpointStore {
|
|
|
480
487
|
}
|
|
481
488
|
async recordExecutionOutcome(token, scope, gate) {
|
|
482
489
|
for (let attempt = 0; attempt < EXECUTION_RECORD_CAS_ATTEMPTS; attempt++) {
|
|
483
|
-
const { rows } = await this.db.query(this.q(`SELECT scope, status, ${jsonColumnText("
|
|
490
|
+
const { rows } = await this.db.query(this.q(`SELECT scope, status, ${jsonColumnText("mysql", "execution_outcome")} FROM checkpoint WHERE token = ?`, `SELECT scope, status, ${jsonColumnText("pg", "execution_outcome")} FROM checkpoint WHERE token = $1`), [token]);
|
|
484
491
|
const row = rows[0];
|
|
485
492
|
const facts = row === undefined
|
|
486
493
|
? undefined
|
|
@@ -504,7 +511,7 @@ export class SqlCheckpointStore {
|
|
|
504
511
|
async setPendingSteer(token, scope, steer) {
|
|
505
512
|
const clean = validatePendingSteer(steer);
|
|
506
513
|
for (let attempt = 0; attempt < STEER_CAS_ATTEMPTS; attempt++) {
|
|
507
|
-
const { rows } = await this.db.query(this.q(`SELECT status, ${jsonColumnText("
|
|
514
|
+
const { rows } = await this.db.query(this.q(`SELECT status, ${jsonColumnText("mysql", "pending_steer_queue")}, pending_steer_rev FROM checkpoint WHERE token = ? AND scope = ?`, `SELECT status, ${jsonColumnText("pg", "pending_steer_queue")}, pending_steer_rev FROM checkpoint WHERE token = $1 AND scope = $2`), [token, scope]);
|
|
508
515
|
const row = rows[0];
|
|
509
516
|
if (!row || row.status !== "pending")
|
|
510
517
|
return false;
|
|
@@ -537,12 +544,12 @@ export class SqlCheckpointStore {
|
|
|
537
544
|
}
|
|
538
545
|
async reapExpired(cutoff) {
|
|
539
546
|
const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'expired', decided_at_ms = ? " +
|
|
540
|
-
`WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= ? AND COALESCE(${CHECKPOINT_FACET_SQL.
|
|
547
|
+
`WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= ? AND COALESCE(${CHECKPOINT_FACET_SQL.mysql}, '') <> 'A') OR (terminal_at_ms IS NOT NULL AND terminal_at_ms <= ?))`, "UPDATE checkpoint SET status = 'expired', decided_at_ms = $1 " +
|
|
541
548
|
`WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= $2 AND COALESCE(${CHECKPOINT_FACET_SQL.pg}, '') <> 'A') OR (terminal_at_ms IS NOT NULL AND terminal_at_ms <= $3))`), [Date.now(), cutoff, cutoff]);
|
|
542
549
|
return res.affected;
|
|
543
550
|
}
|
|
544
551
|
async listExpiredApprovalGates(cutoff, limit = 100) {
|
|
545
|
-
const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.
|
|
552
|
+
const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.mysql} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT ?`, `SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= $1 AND ${CHECKPOINT_FACET_SQL.pg} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT $2`), [cutoff, limit]);
|
|
546
553
|
return rows.map((r) => ({ sessionId: String(r.session_id), scope: String(r.scope) }));
|
|
547
554
|
}
|
|
548
555
|
async listPending(scope) {
|
|
@@ -591,7 +598,7 @@ export class SqlCheckpointStore {
|
|
|
591
598
|
return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
|
|
592
599
|
}
|
|
593
600
|
async readParkedSkillCardFacts(token) {
|
|
594
|
-
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("
|
|
601
|
+
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "checkpoint")} FROM checkpoint WHERE token = ? AND status = 'pending'`, `SELECT ${jsonColumnText("pg", "checkpoint")} FROM checkpoint WHERE token = $1 AND status = 'pending'`), [token]);
|
|
595
602
|
const r = rows[0];
|
|
596
603
|
if (r === undefined)
|
|
597
604
|
return {};
|
|
@@ -614,7 +621,7 @@ export class SqlCheckpointStore {
|
|
|
614
621
|
}
|
|
615
622
|
}
|
|
616
623
|
async listByScope(scope) {
|
|
617
|
-
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("
|
|
624
|
+
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at_ms ASC LIMIT 500`, `SELECT ${jsonColumnText("pg", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at_ms ASC LIMIT 500`), [scope]);
|
|
618
625
|
const out = [];
|
|
619
626
|
for (const r of rows) {
|
|
620
627
|
const cp = refusing("a checkpoint nobody can resume", () => decodeCheckpointColumn(r.checkpoint, `in the ${scope} scheduler view`));
|
|
@@ -633,7 +640,7 @@ export class SqlCheckpointStore {
|
|
|
633
640
|
return rows[0] ? String(rows[0].token) : null;
|
|
634
641
|
}
|
|
635
642
|
async findCheckpointCandidatesForAsk(scope, sessionId, toolCallId, sinceMs) {
|
|
636
|
-
const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, version, ${jsonColumnText("
|
|
643
|
+
const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, version, ${jsonColumnText("mysql", "checkpoint")} FROM checkpoint ` +
|
|
637
644
|
"WHERE scope=? AND session_id=? AND created_at_ms>=? AND (tool_call_id=? OR tool_call_id IS NULL) ORDER BY created_at_ms ASC", `SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, version, ${jsonColumnText("pg", "checkpoint")} FROM checkpoint ` +
|
|
638
645
|
"WHERE scope=$1 AND session_id=$2 AND created_at_ms>=$3 AND (tool_call_id=$4 OR tool_call_id IS NULL) ORDER BY created_at_ms ASC"), [scope, sessionId, sinceMs, toolCallId]);
|
|
639
646
|
const out = [];
|
|
@@ -671,7 +678,7 @@ export class SqlCheckpointStore {
|
|
|
671
678
|
return out;
|
|
672
679
|
}
|
|
673
680
|
async findDecidedApprovalsForBinding(sessionId, boundCallId) {
|
|
674
|
-
const { rows } = await this.db.query(this.q(`SELECT scope, bound_input_hash, ${jsonColumnText("
|
|
681
|
+
const { rows } = await this.db.query(this.q(`SELECT scope, bound_input_hash, ${jsonColumnText("mysql", "outcome")}, ${jsonColumnText("mysql", "execution_outcome")} FROM checkpoint ` +
|
|
675
682
|
`WHERE session_id=? AND tool_call_id=? AND status='resolved' AND decided_at_ms IS NOT NULL ORDER BY decided_at_ms DESC LIMIT ${DECIDED_REPLAY_SCAN_CAP}`, `SELECT scope, bound_input_hash, ${jsonColumnText("pg", "outcome")}, ${jsonColumnText("pg", "execution_outcome")} FROM checkpoint ` +
|
|
676
683
|
`WHERE session_id=$1 AND tool_call_id=$2 AND status='resolved' AND decided_at_ms IS NOT NULL ORDER BY decided_at_ms DESC LIMIT ${DECIDED_REPLAY_SCAN_CAP}`), [sessionId, boundCallId]);
|
|
677
684
|
return rows.map((r) => {
|
|
@@ -733,14 +740,37 @@ export class SqlCheckpointStore {
|
|
|
733
740
|
}
|
|
734
741
|
}
|
|
735
742
|
async getCtx(sessionId) {
|
|
736
|
-
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("
|
|
743
|
+
const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
|
|
737
744
|
return rows[0] ? refusing("a resume context nobody can read", () => decodeCtxColumn(rows[0].ctx, `for session ${JSON.stringify(sessionId)}`)) : null;
|
|
738
745
|
}
|
|
746
|
+
async rewriteCtxWhileClaimed(sessionId, taskId, ctx, legHolds) {
|
|
747
|
+
const claims = this.claims;
|
|
748
|
+
if (claims === undefined)
|
|
749
|
+
throw new Error("SqlCheckpointStore.rewriteCtxWhileClaimed: no claim fence is bound to this store (wiring defect: the backend binds its own run store)");
|
|
750
|
+
const text = this.json(ctx, "checkpoint ctx");
|
|
751
|
+
assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (claim-fenced rewrite)`);
|
|
752
|
+
let fenced;
|
|
753
|
+
try {
|
|
754
|
+
fenced = await claims.whileHoldingClaim(sessionId, taskId, async (tx, run) => {
|
|
755
|
+
if (!legHolds(run))
|
|
756
|
+
return "claim_released";
|
|
757
|
+
const row = await tx.query(this.q("SELECT session_id FROM checkpoint_ctx WHERE session_id = ? FOR UPDATE", "SELECT session_id FROM checkpoint_ctx WHERE session_id = $1 FOR UPDATE"), [sessionId]);
|
|
758
|
+
if (row.rows.length === 0)
|
|
759
|
+
return "context_absent";
|
|
760
|
+
await tx.query(this.q("UPDATE checkpoint_ctx SET ctx = ?, updated_at_ms = ? WHERE session_id = ?", "UPDATE checkpoint_ctx SET ctx = $1::jsonb, updated_at_ms = $2 WHERE session_id = $3"), [text, Date.now(), sessionId]);
|
|
761
|
+
return "written";
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
catch (e) {
|
|
765
|
+
throw rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (claim-fenced rewrite)`);
|
|
766
|
+
}
|
|
767
|
+
return fenced.held ? fenced.value : "claim_released";
|
|
768
|
+
}
|
|
739
769
|
async rewriteCtxRows(rewrite) {
|
|
740
770
|
const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
|
|
741
771
|
let after = "";
|
|
742
772
|
for (;;) {
|
|
743
|
-
const { rows } = await this.db.query(this.q(`SELECT session_id, ${jsonColumnText("
|
|
773
|
+
const { rows } = await this.db.query(this.q(`SELECT session_id, ${jsonColumnText("mysql", "ctx")} FROM checkpoint_ctx WHERE session_id > ? ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`, `SELECT session_id, ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id > $1 ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`), [after]);
|
|
744
774
|
for (const row of rows) {
|
|
745
775
|
const sessionId = String(row.session_id);
|
|
746
776
|
after = sessionId;
|
|
@@ -779,6 +809,47 @@ export class SqlCheckpointStore {
|
|
|
779
809
|
}
|
|
780
810
|
return out;
|
|
781
811
|
}
|
|
812
|
+
async scanKindlessBatchRows() {
|
|
813
|
+
const out = [];
|
|
814
|
+
let unreadableCells = 0;
|
|
815
|
+
const tolerant = (read) => {
|
|
816
|
+
try {
|
|
817
|
+
return read();
|
|
818
|
+
}
|
|
819
|
+
catch (e) {
|
|
820
|
+
if (!isJsonColumnBodyProblem(e))
|
|
821
|
+
throw e;
|
|
822
|
+
unreadableCells += 1;
|
|
823
|
+
return undefined;
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
const prefilter = (d) => `(rule_suggestions IS NOT NULL OR ${jsonColumnTextExpr(d, "checkpoint", PARK_ROW_PENDING_ACTION.key)} LIKE '%"batch"%')`;
|
|
827
|
+
let after = "";
|
|
828
|
+
for (;;) {
|
|
829
|
+
const { rows } = await this.db.query(this.q(`SELECT token, ${jsonColumnText("mysql", "rule_suggestions")}, ${jsonColumnText("mysql", "checkpoint", PARK_ROW_PENDING_ACTION)} FROM checkpoint WHERE token > ? AND ${prefilter("mysql")} ORDER BY token LIMIT ${KINDLESS_SCAN_PAGE}`, `SELECT token, ${jsonColumnText("pg", "rule_suggestions")}, ${jsonColumnText("pg", "checkpoint", PARK_ROW_PENDING_ACTION)} FROM checkpoint WHERE token > $1 AND ${prefilter("pg")} ORDER BY token LIMIT ${KINDLESS_SCAN_PAGE}`), [after]);
|
|
830
|
+
for (const row of rows) {
|
|
831
|
+
const token = String(row.token);
|
|
832
|
+
after = token;
|
|
833
|
+
const carriers = [];
|
|
834
|
+
const column = tolerant(() => decodeNullableJsonColumn(row.rule_suggestions, readJsonArrayColumn, "checkpoint.rule_suggestions (kind-less batch census)"));
|
|
835
|
+
if (countKindlessBatchOffers(boundedReaderInput(column)) > 0)
|
|
836
|
+
carriers.push("rule_suggestions");
|
|
837
|
+
const pendingAction = tolerant(() => decodeNullableJsonColumn(row[PARK_ROW_PENDING_ACTION.as], readJsonObjectColumn, "checkpoint.checkpoint pendingAction member (kind-less batch census)"));
|
|
838
|
+
if (typeof pendingAction === "object" && pendingAction !== null) {
|
|
839
|
+
const blobOffers = Reflect.get(pendingAction, "ruleOffers");
|
|
840
|
+
if (blobOffers !== undefined && !Array.isArray(blobOffers))
|
|
841
|
+
unreadableCells += 1;
|
|
842
|
+
else if (countKindlessBatchOffers(blobOffers) > 0)
|
|
843
|
+
carriers.push("pendingAction.ruleOffers");
|
|
844
|
+
}
|
|
845
|
+
if (carriers.length > 0)
|
|
846
|
+
out.push({ ref: tokenFingerprint(token), carriers });
|
|
847
|
+
}
|
|
848
|
+
if (rows.length < KINDLESS_SCAN_PAGE)
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
return { rows: out, unreadableCells };
|
|
852
|
+
}
|
|
782
853
|
async reapCtx(cutoff) {
|
|
783
854
|
const res = await this.db.query(this.q("DELETE FROM checkpoint_ctx WHERE updated_at_ms < ? " +
|
|
784
855
|
"AND session_id NOT IN (SELECT session_id FROM checkpoint WHERE status='pending') " +
|
|
@@ -789,7 +860,7 @@ export class SqlCheckpointStore {
|
|
|
789
860
|
}
|
|
790
861
|
async deleteBySession(sessionId, owner) {
|
|
791
862
|
const ownerGuard = this.q("EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = ? AND sm.owner <=> ?)", "EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = $1 AND sm.owner IS NOT DISTINCT FROM $2)");
|
|
792
|
-
const params = this.db.dialect === "
|
|
863
|
+
const params = this.db.dialect === "mysql" ? [sessionId, sessionId, owner] : [sessionId, owner];
|
|
793
864
|
const res = await this.db.query(this.q(`DELETE FROM checkpoint WHERE session_id = ? AND ${ownerGuard}`, `DELETE FROM checkpoint WHERE session_id = $1 AND ${ownerGuard}`), params);
|
|
794
865
|
await this.db.query(this.q(`DELETE FROM checkpoint_ctx WHERE session_id = ? AND ${ownerGuard}`, `DELETE FROM checkpoint_ctx WHERE session_id = $1 AND ${ownerGuard}`), params);
|
|
795
866
|
return res.affected;
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* · `device_enroll_tokens` —— 一次性 enrollment 凭据(明文一现,落库只有 sha256)
|
|
10
10
|
* · `device_audit` —— 安全轴事件审计(§4.8 标「审计行」的 8 个 event)
|
|
11
11
|
* **根会话 ↔ 设备绑定不在本店的表里**:它是根会话行(`session_meta`)上这一位住客的执行格 writer(device, D)(列与语句的唯一属主 =
|
|
12
|
-
* `execution-placement-sql.ts`;规则在 `../execution-placement.ts` 头注)
|
|
13
|
-
* 首绑 {@link SqlDeviceStore.placeDeviceExecution} 与换绑 {@link SqlDeviceStore.rebindDeviceExecution},各是一只事务,锁序全局一条:设备行 → 根 `session_meta` → `task_
|
|
12
|
+
* `execution-placement-sql.ts`;规则在 `../execution-placement.ts` 头注)。按根的读只经会话店的执行席 `seat.read(root)`;本店有两只写 ——
|
|
13
|
+
* 首绑 {@link SqlDeviceStore.placeDeviceExecution} 与换绑 {@link SqlDeviceStore.rebindDeviceExecution},各是一只事务,锁序全局一条:设备行 → 根 `session_meta` → `task_*` ——
|
|
14
|
+
* 与一只聚合读 {@link SqlDeviceStore.countDeviceBindings}(绑定数,池级一读、不锁,只交一个数)。
|
|
14
15
|
* 刻意**没有** `device_instructions` 表:指令 pending 表是内存态(§6 注③——指令比 run 短命,落库只会
|
|
15
16
|
* 造「表里 pending、run 已死」的僵尸行;at-most-once 靠 §5.3 投递边界,不靠指令落库)。
|
|
16
17
|
*
|
|
@@ -157,6 +158,8 @@ export declare class SqlDeviceStore implements DeviceStore {
|
|
|
157
158
|
* 审计写不进去 = 整次换绑失败(fail-closed,[ref])。持锁判过之后 CAS 不可能 0 行 —— 0 行即引擎异常,响亮抛。三形语义 oracle = `InMemoryDeviceStore.rebindDeviceExecution`。
|
|
158
159
|
*/
|
|
159
160
|
rebindDeviceExecution(input: RebindDeviceExecutionInput): Promise<RebindDeviceExecutionResult>;
|
|
161
|
+
/** 绑定数(契约注见 `DeviceStore.countDeviceBindings`):池级一读,读与判的唯一属主 = `execution-placement-sql.ts` 的 `countDeviceBindingsOn`(行过唯一解码 + `boundDeviceOf`)。 */
|
|
162
|
+
countDeviceBindings(): Promise<number>;
|
|
160
163
|
appendAudit(input: AppendAuditInput): Promise<void>;
|
|
161
164
|
/** 审计写的**唯一**语句(池级与事务内共用;`SqlExec` 是两者的公共面)。 */
|
|
162
165
|
private insertAuditOn;
|
|
@@ -2,7 +2,7 @@ import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
|
2
2
|
import { isDupKeyError, isCheckViolationError } from "./sql-errors.js";
|
|
3
3
|
import { DEVICE_AUDIT_EVENTS, assertAcquireShape, assertDeviceIdShape, assertDeviceOwnerShape, assertEnrollTokenHashShape, assertIssueTokenShape, assertNewDeviceShape, assertPlaceDeviceShape, assertRebindShape, assertRevokeShape, assertWholeSeconds, parseDeviceAuditEvent, parseDevicePlatformOs, parseDeviceStatus, } from "../device-store.js";
|
|
4
4
|
import { WRITABLE, boundDeviceOf, settledDeviceBindOutcome, settledDeviceRebindOutcome } from "../execution-placement.js";
|
|
5
|
-
import { bindDeviceStatement, lockRootAndReadClaim, placeWriterStatement, readPlacementOn, rebindDeviceStatement, rowsOnSqlExec } from "./execution-placement-sql.js";
|
|
5
|
+
import { bindDeviceStatement, countDeviceBindingsOn, lockRootAndReadClaim, placeWriterStatement, readPlacementOn, rebindDeviceStatement, rowsOnSqlExec } from "./execution-placement-sql.js";
|
|
6
6
|
import { ensureIndexes } from "./ensure-index.js";
|
|
7
7
|
import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn } from "./sql-json-column.js";
|
|
8
8
|
export const DEVICES_TABLE = "device";
|
|
@@ -140,7 +140,7 @@ export async function assertDeviceAuditRebindEventSchema(db) {
|
|
|
140
140
|
try {
|
|
141
141
|
await conn.begin();
|
|
142
142
|
try {
|
|
143
|
-
await conn.query(db.dialect === "
|
|
143
|
+
await conn.query(db.dialect === "mysql"
|
|
144
144
|
? `INSERT INTO ${DEVICE_AUDIT_TABLE} (at, event, device_id, owner_tenant, owner_subject, detail) VALUES (NOW(3), ?, NULL, NULL, NULL, NULL)`
|
|
145
145
|
: `INSERT INTO ${DEVICE_AUDIT_TABLE} (at, event, device_id, owner_tenant, owner_subject, detail) VALUES (now(), $1, NULL, NULL, NULL, NULL)`, ["session_rebound"]);
|
|
146
146
|
}
|
|
@@ -168,9 +168,9 @@ export async function assertDeviceAuditRebindEventSchema(db) {
|
|
|
168
168
|
conn.release();
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
const ms = (dialect, col, alias) => dialect === "
|
|
172
|
-
const NOW = (dialect) => (dialect === "
|
|
173
|
-
const nowPlusSeconds = (dialect, placeholder) => dialect === "
|
|
171
|
+
const ms = (dialect, col, alias) => dialect === "mysql" ? `ROUND(UNIX_TIMESTAMP(${col}) * 1000) AS ${alias}` : `(EXTRACT(EPOCH FROM ${col}) * 1000)::bigint AS ${alias}`;
|
|
172
|
+
const NOW = (dialect) => (dialect === "mysql" ? "NOW(3)" : "now()");
|
|
173
|
+
const nowPlusSeconds = (dialect, placeholder) => dialect === "mysql" ? `NOW(3) + INTERVAL ${placeholder} SECOND` : `now() + (${placeholder}::double precision * INTERVAL '1 second')`;
|
|
174
174
|
function deviceCols(dialect) {
|
|
175
175
|
return [
|
|
176
176
|
"device_id",
|
|
@@ -286,10 +286,10 @@ function readAuditDetail(raw, id) {
|
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
function ph(dialect, n) {
|
|
289
|
-
return dialect === "
|
|
289
|
+
return dialect === "mysql" ? "?" : `$${n}`;
|
|
290
290
|
}
|
|
291
291
|
function dupKeyName(dialect, err) {
|
|
292
|
-
if (dialect === "
|
|
292
|
+
if (dialect === "mysql") {
|
|
293
293
|
const msg = String(err?.sqlMessage ?? err?.message ?? "");
|
|
294
294
|
if (new RegExp(UQ_DEVICES_PUBKEY, "i").test(msg))
|
|
295
295
|
return "pubkey";
|
|
@@ -309,8 +309,8 @@ export class SqlDeviceStore {
|
|
|
309
309
|
constructor(db) {
|
|
310
310
|
this.db = db;
|
|
311
311
|
}
|
|
312
|
-
q(
|
|
313
|
-
return this.db.dialect === "
|
|
312
|
+
q(mysql, pg) {
|
|
313
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
314
314
|
}
|
|
315
315
|
json(value) {
|
|
316
316
|
return dialectJsonEncoder(this.db.dialect)(value);
|
|
@@ -319,7 +319,7 @@ export class SqlDeviceStore {
|
|
|
319
319
|
assertIssueTokenShape(input);
|
|
320
320
|
try {
|
|
321
321
|
await this.db.query(this.q(`INSERT INTO ${DEVICE_ENROLL_TOKENS_TABLE} (token_hash, target_tenant, target_subject, created_by, created_at, expires_at, consumed_at, consumed_device_id, consumed_pubkey, rev)
|
|
322
|
-
VALUES (?, ?, ?, ?, NOW(3), ${nowPlusSeconds("
|
|
322
|
+
VALUES (?, ?, ?, ?, NOW(3), ${nowPlusSeconds("mysql", "?")}, NULL, NULL, NULL, 0)`, `INSERT INTO ${DEVICE_ENROLL_TOKENS_TABLE} (token_hash, target_tenant, target_subject, created_by, created_at, expires_at, consumed_at, consumed_device_id, consumed_pubkey, rev)
|
|
323
323
|
VALUES ($1, $2, $3, $4, now(), ${nowPlusSeconds("pg", "$5")}, NULL, NULL, NULL, 0)`), [input.tokenHash, input.target.tenant, input.target.subject, input.createdBy, input.ttlSeconds]);
|
|
324
324
|
}
|
|
325
325
|
catch (err) {
|
|
@@ -334,7 +334,7 @@ export class SqlDeviceStore {
|
|
|
334
334
|
}
|
|
335
335
|
async getEnrollToken(tokenHash) {
|
|
336
336
|
assertEnrollTokenHashShape(tokenHash);
|
|
337
|
-
const { rows } = await this.db.query(this.q(`SELECT ${tokenCols("
|
|
337
|
+
const { rows } = await this.db.query(this.q(`SELECT ${tokenCols("mysql")} FROM ${DEVICE_ENROLL_TOKENS_TABLE} WHERE token_hash = ?`, `SELECT ${tokenCols("pg")} FROM ${DEVICE_ENROLL_TOKENS_TABLE} WHERE token_hash = $1`), [tokenHash]);
|
|
338
338
|
return rows[0] ? mapTokenRow(rows[0]) : null;
|
|
339
339
|
}
|
|
340
340
|
async consumeEnrollToken(input) {
|
|
@@ -382,7 +382,7 @@ export class SqlDeviceStore {
|
|
|
382
382
|
return { outcome: "enrolled", device };
|
|
383
383
|
}
|
|
384
384
|
await conn.rollback();
|
|
385
|
-
const { rows } = await conn.query(this.q(`SELECT ${tokenCols("
|
|
385
|
+
const { rows } = await conn.query(this.q(`SELECT ${tokenCols("mysql")} FROM ${DEVICE_ENROLL_TOKENS_TABLE} WHERE token_hash = ?`, `SELECT ${tokenCols("pg")} FROM ${DEVICE_ENROLL_TOKENS_TABLE} WHERE token_hash = $1`), [input.tokenHash]);
|
|
386
386
|
const token = rows[0] ? mapTokenRow(rows[0]) : null;
|
|
387
387
|
if (!token)
|
|
388
388
|
return { outcome: "unknown_token" };
|
|
@@ -405,28 +405,28 @@ export class SqlDeviceStore {
|
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
async readDeviceOn(conn, deviceId) {
|
|
408
|
-
const { rows } = await conn.query(this.q(`SELECT ${deviceCols("
|
|
408
|
+
const { rows } = await conn.query(this.q(`SELECT ${deviceCols("mysql")} FROM ${DEVICES_TABLE} WHERE device_id = ?`, `SELECT ${deviceCols("pg")} FROM ${DEVICES_TABLE} WHERE device_id = $1`), [deviceId]);
|
|
409
409
|
return rows[0] ? mapDeviceRow(rows[0]) : null;
|
|
410
410
|
}
|
|
411
411
|
async readDeviceForAdmission(deviceId, owner) {
|
|
412
412
|
assertDeviceIdShape(deviceId);
|
|
413
413
|
assertDeviceOwnerShape(owner);
|
|
414
|
-
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("
|
|
414
|
+
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("mysql")}, ${onlineCol("mysql")} FROM ${DEVICES_TABLE} WHERE device_id = ? AND owner_tenant = ? AND owner_subject = ?`, `SELECT ${deviceCols("pg")}, ${onlineCol("pg")} FROM ${DEVICES_TABLE} WHERE device_id = $1 AND owner_tenant = $2 AND owner_subject = $3`), [deviceId, owner.tenant, owner.subject]);
|
|
415
415
|
const r = rows[0];
|
|
416
416
|
return r ? { row: mapDeviceRow(r), online: Number(r.online) === 1 } : null;
|
|
417
417
|
}
|
|
418
418
|
async getDeviceUnscoped(deviceId) {
|
|
419
419
|
assertDeviceIdShape(deviceId);
|
|
420
|
-
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("
|
|
420
|
+
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("mysql")} FROM ${DEVICES_TABLE} WHERE device_id = ?`, `SELECT ${deviceCols("pg")} FROM ${DEVICES_TABLE} WHERE device_id = $1`), [deviceId]);
|
|
421
421
|
return rows[0] ? mapDeviceRow(rows[0]) : null;
|
|
422
422
|
}
|
|
423
423
|
async listDevicesByOwner(owner) {
|
|
424
424
|
assertDeviceOwnerShape(owner);
|
|
425
|
-
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("
|
|
425
|
+
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("mysql")} FROM ${DEVICES_TABLE} WHERE owner_tenant = ? AND owner_subject = ? ORDER BY device_id ASC`, `SELECT ${deviceCols("pg")} FROM ${DEVICES_TABLE} WHERE owner_tenant = $1 AND owner_subject = $2 ORDER BY device_id ASC`), [owner.tenant, owner.subject]);
|
|
426
426
|
return rows.map(mapDeviceRow);
|
|
427
427
|
}
|
|
428
428
|
async listDevicesUnscoped() {
|
|
429
|
-
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("
|
|
429
|
+
const { rows } = await this.db.query(this.q(`SELECT ${deviceCols("mysql")} FROM ${DEVICES_TABLE} ORDER BY device_id ASC`, `SELECT ${deviceCols("pg")} FROM ${DEVICES_TABLE} ORDER BY device_id ASC`));
|
|
430
430
|
return rows.map(mapDeviceRow);
|
|
431
431
|
}
|
|
432
432
|
async revokeDevice(input) {
|
|
@@ -446,7 +446,7 @@ export class SqlDeviceStore {
|
|
|
446
446
|
return { outcome: "revoked", row };
|
|
447
447
|
}
|
|
448
448
|
await conn.rollback();
|
|
449
|
-
const { rows } = await conn.query(this.q(`SELECT ${deviceCols("
|
|
449
|
+
const { rows } = await conn.query(this.q(`SELECT ${deviceCols("mysql")} FROM ${DEVICES_TABLE} WHERE device_id = ? AND owner_tenant = ? AND owner_subject = ?`, `SELECT ${deviceCols("pg")} FROM ${DEVICES_TABLE} WHERE device_id = $1 AND owner_tenant = $2 AND owner_subject = $3`), [input.deviceId, input.owner.tenant, input.owner.subject]);
|
|
450
450
|
if (!rows[0])
|
|
451
451
|
return { outcome: "not_found" };
|
|
452
452
|
return { outcome: "already_revoked", row: mapDeviceRow(rows[0]) };
|
|
@@ -464,7 +464,7 @@ export class SqlDeviceStore {
|
|
|
464
464
|
const conn = await this.db.connect();
|
|
465
465
|
try {
|
|
466
466
|
await conn.begin();
|
|
467
|
-
const cas = await conn.query(this.q(`UPDATE ${DEVICES_TABLE} SET conn_generation = conn_generation + 1, conn_epoch = ?, replica_id = ?, lease_expires_at = ${nowPlusSeconds("
|
|
467
|
+
const cas = await conn.query(this.q(`UPDATE ${DEVICES_TABLE} SET conn_generation = conn_generation + 1, conn_epoch = ?, replica_id = ?, lease_expires_at = ${nowPlusSeconds("mysql", "?")}, connected_at = NOW(3), last_seen_at = NOW(3), rev = rev + 1
|
|
468
468
|
WHERE device_id = ? AND owner_tenant = ? AND owner_subject = ? AND status = 'active' AND (lease_expires_at IS NULL OR lease_expires_at <= NOW(3))`, `UPDATE ${DEVICES_TABLE} SET conn_generation = conn_generation + 1, conn_epoch = $1, replica_id = $2, lease_expires_at = ${nowPlusSeconds("pg", "$3")}, connected_at = now(), last_seen_at = now(), rev = rev + 1
|
|
469
469
|
WHERE device_id = $4 AND owner_tenant = $5 AND owner_subject = $6 AND status = 'active' AND (lease_expires_at IS NULL OR lease_expires_at <= now())`), [input.connEpoch, input.replicaId, input.leaseSeconds, input.deviceId, input.owner.tenant, input.owner.subject]);
|
|
470
470
|
const { rows } = await conn.query(this.q(`SELECT conn_generation, status FROM ${DEVICES_TABLE} WHERE device_id = ? AND owner_tenant = ? AND owner_subject = ? FOR UPDATE`, `SELECT conn_generation, status FROM ${DEVICES_TABLE} WHERE device_id = $1 AND owner_tenant = $2 AND owner_subject = $3 FOR UPDATE`), [input.deviceId, input.owner.tenant, input.owner.subject]);
|
|
@@ -488,7 +488,7 @@ export class SqlDeviceStore {
|
|
|
488
488
|
async heartbeatConnection(input) {
|
|
489
489
|
assertDeviceIdShape(input.deviceId);
|
|
490
490
|
assertWholeSeconds("leaseSeconds", input.leaseSeconds);
|
|
491
|
-
const res = await this.db.query(this.q(`UPDATE ${DEVICES_TABLE} SET last_seen_at = NOW(3), lease_expires_at = ${nowPlusSeconds("
|
|
491
|
+
const res = await this.db.query(this.q(`UPDATE ${DEVICES_TABLE} SET last_seen_at = NOW(3), lease_expires_at = ${nowPlusSeconds("mysql", "?")}, rev = rev + 1 WHERE device_id = ? AND conn_generation = ? AND status = 'active'`, `UPDATE ${DEVICES_TABLE} SET last_seen_at = now(), lease_expires_at = ${nowPlusSeconds("pg", "$1")}, rev = rev + 1 WHERE device_id = $2 AND conn_generation = $3 AND status = 'active'`), [input.leaseSeconds, input.deviceId, input.generation]);
|
|
492
492
|
return res.affected === 1;
|
|
493
493
|
}
|
|
494
494
|
async releaseConnection(deviceId, generation) {
|
|
@@ -581,6 +581,9 @@ export class SqlDeviceStore {
|
|
|
581
581
|
conn.release();
|
|
582
582
|
}
|
|
583
583
|
}
|
|
584
|
+
async countDeviceBindings() {
|
|
585
|
+
return countDeviceBindingsOn(rowsOnSqlExec(this.db));
|
|
586
|
+
}
|
|
584
587
|
async appendAudit(input) {
|
|
585
588
|
await this.insertAuditOn(this.db, input);
|
|
586
589
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export type SqlDialect = "mysql" | "pg";
|
|
1
|
+
import type { SqlDialect } from "./sql-driver.js";
|
|
3
2
|
/**
|
|
4
3
|
* 一条索引的**声明**(纯数据)。两方言共用同一份声明 —— 这就是"一处属主"的兑现物:
|
|
5
4
|
* per-store 文件各导出自己那份 `readonly IndexSpec[]`,PG 侧由该 store 的 `ensurePg*Schema` 应用,
|