@sema-agent/server 7.142.0 → 7.144.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 +608 -0
- package/USAGE.md +63 -36
- package/dist/adoption/plan.js +22 -0
- package/dist/approval-ask-audit-store.d.ts +7 -5
- package/dist/approval-ask-audit-store.js +12 -6
- package/dist/approval-ask-machine.d.ts +15 -7
- package/dist/approval-ask-machine.js +10 -4
- package/dist/approval-card.d.ts +155 -39
- package/dist/approval-card.js +111 -31
- package/dist/approval-queue-kind.d.ts +27 -0
- package/dist/approval-queue-kind.js +6 -0
- package/dist/approval-reconciler.d.ts +8 -8
- package/dist/approval-reconciler.js +3 -3
- package/dist/audit.js +4 -2
- package/dist/boot/budget-tracing.d.ts +1 -1
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/governance-seams.d.ts +26 -41
- package/dist/boot/governance-seams.js +61 -28
- package/dist/boot/late-binds.d.ts +7 -4
- package/dist/boot/late-binds.js +3 -1
- package/dist/boot/leader.js +2 -4
- package/dist/boot/memory-consolidation.d.ts +0 -8
- package/dist/boot/memory-consolidation.js +3 -3
- package/dist/boot/reapers.js +5 -4
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +8 -8
- package/dist/boot/runner-deps.d.ts +14 -6
- package/dist/boot/runner-deps.js +6 -17
- package/dist/boot/shutdown.d.ts +9 -0
- package/dist/boot/shutdown.js +18 -2
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-02-web-admin-pull.js +3 -0
- package/dist/boot/stage-03-store-backend.d.ts +4 -1
- package/dist/boot/stage-03-store-backend.js +1 -2
- package/dist/boot/stage-04-stores.d.ts +5 -2
- package/dist/boot/stage-04-stores.js +3 -5
- package/dist/boot/stage-05-execution-env.d.ts +5 -2
- package/dist/boot/stage-06-runners.d.ts +5 -2
- package/dist/boot/stage-06-runners.js +1 -3
- package/dist/boot/stage-07-capability-layer.d.ts +5 -2
- package/dist/boot/stage-07-capability-layer.js +6 -2
- package/dist/boot/stage-08-reapers.d.ts +5 -2
- package/dist/boot/stage-09-leader.d.ts +5 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -2
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/boot/stores.d.ts +5 -2
- package/dist/boot/stores.js +7 -7
- package/dist/boot/web-admin.d.ts +5 -1
- package/dist/boot/web-admin.js +31 -19
- package/dist/capabilities/hands-lane.d.ts +9 -39
- package/dist/capabilities/hands-lane.js +8 -13
- package/dist/capabilities/mcp-probe.d.ts +3 -2
- package/dist/capabilities/scenarios.d.ts +23 -17
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capabilities/skills.d.ts +1 -17
- package/dist/capabilities/skills.js +0 -8
- package/dist/capability-facts.d.ts +8 -8
- package/dist/capability-facts.js +4 -4
- package/dist/card-void.d.ts +6 -9
- package/dist/config-catalog.js +1 -1
- package/dist/config-d-migration.d.ts +2 -0
- package/dist/config-dir-trust.js +58 -29
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +7 -0
- package/dist/config-provider.d.ts +10 -0
- package/dist/config-provider.js +15 -4
- package/dist/config-root-migration.d.ts +13 -0
- package/dist/config-root-migration.js +99 -0
- package/dist/config-types.d.ts +5 -5
- package/dist/config.d.ts +4 -3
- package/dist/config.js +1 -1
- package/dist/delegated-session-owner.d.ts +35 -151
- package/dist/delegated-session-owner.js +1 -83
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/entry-mode-clamp-notice.d.ts +1 -1
- package/dist/env-model-table-gate.d.ts +53 -0
- package/dist/env-model-table-gate.js +53 -0
- package/dist/execution-placement.d.ts +7 -4
- package/dist/fleet/fleet-bus.d.ts +28 -52
- package/dist/fleet/fleet-bus.js +35 -100
- package/dist/fleet/fleet-reconciler.d.ts +4 -4
- package/dist/fleet/fleet-reconciler.js +1 -1
- package/dist/fleet/subagent-tail-bus.d.ts +14 -11
- package/dist/fleet/subagent-tail-bus.js +15 -28
- package/dist/hooks/classic-stations.d.ts +3 -1
- package/dist/hooks/classic-stations.js +2 -0
- package/dist/hooks/hook-runner.js +28 -7
- package/dist/host-decision.d.ts +54 -20
- package/dist/host-decision.js +42 -14
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/admission.js +7 -3
- package/dist/http/approval-sweeps.js +11 -8
- package/dist/http/caller-trust.d.ts +1 -1
- package/dist/http/durable-run-leg.js +2 -2
- package/dist/http/engine-refusal.d.ts +16 -0
- package/dist/http/engine-refusal.js +17 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
- package/dist/http/memory-compliance-failure-reply.js +4 -6
- package/dist/http/owner-access.d.ts +11 -0
- package/dist/http/owner-access.js +13 -0
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/refusal-body.d.ts +7 -3
- package/dist/http/refusal-body.js +1 -0
- package/dist/http/resume-legs.d.ts +21 -7
- package/dist/http/resume-legs.js +82 -53
- package/dist/http/resume-restatement.d.ts +39 -0
- package/dist/http/resume-restatement.js +25 -0
- package/dist/http/route-ctx.d.ts +35 -10
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/routes/approvals-assistant.d.ts +9 -1
- package/dist/http/routes/approvals-assistant.js +143 -31
- package/dist/http/routes/capabilities.js +11 -2
- package/dist/http/routes/memory-policy.d.ts +2 -2
- package/dist/http/routes/memory-policy.js +1 -1
- package/dist/http/routes/notify-wake.js +23 -25
- package/dist/http/routes/rules.js +48 -13
- package/dist/http/routes/runs.js +9 -8
- package/dist/http/routes/session-memory-sync.d.ts +34 -6
- package/dist/http/routes/session-memory-sync.js +27 -10
- package/dist/http/routes/session-sync.js +13 -20
- package/dist/http/routes/sessions.d.ts +2 -2
- package/dist/http/routes/sessions.js +22 -23
- package/dist/http/routes/tasks.js +27 -49
- package/dist/http/server.d.ts +13 -7
- package/dist/http/server.js +2 -2
- package/dist/http/stored-body-replay.js +15 -4
- package/dist/http/wire-types.d.ts +17 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lane-home-probe.d.ts +15 -0
- package/dist/lane-home-probe.js +4 -0
- package/dist/leader/leader.js +5 -2
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +6 -5
- package/dist/leg-facts.d.ts +37 -0
- package/dist/leg-facts.js +15 -0
- package/dist/memory-bundle-engine.d.ts +1 -4
- package/dist/memory-bundle-engine.js +2 -2
- package/dist/memory-operator-faces.d.ts +30 -66
- package/dist/memory-operator-faces.js +17 -88
- package/dist/memory-scope-stores.d.ts +38 -40
- package/dist/memory-scope-stores.js +51 -36
- package/dist/memory-scope.d.ts +0 -9
- package/dist/memory-scope.js +0 -9
- package/dist/memory-sync-client.js +14 -3
- package/dist/memory-sync.d.ts +20 -4
- package/dist/memory-sync.js +23 -4
- package/dist/model-plane-face.d.ts +13 -7
- package/dist/model-plane-face.js +2 -1
- package/dist/observability/corrupt-read-seat.d.ts +5 -9
- package/dist/observability/corrupt-read-seat.js +1 -2
- package/dist/observability/fail-open.d.ts +30 -10
- package/dist/observability/fail-open.js +27 -10
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +6 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
- package/dist/orchestration/workflow-completion-inbox.js +0 -12
- package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
- package/dist/orchestration/workflow-park-decision.d.ts +8 -3
- package/dist/orchestration/workflow-park-decision.js +5 -3
- package/dist/org-delivery-gate.d.ts +11 -0
- package/dist/org-delivery-gate.js +19 -1
- package/dist/parked-decide.d.ts +13 -9
- package/dist/parked-decide.js +7 -7
- package/dist/parked-revive-surface.d.ts +6 -7
- package/dist/parked-revive-surface.js +1 -1
- package/dist/peer-inbound-face.d.ts +122 -0
- package/dist/peer-inbound-face.js +123 -0
- package/dist/permission-rule-list.d.ts +13 -9
- package/dist/permission-rule-list.js +7 -3
- package/dist/plugins/approval-ask-store-file.js +18 -6
- package/dist/plugins/approval-ask-store-memory.js +8 -6
- package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
- package/dist/plugins/approval-ask-store-sql.js +30 -11
- package/dist/plugins/caching-session-store.d.ts +0 -3
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +45 -38
- package/dist/plugins/checkpoint-store-sql.js +108 -45
- package/dist/plugins/file-history-store-sql.d.ts +24 -18
- package/dist/plugins/file-history-store-sql.js +24 -19
- package/dist/plugins/file-run-store.d.ts +29 -0
- package/dist/plugins/file-run-store.js +97 -8
- package/dist/plugins/fork-routing-session-store.d.ts +8 -1
- package/dist/plugins/fork-routing-session-store.js +6 -0
- package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
- package/dist/plugins/imported-layer-root-retirement.js +96 -0
- package/dist/plugins/local-checkpoint-store.d.ts +6 -8
- package/dist/plugins/local-checkpoint-store.js +5 -4
- package/dist/plugins/local-session-store.d.ts +69 -30
- package/dist/plugins/local-session-store.js +133 -57
- package/dist/plugins/local-session-window.d.ts +4 -2
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
- package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
- package/dist/plugins/mailbox-store-sql.d.ts +12 -8
- package/dist/plugins/mailbox-store-sql.js +76 -7
- package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
- package/dist/plugins/memory-sync-store-pg.js +21 -0
- package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
- package/dist/plugins/memory-sync-store-tidb.js +13 -1
- package/dist/plugins/one-time-migrations.d.ts +44 -14
- package/dist/plugins/one-time-migrations.js +118 -15
- package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
- package/dist/plugins/org-delivery-quota-store.js +2 -12
- package/dist/plugins/park-commit-watch.d.ts +1 -1
- package/dist/plugins/permission-rule-store-file.d.ts +10 -1
- package/dist/plugins/permission-rule-store-file.js +15 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
- package/dist/plugins/permission-rule-store-sql.js +35 -2
- package/dist/plugins/pg-session-storage.d.ts +1 -9
- package/dist/plugins/pg-session-storage.js +2 -19
- package/dist/plugins/remote-env-device.d.ts +8 -11
- package/dist/plugins/remote-env-device.js +6 -3
- package/dist/plugins/remote-env-host.d.ts +8 -4
- package/dist/plugins/remote-env-host.js +7 -9
- package/dist/plugins/remote-env-k8s.d.ts +37 -24
- package/dist/plugins/remote-env-k8s.js +50 -17
- package/dist/plugins/remote-shell.js +12 -2
- package/dist/plugins/retention-store-sql.d.ts +2 -2
- package/dist/plugins/sandbox-read-floor.d.ts +5 -7
- package/dist/plugins/sandbox-read-floor.js +2 -7
- package/dist/plugins/schema-consistency.d.ts +5 -1
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-ownership.d.ts +9 -0
- package/dist/plugins/session-ownership.js +6 -0
- package/dist/plugins/session-store.d.ts +13 -16
- package/dist/plugins/session-store.js +9 -10
- package/dist/plugins/sql-errors.d.ts +10 -1
- package/dist/plugins/sql-errors.js +13 -0
- package/dist/plugins/sql-json-column.d.ts +24 -2
- package/dist/plugins/sql-json-column.js +38 -3
- package/dist/plugins/store-backend.d.ts +23 -28
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +29 -5
- package/dist/plugins/store-contracts.js +4 -1
- package/dist/plugins/task-attachment-store.d.ts +2 -2
- package/dist/plugins/task-attachment-store.js +37 -52
- package/dist/plugins/tidb-session-store.d.ts +2 -14
- package/dist/plugins/tidb-session-store.js +2 -20
- package/dist/plugins/tool-result-store-sql.d.ts +9 -8
- package/dist/plugins/tool-result-store-sql.js +23 -2
- package/dist/plugins/wiring-section-codec.d.ts +32 -0
- package/dist/plugins/wiring-section-codec.js +55 -0
- package/dist/plugins/workspace-fill.d.ts +1 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +5 -1
- package/dist/rules-consent.d.ts +28 -5
- package/dist/rules-consent.js +40 -28
- package/dist/run-local.js +11 -6
- package/dist/runs.d.ts +12 -8
- package/dist/runs.js +10 -7
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/security.d.ts +0 -4
- package/dist/server-secret-env.d.ts +4 -0
- package/dist/session-error-wire.d.ts +34 -23
- package/dist/session-error-wire.js +8 -3
- package/dist/session-incarnation.d.ts +3 -3
- package/dist/session-notify-door.d.ts +106 -0
- package/dist/session-notify-door.js +97 -0
- package/dist/session-sync.d.ts +9 -14
- package/dist/session-sync.js +5 -5
- package/dist/session-workspace.d.ts +3 -13
- package/dist/session-workspace.js +0 -9
- package/dist/standing-answer-grant.d.ts +5 -0
- package/dist/standing-answer-grant.js +10 -0
- package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
- package/dist/stores-sql/session-policy-store-sql.js +3 -13
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +11 -8
- package/dist/task-settings.js +31 -11
- package/dist/tool-approval.d.ts +55 -35
- package/dist/tool-approval.js +112 -74
- package/dist/trace/core-keyset-guard.d.ts +12 -9
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +4 -2
- package/dist/trace/exit-seat.d.ts +7 -1
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +2 -2
- package/dist/trace/ledger-sink.js +12 -4
- package/dist/trace/model-segment.d.ts +74 -25
- package/dist/trace/model-segment.js +141 -32
- package/dist/trace/project.d.ts +15 -6
- package/dist/trace/project.js +12 -4
- package/dist/trace/redact.d.ts +72 -9
- package/dist/trace/redact.js +1509 -471
- package/dist/trace/self-minted-wire.d.ts +598 -47
- package/dist/trace/self-minted-wire.js +190 -20
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/verifier-pause-card.d.ts +33 -5
- package/dist/verifier-pause-card.js +15 -10
- package/dist/web-admin-client/apply-effective.d.ts +12 -16
- package/dist/web-admin-client/apply-effective.js +90 -10
- package/dist/workspace-space.d.ts +19 -3
- package/dist/workspace-space.js +8 -4
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
- package/package.json +3 -2
- package/wire-changes/server@7.143.0.json +1167 -0
- package/wire-changes/server@7.144.0.json +291 -0
- package/dist/session-records-skipped-notice.d.ts +0 -21
- package/dist/session-records-skipped-notice.js +0 -27
|
@@ -9,7 +9,7 @@ import { ensureIndexes } from "./ensure-index.js";
|
|
|
9
9
|
import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, readNonNullJsonValueColumn, readOpaqueJsonValueColumn, } from "./sql-json-column.js";
|
|
10
10
|
import { incarnationFromCell, sessionFaceFromRow } from "../session-incarnation.js";
|
|
11
11
|
import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError } from "./store-contracts.js";
|
|
12
|
-
import { PERMISSION_RULE_TABLE, purgeUndurableScopeRows } from "./permission-rule-store-sql.js";
|
|
12
|
+
import { PERMISSION_RULE_TABLE, SqlDurableRulePartitionProvider, isSqlBucketRefusal, isSqlBucketVanished, listRuleBucketOwners, purgeUndurableScopeRows } from "./permission-rule-store-sql.js";
|
|
13
13
|
import { SESSION_RULES_COLUMN } from "./session-rules-column.js";
|
|
14
14
|
import { readJsonObjectColumn } from "./sql-json-column.js";
|
|
15
15
|
import { retireStoredCardWords } from "../retired-card-words.js";
|
|
@@ -18,6 +18,7 @@ import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, isOrphanSessionOwner,
|
|
|
18
18
|
import { MAILBOX_KEYED_TABLES, mailboxKeyDeleteSql, storedMailboxKeyless } from "./mailbox-store-sql.js";
|
|
19
19
|
import { SESSION_META_PLACEMENT_COLUMNS, backfillWriterStatement, countOrphanBindingsStatement, migrateDeviceBindingsStatement } from "./execution-placement-sql.js";
|
|
20
20
|
import { APPROVAL_ASK_ENDING_CAUSE_COLUMN, APPROVAL_ASK_EXECUTION_LANE_COLUMN, APPROVAL_ASK_PARK_TRIGGER_COLUMN, APPROVAL_ASK_TABLE } from "./approval-ask-store-sql.js";
|
|
21
|
+
import { retireImportedLayerRootAllows } from "./imported-layer-root-retirement.js";
|
|
21
22
|
import { isUnusableArchive } from "../execution-placement.js";
|
|
22
23
|
const RETIRED_SESSION_RULE_BUCKETS = ["commandAllow", "commandDeny"];
|
|
23
24
|
export function buildMigrationReceipts() {
|
|
@@ -63,6 +64,8 @@ export function buildMigrationReceipts() {
|
|
|
63
64
|
askCauseColumnsChanged: 0,
|
|
64
65
|
askParkTriggersMoved: 0,
|
|
65
66
|
askJournalRecordsUpgraded: 0,
|
|
67
|
+
importedLayerRootAllowsRetired: [],
|
|
68
|
+
importedLayerRootBucketsSkipped: [],
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
function completedReport(r) {
|
|
@@ -116,7 +119,9 @@ function changedAnything(r) {
|
|
|
116
119
|
r.localArchiveSnapshotsRecovered > 0 ||
|
|
117
120
|
r.askCauseColumnsChanged > 0 ||
|
|
118
121
|
r.askParkTriggersMoved > 0 ||
|
|
119
|
-
r.askJournalRecordsUpgraded > 0
|
|
122
|
+
r.askJournalRecordsUpgraded > 0 ||
|
|
123
|
+
r.importedLayerRootAllowsRetired.length > 0 ||
|
|
124
|
+
r.importedLayerRootBucketsSkipped.length > 0);
|
|
120
125
|
}
|
|
121
126
|
function readingsSnapshot(r) {
|
|
122
127
|
return {
|
|
@@ -131,6 +136,8 @@ function readingsSnapshot(r) {
|
|
|
131
136
|
executionPlacementSessionsUnreadable: [...r.executionPlacementSessionsUnreadable],
|
|
132
137
|
localArchivesKeptStamped: [...r.localArchivesKeptStamped],
|
|
133
138
|
localArchivesUnreadable: [...r.localArchivesUnreadable],
|
|
139
|
+
importedLayerRootAllowsRetired: [...r.importedLayerRootAllowsRetired],
|
|
140
|
+
importedLayerRootBucketsSkipped: [...r.importedLayerRootBucketsSkipped],
|
|
134
141
|
};
|
|
135
142
|
}
|
|
136
143
|
function receiptSentence(r) {
|
|
@@ -141,6 +148,7 @@ function receiptSentence(r) {
|
|
|
141
148
|
...r.undurableScopeRuleRows.map((x) => `removed ${x.kind} in permission-rule bucket ${x.ownerKey} (${x.behavior} ${x.tool}, scope root ${JSON.stringify(x.scopeRoot)})`),
|
|
142
149
|
...r.sessionRuleRowsWithOldGrammarEntries.map((x) => `${x.policyKey} (session ${x.sessionId}): toolAllowDropped=${JSON.stringify(x.toolAllowDropped)}`),
|
|
143
150
|
...r.sessionRuleRowsWithUnreadableDeny.map((x) => `${x.policyKey} (session ${x.sessionId}): left as stored, toolDenyUnreadable=${JSON.stringify(x.toolDenyUnreadable)}${x.retiredBuckets !== undefined ? `, retiredBuckets=${JSON.stringify(x.retiredBuckets)}` : ""}`),
|
|
151
|
+
...r.importedLayerRootAllowsRetired.map((x) => `retired ${x.addsRetired} imported add(s) of an allow ${x.tool} rule in permission-rule bucket ${x.ownerKey} (${x.stillLive ? "the rule stays in effect through another approval" : "the rule no longer takes effect"})`),
|
|
144
152
|
];
|
|
145
153
|
return [counts.join(", "), ...rows].filter((part) => part !== "").join("; ");
|
|
146
154
|
}
|
|
@@ -396,17 +404,17 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
396
404
|
introducedIn: "7.133.0",
|
|
397
405
|
releasedOn: "2026-10-21",
|
|
398
406
|
removableAfter: "2026-11-20",
|
|
399
|
-
removal: "删 migrateOn 的 M23 步(`inheritPlacedSessionOwners`:placed ∧ 属主 NULL 的行按 session_id 键集分页单趟扫描 + 同一条语句连读根 / 父两行 + 按认领句回写属主)与 `PLACED_OWNER_SCAN_PAGE` / `placedOwnerScanSql` / `scannedPlacement` / `scannedFaces` / `scannedPlacedSources
|
|
400
|
-
"**同窗同删的另一半**(
|
|
401
|
-
"`
|
|
402
|
-
"两只 SQL 店(`tidb-session-store.ts` / `pg-session-storage.ts`)与 local 店(`local-session-store.ts` 的 `claimOwnerless` 字段与 `claimOwnerlessPlaced`)的 `claimOwnerless`、`CachingSessionStore` 的转发、`OwnerAwareSessionStore.claimOwnerless`、`delegatedOwnerHostOf` 对它的「与 `placementOf` 同在或同缺」;" +
|
|
407
|
+
removal: "删 migrateOn 的 M23 步(`inheritPlacedSessionOwners`:placed ∧ 属主 NULL 的行按 session_id 键集分页单趟扫描 + 同一条语句连读根 / 父两行 + 按认领句回写属主)与 `PLACED_OWNER_SCAN_PAGE` / `placedOwnerScanSql` / `scannedPlacement` / `scannedFaces` / `scannedPlacedSources`;" +
|
|
408
|
+
"**同窗同删的另一半**(旧律的判据与写句 —— 7.144.0 起建行与停驻挂点都不再读它们,M23 是唯一读者):`delegated-session-owner.ts` 头注 M23 一节与 `judgeDelegatedOwner` / `inheritedOwnerOf` / `ownerSourcesOf` / " +
|
|
409
|
+
"`orphanSessionOwner` / `isOrphanSessionOwner` / `SourceOwnerReading` / `InheritedOwner` / `PlacementSources` / `CLAIM_OWNERLESS_PLACED_SQL` / `claimOwnerlessParams`;" +
|
|
403
410
|
"报告、日志行与 `NO_SQL_MIGRATIONS` 的 `placedSessionOwnersInherited` / `placedSessionOwnersOrphaned` / `placedSessionOwnersRemaining` 与 `placed_session_owners_orphaned` 那一行 warn;" +
|
|
404
411
|
"`test/one-time-migrations.test.ts` 的 M23 组与 `test/one-time-migrations-db-integration.test.ts` 的 M23 格。" +
|
|
405
|
-
"
|
|
412
|
+
"**不随本条删**:`ORPHAN_SESSION_OWNER_PREFIX` 与它在 `security.ts` 保留主体前缀里的登记(已落库的行可能带哨兵,永久保留);" +
|
|
413
|
+
"属主为 NULL 的 placed 行只对 fleet 调用方放行(`security.ts` 的 `ownerlessRowOpen` / `sessionOwnerCellOf` 与各门读 `placementOf` 的那一次)—— 那是常设的读面律",
|
|
406
414
|
afterRemoval: "会话面的读面律(常设,不随本条删)接住:跳过了带本条的版本直接升的库里,属主为 NULL 的存量 placed 子代行只对 fleet 调用方放行(出示 service 凭据、不报主体;dev-open)," +
|
|
407
415
|
"其余调用方答 404(与别人的行同体)—— 出路是升级时先过一次带本条的版本,或运维按 `placement_root_session_id`(缺则 `placement_parent_session_id`)那一行的属主手工回填、" +
|
|
408
|
-
"
|
|
409
|
-
"
|
|
416
|
+
"推不出的写一个任何主体都不匹配的值;停驻挂点只比对宿主行属主与引擎交来的属主(复活腿交那一行自己的属主),不写无主行;" +
|
|
417
|
+
"建行那一笔记 `placement.owner` 不随本条删(那是常设的写法 —— core 盒义务 7,不是迁移)",
|
|
410
418
|
reportKeys: ["placedSessionOwnersInherited", "placedSessionOwnersOrphaned", "placedSessionOwnersRemaining"],
|
|
411
419
|
},
|
|
412
420
|
{
|
|
@@ -563,7 +571,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
563
571
|
"`local_archive_kept_stamped` / `local_archive_unreadable` 两行 warn、读数行 note 里的 M33 句与头注 M33 段;`test/local-archive-owner.test.ts` 里标题含 `P3` / `P4` / `P5` / `P6` / `F1` / `F2` / `F3` 的格与 `legacyRoot` / `writeArchive` / `boot` / `M33_ZERO` / `m33Of` 助手。" +
|
|
564
572
|
"**不随本条删**:行格的 `createdAt` / `forkedFrom` 两键(常设的数据形)、`LocalArchiveSnapshotsPresentError` / `assertNoArchiveSnapshots` / `LOCAL_ARCHIVE_SNAPSHOT_DIR` 与 `LocalBackend.ensureSchema` 里调它的那一句、" +
|
|
565
573
|
"`session-error-wire.ts` 的 `StampedSessionArchiveError` / `STAMPED_SESSION_ARCHIVE_SENTENCE` / `sessionErrorWireOf` 与本店两处带戳判(fork 的源、改日志推送的落点)、行上缺建档时刻的 F 类读法(`server.local-session-row.created-at-absent`)",
|
|
566
|
-
afterRemoval: "三只常设臂接住:① 数据根下的快照目录非空 ⇒ 拒启(`config.local_archive_snapshot_present`,点名文件与两条出路);② 跳过了带本条的版本直接升的数据根(或本条留着的存档),带属主戳的存档被 fork 与改日志的推送同一只具名拒(409 `conflict` + 固定句,零写 / 行格净零写)," +
|
|
574
|
+
afterRemoval: "三只常设臂接住:① 数据根下的快照目录非空 ⇒ 拒启(`config.local_archive_snapshot_present`,点名文件与两条出路);② 跳过了带本条的版本直接升的数据根(或本条留着的存档),带属主戳的存档被 fork 与改日志的推送同一只具名拒(409 `conflict.session_archive_stamped` + 固定句,零写 / 行格净零写)," +
|
|
567
575
|
"读 / 续跑 / 拉取照常 —— 出路 = 删掉会话后重推,或升级时先过一次带本条的版本并在窗内起服;③ 行上没有建档时刻的格(同一类数据根)⇒ 列表 / 会话视图 / 交出的元数据读纪元(F 类 tag 计数)",
|
|
568
576
|
reportKeys: ["localArchiveRowFactsBackfilled", "localArchivesAnonymized", "localArchivesKeptStamped", "localArchivesUnreadable", "localArchiveSnapshotsRecovered"],
|
|
569
577
|
},
|
|
@@ -583,6 +591,47 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
583
591
|
"② local —— 信封号 1 的日志记录走 File 问账店「认不出的信封号」拒启(`unknown envelope version`,点名文件与出路:用写下它的那一版起服一次,或移开文件)",
|
|
584
592
|
reportKeys: ["askCauseColumnsChanged", "askParkTriggersMoved", "askJournalRecordsUpgraded"],
|
|
585
593
|
},
|
|
594
|
+
{
|
|
595
|
+
id: "M35",
|
|
596
|
+
introducedIn: "7.143.0",
|
|
597
|
+
releasedOn: "2026-10-31",
|
|
598
|
+
removableAfter: "2026-11-30",
|
|
599
|
+
removal: "删 `config-root-migration.ts` 整只文件与两个入口的调用(`boot/stage-02-web-admin-pull.ts` 目录信任判官之后、`run-local.ts` 同一位置各两行 + import);" +
|
|
600
|
+
"`test/env-model-table-gate.test.ts` 里标题以 `迁移` 起头的那一组;部署文档里「起服自动铸 config.env-models.json」那一句。" +
|
|
601
|
+
"**不随本条删**:模型面闸 `env-model-table-gate.ts`、`LocalConfigProvider` 读决定(etag 覆盖)、目录信任判官的根级文档名单(常设)",
|
|
602
|
+
afterRemoval: "常设的模型面闸接住:跳过了带本条的版本直接升的本地根(或运行记录在场而决定缺席的根),决定缺席读作 kept ⇒ env 模型表已声明时管模型面的本地目录被组拒 " +
|
|
603
|
+
"`config.catalog_replaces_env_models`,拒句点名被顶替的 env 模型与两条出路(在配置根写 `config.env-models.json` = `{\"envModels\":\"replaced\"}`,或撤掉 MODEL_ID / MODEL_CHEAP_ID)",
|
|
604
|
+
reportKeys: [],
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
id: "M36",
|
|
608
|
+
introducedIn: "7.143.0",
|
|
609
|
+
releasedOn: "2026-10-31",
|
|
610
|
+
removableAfter: "2026-11-30",
|
|
611
|
+
removal: "**删码前提**:本服务已不支持回滚到引擎 7.61.0 之前的版本(更早的引擎的导入门会重新铸出这种 add,本条要在每次起服把它们收掉)—— 窗关时仍支持这种回滚 ⇒ 本行两格顺延(改日期,并在本注里写明为什么),不删。" +
|
|
612
|
+
"删什么:`src/plugins/imported-layer-root-retirement.ts` 整个文件;migrateStepsOn 的 M36 步(规则表与 `grammar` 列的在场探针 + 对 `retireImportedLayerRootAllows` 的调用)与握锁连接上的 `query` 口" +
|
|
613
|
+
"(`LockedMigrationDb` 与 `mysqlDb` / `pgDb` 里的那一员,若届时再无别的步骤用它);`permission-rule-store-sql.ts` 的 `listRuleBucketOwners` / `isSqlBucketRefusal` / `isSqlBucketVanished` / `RuleBucketVanishedError`(取点那一句的具名类,届时若再无别的读者按类判它,收回成裸错)/ `IMPORT_RECORD_KIND`;" +
|
|
614
|
+
"`permission-rule-store-file.ts` 的 `FileRuleApprovalRecordStore.importRecordOwners`;`store-backend.ts` 的 `LocalBackend.runOneTimeMigrations` 里 M36 那一腿(规则目录在场判与那一处调用)与报告两键的那两行;" +
|
|
615
|
+
"本文件的 `createSqlImportedLayerRootSource` 与 `importedLayerRootAllowsRetired` / `importedLayerRootBucketsSkipped`(报告 / 收据 / `changedAnything` / `readingsSnapshot` / `readingFields` / `receiptSentence` / `NO_SQL_MIGRATIONS` 的排除表)、" +
|
|
616
|
+
"`imported_layer_root_allow_retired` / `imported_layer_root_bucket_skipped` 两行 warn 与 `importedLayerRootSkipNote`、读数行 note 里的 M36 句与头注 M36 段;" +
|
|
617
|
+
"`test/imported-layer-root-retirement.test.ts`、`test/imported-layer-root-retirement-db-integration.test.ts`(连同 `scripts/db-integration-files.json` 里它那一行)与 `test/helpers/imported-layer-root-fixture.ts` 整文件。" +
|
|
618
|
+
"**不随本条删**:`SqlDurableRulePartitionProvider` 收「方言 + 执行口」的那一形(`BucketSql`;常设,池照旧满足它)",
|
|
619
|
+
afterRemoval: "没有响亮臂(如实):删码之后,跳过了带本条的版本直接升的库(或回滚到引擎 7.61.0 之前、在其上又导入过的库),存量的这种全局 `/x` allow 照旧在每个项目根放行,本服务不再点名 —— " +
|
|
620
|
+
"出路 = 升级时先过一次带本条的版本并起服;或运维列出桶里出处为导入(`adds[].origin` = `imported-cc`)、scope 为全局、从 `/` 写起的 allow(`GET /v1/rules`)并撤掉(`DELETE /v1/rules`,撤的是这条规则的全部批准)",
|
|
621
|
+
reportKeys: ["importedLayerRootAllowsRetired", "importedLayerRootBucketsSkipped"],
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
id: "M37",
|
|
625
|
+
introducedIn: "7.143.0",
|
|
626
|
+
releasedOn: "2026-10-31",
|
|
627
|
+
removableAfter: "2026-11-30",
|
|
628
|
+
removal: "删 `approval-card.ts` 的 `renameRetiredSourceKey` 与 `readStoredCardEnvelope` 里调它的那一步(回到直接把传入的那一只交 M18 改写)、" +
|
|
629
|
+
"fail-open tag `server.approval-card.retired-card-key-read`(`observability/fail-open.ts` 与 `docs/FAIL-OPEN-CENSUS.md` 那一行)、" +
|
|
630
|
+
"`test/approval-card-source-key-m37.test.ts` 整只文件、本文件头注 M37 段",
|
|
631
|
+
afterRemoval: "既有坏行臂接住(响亮,不再改名):仍带旧键的存卡(跳过了带本条的版本直接升的库、窗关之后还在写的 7.142.x 副本)整卡读不出 —— 断连重放跳过 + 一次 warn、" +
|
|
632
|
+
"幂等重入走 park、回决口按要人",
|
|
633
|
+
reportKeys: [],
|
|
634
|
+
},
|
|
586
635
|
];
|
|
587
636
|
export function migrationWindowOpen(window, today) {
|
|
588
637
|
return window.removableAfter === null || today <= window.removableAfter;
|
|
@@ -633,6 +682,10 @@ export const NO_SQL_MIGRATIONS = {
|
|
|
633
682
|
};
|
|
634
683
|
function mysqlDb(conn) {
|
|
635
684
|
return {
|
|
685
|
+
async query(sql, params) {
|
|
686
|
+
const [r] = await conn.query(sql, params === undefined ? [] : [...params]);
|
|
687
|
+
return Array.isArray(r) ? { rows: r, affected: 0 } : { rows: [], affected: Number(r.affectedRows ?? 0) };
|
|
688
|
+
},
|
|
636
689
|
async rows(sql, params) {
|
|
637
690
|
const [rows] = params === undefined ? await conn.query(sql) : await conn.query(sql, [...params]);
|
|
638
691
|
return rows;
|
|
@@ -645,6 +698,10 @@ function mysqlDb(conn) {
|
|
|
645
698
|
}
|
|
646
699
|
function pgDb(client) {
|
|
647
700
|
return {
|
|
701
|
+
async query(sql, params) {
|
|
702
|
+
const r = await client.query(sql, params === undefined ? [] : [...params]);
|
|
703
|
+
return { rows: r.rows, affected: r.rowCount ?? 0 };
|
|
704
|
+
},
|
|
648
705
|
async rows(sql, params) {
|
|
649
706
|
const r = params === undefined ? await client.query(sql) : await client.query(sql, [...params]);
|
|
650
707
|
return r.rows;
|
|
@@ -675,6 +732,9 @@ async function columnExists(db, dialect, table, column) {
|
|
|
675
732
|
const sql = `SELECT 1 AS present FROM information_schema.columns WHERE table_schema = ${scope} AND table_name = '${table}' AND column_name = '${column}'`;
|
|
676
733
|
return (await db.rows(sql)).length > 0;
|
|
677
734
|
}
|
|
735
|
+
export function createSqlImportedLayerRootSource(exec) {
|
|
736
|
+
return { candidateOwners: () => listRuleBucketOwners(exec), durable: new SqlDurableRulePartitionProvider(exec), isWholeBucketRefusal: isSqlBucketRefusal, isVanishedBucket: isSqlBucketVanished };
|
|
737
|
+
}
|
|
678
738
|
export async function runSqlOneTimeMigrations(dialect, pool, input) {
|
|
679
739
|
return dialect === "mysql" ? withMysqlLock(pool, (db) => migrateOn(db, "mysql", input)) : withPgLock(pool, (db) => migrateOn(db, "pg", input));
|
|
680
740
|
}
|
|
@@ -734,6 +794,9 @@ async function migrateStepsOn(db, dialect, receipts, input) {
|
|
|
734
794
|
await inheritPlacedSessionOwners(db, dialect, receipts);
|
|
735
795
|
if ((await tableExists(db, dialect, PERMISSION_RULE_TABLE)) && (await columnExists(db, dialect, PERMISSION_RULE_TABLE, "grammar")))
|
|
736
796
|
await purgeUndurableScopeRows(db, dialect, receipts);
|
|
797
|
+
if ((await tableExists(db, dialect, PERMISSION_RULE_TABLE)) && (await columnExists(db, dialect, PERMISSION_RULE_TABLE, "grammar"))) {
|
|
798
|
+
await retireImportedLayerRootAllows(createSqlImportedLayerRootSource({ dialect, query: (sql, params) => db.query(sql, params) }), receipts);
|
|
799
|
+
}
|
|
737
800
|
await moveWorkflowJournalScope(db, dialect, receipts);
|
|
738
801
|
if (await tableExists(db, dialect, RETIRED_BREAKER_TABLE))
|
|
739
802
|
await dropRetiredBreakerTable(db, dialect, receipts);
|
|
@@ -1624,10 +1687,10 @@ export async function inheritPlacedSessionOwners(db, dialect, receipts) {
|
|
|
1624
1687
|
const sessionId = String(r.session_id);
|
|
1625
1688
|
after = sessionId;
|
|
1626
1689
|
const incarnation = incarnationFromCell(sessionId, r.incarnation);
|
|
1627
|
-
const
|
|
1628
|
-
const owner =
|
|
1690
|
+
const inherited = judgeDelegatedOwner(sessionId, scannedPlacement(r), scannedFaces(r));
|
|
1691
|
+
const owner = inherited.owner;
|
|
1629
1692
|
if (owner === null) {
|
|
1630
|
-
if (
|
|
1693
|
+
if (inherited.kind === "inherited" && scannedPlacedSources(r).has(inherited.from))
|
|
1631
1694
|
receipts.placedSessionOwnersRemaining += 1;
|
|
1632
1695
|
continue;
|
|
1633
1696
|
}
|
|
@@ -1687,6 +1750,8 @@ function readingFields(r) {
|
|
|
1687
1750
|
askCauseColumnsChanged: r.askCauseColumnsChanged,
|
|
1688
1751
|
askParkTriggersMoved: r.askParkTriggersMoved,
|
|
1689
1752
|
askJournalRecordsUpgraded: r.askJournalRecordsUpgraded,
|
|
1753
|
+
importedLayerRootAllowsRetired: r.importedLayerRootAllowsRetired.length,
|
|
1754
|
+
importedLayerRootBucketsSkipped: r.importedLayerRootBucketsSkipped.length,
|
|
1690
1755
|
};
|
|
1691
1756
|
}
|
|
1692
1757
|
export function logOneTimeMigrations(log, backend, report) {
|
|
@@ -1744,7 +1809,11 @@ export function logOneTimeMigrations(log, backend, report) {
|
|
|
1744
1809
|
"M34: the approval ask table's single cause column denied_reason — the reason a row was moved to the delivery surface while it is PARKING or PARKED, " +
|
|
1745
1810
|
"the reason it ended without a human answer while it is VOID or DENIED, overwritten by the second when a parked row was closed — renamed ending_cause, " +
|
|
1746
1811
|
"a column park_trigger added, and the word on PARKING and PARKED rows moved into park_trigger; on the local backend the approval ask journal is rewritten " +
|
|
1747
|
-
"once with record envelope 2, its envelope-1 records read with the same meaning
|
|
1812
|
+
"once with record envelope 2, its envelope-1 records read with the same meaning; " +
|
|
1813
|
+
"M36: allow approvals that an import before engine 7.61.0 stored as global rules from a settings file's user-layer path written from `/` — which the user layer reads at its " +
|
|
1814
|
+
"configuration directory and a global rule reads at every task's project root — retired by tombstones naming exactly those approvals (a rule that also holds an approval a person " +
|
|
1815
|
+
"gave directly stays in effect; deny and ask rules are never retired), one imported_layer_root_allow_retired line per rule; a bucket that could not be read or written, or that another replica deleted while it was being written, is named by an " +
|
|
1816
|
+
"imported_layer_root_bucket_skipped line on every boot). Each step " +
|
|
1748
1817
|
"runs on every boot until its window closes (the release that introduced it + 30 days; the per-step date is in " +
|
|
1749
1818
|
"removableAfter, null while that release is not published yet) and is deleted in the first minor after that — start a " +
|
|
1750
1819
|
"version that still carries a step at least once before upgrading past its deletion.",
|
|
@@ -1789,7 +1858,7 @@ function logReceiptRows(log, backend, r) {
|
|
|
1789
1858
|
...(row.errno !== undefined ? { errno: row.errno } : {}),
|
|
1790
1859
|
note: "a local session that has runs (and either no session-metadata cell — it was written before the cell existed and has not been read since — or a fork-created cell) has an archive the server cannot read, " +
|
|
1791
1860
|
"so its execution placement could not be backfilled. The session was skipped and nothing was written or changed: boot is not blocked, and every request that needs the " +
|
|
1792
|
-
"session keeps answering the archive's own fault (fault names it: corrupt_session = the session header cannot be read
|
|
1861
|
+
"session keeps answering the archive's own fault (fault names it: corrupt_session = the session header cannot be read, or the stored log does not form one tree; " +
|
|
1793
1862
|
"storage = reading the archive file itself failed — errno names it, e.g. EACCES = no permission, EIO = disk I/O error, ERR_STRING_TOO_LONG = the file is too large to read) until the " +
|
|
1794
1863
|
"archive file under the data root's sessions directory is readable again — restored from a kept copy, or (storage) its permissions or disk repaired; the next boot then backfills its placement as for any other session. " +
|
|
1795
1864
|
"(Removing the archive file ends the warning but places nothing: the session then no longer exists on this server — except a fork-created session, whose cell stays and reads as a fork that has not landed: retry the same fork, or delete the session.) The session is named on every boot until then",
|
|
@@ -1847,6 +1916,23 @@ function logReceiptRows(log, backend, r) {
|
|
|
1847
1916
|
note: removedRuleRowNote(row.kind),
|
|
1848
1917
|
});
|
|
1849
1918
|
}
|
|
1919
|
+
for (const row of r.importedLayerRootAllowsRetired) {
|
|
1920
|
+
log.warn("imported_layer_root_allow_retired", {
|
|
1921
|
+
backend,
|
|
1922
|
+
ownerKey: row.ownerKey,
|
|
1923
|
+
tool: row.tool,
|
|
1924
|
+
addsRetired: row.addsRetired,
|
|
1925
|
+
stillLive: row.stillLive,
|
|
1926
|
+
note: "an allow rule imported from a settings file's user layer by an import before engine 7.61.0 was written from `/` — the user layer reads such a path at its own configuration " +
|
|
1927
|
+
"directory — and was stored as a global rule, which reads it at every task's project root: it let that path be acted on in every project without a question. A one-time migration " +
|
|
1928
|
+
"retired the imported approval of it (a tombstone naming exactly that approval). stillLive false = the rule no longer takes effect and an action it covered asks again; stillLive true = " +
|
|
1929
|
+
"the rule stays in effect through another approval of it (one a person gave directly). To carry the rule again, spell its directory out (`//<directory>/…`, or `~/<path>` under the home) " +
|
|
1930
|
+
"and approve it again. This line is written once, by the boot that retired it — keep it; the rule text is not on it (list the bucket's rules to see what remains)",
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
for (const bucket of r.importedLayerRootBucketsSkipped) {
|
|
1934
|
+
log.warn("imported_layer_root_bucket_skipped", { backend, ownerKey: bucket.ownerKey, reason: bucket.reason, why: bucket.why, note: importedLayerRootSkipNote(bucket.reason) });
|
|
1935
|
+
}
|
|
1850
1936
|
for (const bucket of r.undurableScopeRuleBucketsUnreadable) {
|
|
1851
1937
|
log.warn("permission_rule_bucket_unreadable", {
|
|
1852
1938
|
backend,
|
|
@@ -1925,6 +2011,23 @@ export async function runAndLogOneTimeMigrations(log, backend) {
|
|
|
1925
2011
|
}
|
|
1926
2012
|
logOneTimeMigrations(log, backend.kind, report);
|
|
1927
2013
|
}
|
|
2014
|
+
function importedLayerRootSkipNote(reason) {
|
|
2015
|
+
const head = "the one-time retirement of allow approvals that an import before engine 7.61.0 stored as global rules from a user-layer path written from `/` (read at every project root) did not finish in this permission-rule bucket: ";
|
|
2016
|
+
switch (reason) {
|
|
2017
|
+
case "bucket_unreadable":
|
|
2018
|
+
return `${head}the bucket could not be read. The read path refuses such a bucket in whole as well, so none of its rules takes effect; once it is repaired, the next boot retires what it selects. Named on every boot until then`;
|
|
2019
|
+
case "vanished":
|
|
2020
|
+
return `${head}the bucket was deleted (by another replica) while this step was writing to it. Nothing is left to retire and none of its rules takes effect; not a boot refusal`;
|
|
2021
|
+
case "not_writable":
|
|
2022
|
+
return `${head}it holds approvals the retirement selects, but no tombstone could be written (the bucket is served read-only — a rule-store adoption in progress — or the store refused the tombstone), so those allow rules keep taking effect at every project root. Finish the adoption or repair the store and restart; named on every boot until then`;
|
|
2023
|
+
case "contended":
|
|
2024
|
+
return `${head}every write lost to a concurrent writer. Those allow rules keep taking effect until a later boot retires them; named on every boot until then`;
|
|
2025
|
+
default: {
|
|
2026
|
+
const unknown = reason;
|
|
2027
|
+
throw new Error(`M36: no log sentence for skip reason ${JSON.stringify(unknown)}`);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
1928
2031
|
function removedRuleRowNote(kind) {
|
|
1929
2032
|
switch (kind) {
|
|
1930
2033
|
case "rule":
|
|
@@ -101,7 +101,7 @@ export declare class SqlOrgDeliveryQuotaStore implements OrgDeliveryQuotaStore {
|
|
|
101
101
|
take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
|
|
102
102
|
/**
|
|
103
103
|
* 一个记账事务:库钟先读(池上一条值语句,不在事务里留普通 SELECT),判过 ⇒ 提交,判拒 ⇒ 回滚(零残留)。
|
|
104
|
-
* 🔴 **引擎判了死锁 ⇒ 整事务重来**(
|
|
104
|
+
* 🔴 **引擎判了死锁 ⇒ 整事务重来**(`retryOnDeadlock`:全仓唯一的重来写法、唯一的上界与判据):被挑中的一方已整事务回滚、锁全放,
|
|
105
105
|
* 重来时读-判-写全部重做,语义等价。真 InnoDB(REPEATABLE READ)上这条路是真的:窗前进时按收件人前缀删旧对偶行要取范围 / 间隙锁,
|
|
106
106
|
* 两位相邻收件人同时翻窗、各自往两人之间的间隙里插新对偶行 ⇒ 互等(TiDB 无间隙锁、PG 读已提交不取间隙锁)。谁当牺牲者是引擎的任意选择,
|
|
107
107
|
* 抛给调用方等于把一次内部仲裁当成投递失败。锁等待超时**不**重来(延迟信号,照抛)。
|
|
@@ -2,7 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { atomicWriteFile, ensureDir } from "@sema-agent/core";
|
|
4
4
|
import { mysqlDriver, pgDriver, sqlDbNowMs } from "./sql-driver.js";
|
|
5
|
-
import {
|
|
5
|
+
import { retryOnDeadlock } from "./sql-errors.js";
|
|
6
6
|
import { alignedWindow } from "../observability/cost-quota.js";
|
|
7
7
|
export const ORG_DELIVERY_SENDER_QUOTA_TABLE = "org_delivery_sender_quota";
|
|
8
8
|
export const ORG_DELIVERY_RECIPIENT_QUOTA_TABLE = "org_delivery_recipient_quota";
|
|
@@ -122,7 +122,6 @@ export class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaStore {
|
|
|
122
122
|
atomicWriteFile(this.tmpDir, this.path, `${JSON.stringify(snapshot)}\n`);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
const DEADLOCK_RETRY_LIMIT = 3;
|
|
126
125
|
export class SqlOrgDeliveryQuotaStore {
|
|
127
126
|
db;
|
|
128
127
|
constructor(db) {
|
|
@@ -138,16 +137,7 @@ export class SqlOrgDeliveryQuotaStore {
|
|
|
138
137
|
return this.inTx(ORG_DELIVERY_RECIPIENT_QUOTA_TABLE, policy, (conn, at) => this.takeIn(conn, fromScope, toScope, at, policy));
|
|
139
138
|
}
|
|
140
139
|
async inTx(table, policy, body) {
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
return await this.inTxOnce(table, policy, body);
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
if (tries < DEADLOCK_RETRY_LIMIT && isDeadlockError(this.db.dialect, err))
|
|
147
|
-
continue;
|
|
148
|
-
throw err;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
140
|
+
return retryOnDeadlock(this.db.dialect, () => this.inTxOnce(table, policy, body));
|
|
151
141
|
}
|
|
152
142
|
async inTxOnce(table, policy, body) {
|
|
153
143
|
const at = quotaWindowStart(await sqlDbNowMs(this.db, table), policy.windowMs);
|
|
@@ -28,7 +28,7 @@ import type { CheckpointStore } from "@sema-agent/core";
|
|
|
28
28
|
/**
|
|
29
29
|
* 一只**已提交**的审批停车行说它握着哪些调用(停车行自己的陈述,原样,不是推断):
|
|
30
30
|
* · `sessionId` —— 行停住的会话 `cp.sessionId`(执行这次调用的会话:委派 / 后台子代的行落在**子会话**上)。core 铸行用的是 prepare-task 的
|
|
31
|
-
* 同一个 `sessionId`,它也是同一次调用的 ask 上的 `
|
|
31
|
+
* 同一个 `sessionId`,它也是同一次调用的 ask 上的 `sourceSessionId`(`askSourceIdentity`)⇒ 订阅者按**相等**配,不做别名(车 SO:
|
|
32
32
|
* 调用 id 只在 run 内唯一,宿主会话若当子代的等价别名,宿主停车行会冒充子代的提交);
|
|
33
33
|
* · `toolCallIds` —— 停在门前的那只调用(`pendingAction.toolCallId`)∪ 同一条助手消息里**尚未执行**的调用
|
|
34
34
|
* (`batchToolCallIds − completedCallIds`,core d.ts「#k+1..N are still pending」):那些调用随这一行一起被持久挂起,
|
|
@@ -2,7 +2,7 @@ import { FileDurableRulePartitionProvider, type RuleApprovalRecord, type RuleApp
|
|
|
2
2
|
import { type RuleImportTicket, type RuleTicketPurpose, type RuleTicketReclaimResult, type RuleTicketRedeemResult, type RuleTicketSnapshot } from "./permission-rule-store-sql.js";
|
|
3
3
|
import type { PermissionRuleStoreBundle } from "../rules-consent.js";
|
|
4
4
|
import type { Logger } from "../observability/logger.js";
|
|
5
|
-
import { type RuleCandidate } from "@sema-agent/core";
|
|
5
|
+
import { type RuleCandidate, type RuleOwner } from "@sema-agent/core";
|
|
6
6
|
/**
|
|
7
7
|
* `RuleApprovalRecordStore` 的 File 形。
|
|
8
8
|
*
|
|
@@ -26,6 +26,13 @@ export declare class FileRuleApprovalRecordStore implements RuleApprovalRecordSt
|
|
|
26
26
|
get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
|
|
27
27
|
create(record: RuleApprovalRecord): Promise<void>;
|
|
28
28
|
cas(id: string, expectRev: number, next: RuleApprovalRecord): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* 一次性迁移 M36(窗口见 `plugins/one-time-migrations.ts` 登记表)在 File 店能叫出名字的桶:本店**导入记录**点名过的每一个属主(去重)。
|
|
31
|
+
* 引擎 File 分区的桶文件按属主名的单向摘要命名、分区后端没有列桶口,记录是本店唯一叫得出桶属主的地方(SQL 店直接列规则表;File 侧的缺口见
|
|
32
|
+
* `imported-layer-root-retirement.ts` 头注)。上一形的记录(Stale 信封)读不出种类 ⇒ 属主读得出就算上(多读一只桶零写,少读一只会漏)。
|
|
33
|
+
* 损坏闸合着 ⇒ 抛(读不全记录就叫不全桶;与本面其余口同一条 fail-closed)。
|
|
34
|
+
*/
|
|
35
|
+
importRecordOwners(): Promise<RuleOwner[]>;
|
|
29
36
|
/** 与 SQL 侧同名方法同义(超帽拒绝时收掉 `prepareCcImport` 已落盘的那条 pending 记录)。
|
|
30
37
|
* `state === "pending"` 是硬的:已确认/已兑付的记录是一次真人同意的审计事实。 */
|
|
31
38
|
discardPendingRecord(recordId: string): Promise<boolean>;
|
|
@@ -87,6 +94,8 @@ export declare class FileRuleImportTicketStore {
|
|
|
87
94
|
* 那一条:过期只挡开始一次兑付,不挡做完一次已经开始的)。抢到之后**换一代认领**。 */
|
|
88
95
|
reclaim(ticketId: string, principal: string, purpose: RuleTicketPurpose, minClaimAgeMs: number): Promise<RuleTicketReclaimResult>;
|
|
89
96
|
}
|
|
97
|
+
/** 三面束在数据根下的目录名(唯一一处;束在第一次装配时建它 —— 没开过规则店的数据根下没有它)。 */
|
|
98
|
+
export declare const FILE_PERMISSION_RULE_DIR = "permission-rules";
|
|
90
99
|
/** local 车道的三面束 + 生命周期。`dispose()` 释放 core File durable 分区 provider 的写锁(见 {@link createFilePermissionRuleStores})。 */
|
|
91
100
|
export interface FilePermissionRuleStores extends PermissionRuleStoreBundle {
|
|
92
101
|
durable: FileDurableRulePartitionProvider;
|
|
@@ -4,7 +4,7 @@ import { readdirSync } from "node:fs";
|
|
|
4
4
|
import { performance } from "node:perf_hooks";
|
|
5
5
|
import { FileDurableRulePartitionProvider, AppendLog, ensureDir, } from "@sema-agent/core";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { DurableRuleScopeSchema, RuleOfferColumn, buildRulePayloadHash, recordOwnerOf, refusedOwnerSentence } from "./permission-rule-store-sql.js";
|
|
7
|
+
import { DurableRuleScopeSchema, IMPORT_RECORD_KIND, RuleOfferColumn, buildRulePayloadHash, recordOwnerOf, refusedOwnerSentence } from "./permission-rule-store-sql.js";
|
|
8
8
|
import { replayJsonlGuarded } from "../observability/corrupt-read-seat.js";
|
|
9
9
|
import { EDITED_RULE_BREADTH_WARNING_CODES, RULE_APPROVAL_KINDS, RULE_APPROVAL_RECORD_SCHEMA, RULE_APPROVAL_STATES, RULE_BEHAVIORS, readRuleOwner } from "@sema-agent/core";
|
|
10
10
|
const RuleDotSchema = z.object({ actor: z.string().min(1), counter: z.number().int().nonnegative().safe() }).strict();
|
|
@@ -173,6 +173,18 @@ export class FileRuleApprovalRecordStore {
|
|
|
173
173
|
this.rows.set(id, structuredClone(next));
|
|
174
174
|
return true;
|
|
175
175
|
}
|
|
176
|
+
async importRecordOwners() {
|
|
177
|
+
this.gate.assertUsable();
|
|
178
|
+
const owners = [];
|
|
179
|
+
for (const r of this.rows.values()) {
|
|
180
|
+
if (!("staleSchema" in r) && r.kind !== IMPORT_RECORD_KIND)
|
|
181
|
+
continue;
|
|
182
|
+
const owner = recordOwnerOf(r);
|
|
183
|
+
if (owner !== undefined)
|
|
184
|
+
owners.push(owner);
|
|
185
|
+
}
|
|
186
|
+
return owners;
|
|
187
|
+
}
|
|
176
188
|
async discardPendingRecord(recordId) {
|
|
177
189
|
this.gate.assertUsable();
|
|
178
190
|
const cur = this.rows.get(recordId);
|
|
@@ -319,8 +331,9 @@ export class FileRuleImportTicketStore {
|
|
|
319
331
|
return { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId };
|
|
320
332
|
}
|
|
321
333
|
}
|
|
334
|
+
export const FILE_PERMISSION_RULE_DIR = "permission-rules";
|
|
322
335
|
export function createFilePermissionRuleStores(root, opts) {
|
|
323
|
-
const dir = join(root,
|
|
336
|
+
const dir = join(root, FILE_PERMISSION_RULE_DIR);
|
|
324
337
|
ensureDir(dir);
|
|
325
338
|
const durable = opts?.onError ? new FileDurableRulePartitionProvider(dir, opts.onError) : new FileDurableRulePartitionProvider(dir);
|
|
326
339
|
const approvals = new FileRuleApprovalRecordStore(dir, opts?.warn);
|
|
@@ -158,6 +158,8 @@ export declare function buildRuleBucketChecksum(body: {
|
|
|
158
158
|
/** 这只桶的行写在哪一版规则文法里(`SESSION_RULES_GRAMMAR` 的数);缺席 = 存量无戳行(列 NULL)。 */
|
|
159
159
|
grammar?: number;
|
|
160
160
|
}): string;
|
|
161
|
+
/** 一只桶要的全部 SQL 能力:方言 + 一只执行口(池,或一次性迁移握着锁的那一条连接 —— M36 在那条连接上经同一只写面落墓碑)。 */
|
|
162
|
+
type BucketSql = Pick<SqlDriver, "dialect" | "query">;
|
|
161
163
|
/**
|
|
162
164
|
* SQL 双方言的 {@link DurableRulePartitionProvider} —— [ref] 里的 **durable 分区后端**
|
|
163
165
|
* (`user` + `project` 两源的 OR-Set CRDT),**不是**引擎接缝本身:
|
|
@@ -170,7 +172,7 @@ export declare function buildRuleBucketChecksum(body: {
|
|
|
170
172
|
export declare class SqlDurableRulePartitionProvider implements DurableRulePartitionProvider {
|
|
171
173
|
private readonly db;
|
|
172
174
|
private readonly now;
|
|
173
|
-
constructor(db:
|
|
175
|
+
constructor(db: BucketSql, now?: () => number);
|
|
174
176
|
forPrincipal(principal: string | undefined): DurableRulePartition;
|
|
175
177
|
forLocalOwner(): DurableRulePartition;
|
|
176
178
|
}
|
|
@@ -182,6 +184,33 @@ export declare function recordOwnerOf(record: {
|
|
|
182
184
|
readonly owner?: unknown;
|
|
183
185
|
readonly principal?: unknown;
|
|
184
186
|
}): RuleOwner | undefined;
|
|
187
|
+
/**
|
|
188
|
+
* 一次性迁移 M36(窗口见 `plugins/one-time-migrations.ts` 登记表)要读的桶:**规则表里的每一只桶**(按 `owner_key` 分页,与 M11 同一只走法)。
|
|
189
|
+
* 不从审批记录推:收编腿把规则桶整桶改键到新身份、而审批史按 D2 留在旧身份名下(`src/adoption/plan.ts` 的 `permission_rule#owner_key` 腿与
|
|
190
|
+
* `permission-rule-approval-history` 一员)—— 从记录推出的属主集在收编之后漏掉新桶。属主读法只有引擎的 `readRuleOwner`(与本店读口同一只);
|
|
191
|
+
* 读不出属主的行不点名任何可解析的桶(读腿同样解析不到它),不返回。`db` 是池或迁移握锁的那一条连接。
|
|
192
|
+
*/
|
|
193
|
+
export declare function listRuleBucketOwners(db: Pick<SqlDriver, "dialect" | "query">): Promise<RuleOwner[]>;
|
|
194
|
+
/**
|
|
195
|
+
* 取 dot 那一步「建行之后、读行之前」这只桶被并发删掉了(另一只副本删桶恰落在这一窗里)。具名类 ⇒ 调用方按类判(M36 退役读作「无可退役」),不按文案。
|
|
196
|
+
*/
|
|
197
|
+
export declare class RuleBucketVanishedError extends Error {
|
|
198
|
+
readonly ownerKey: string;
|
|
199
|
+
constructor(ownerKey: string);
|
|
200
|
+
}
|
|
201
|
+
/** 这只抛出物是不是本店的「桶在写它的那一步里没了」({@link RuleBucketVanishedError})。 */
|
|
202
|
+
export declare function isSqlBucketVanished(e: unknown): boolean;
|
|
203
|
+
/**
|
|
204
|
+
* 这只读桶抛出物是不是**本店读腿的整桶拒** —— 判的是读腿({@link SqlDurableRulePartition} 的 `list` / 写的读基)**真往外交的**那两类,不是判官内部的抛出形:
|
|
205
|
+
* · {@link BucketRefusedError} —— 判官四道(文法戳 / 指纹 / 行的 behavior / 引擎校验器)的拒;
|
|
206
|
+
* · {@link StoreCorruptError} —— 某只 JSON 列的体读不出(`not-json` / `malformed`),读腿在 {@link refusingUnreadableColumns} 把判官抛的列错误折成的店拒绝契约。
|
|
207
|
+
* 两类都是这一行的确定性判决:原样再读也读不出,读腿对它同样整桶拒(零规则),所以它什么都不放行。
|
|
208
|
+
* 别的抛出物**不是**这一类:连接 / 超时 / 权限(一次读失败证明不了以后也读不出);以及仍以 {@link SqlJsonColumnError} 穿出读腿的唯一一形 `not-text`
|
|
209
|
+
* (列没按文本投影:读腿与解码点漂了,是码缺陷不是坏数据 —— 按列错误类判它会把一次码漂读成「这只桶坏了」)。
|
|
210
|
+
*/
|
|
211
|
+
export declare function isSqlBucketRefusal(e: unknown): boolean;
|
|
212
|
+
/** 导入记录的种类词(引擎闭集 `RULE_APPROVAL_KINDS` 的一员;`imported-cc` 出处由它派生)。 */
|
|
213
|
+
export declare const IMPORT_RECORD_KIND = "import";
|
|
185
214
|
/** 写口拒句(两只店同一句):属主引擎读不出,记录不落。 */
|
|
186
215
|
export declare function refusedOwnerSentence(recordId: string): string;
|
|
187
216
|
/**
|
|
@@ -542,4 +571,5 @@ export interface PermissionRuleStores {
|
|
|
542
571
|
reapExpired(nowMs: number): Promise<number>;
|
|
543
572
|
}
|
|
544
573
|
export declare function createSqlPermissionRuleStores(db: SqlDriver, now?: () => number): PermissionRuleStores;
|
|
574
|
+
export {};
|
|
545
575
|
//# sourceMappingURL=permission-rule-store-sql.d.ts.map
|
|
@@ -6,7 +6,7 @@ import { isMissingColumnError } from "./sql-errors.js";
|
|
|
6
6
|
import { describeProbedTableDrift } from "./schema-drift-sentence.js";
|
|
7
7
|
import { canonicalJsonStringify } from "@sema-agent/core";
|
|
8
8
|
import { ensureIndexes } from "./ensure-index.js";
|
|
9
|
-
import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, SqlJsonColumnError } from "./sql-json-column.js";
|
|
9
|
+
import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, SqlJsonColumnError, StoreCorruptError } from "./sql-json-column.js";
|
|
10
10
|
import { redactSecrets } from "../trace/redact.js";
|
|
11
11
|
export function writerOfSqlRuleStore(store) {
|
|
12
12
|
const w = Reflect.get(store, PERMISSION_RULE_WRITER);
|
|
@@ -508,7 +508,7 @@ class SqlDurableRulePartition {
|
|
|
508
508
|
const { rows } = await this.db.query(this.q(`SELECT actor, counter, grammar FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?`, `SELECT actor, counter, grammar FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`), [this.ownerKey]);
|
|
509
509
|
const row = rows[0];
|
|
510
510
|
if (row === undefined)
|
|
511
|
-
throw new
|
|
511
|
+
throw new RuleBucketVanishedError(this.ownerKey);
|
|
512
512
|
readBucketStamp(this.ownerKey, row.grammar);
|
|
513
513
|
const cur = Number(row.counter);
|
|
514
514
|
const next = cur + 1;
|
|
@@ -593,6 +593,39 @@ export class SqlDurableRulePartitionProvider {
|
|
|
593
593
|
export function recordOwnerOf(record) {
|
|
594
594
|
return readRuleOwner(record.owner ?? { kind: "principal", principal: record.principal });
|
|
595
595
|
}
|
|
596
|
+
export async function listRuleBucketOwners(db) {
|
|
597
|
+
const owners = [];
|
|
598
|
+
let after = "";
|
|
599
|
+
for (;;) {
|
|
600
|
+
const { rows } = await db.query(db.dialect === "mysql"
|
|
601
|
+
? `SELECT owner_key, owner_kind, principal FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > ? ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`
|
|
602
|
+
: `SELECT owner_key, owner_kind, principal FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > $1 ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`, [after]);
|
|
603
|
+
for (const row of rows) {
|
|
604
|
+
after = String(row["owner_key"]);
|
|
605
|
+
const principal = row["principal"] === null || row["principal"] === undefined ? undefined : String(row["principal"]);
|
|
606
|
+
const owner = readRuleOwner({ kind: row["owner_kind"], ...(principal !== undefined ? { principal } : {}) });
|
|
607
|
+
if (owner !== undefined)
|
|
608
|
+
owners.push(owner);
|
|
609
|
+
}
|
|
610
|
+
if (rows.length < UNDURABLE_PURGE_PAGE)
|
|
611
|
+
return owners;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
export class RuleBucketVanishedError extends Error {
|
|
615
|
+
ownerKey;
|
|
616
|
+
constructor(ownerKey) {
|
|
617
|
+
super(`permission_rule bucket ${ownerKey} vanished while minting a dot`);
|
|
618
|
+
this.name = "RuleBucketVanishedError";
|
|
619
|
+
this.ownerKey = ownerKey;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
export function isSqlBucketVanished(e) {
|
|
623
|
+
return e instanceof RuleBucketVanishedError;
|
|
624
|
+
}
|
|
625
|
+
export function isSqlBucketRefusal(e) {
|
|
626
|
+
return e instanceof BucketRefusedError || e instanceof StoreCorruptError;
|
|
627
|
+
}
|
|
628
|
+
export const IMPORT_RECORD_KIND = "import";
|
|
596
629
|
export function refusedOwnerSentence(recordId) {
|
|
597
630
|
return `refusing to create approval record ${recordId}: its owner is not one the engine reads as an owner (a principal name must be a non-blank string; the kind must be a member of the owner set) — nothing was written`;
|
|
598
631
|
}
|
|
@@ -3,7 +3,6 @@ import type { Pool, PoolClient } from "pg";
|
|
|
3
3
|
import type { SessionSummary } from "./store-contracts.js";
|
|
4
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
|
-
import { type OrphanOwnerLog } from "../delegated-session-owner.js";
|
|
7
6
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
8
7
|
import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
9
8
|
import type { SessionWorkspace } from "../session-workspace.js";
|
|
@@ -71,26 +70,19 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
71
70
|
readonly durability: "durable";
|
|
72
71
|
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */
|
|
73
72
|
private readonly pending;
|
|
74
|
-
/** 委派子代孤行那一行 warn 的去处(TiDB 孪生同格)。 */
|
|
75
|
-
private readonly log;
|
|
76
73
|
/** 执行席(TiDB 孪生同格;实现与语句的唯一属主 = `execution-placement-sql.ts`,本店只交自己的池)。 */
|
|
77
74
|
readonly executionPlacement: ExecutionPlacementSeat;
|
|
78
75
|
/** 本店的池包成的 SqlDriver(TiDB 孪生同格):执行席与会话行格写原语的事务都经它借连接。 */
|
|
79
76
|
private readonly db;
|
|
80
77
|
constructor(pool: Pool, opts?: {
|
|
81
|
-
readonly log?: OrphanOwnerLog;
|
|
82
78
|
readonly retention?: RetentionDeclaration;
|
|
83
79
|
});
|
|
84
80
|
acquire(sessionId?: string, opts?: PlacementAcquireOpts): Promise<AcquiredSession>;
|
|
85
81
|
private load;
|
|
86
82
|
/** Idempotently create a session_meta row with an owner (core `SessionStore.register`). Existing owner is never overwritten. */
|
|
87
83
|
register(sessionId: string, owner: string | null): Promise<void>;
|
|
88
|
-
/** acquire 的建行(唯一一处;[ref]
|
|
84
|
+
/** acquire 的建行(唯一一处;placed 会话的属主 = `placement.owner` 原样,core 7.62.0 [ref] 盒义务 7 —— TiDB 孪生同名方法同注)。 */
|
|
89
85
|
private createOnAcquire;
|
|
90
|
-
/** [ref] 委派继承律的认领口(TiDB 孪生 {@link TiDBSessionStore.claimOwnerless} 同义;句 = `CLAIM_OWNERLESS_PLACED_SQL`,规则在 `delegated-session-owner.ts`)。 */
|
|
91
|
-
claimOwnerless(sessionId: string, incarnation: string, owner: string): Promise<boolean>;
|
|
92
|
-
/** 若干来源行的会话面(属主 + 化身)一次读回(TiDB 孪生同义;活住客的投影 `sessionFaceFromRow`,空化身 ⇒ 响亮拒)。 */
|
|
93
|
-
private sourceFaces;
|
|
94
86
|
/** The one session_meta INSERT. Existing owner is never overwritten.
|
|
95
87
|
* [ref]:`placement` 与行同一条 INSERT;`DO NOTHING` 臂 ⇒ 已存在行的元组永不被重写(first-write 不可变)。 */
|
|
96
88
|
private insertMeta;
|
|
@@ -10,8 +10,6 @@ import { pgSafeJsonStringify } from "./pg-safe-json.js";
|
|
|
10
10
|
import { toIso } from "./sql-row-helpers.js";
|
|
11
11
|
import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SESSION_DURABILITY, SESSION_PLACEMENTS, } from "./session-placement.js";
|
|
12
12
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
13
|
-
import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, createPlacedSessionRow } from "../delegated-session-owner.js";
|
|
14
|
-
import { createLogger } from "../observability/logger.js";
|
|
15
13
|
import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
16
14
|
import { isPgUniqueViolation } from "./sql-errors.js";
|
|
17
15
|
import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
|
|
@@ -19,7 +17,7 @@ import { SESSION_META_WORKSPACE_COLUMN, decodeSessionWorkspaceCell, encodeSessio
|
|
|
19
17
|
import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
|
|
20
18
|
import { SESSION_META_INCARNATION_COLUMN, carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
21
19
|
import { ensureIndexes } from "./ensure-index.js";
|
|
22
|
-
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue } from "./session-ownership.js";
|
|
20
|
+
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue, placedSessionOwnerOf } from "./session-ownership.js";
|
|
23
21
|
import { selectForkRows } from "@sema-agent/core";
|
|
24
22
|
import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "./session-fork.js";
|
|
25
23
|
import { pgDriver } from "./sql-driver.js";
|
|
@@ -191,12 +189,10 @@ export class PgSessionStore {
|
|
|
191
189
|
placements = SESSION_PLACEMENTS;
|
|
192
190
|
durability = SESSION_DURABILITY;
|
|
193
191
|
pending = new Map();
|
|
194
|
-
log;
|
|
195
192
|
executionPlacement;
|
|
196
193
|
db;
|
|
197
194
|
constructor(pool, opts = {}) {
|
|
198
195
|
this.pool = pool;
|
|
199
|
-
this.log = opts.log ?? createLogger();
|
|
200
196
|
this.retention = opts.retention ?? UNMANAGED_RETENTION;
|
|
201
197
|
this.db = pgDriver(pool);
|
|
202
198
|
this.executionPlacement = createSqlExecutionPlacementSeat(this.db);
|
|
@@ -244,20 +240,7 @@ export class PgSessionStore {
|
|
|
244
240
|
await this.insertMeta(sessionId, owner, undefined);
|
|
245
241
|
}
|
|
246
242
|
async createOnAcquire(sessionId, placement) {
|
|
247
|
-
|
|
248
|
-
return this.insertMeta(sessionId, null, undefined);
|
|
249
|
-
await createPlacedSessionRow(sessionId, placement, {
|
|
250
|
-
readFaces: (ids) => this.sourceFaces(ids),
|
|
251
|
-
insert: (owner) => this.insertMeta(sessionId, owner, placement),
|
|
252
|
-
readOwner: async () => (await this.headOf(sessionId))?.owner,
|
|
253
|
-
}, this.log);
|
|
254
|
-
}
|
|
255
|
-
async claimOwnerless(sessionId, incarnation, owner) {
|
|
256
|
-
return ((await this.pool.query(CLAIM_OWNERLESS_PLACED_SQL.pg, [...claimOwnerlessParams(sessionId, incarnation, owner)])).rowCount ?? 0) > 0;
|
|
257
|
-
}
|
|
258
|
-
async sourceFaces(ids) {
|
|
259
|
-
const res = await this.pool.query(`SELECT session_id, owner, incarnation FROM session_meta WHERE session_id IN (${ids.map((_, i) => `$${i + 1}`).join(", ")})`, [...ids]);
|
|
260
|
-
return new Map(res.rows.map((r) => [String(r.session_id), sessionFaceFromRow(String(r.session_id), r.owner ?? null, r.incarnation)]));
|
|
243
|
+
await this.insertMeta(sessionId, placement === undefined ? null : placedSessionOwnerOf(sessionId, placement), placement);
|
|
261
244
|
}
|
|
262
245
|
async insertMeta(sessionId, owner, placement) {
|
|
263
246
|
const now = new Date();
|