@sema-agent/server 7.138.0 → 7.140.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 +457 -7
- package/README.md +7 -4
- package/README.zh-CN.md +6 -4
- package/USAGE.md +91 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +26 -26
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- 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/bench/s1/run-firm.js +2 -2
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +28 -34
- package/dist/boot/execution-lane.d.ts +9 -1
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/governance-seams.d.ts +3 -2
- package/dist/boot/governance-seams.js +9 -11
- package/dist/boot/memory-boundary.d.ts +14 -51
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +15 -11
- package/dist/boot/memory-consolidation.js +25 -14
- package/dist/boot/resolve-spec.d.ts +4 -6
- package/dist/boot/resolve-spec.js +10 -13
- package/dist/boot/retention-lane.d.ts +5 -5
- package/dist/boot/retention-lane.js +0 -6
- package/dist/boot/runner-deps.d.ts +6 -4
- package/dist/boot/runner-deps.js +3 -1
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- 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 +2 -2
- package/dist/boot/stage-06-runners.d.ts +2 -6
- package/dist/boot/stage-06-runners.js +2 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -6
- package/dist/boot/stage-08-reapers.d.ts +2 -6
- package/dist/boot/stage-09-leader.d.ts +2 -6
- package/dist/boot/stage-09-leader.js +1 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -6
- package/dist/boot/stage-10-http-server.js +7 -6
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +27 -40
- 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 +37 -11
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/mcp-probe.js +1 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +15 -7
- package/dist/capability-facts.js +8 -3
- package/dist/config-catalog.js +9 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +33 -37
- package/dist/config-provider.js +51 -30
- package/dist/config-types.d.ts +21 -7
- package/dist/config.d.ts +33 -0
- package/dist/config.js +46 -16
- package/dist/delegated-session-owner.d.ts +4 -3
- package/dist/delegated-session-owner.js +1 -1
- package/dist/deployment-shape.d.ts +10 -9
- package/dist/deployment-shape.js +4 -4
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +115 -105
- package/dist/execution-lane-caps.js +76 -82
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +8 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/durable-run-leg.js +9 -5
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -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 +63 -36
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-ctx.d.ts +14 -8
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +8 -1
- 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 +5 -0
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +143 -22
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +4 -5
- package/dist/http/routes/tasks.js +41 -33
- package/dist/http/server.d.ts +40 -44
- package/dist/http/server.js +13 -2
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +8 -12
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/index.d.ts +1 -0
- 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/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-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 +29 -5
- package/dist/memory-scope.js +16 -4
- package/dist/memory-sync-client.d.ts +70 -18
- package/dist/memory-sync-client.js +215 -117
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.js +0 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +34 -9
- package/dist/parked-decide.js +12 -19
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +13 -7
- package/dist/peer-seat.js +37 -3
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +44 -26
- 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 -2
- package/dist/plugins/caching-session-store.js +9 -3
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
- package/dist/plugins/checkpoint-store-sql.js +21 -23
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +68 -122
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +9 -8
- package/dist/plugins/file-history-store-sql.js +9 -9
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/local-session-store.d.ts +149 -50
- package/dist/plugins/local-session-store.js +382 -107
- 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-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -8
- package/dist/plugins/one-time-migrations.js +408 -38
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -19
- package/dist/plugins/permission-rule-store-sql.js +29 -32
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +31 -20
- package/dist/plugins/pg-session-storage.js +120 -52
- package/dist/plugins/remote-env-adb.d.ts +18 -19
- package/dist/plugins/remote-env-adb.js +91 -66
- package/dist/plugins/remote-env-device.d.ts +23 -31
- package/dist/plugins/remote-env-device.js +19 -17
- package/dist/plugins/remote-env-e2b.d.ts +27 -21
- package/dist/plugins/remote-env-e2b.js +91 -45
- package/dist/plugins/remote-env-file-error.d.ts +3 -1
- package/dist/plugins/remote-env-file-error.js +9 -3
- package/dist/plugins/remote-env-host.d.ts +31 -34
- package/dist/plugins/remote-env-host.js +91 -72
- package/dist/plugins/remote-env-k8s.d.ts +13 -29
- package/dist/plugins/remote-env-k8s.js +63 -55
- package/dist/plugins/remote-env-local-docker.d.ts +24 -12
- package/dist/plugins/remote-env-local-docker.js +49 -61
- package/dist/plugins/remote-env-ssh.d.ts +11 -19
- package/dist/plugins/remote-env-ssh.js +74 -57
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- 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 +10 -7
- package/dist/plugins/retention-store-sql.js +13 -7
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +20 -15
- package/dist/plugins/schema-consistency.d.ts +21 -11
- package/dist/plugins/schema-consistency.js +15 -9
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- 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-fork.d.ts +54 -0
- package/dist/plugins/session-fork.js +38 -1
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- 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 +1 -1
- 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 +5 -7
- package/dist/plugins/store-backend.js +23 -11
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +13 -7
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +57 -26
- package/dist/plugins/tidb-session-store.js +127 -59
- package/dist/plugins/tool-result-store-sql.d.ts +5 -4
- package/dist/plugins/tool-result-store-sql.js +21 -13
- 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/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- 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-local.d.ts +6 -0
- package/dist/run-local.js +34 -35
- package/dist/runs.d.ts +10 -5
- package/dist/runs.js +52 -66
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +9 -5
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +20 -10
- package/dist/session-incarnation.js +5 -3
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +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-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +22 -9
- package/dist/task-mcp.js +17 -9
- package/dist/terminal.d.ts +27 -0
- package/dist/terminal.js +6 -0
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +8 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +80 -28
- package/dist/trace/self-minted-wire.d.ts +169 -2
- package/dist/trace/self-minted-wire.js +54 -1
- package/dist/url-refusal.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- 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/dist/web-admin-client/skills-mcp.js +3 -2
- package/dist/web-admin-client/types.d.ts +6 -0
- package/package.json +3 -3
- package/wire-changes/server@7.139.0.json +348 -0
- package/wire-changes/server@7.140.0.json +184 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
|
@@ -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));
|
|
@@ -85,8 +85,8 @@ export declare function coreOutcomeToLedgerRow(o: CoreTaskOutcome): TaskOutcome;
|
|
|
85
85
|
* JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), TINYINT(1)→SMALLINT, INT→INTEGER, inline KEY→separate CREATE INDEX.
|
|
86
86
|
* The table is DISJOINT from every other store's tables, so this self-contained set is safe (no shadowing).
|
|
87
87
|
*
|
|
88
|
-
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth,
|
|
89
|
-
*
|
|
88
|
+
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, there is no in-place
|
|
89
|
+
* migration, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead.
|
|
90
90
|
*/
|
|
91
91
|
export declare const PG_OUTCOME_LEDGER_SCHEMA: string[];
|
|
92
92
|
/** [ref]:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgOutcomeLedgerSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
@@ -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 从引擎侧看是只读的)。
|
|
@@ -73,7 +73,7 @@ export declare function ensureTiDBPermissionRuleSchema(pool: MySqlPool): Promise
|
|
|
73
73
|
* [ref] 升级前置断言 —— **拒启**,不是 warn(codex 交叉复审 [high],验真后修;形照 [ref] 的
|
|
74
74
|
* `assertToolResultProvenanceSchema` 先例)。
|
|
75
75
|
*
|
|
76
|
-
* 病:本仓不发 `ALTER TABLE` 迁移(SCHEMA POLICY
|
|
76
|
+
* 病:本仓不发 `ALTER TABLE` 迁移(SCHEMA POLICY:代码是唯一真源,存量库缺列由部署者按拒启句打印的 ALTER 自己执行)。于是一台**没跟上**就升
|
|
77
77
|
* 上来的部署,`CREATE TABLE IF NOT EXISTS` 对存量 `permission_rule_approval` 是空操作 —— 本车新加的
|
|
78
78
|
* `command` / `edited_json` 两列不在,而 `get` / `create` / `cas` 三条语句**无条件**引用它们。
|
|
79
79
|
* 后果不是「少一个新功能」,是**整条审批记录面**在那台机器上是坏的:
|
|
@@ -81,16 +81,16 @@ export declare function ensureTiDBPermissionRuleSchema(pool: MySqlPool): Promise
|
|
|
81
81
|
* `rule_store_error` —— 裁决照常 200,服务看起来完全健康;
|
|
82
82
|
* · CC 导入的 prepare 连 pending 记录都落不下;
|
|
83
83
|
* · 而 `/v1/capabilities` 的 `permissionRules` 仍然报 true ⇒ 本仓「says yes ⟺ route works」整句为假。
|
|
84
|
-
*
|
|
84
|
+
* 没有这道断言,运维得不到**任何**「这次升级必须先给表加列」的信号 —— 那正是 [ref]「禁静默降级」要挡的形态。
|
|
85
85
|
*
|
|
86
86
|
* 判据用**能力探测**而不是版本号/information_schema:发一条恒空的 `WHERE 1=0` 读,列不在就报错。
|
|
87
87
|
* 只判「五列在不在」(command / edited_json / offers_json 三列 TEXT 族 + version / selected_offer 两列
|
|
88
88
|
* INT),不判宽度(TEXT 族无截断轴,INT 无宽度轴)。
|
|
89
89
|
*
|
|
90
|
-
* 🔴
|
|
90
|
+
* 🔴 **只有可证的缺列才给「缺列」的拒因**(codex 交叉复审 round2/round3 [high],两轮收窄后的终形):
|
|
91
91
|
* 「探针失败」**不等于**「列不在」。超时、连接被重置、取消、资源不足、列级权限被拒、表整个不存在 ——
|
|
92
|
-
*
|
|
93
|
-
*
|
|
92
|
+
* 每一种都会让这条读抛错,而把它们诊断成「缺列」是一条比它要挡的缺陷更贵的诊断(当年这句拒因还带着
|
|
93
|
+
* 一条会真的毁掉审计记录的指路;现在的出路是闭集三条,没有销毁性的一条)。round2 的两步探(先读一列老列)也不够:两次读之间同样可以插进一次瞬时故障。
|
|
94
94
|
* 终形判据 = **方言的缺列错误码**,一行一方言的闭集(见 {@link isMissingColumnError});其余错误
|
|
95
95
|
* **原样 rethrow**,一个字都不加工。真库两条腿各自跑过这条路径(db-integration 的存量表格),所以
|
|
96
96
|
* 这张码表不是抄手册抄来的,是实测钉住的。
|
|
@@ -105,13 +105,13 @@ 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`
|
|
112
112
|
* 对存量表是空操作,而新列被**无条件**引用)。
|
|
113
113
|
*
|
|
114
|
-
*
|
|
114
|
+
* 病:一台没跟上就升上来的部署,`permission_rule_ticket` 上没有 `redeemed_outcome` 列,而 A7 的三态
|
|
115
115
|
* 协议(`settle` / `peek` / `reclaim`)三条语句全都引用它。后果不是"少一个新功能":
|
|
116
116
|
* · `POST /v1/rules/local-import/redeem` 的落定那一跳撞 unknown column ⇒ 票停在 CLAIMED 永不结算,
|
|
117
117
|
* 属主的重放只能拿到 409,一次人的确认就此卡死到票过期;
|
|
@@ -119,16 +119,15 @@ export declare function assertPermissionRuleApprovalSchema(query: (sql: string)
|
|
|
119
119
|
* 的同意**被报成没发生。
|
|
120
120
|
* 所以判据放在启动期、拒启,而不是等第一位用户去踩。
|
|
121
121
|
*
|
|
122
|
-
* 判据同款:发一条恒空的 `WHERE 1=0`
|
|
123
|
-
*
|
|
122
|
+
* 判据同款:发一条恒空的 `WHERE 1=0` 读;**只有可证的缺列**才给「缺列」的拒因(其余错误原样 rethrow ——
|
|
123
|
+
* 超时/连接重置/权限/表不在都会让这条读抛,把它们诊断成「缺列」是一条比缺陷更贵的诊断)。出路是**闭集三条**
|
|
124
|
+
* (与整库对账同一份,{@link describeProbedTableDrift}),没有销毁性的一条。
|
|
124
125
|
*
|
|
125
|
-
*
|
|
126
|
-
* 删表最多让**正在预览、还没按确认**的那几个人重走一次 prepare;持久规则在 `permission_rule` 里,
|
|
127
|
-
* 一行都不动。
|
|
126
|
+
* 文案照实说本表的分量:票是分钟级 TTL 的治理瞬态(过期即死),持久规则在 `permission_rule` 里,一行都不动。
|
|
128
127
|
*/
|
|
129
128
|
export declare function assertPermissionRuleTicketSchema(query: (sql: string) => Promise<{
|
|
130
129
|
rows: Record<string, unknown>[];
|
|
131
|
-
}>, dialect:
|
|
130
|
+
}>, dialect: SqlDialect): Promise<void>;
|
|
132
131
|
/** [ref]:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgPermissionRuleSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
133
132
|
export declare const PERMISSION_RULE_INDEXES: readonly IndexSpec[];
|
|
134
133
|
export declare function ensurePgPermissionRuleSchema(q: PgQueryFn): Promise<void>;
|
|
@@ -440,10 +439,9 @@ export declare const RULE_PENDING_APPROVAL_RETENTION_MS: number;
|
|
|
440
439
|
* 处理整个积压。500 的取值:一轮的最坏工作量钉在「几百行删除」这个数量级,而常态每轮的真实行数是个位数
|
|
441
440
|
* (一次 prepare 留一行);积压按轮渐进清空,每轮都是完整语义,绝不留半干净状态。
|
|
442
441
|
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
* 同口径)。在那之前,MySQL 存量库上这条腿仍是全表扫 —— 但每轮 500 行的上界让它的**单次**代价仍然有界。
|
|
442
|
+
* 索引到货:两方言的存量库都由幂等加索引原语(`ensureIndexes`)在启动时补建 —— PG 侧是独立的 `CREATE INDEX`,
|
|
443
|
+
* MySQL 协议侧先探测、缺才发在线 `ALTER TABLE … ADD INDEX`(中央 schema 的索引列表含 `PERMISSION_RULE_INDEXES`)。
|
|
444
|
+
* 补建完成之前(或某个副本还没起过新版),这条腿在存量库上仍是全表扫 —— 但每轮 500 行的上界让它的**单次**代价仍然有界。
|
|
447
445
|
*/
|
|
448
446
|
export declare const RULE_REAP_BATCH = 500;
|
|
449
447
|
/**
|
|
@@ -504,7 +502,7 @@ export interface UndurableScopePurgeDb {
|
|
|
504
502
|
* 读数写进调用方递进来的账(`into`,{@link UndurableScopePurgeReadings}):删掉的行在那只桶的回写提交之后追加 ⇒ 本函数半途抛出时,已删的行已经在账上;
|
|
505
503
|
* 错误**原样**上抛(不加工、不自铸拒因句 —— 收据由迁移段那一只收据账随拒因带走,[ref])。
|
|
506
504
|
*/
|
|
507
|
-
export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect:
|
|
505
|
+
export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect: SqlDialect, into: UndurableScopePurgeReadings, now?: () => number): Promise<void>;
|
|
508
506
|
/** 三个 SQL 面的一次性装配束(one driver, three faces)。`durable` = [ref] 的 durable 分区后端;
|
|
509
507
|
* 引擎接缝上的统一店由 `main.ts` 用 `createPermissionRuleStoreProvider({ durable })` 合成一次。 */
|
|
510
508
|
export interface PermissionRuleStores {
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, isValidDurableScope, parseRuleText, readRowBehavior, screenRuleSyncState, PERMISSION_RULE_WRITER, RULE_ADD_ORIGINS, RULE_APPROVAL_KINDS, RULE_APPROVAL_RECORD_SCHEMA, RULE_APPROVAL_STATES, RULE_BEHAVIORS, RULE_SYNC_DROP_CODES, EDITED_RULE_BREADTH_WARNING_CODES, PERSISTED_RULE_MATCHES, EMPTY_DURABLE_RULE_PARTITION, PERMISSION_RULES_LATER_GRAMMAR_CODE, SESSION_RULES_GRAMMAR, readRuleGrammarStamp, readRuleOwner, principalNameOf, readRuleOffer2, } from "@sema-agent/core";
|
|
4
4
|
import { dialectProtocolJsonEncoder, sqlDbNowMs, SQL_DB_NOW_MS } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
6
|
+
import { describeProbedTableDrift } from "./schema-drift-sentence.js";
|
|
6
7
|
import { canonicalJsonStringify } from "@sema-agent/core";
|
|
7
8
|
import { ensureIndexes } from "./ensure-index.js";
|
|
8
9
|
import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, SqlJsonColumnError } from "./sql-json-column.js";
|
|
@@ -144,8 +145,8 @@ export const TIDB_PERMISSION_RULE_STATEMENTS = [
|
|
|
144
145
|
-- version:**数据格式版本**(= core \`RuleApprovalRecord.schema\`,本形恒 4 —— core 7.9.0 #625 由 3 升,
|
|
145
146
|
-- 代码里的单点是 RULE_APPROVAL_RECORD_SCHEMA;OCC 守卫另在 rev,两轴禁混,schema-naming 门③ 白名单
|
|
146
147
|
-- 登记)。读侧对非 4 的行(含上一形的 3)交还 design/375 §4.5 的
|
|
147
|
-
-- StaleRuleApprovalRecord 信封(identity 键 only)——
|
|
148
|
-
--
|
|
148
|
+
-- StaleRuleApprovalRecord 信封(identity 键 only)—— 这一臂是「未来再换形」的防御位,
|
|
149
|
+
-- 绝不静默 widen 成一条本形担保不了的记录。
|
|
149
150
|
version INT NOT NULL,
|
|
150
151
|
-- offers_json:core \`RuleOffer2[]\`(design/375 §4.1)—— 按 index 引用 candidates 的 OPTION 结构,
|
|
151
152
|
-- **绝不存规则文本副本**(显示三元组由 core \`ruleOffersOfRecord\` 从候选文本投影;存副本=造一个
|
|
@@ -240,13 +241,11 @@ export async function assertPermissionRuleApprovalSchema(query, dialect) {
|
|
|
240
241
|
`#340 card-edit columns (command / edited_json) and the design/375 record-form columns ` +
|
|
241
242
|
`(version / offers_json / selected_offer): the approval record now stores its OPTION structure and the ` +
|
|
242
243
|
`chosen offer, and the store refuses to read rows whose record-form version it cannot vouch for. ` +
|
|
243
|
-
`This
|
|
244
|
-
`
|
|
245
|
-
|
|
246
|
-
`
|
|
247
|
-
|
|
248
|
-
`loses in-flight CC-import previews (re-runnable) and the historical record trail. ` +
|
|
249
|
-
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
|
|
244
|
+
`This table holds pending/settled approval RECORDS (the audit trail of who said yes to which ` +
|
|
245
|
+
`rule), not the rules themselves — persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched; ` +
|
|
246
|
+
`an in-place ALTER keeps the record trail.\n` +
|
|
247
|
+
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
248
|
+
(await describeProbedTableDrift(query, dialect, [PERMISSION_RULE_APPROVAL_TABLE])));
|
|
250
249
|
}
|
|
251
250
|
}
|
|
252
251
|
export async function assertPermissionRuleTicketSchema(query, dialect) {
|
|
@@ -260,12 +259,10 @@ export async function assertPermissionRuleTicketSchema(query, dialect) {
|
|
|
260
259
|
`the #265 A7 three-state redemption columns (MINTED → CLAIMED → SETTLED, the claim generation that fences a superseded ` +
|
|
261
260
|
`claimant, and the lane binding that keeps cc-import and local-import tickets from being interchangeable): without them a ` +
|
|
262
261
|
`local-import ticket can be claimed but never settled, so a redemption that already wrote rules to the store answers its ` +
|
|
263
|
-
`owner with a retry-later it can never satisfy. This
|
|
264
|
-
|
|
265
|
-
`
|
|
266
|
-
|
|
267
|
-
`persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched. ` +
|
|
268
|
-
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
|
|
262
|
+
`owner with a retry-later it can never satisfy. This table holds minute-TTL single-use import ` +
|
|
263
|
+
`tickets (a governance transient that dies on expiry); persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched.\n` +
|
|
264
|
+
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
265
|
+
(await describeProbedTableDrift(query, dialect, [PERMISSION_RULE_TICKET_TABLE])));
|
|
269
266
|
}
|
|
270
267
|
}
|
|
271
268
|
export const PERMISSION_RULE_INDEXES = [
|
|
@@ -413,7 +410,7 @@ function refuseRowsTheValidatorRefuses(ownerKey, rules, tombstones) {
|
|
|
413
410
|
}
|
|
414
411
|
}
|
|
415
412
|
function fencedCasWhere(dialect, firstPlaceholder, ownerKey, cas) {
|
|
416
|
-
const ph = (offset) => (dialect === "
|
|
413
|
+
const ph = (offset) => (dialect === "mysql" ? "?" : `$${firstPlaceholder + offset}`);
|
|
417
414
|
return {
|
|
418
415
|
sql: `owner_key = ${ph(0)} AND ${cas.column} = ${ph(1)} AND (grammar IS NULL OR grammar <= ${ph(2)})`,
|
|
419
416
|
params: [ownerKey, cas.expected, SESSION_RULES_GRAMMAR],
|
|
@@ -433,7 +430,7 @@ async function writeBucket(exec, dialect, ownerKey, cur, next, nowMs) {
|
|
|
433
430
|
const body = { rules: next.rules, tombstones: next.tombstones, quarantined: cur.quarantined, ...(cur.observedVector !== undefined ? { observedVector: cur.observedVector } : {}) };
|
|
434
431
|
const enc = dialectProtocolJsonEncoder(dialect);
|
|
435
432
|
const where = fencedCasWhere(dialect, 7, ownerKey, { column: "rev", expected: cur.rev });
|
|
436
|
-
const affected = await exec(dialect === "
|
|
433
|
+
const affected = await exec(dialect === "mysql"
|
|
437
434
|
? `UPDATE ${PERMISSION_RULE_TABLE} SET rules_json = ?, tombstones_json = ?, quarantined_json = ?, checksum = ?, grammar = ?, rev = rev + 1, updated_at_ms = ? WHERE ${where.sql}`
|
|
438
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}`, [
|
|
439
436
|
enc(next.rules, "permission_rule.rules_json"),
|
|
@@ -463,13 +460,13 @@ class SqlDurableRulePartition {
|
|
|
463
460
|
return dialectProtocolJsonEncoder(this.db.dialect)(v, label);
|
|
464
461
|
}
|
|
465
462
|
q(tidbSql, pgSql) {
|
|
466
|
-
return this.db.dialect === "
|
|
463
|
+
return this.db.dialect === "mysql" ? tidbSql : pgSql;
|
|
467
464
|
}
|
|
468
465
|
async read() {
|
|
469
466
|
return refusingUnreadableColumns(`bucket ${this.ownerKey}`, () => this.readBucketRow());
|
|
470
467
|
}
|
|
471
468
|
async readBucketRow() {
|
|
472
|
-
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]);
|
|
473
470
|
const row = rows[0];
|
|
474
471
|
if (row === undefined)
|
|
475
472
|
return { ...EMPTY_BUCKET, rules: [], tombstones: [], quarantined: [] };
|
|
@@ -537,7 +534,7 @@ class SqlDurableRulePartition {
|
|
|
537
534
|
},
|
|
538
535
|
apply: async (delta, opts) => {
|
|
539
536
|
if (delta.kind === "sync-join") {
|
|
540
|
-
throw new Error("the SQL permission-rule backend does not implement the sync-join landing
|
|
537
|
+
throw new Error("the SQL permission-rule backend does not implement the sync-join landing arm; " +
|
|
541
538
|
"no sync client is wired in this deployment, so this arm has no caller — see permission-rule-store-sql.ts header");
|
|
542
539
|
}
|
|
543
540
|
assertWriteDeltaScopeDurable(delta);
|
|
@@ -607,7 +604,7 @@ export class SqlRuleApprovalRecordStore {
|
|
|
607
604
|
this.now = now;
|
|
608
605
|
}
|
|
609
606
|
q(tidbSql, pgSql) {
|
|
610
|
-
return this.db.dialect === "
|
|
607
|
+
return this.db.dialect === "mysql" ? tidbSql : pgSql;
|
|
611
608
|
}
|
|
612
609
|
enc(v, label) {
|
|
613
610
|
return dialectProtocolJsonEncoder(this.db.dialect)(v, label);
|
|
@@ -616,7 +613,7 @@ export class SqlRuleApprovalRecordStore {
|
|
|
616
613
|
return refusingUnreadableColumns(`approval record ${id}`, () => this.readApprovalRow(id));
|
|
617
614
|
}
|
|
618
615
|
async readApprovalRow(id) {
|
|
619
|
-
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]);
|
|
620
617
|
const row = rows[0];
|
|
621
618
|
if (row === undefined)
|
|
622
619
|
return undefined;
|
|
@@ -720,7 +717,7 @@ export class SqlRuleImportTicketStore {
|
|
|
720
717
|
this.db = db;
|
|
721
718
|
}
|
|
722
719
|
q(tidbSql, pgSql) {
|
|
723
|
-
return this.db.dialect === "
|
|
720
|
+
return this.db.dialect === "mysql" ? tidbSql : pgSql;
|
|
724
721
|
}
|
|
725
722
|
async dbNowMs() {
|
|
726
723
|
return sqlDbNowMs(this.db, PERMISSION_RULE_TICKET_TABLE);
|
|
@@ -758,7 +755,7 @@ export class SqlRuleImportTicketStore {
|
|
|
758
755
|
if (row.expiresAtMs <= row.dbNowMs)
|
|
759
756
|
return { ok: false, reason: "expired" };
|
|
760
757
|
const claimId = randomUUID();
|
|
761
|
-
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]);
|
|
762
759
|
if (affected === 1)
|
|
763
760
|
return { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId };
|
|
764
761
|
const after = await this.readRow(ticketId);
|
|
@@ -771,11 +768,11 @@ export class SqlRuleImportTicketStore {
|
|
|
771
768
|
return { ok: false, reason: "consumed" };
|
|
772
769
|
}
|
|
773
770
|
async release(ticketId, principal, claimId, mustRemainValidMs = 0) {
|
|
774
|
-
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]);
|
|
775
772
|
return affected === 1;
|
|
776
773
|
}
|
|
777
774
|
async settle(ticketId, principal, claimId, outcome) {
|
|
778
|
-
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`), [
|
|
779
776
|
dialectProtocolJsonEncoder(this.db.dialect)(outcome, "permission_rule_ticket.redeemed_outcome"),
|
|
780
777
|
ticketId,
|
|
781
778
|
buildRuleOwnerKey({ kind: "principal", principal }),
|
|
@@ -808,11 +805,11 @@ export class SqlRuleImportTicketStore {
|
|
|
808
805
|
if (row.claimId === undefined)
|
|
809
806
|
return { ok: false };
|
|
810
807
|
const claimId = randomUUID();
|
|
811
|
-
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]);
|
|
812
809
|
return affected === 1 ? { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId } : { ok: false };
|
|
813
810
|
}
|
|
814
811
|
async readRow(ticketId) {
|
|
815
|
-
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]);
|
|
816
813
|
const row = rows[0];
|
|
817
814
|
if (row === undefined)
|
|
818
815
|
return undefined;
|
|
@@ -837,7 +834,7 @@ export async function purgeUndurableScopeRows(db, dialect, into, now = Date.now)
|
|
|
837
834
|
const cols = `owner_key, ${bucketRowColumns(dialect)}`;
|
|
838
835
|
let after = "";
|
|
839
836
|
for (;;) {
|
|
840
|
-
const page = await db.rows(dialect === "
|
|
837
|
+
const page = await db.rows(dialect === "mysql"
|
|
841
838
|
? `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > ? ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`
|
|
842
839
|
: `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > $1 ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`, [after]);
|
|
843
840
|
for (const first of page) {
|
|
@@ -860,7 +857,7 @@ export async function purgeUndurableScopeRows(db, dialect, into, now = Date.now)
|
|
|
860
857
|
settled = true;
|
|
861
858
|
break;
|
|
862
859
|
case "conflict":
|
|
863
|
-
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];
|
|
864
861
|
if (row === undefined)
|
|
865
862
|
settled = true;
|
|
866
863
|
break;
|
|
@@ -910,13 +907,13 @@ function contentFreeRefusal(ownerKey, e) {
|
|
|
910
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)`;
|
|
911
908
|
}
|
|
912
909
|
export function createSqlPermissionRuleStores(db, now) {
|
|
913
|
-
const q = (
|
|
910
|
+
const q = (mysql, pg) => (db.dialect === "mysql" ? mysql : pg);
|
|
914
911
|
return {
|
|
915
912
|
durable: new SqlDurableRulePartitionProvider(db, now),
|
|
916
913
|
approvals: new SqlRuleApprovalRecordStore(db, now),
|
|
917
914
|
tickets: new SqlRuleImportTicketStore(db),
|
|
918
915
|
reapExpired: async (nowMs) => {
|
|
919
|
-
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})`), []);
|
|
920
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]);
|
|
921
918
|
return deadTickets.affected + orphanPending.affected;
|
|
922
919
|
},
|
|
@@ -10,9 +10,13 @@
|
|
|
10
10
|
* (run-store + checkpoint); the remaining tidb-* tables are added as their PG stores land (tracked).
|
|
11
11
|
*
|
|
12
12
|
* ─────────────────────────────── SCHEMA POLICY (clay 裁 2026-07-26) ───────────────────────────────
|
|
13
|
-
* THIS CODE IS THE SINGLE SOURCE OF TRUTH for the PG schema
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* THIS CODE IS THE SINGLE SOURCE OF TRUTH for the PG schema, and there is NO in-place migration seam in this statement array: a database that
|
|
14
|
+
* lags behind the code is refused at boot with the exact `ALTER TABLE … ADD COLUMN …` statements printed, and the
|
|
15
|
+
* deployer runs them themselves: the column-set check and its refusal never alter a database. The only DDL this
|
|
16
|
+
* process runs by itself on a table that already exists is idempotent index creation (`ensureIndex`: indexes only,
|
|
17
|
+
* never a column) and the registered one-time migrations (each has a window and a registry row in
|
|
18
|
+
* `one-time-migrations.ts`) — adding / changing / renaming a column or dropping a table is done by those migrations
|
|
19
|
+
* and by nothing else.
|
|
16
20
|
*
|
|
17
21
|
* Consequence, and it is a hard rule: **do NOT add new `ALTER TABLE` seams here.** Every column / default /
|
|
18
22
|
* constraint change is folded into its own `CREATE TABLE`, and this array stays pure `CREATE TABLE` +
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { BaseSessionStorage, type AcquiredSession, type PlacedSessionRow, type SessionHead, type SessionMetadata, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry, type SessionWriteOptions } from "@sema-agent/core";
|
|
2
2
|
import type { Pool, PoolClient } from "pg";
|
|
3
3
|
import type { SessionSummary } from "./store-contracts.js";
|
|
4
|
-
import { type SessionRowPatch } from "../session-row.js";
|
|
4
|
+
import { type ClientSessionRowWrite, type ReplaySessionRowWrite, type ReplaySessionRowWriteOutcome, type SessionRowPatch, type SessionRowWriteOutcome } from "../session-row.js";
|
|
5
5
|
import { type PlacementAcquireOpts } from "./session-placement.js";
|
|
6
6
|
import { type OrphanOwnerLog } from "../delegated-session-owner.js";
|
|
7
7
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
8
8
|
import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
9
9
|
import type { SessionWorkspace } from "../session-workspace.js";
|
|
10
10
|
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
11
|
-
import { type SessionFace } from "../session-incarnation.js";
|
|
12
11
|
import { type IndexSpec } from "./ensure-index.js";
|
|
12
|
+
import { type ForkTarget, type ForkToOutcome } from "./session-fork.js";
|
|
13
|
+
import { type ExecutionPlacementSeat } from "../execution-placement.js";
|
|
13
14
|
/**
|
|
14
15
|
* PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS:
|
|
15
16
|
* JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), inline `KEY`/`UNIQUE KEY`→separate CREATE INDEX (UNIQUE for uq_session_event_entry).
|
|
16
17
|
*
|
|
17
|
-
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth,
|
|
18
|
-
*
|
|
18
|
+
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, there is no in-place
|
|
19
|
+
* migration, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead.
|
|
19
20
|
*/
|
|
20
21
|
export declare const PG_SESSION_SCHEMA: string[];
|
|
21
22
|
/** [ref]:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensureSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
@@ -70,6 +71,10 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
70
71
|
private readonly pending;
|
|
71
72
|
/** 委派子代孤行那一行 warn 的去处(TiDB 孪生同格)。 */
|
|
72
73
|
private readonly log;
|
|
74
|
+
/** 执行席(TiDB 孪生同格;实现与语句的唯一属主 = `execution-placement-sql.ts`,本店只交自己的池)。 */
|
|
75
|
+
readonly executionPlacement: ExecutionPlacementSeat;
|
|
76
|
+
/** 本店的池包成的 SqlDriver(TiDB 孪生同格):执行席与会话行格写原语的事务都经它借连接。 */
|
|
77
|
+
private readonly db;
|
|
73
78
|
constructor(pool: Pool, opts?: {
|
|
74
79
|
readonly log?: OrphanOwnerLog;
|
|
75
80
|
readonly retention?: RetentionDeclaration;
|
|
@@ -121,13 +126,14 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
121
126
|
}): Promise<SessionSummary[]>;
|
|
122
127
|
/** @see TiDBSessionStore.sessionSummaryOf — the PG twin (same projection, run-preview subqueries scoped to the id). */
|
|
123
128
|
sessionSummaryOf(sessionId: string): Promise<SessionSummary | undefined>;
|
|
124
|
-
/** @see TiDBSessionStore.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
/** @see TiDBSessionStore.listRowOf — the PG twin. */
|
|
130
|
+
private listRowOf;
|
|
131
|
+
/** @see TiDBSessionStore.writeSessionRowCells — the PG twin (the same SQL half, `session-row-sql.ts`; only the statement text differs). */
|
|
132
|
+
writeSessionRowCells(sessionId: string, cells: SessionRowPatch, how: ClientSessionRowWrite): Promise<SessionRowWriteOutcome<SessionSummary>>;
|
|
133
|
+
writeSessionRowCells(sessionId: string, cells: SessionRowPatch, how: ReplaySessionRowWrite): Promise<ReplaySessionRowWriteOutcome<SessionSummary>>;
|
|
134
|
+
/** @see TiDBSessionStore.selectListRows — the PG twin. `where` already carries `$n` placeholders numbered over `params`;
|
|
129
135
|
* the run scope and the LIMIT take the next numbers (PG binds by number, so text order does not matter). */
|
|
130
|
-
private
|
|
136
|
+
private selectListRows;
|
|
131
137
|
/** @see TiDBSessionStore.probeTitle — the PG twin. */
|
|
132
138
|
probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">;
|
|
133
139
|
/** @see TiDBSessionStore.setTitleIfNull — the PG twin (the auto-titler's NULL-only write). */
|
|
@@ -143,9 +149,13 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
143
149
|
/** @see TiDBSessionStore.recordSessionWorkspace — the PG twin (whole-cell overwrite fenced to the occupant; `pgSafeJsonStringify` + `::jsonb`, like every PG JSON write;
|
|
144
150
|
* the general query surface — the pool-wide `DB_QUERY_TIMEOUT_MS` server-side `statement_timeout` bounds it, no per-call client timer). */
|
|
145
151
|
recordSessionWorkspace(sessionId: string, incarnation: string, workspace: SessionWorkspace): Promise<void>;
|
|
146
|
-
/** @see TiDBSessionStore.fork —
|
|
147
|
-
* NOT wake's bounded tail) to a NEW uuidv7 session, re-seq dense from 0, ids/parents verbatim, owner=caller. */
|
|
152
|
+
/** @see TiDBSessionStore.fork — core 契约 `fork(sourceId, owner?)` = {@link forkTo} 的投影(新铸 uuidv7 目标,`forkByFreshId`;三店同一只)。 */
|
|
148
153
|
fork(sourceId: string, owner?: string | null): Promise<string | null>;
|
|
154
|
+
/** @see TiDBSessionStore.forkTo — fork 续写的店契约(PG 孪生逐字同义):目标行 meta 先、主键排他;撞已在 ⇒ 回滚后在新视图读目标按属主与 fork 键分类;
|
|
155
|
+
* 读不到 ⇒ 整笔重跑一次,再不行 ⇒ {@link ForkTargetUnsettledError}。 */
|
|
156
|
+
forkTo(sourceId: string, owner: string | null, target: ForkTarget): Promise<ForkToOutcome | null>;
|
|
157
|
+
/** @see TiDBSessionStore.forkToOnce — 一次尝试(PG 孪生):拷**整份**持久日志(压缩地板绕过,不是 wake 的有界尾),seq 从 0 重铸稠密,id / 父 / 载荷原样。 */
|
|
158
|
+
private forkToOnce;
|
|
149
159
|
/** @see TiDBSessionStore.exportEntries — 2c session-sync EXPORT: the FULL durable session_event log (compaction floor
|
|
150
160
|
* bypassed, NOT wake's tail) as `SessionTreeEntry[]`, oldest-first, verbatim. null if the source is unknown. */
|
|
151
161
|
exportEntries(sessionId: string): Promise<SessionTreeEntry[] | null>;
|
|
@@ -163,18 +173,19 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
163
173
|
batchSize?: number;
|
|
164
174
|
}): Promise<AsyncIterable<SessionTreeEntry> | null>;
|
|
165
175
|
/** @see TiDBSessionStore.importEntries — 2c session-sync IMPORT: core `validateEntriesForImport` gate FIRST (fail-closed),
|
|
166
|
-
* then persist its verbatim output (re-seq dense from 0, ids/parents/payload/ts unchanged) in ONE txn
|
|
167
|
-
*
|
|
176
|
+
* then persist its verbatim output (re-seq dense from 0, ids/parents/payload/ts unchanged) in ONE txn, **lock-or-create, meta first**
|
|
177
|
+
* (读写序律:占行 → 锁行判是不是本次占下的 → 锁定读认领 → 事件);owner = the RE-STAMPED authenticated principal (never the bundle's). */
|
|
168
178
|
importEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
|
|
169
179
|
/** @see TiDBSessionStore.replaceEntries — 2c (§7/§8) IDEMPOTENT REPLACE: core `validateEntriesForImport` gate FIRST
|
|
170
|
-
* (fail-closed), then
|
|
171
|
-
*
|
|
180
|
+
* (fail-closed), then the owner-guarded meta delete (`owner IS NOT DISTINCT FROM $n` = the PG `<=>`, defense-in-depth), then the
|
|
181
|
+
* SHARED body (session_meta first, the in-txn claim read, then the old events out and the new ones in), ALL in
|
|
172
182
|
* ONE txn. Safe to run OVER an existing session (fast-forward / §8 retry) — unlike importEntries' fresh-only INSERT. */
|
|
173
183
|
replaceEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
|
|
174
|
-
/** @see TiDBSessionStore.
|
|
175
|
-
*
|
|
184
|
+
/** @see TiDBSessionStore.writeImportedLog — the shared write half of importEntries / replaceEntries (ONE source, no drift):
|
|
185
|
+
* session_meta FIRST (create = occupy the row, an existing one ⇒ refused; replace = plain INSERT after the caller's owner-guarded
|
|
186
|
+
* delete), the in-txn claim read on THIS connection (读写序律), then the entries re-seq dense from 0 (ids/parents/payload/ts verbatim).
|
|
176
187
|
* Runs inside the caller's open txn (`conn` already in BEGIN); the caller owns COMMIT/ROLLBACK + the validate gate. */
|
|
177
|
-
private
|
|
188
|
+
private writeImportedLog;
|
|
178
189
|
/** @see TiDBSessionStore.beginImportStaging — 2c P1d-β PUSH staging: stream batches into the shadow id
|
|
179
190
|
* `${realSessionId}#stg-${token}` (route-minted token), then commit (atomic swap) / abort. */
|
|
180
191
|
beginImportStaging(realSessionId: string, token: string): StagingHandle;
|
|
@@ -188,7 +199,7 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
188
199
|
sweepStagingSessions(): Promise<number>;
|
|
189
200
|
/** @see TiDBSessionStore.deleteSession — purge session_meta + session_event; idempotent (false if absent).
|
|
190
201
|
* Owner-guarded on session_meta.owner (`IS NOT DISTINCT FROM` = the PG `<=>`); the event purge is gated on the
|
|
191
|
-
* owner-matched meta delete (session_event has no owner column). */
|
|
202
|
+
* owner-matched meta delete (session_event has no owner column). A live run claim read inside the txn ⇒ `SessionActiveRunError`. */
|
|
192
203
|
deleteSession(sessionId: string, owner: string | null): Promise<boolean>;
|
|
193
204
|
get size(): number;
|
|
194
205
|
dispose(): void;
|