@sema-agent/server 7.150.0 → 7.151.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 +185 -0
- package/USAGE.md +53 -25
- package/deploy/sema-up/README.md +7 -1
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
- package/deploy/sema-up/chart/templates/server.yaml +5 -3
- package/deploy/sema-up/chart/values-full-single.yaml +3 -1
- package/deploy/sema-up/chart/values.yaml +8 -1
- package/deploy/sema-up/cluster-up.sh +1 -0
- package/deploy/sema-up/kube-up.sh +1 -1
- package/deploy/sema-up/lib/flagshape.sh +1 -1
- package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
- package/deploy/sema-up/lib/helm-values-kube.py +2 -0
- package/dist/adoption/import.js +3 -3
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +2 -2
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/auth-bridge.d.ts +20 -0
- package/dist/auth-bridge.js +9 -0
- package/dist/background-session-reap.d.ts +1 -1
- package/dist/bake-runner/main.d.ts +4 -2
- package/dist/bind-host.d.ts +2 -3
- package/dist/bind-host.js +2 -4
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/engine-lease.d.ts +5 -0
- package/dist/boot/engine-lease.js +1 -0
- package/dist/boot/execution-env.d.ts +8 -14
- package/dist/boot/execution-env.js +54 -17
- package/dist/boot/execution-lane.js +32 -5
- package/dist/boot/late-binds.d.ts +8 -4
- package/dist/boot/late-binds.js +7 -1
- package/dist/boot/memory-consolidation.d.ts +3 -2
- package/dist/boot/memory-consolidation.js +6 -6
- package/dist/boot/reapers.d.ts +5 -0
- package/dist/boot/reapers.js +12 -1
- package/dist/boot/resolve-spec.d.ts +3 -3
- package/dist/boot/resolve-spec.js +31 -22
- package/dist/boot/runner-deps.d.ts +9 -2
- package/dist/boot/runner-deps.js +3 -3
- package/dist/boot/session-age-out.d.ts +36 -0
- package/dist/boot/session-age-out.js +101 -0
- package/dist/boot/session-faces.d.ts +5 -14
- package/dist/boot/session-faces.js +9 -5
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-05-execution-env.d.ts +5 -3
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +7 -4
- package/dist/boot/stage-06-runners.js +15 -6
- package/dist/boot/stage-07-capability-layer.d.ts +7 -4
- package/dist/boot/stage-07-capability-layer.js +2 -1
- package/dist/boot/stage-08-reapers.d.ts +7 -4
- package/dist/boot/stage-08-reapers.js +4 -2
- package/dist/boot/stage-09-leader.d.ts +11 -5
- package/dist/boot/stage-09-leader.js +12 -3
- package/dist/boot/stage-10-http-server.d.ts +11 -5
- package/dist/boot/stage-10-http-server.js +3 -6
- package/dist/boot/web-admin.js +3 -1
- package/dist/boot/workflow-orchestration.d.ts +7 -2
- package/dist/boot/workflow-orchestration.js +24 -7
- package/dist/bounded-rotation.d.ts +29 -0
- package/dist/bounded-rotation.js +24 -0
- package/dist/capability-facts.d.ts +22 -13
- package/dist/capability-facts.js +28 -18
- package/dist/center-credential.d.ts +25 -6
- package/dist/center-credential.js +25 -5
- package/dist/config-catalog.d.ts +26 -7
- package/dist/config-catalog.js +14 -11
- package/dist/config-invariants.d.ts +2 -1
- package/dist/config-invariants.js +4 -4
- package/dist/config-types.d.ts +10 -4
- package/dist/config.d.ts +51 -2
- package/dist/config.js +35 -24
- package/dist/deployment-shape.d.ts +9 -1
- package/dist/deployment-shape.js +2 -1
- package/dist/device-protocol.d.ts +5 -4
- package/dist/device-protocol.js +1 -1
- package/dist/device-ws-hub.js +3 -2
- package/dist/device-ws-protocol.d.ts +63 -10
- package/dist/device-ws-protocol.js +56 -25
- package/dist/engine-identity.d.ts +19 -7
- package/dist/engine-identity.js +10 -2
- package/dist/entry-mode-clamp-notice.d.ts +2 -1
- package/dist/execution-env-refusal.d.ts +23 -0
- package/dist/execution-env-refusal.js +5 -0
- package/dist/execution-lane-caps.d.ts +3 -1
- package/dist/field-conflict.d.ts +1 -1
- package/dist/file-history-capture.d.ts +11 -4
- package/dist/file-history-capture.js +9 -6
- package/dist/file-history-disclosure.d.ts +7 -5
- package/dist/file-history-disclosure.js +7 -5
- package/dist/fleet/drain-entry.d.ts +4 -0
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/host-lsp-manager.d.ts +2 -2
- package/dist/http/admission.js +17 -2
- package/dist/http/approval-sweeps.js +5 -5
- package/dist/http/checkpoint-ctx.d.ts +2 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/dispatch.d.ts +2 -1
- package/dist/http/dispatch.js +34 -27
- package/dist/http/durable-run-leg.js +16 -16
- package/dist/http/health-self-report.d.ts +9 -1
- package/dist/http/health-self-report.js +2 -0
- package/dist/http/owner-access.d.ts +31 -11
- package/dist/http/owner-access.js +37 -6
- package/dist/http/resume-ctx-read.js +25 -3
- package/dist/http/resume-legs.d.ts +2 -1
- package/dist/http/resume-legs.js +259 -79
- package/dist/http/route-ctx.d.ts +18 -8
- package/dist/http/route-table.d.ts +1 -1
- package/dist/http/routes/admin-memory-optout.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +4 -3
- package/dist/http/routes/background.d.ts +3 -3
- package/dist/http/routes/capabilities.js +8 -5
- package/dist/http/routes/fleet.js +10 -8
- package/dist/http/routes/projects.d.ts +3 -2
- package/dist/http/routes/projects.js +4 -5
- package/dist/http/routes/runs.js +102 -36
- package/dist/http/routes/session-sync.d.ts +4 -3
- package/dist/http/routes/session-sync.js +172 -77
- package/dist/http/routes/sessions-list.js +13 -7
- package/dist/http/routes/sessions.js +10 -6
- package/dist/http/routes/tasks.js +30 -16
- package/dist/http/routes/trace-usage.js +2 -2
- package/dist/http/server.d.ts +21 -27
- package/dist/http/server.js +2 -2
- package/dist/http/submit-ctx.js +5 -4
- package/dist/http/wire-types.d.ts +14 -4
- package/dist/identity-source.d.ts +20 -3
- package/dist/identity-source.js +14 -0
- package/dist/index.d.ts +4 -0
- package/dist/lane-home-probe.d.ts +3 -7
- package/dist/lane-home-probe.js +0 -1
- package/dist/leader/leader.d.ts +2 -1
- package/dist/leg-facts.d.ts +13 -5
- package/dist/leg-facts.js +2 -1
- package/dist/memory-scope.d.ts +5 -4
- package/dist/memory-scope.js +4 -4
- package/dist/memory-sync-client.d.ts +30 -2
- package/dist/memory-sync-client.js +16 -9
- package/dist/observability/err-text.d.ts +7 -0
- package/dist/observability/err-text.js +5 -0
- package/dist/observability/fail-open.d.ts +7 -3
- package/dist/observability/fail-open.js +7 -3
- package/dist/observability/metrics.js +1 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
- package/dist/orchestration/workflow-notify-journal.js +12 -1
- package/dist/orchestration/workflow-park-decision.d.ts +70 -18
- package/dist/orchestration/workflow-park-decision.js +49 -10
- package/dist/orchestration/workflow-park-holder.d.ts +65 -0
- package/dist/orchestration/workflow-park-holder.js +98 -0
- package/dist/orchestration/workflow-record-fence.d.ts +36 -0
- package/dist/orchestration/workflow-record-fence.js +42 -0
- package/dist/owner-faces.d.ts +1 -1
- package/dist/parked-decide.d.ts +104 -53
- package/dist/parked-decide.js +153 -117
- package/dist/peer-directory.d.ts +2 -2
- package/dist/peer-inbound-face.d.ts +2 -6
- package/dist/peer-inbound-face.js +0 -3
- package/dist/permission-mode-admission.d.ts +41 -5
- package/dist/permission-mode-admission.js +29 -3
- package/dist/plugins/adoption-log-sql.d.ts +3 -1
- package/dist/plugins/adoption-log-sql.js +7 -1
- package/dist/plugins/approval-ask-store-file.d.ts +10 -2
- package/dist/plugins/approval-ask-store-file.js +8 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
- package/dist/plugins/approval-ask-store-memory.js +28 -9
- package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
- package/dist/plugins/approval-ask-store-sql.js +34 -5
- package/dist/plugins/approval-exemption-store.js +3 -0
- package/dist/plugins/approval-nonce-store.js +2 -0
- package/dist/plugins/breaker-state-sql.js +2 -0
- package/dist/plugins/caching-session-store.d.ts +9 -15
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/device-store-sql.js +6 -0
- package/dist/plugins/error-origin.d.ts +18 -0
- package/dist/plugins/error-origin.js +10 -0
- package/dist/plugins/execution-placement-sql.js +6 -6
- package/dist/plugins/image-bake-store-sql.js +6 -0
- package/dist/plugins/image-index-sql.js +4 -0
- package/dist/plugins/landed-attendants.d.ts +212 -0
- package/dist/plugins/landed-attendants.js +161 -0
- package/dist/plugins/leader-run-store-sql.js +4 -0
- package/dist/plugins/local-session-live-reference.d.ts +40 -0
- package/dist/plugins/local-session-live-reference.js +39 -0
- package/dist/plugins/local-session-store.d.ts +71 -33
- package/dist/plugins/local-session-store.js +129 -27
- package/dist/plugins/memory-engine-pg.js +4 -0
- package/dist/plugins/memory-engine-sql-apply.js +5 -2
- package/dist/plugins/memory-engine-tidb.js +4 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
- package/dist/plugins/memory-sync-store-pg.js +5 -0
- package/dist/plugins/memory-sync-store-tidb.js +5 -0
- package/dist/plugins/one-time-migrations.d.ts +57 -63
- package/dist/plugins/one-time-migrations.js +162 -90
- package/dist/plugins/org-delivery-quota-store.js +4 -1
- package/dist/plugins/outcome-ledger-sql.js +2 -0
- package/dist/plugins/permission-rule-store-sql.js +11 -4
- package/dist/plugins/pg-cost-quota.js +4 -0
- package/dist/plugins/pg-pool.js +24 -1
- package/dist/plugins/pg-rate-limiter.js +2 -0
- package/dist/plugins/posix-shell-fs.d.ts +6 -1
- package/dist/plugins/posix-shell-fs.js +4 -1
- package/dist/plugins/remote-env-adb.d.ts +19 -15
- package/dist/plugins/remote-env-adb.js +41 -15
- package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
- package/dist/plugins/remote-env-connect-seat.js +72 -0
- package/dist/plugins/remote-env-device.d.ts +22 -5
- package/dist/plugins/remote-env-device.js +106 -8
- package/dist/plugins/remote-env-e2b.d.ts +15 -6
- package/dist/plugins/remote-env-e2b.js +42 -32
- package/dist/plugins/remote-env-file-error.js +6 -2
- package/dist/plugins/remote-env-host.d.ts +17 -0
- package/dist/plugins/remote-env-host.js +30 -38
- package/dist/plugins/remote-env-k8s.d.ts +41 -39
- package/dist/plugins/remote-env-k8s.js +53 -30
- package/dist/plugins/remote-env-local-docker.d.ts +13 -8
- package/dist/plugins/remote-env-local-docker.js +34 -41
- package/dist/plugins/remote-env-ssh.d.ts +18 -12
- package/dist/plugins/remote-env-ssh.js +60 -59
- package/dist/plugins/remote-shell.d.ts +98 -1
- package/dist/plugins/remote-shell.js +218 -0
- package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
- package/dist/plugins/resume-anchor-store-sql.js +12 -5
- package/dist/plugins/retention-lane-store-sql.js +4 -0
- package/dist/plugins/retention-sql.d.ts +20 -2
- package/dist/plugins/retention-sql.js +11 -0
- package/dist/plugins/run-store-sql.d.ts +21 -2
- package/dist/plugins/run-store-sql.js +31 -7
- package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
- package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
- package/dist/plugins/schema-consistency.d.ts +4 -2
- package/dist/plugins/schema-consistency.js +19 -2
- package/dist/plugins/send-file-ledger.js +3 -0
- package/dist/plugins/serving-requirement.d.ts +26 -7
- package/dist/plugins/serving-requirement.js +11 -4
- package/dist/plugins/session-birth.d.ts +6 -2
- package/dist/plugins/session-capture-record-store-sql.js +2 -0
- package/dist/plugins/session-ext-table.d.ts +14 -1
- package/dist/plugins/session-ext-table.js +5 -1
- package/dist/plugins/session-row-sql.d.ts +49 -2
- package/dist/plugins/session-row-sql.js +56 -13
- package/dist/plugins/session-store-sql.d.ts +34 -18
- package/dist/plugins/session-store-sql.js +98 -17
- package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
- package/dist/plugins/shared-memory-store-sql.js +6 -2
- package/dist/plugins/sql-driver.d.ts +2 -1
- package/dist/plugins/sql-driver.js +18 -3
- package/dist/plugins/sql-errors.d.ts +11 -6
- package/dist/plugins/sql-errors.js +5 -6
- package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
- package/dist/plugins/stop-then-start-sentence.js +14 -10
- package/dist/plugins/stop-then-start.d.ts +109 -0
- package/dist/plugins/stop-then-start.js +104 -0
- package/dist/plugins/store-backend.d.ts +40 -13
- package/dist/plugins/store-backend.js +169 -27
- package/dist/plugins/store-boundary.d.ts +57 -0
- package/dist/plugins/store-boundary.js +139 -0
- package/dist/plugins/store-contracts.d.ts +17 -0
- package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
- package/dist/plugins/store-fault-sentence-m53.js +25 -0
- package/dist/plugins/store-fault.d.ts +26 -7
- package/dist/plugins/store-fault.js +18 -2
- package/dist/plugins/task-attachment-store.js +9 -0
- package/dist/plugins/task-list-store-sql.d.ts +2 -2
- package/dist/plugins/task-list-store-sql.js +10 -8
- package/dist/plugins/tidb-cost-quota.js +4 -0
- package/dist/plugins/tidb-pool.js +2 -1
- package/dist/plugins/tidb-rate-limiter.js +4 -0
- package/dist/plugins/user-input-ledger-sql.js +4 -0
- package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
- package/dist/plugins/workflow-run-store-sql.js +64 -4
- package/dist/plugins/workspace-fill.d.ts +4 -7
- package/dist/plugins/workspace-fill.js +2 -1
- package/dist/plugins/write-behind-counter.js +5 -0
- package/dist/project-context.d.ts +0 -2
- package/dist/project-context.js +4 -8
- package/dist/project-registry.d.ts +46 -0
- package/dist/project-registry.js +44 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +4 -0
- package/dist/request-value-closure.js +10 -0
- package/dist/run-acceptance.d.ts +3 -2
- package/dist/run-acceptance.js +4 -6
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +37 -8
- package/dist/runs.js +53 -12
- package/dist/runtime-governance.d.ts +5 -4
- package/dist/security.d.ts +30 -32
- package/dist/security.js +41 -7
- package/dist/server-secret-env.d.ts +12 -10
- package/dist/server-secret-env.js +24 -15
- package/dist/session-bindings.d.ts +82 -0
- package/dist/session-bindings.js +18 -0
- package/dist/session-children-retire.d.ts +2 -2
- package/dist/session-children-retire.js +2 -6
- package/dist/session-deletion.d.ts +16 -1
- package/dist/session-error-wire.d.ts +3 -3
- package/dist/session-error-wire.js +3 -3
- package/dist/session-incarnation.d.ts +10 -0
- package/dist/session-incarnation.js +10 -1
- package/dist/session-kind.d.ts +57 -0
- package/dist/session-kind.js +22 -0
- package/dist/session-row.d.ts +21 -1
- package/dist/session-row.js +7 -1
- package/dist/session-sync-kernel.d.ts +24 -14
- package/dist/session-sync-kernel.js +3 -6
- package/dist/session-sync.d.ts +4 -98
- package/dist/session-sync.js +1 -71
- package/dist/session-titler.d.ts +10 -3
- package/dist/session-titler.js +19 -6
- package/dist/ssh-host-key.d.ts +5 -0
- package/dist/ssh-host-key.js +3 -0
- package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
- package/dist/stores-sql/file-history-store-sql.js +108 -70
- package/dist/stores-sql/pg-session-storage.d.ts +34 -7
- package/dist/stores-sql/pg-session-storage.js +37 -13
- package/dist/stores-sql/retention-store-sql.d.ts +1 -9
- package/dist/stores-sql/retention-store-sql.js +2 -10
- package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
- package/dist/stores-sql/session-policy-store-sql.js +61 -44
- package/dist/stores-sql/session-write-hooks.d.ts +11 -0
- package/dist/stores-sql/tidb-session-store.d.ts +58 -7
- package/dist/stores-sql/tidb-session-store.js +37 -13
- package/dist/submission-exit.d.ts +3 -2
- package/dist/submission-exit.js +9 -1
- package/dist/task-cwd.d.ts +2 -2
- package/dist/task-settings.d.ts +6 -5
- package/dist/terminal.d.ts +33 -2
- package/dist/terminal.js +139 -9
- package/dist/tool-approval.d.ts +140 -6
- package/dist/tool-approval.js +165 -72
- package/dist/trace/self-minted-wire.d.ts +290 -27
- package/dist/trace/self-minted-wire.js +101 -13
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
- package/package.json +2 -2
- package/wire-changes/server@7.151.0.json +762 -0
- package/dist/http/session-inherit-env.d.ts +0 -36
- package/dist/http/session-inherit-env.js +0 -6
|
@@ -87,11 +87,11 @@ export async function eraseSessionPolicyRowsOn(conn, dialect, sessionIds) {
|
|
|
87
87
|
}
|
|
88
88
|
export class SqlSessionPolicyStore {
|
|
89
89
|
db;
|
|
90
|
-
|
|
90
|
+
root;
|
|
91
91
|
logger;
|
|
92
|
-
constructor(db,
|
|
92
|
+
constructor(db, root, logger) {
|
|
93
93
|
this.db = db;
|
|
94
|
-
this.
|
|
94
|
+
this.root = root;
|
|
95
95
|
this.logger = logger;
|
|
96
96
|
}
|
|
97
97
|
q(mysql, pg) {
|
|
@@ -124,50 +124,67 @@ export class SqlSessionPolicyStore {
|
|
|
124
124
|
}));
|
|
125
125
|
}
|
|
126
126
|
async putRules(sessionId, principal, rules, opts) {
|
|
127
|
-
const key = policyKey(sessionId, principal);
|
|
128
127
|
return this.retrying(() => this.tx(async (conn) => {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts.expectedRev}, have ${priorRev})`);
|
|
135
|
-
}
|
|
136
|
-
const { rows: markRows } = await conn.query(this.q("SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = ? FOR UPDATE", "SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = $1 FOR UPDATE"), [key]);
|
|
137
|
-
const markRev = markRows[0] ? Number(markRows[0].rev) : 0;
|
|
138
|
-
const markGen = markRows[0] ? Number(markRows[0].gen) : 0;
|
|
139
|
-
if (markRows[0] !== undefined && (!isStoredCounter(markGen, 0) || !isStoredCounter(markRev, 0))) {
|
|
140
|
-
throw new SessionPolicyError("corrupt", `the session rules rev mark for session ${JSON.stringify(sessionId)} is unreadable (rev ${String(markRows[0].rev)}, gen ${String(markRows[0].gen)}) — refusing rather than stamp a rev/lineage nothing proves`);
|
|
141
|
-
}
|
|
142
|
-
const clean = judgeSessionRulesWrite(prior ?? null, rules, opts);
|
|
143
|
-
if (this.db.dialect === "pg" && pgHasUnstorable(clean)) {
|
|
144
|
-
throw new SessionPolicyError("loosen_forbidden", "rules contain unstorable bytes (NUL / lone surrogate) — refused; strip them at the source (authorization rules are never rewritten by the store)");
|
|
145
|
-
}
|
|
146
|
-
const nextRev = Math.max(priorRev, markRev) + 1;
|
|
147
|
-
const nextGen = prior?.gen ?? markGen;
|
|
148
|
-
if (!isStoredCounter(nextRev, 1) || !isStoredCounter(nextGen, 0)) {
|
|
149
|
-
throw new SessionPolicyError("corrupt", `session rules for session ${JSON.stringify(sessionId)}: the next stamp (rev ${nextRev}, gen ${nextGen}) would leave the safe-integer range this store reads back — refusing before writing a row nothing could read`);
|
|
150
|
-
}
|
|
151
|
-
if (root.rows.length > 0)
|
|
152
|
-
await this.countClientWrite(conn, this.db.dialect, sessionId);
|
|
153
|
-
const json = JSON.stringify(clean);
|
|
154
|
-
const now = new Date();
|
|
155
|
-
if (prior) {
|
|
156
|
-
const upd = await conn.query(this.q(`UPDATE session_policy SET principal = ?, ${RULES} = ?, rev = ?, gen = ?, updated_at = ? WHERE policy_key = ? AND rev = ?`, `UPDATE session_policy SET principal = $1, ${RULES} = $2::jsonb, rev = $3, gen = $4, updated_at = $5 WHERE policy_key = $6 AND rev = $7`), [principal ?? null, json, nextRev, nextGen, now, key, priorRev]);
|
|
157
|
-
if (upd.affected !== 1)
|
|
158
|
-
throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts?.expectedRev ?? priorRev}, have ${priorRev})`);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
await conn.query(this.q(`INSERT INTO session_policy (policy_key, session_id, principal, ${RULES}, rev, gen, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)`, `INSERT INTO session_policy (policy_key, session_id, principal, ${RULES}, rev, gen, created_at, updated_at) VALUES ($1,$2,$3,$4::jsonb,$5,$6,$7,$8)`), [key, sessionId, principal ?? null, json, nextRev, nextGen, now, now]);
|
|
162
|
-
}
|
|
163
|
-
return structuredClone({ ...clean, rev: nextRev, gen: nextGen });
|
|
128
|
+
const present = await this.root.lockRoot(conn, this.db.dialect, sessionId);
|
|
129
|
+
const out = await this.putRulesOn(conn, sessionId, principal, rules, opts, { count: present });
|
|
130
|
+
if ("skipped" in out)
|
|
131
|
+
throw new Error("SqlSessionPolicyStore.putRules: no skip predicate was given, yet the write was skipped");
|
|
132
|
+
return out;
|
|
164
133
|
}));
|
|
165
134
|
}
|
|
135
|
+
async putRulesOn(conn, sessionId, principal, rules, opts, how) {
|
|
136
|
+
const key = policyKey(sessionId, principal);
|
|
137
|
+
const { rows } = await conn.query(this.q(`SELECT ${jsonColumnText("mysql", RULES)}, rev, gen FROM session_policy WHERE policy_key = ? FOR UPDATE`, `SELECT ${jsonColumnText("pg", RULES)}, rev, gen FROM session_policy WHERE policy_key = $1 FOR UPDATE`), [key]);
|
|
138
|
+
const prior = rows[0] ? rowToStored(rows[0], `for session ${JSON.stringify(sessionId)} (pre-write snapshot)`) : undefined;
|
|
139
|
+
if (how.skip?.(prior ?? null) === true)
|
|
140
|
+
return { skipped: true };
|
|
141
|
+
const priorRev = prior?.rev ?? 0;
|
|
142
|
+
if (opts?.expectedRev !== undefined && priorRev !== opts.expectedRev) {
|
|
143
|
+
throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts.expectedRev}, have ${priorRev})`);
|
|
144
|
+
}
|
|
145
|
+
const { rows: markRows } = await conn.query(this.q("SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = ? FOR UPDATE", "SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = $1 FOR UPDATE"), [key]);
|
|
146
|
+
const markRev = markRows[0] ? Number(markRows[0].rev) : 0;
|
|
147
|
+
const markGen = markRows[0] ? Number(markRows[0].gen) : 0;
|
|
148
|
+
if (markRows[0] !== undefined && (!isStoredCounter(markGen, 0) || !isStoredCounter(markRev, 0))) {
|
|
149
|
+
throw new SessionPolicyError("corrupt", `the session rules rev mark for session ${JSON.stringify(sessionId)} is unreadable (rev ${String(markRows[0].rev)}, gen ${String(markRows[0].gen)}) — refusing rather than stamp a rev/lineage nothing proves`);
|
|
150
|
+
}
|
|
151
|
+
const clean = judgeSessionRulesWrite(prior ?? null, rules, opts);
|
|
152
|
+
if (this.db.dialect === "pg" && pgHasUnstorable(clean)) {
|
|
153
|
+
throw new SessionPolicyError("loosen_forbidden", "rules contain unstorable bytes (NUL / lone surrogate) — refused; strip them at the source (authorization rules are never rewritten by the store)");
|
|
154
|
+
}
|
|
155
|
+
const nextRev = Math.max(priorRev, markRev) + 1;
|
|
156
|
+
const nextGen = prior?.gen ?? markGen;
|
|
157
|
+
if (!isStoredCounter(nextRev, 1) || !isStoredCounter(nextGen, 0)) {
|
|
158
|
+
throw new SessionPolicyError("corrupt", `session rules for session ${JSON.stringify(sessionId)}: the next stamp (rev ${nextRev}, gen ${nextGen}) would leave the safe-integer range this store reads back — refusing before writing a row nothing could read`);
|
|
159
|
+
}
|
|
160
|
+
if (how.count)
|
|
161
|
+
await this.root.countClientWrite(conn, this.db.dialect, sessionId);
|
|
162
|
+
const json = JSON.stringify(clean);
|
|
163
|
+
const now = new Date();
|
|
164
|
+
if (prior) {
|
|
165
|
+
const upd = await conn.query(this.q(`UPDATE session_policy SET principal = ?, ${RULES} = ?, rev = ?, gen = ?, updated_at = ? WHERE policy_key = ? AND rev = ?`, `UPDATE session_policy SET principal = $1, ${RULES} = $2::jsonb, rev = $3, gen = $4, updated_at = $5 WHERE policy_key = $6 AND rev = $7`), [principal ?? null, json, nextRev, nextGen, now, key, priorRev]);
|
|
166
|
+
if (upd.affected !== 1)
|
|
167
|
+
throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts?.expectedRev ?? priorRev}, have ${priorRev})`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
await conn.query(this.q(`INSERT INTO session_policy (policy_key, session_id, principal, ${RULES}, rev, gen, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)`, `INSERT INTO session_policy (policy_key, session_id, principal, ${RULES}, rev, gen, created_at, updated_at) VALUES ($1,$2,$3,$4::jsonb,$5,$6,$7,$8)`), [key, sessionId, principal ?? null, json, nextRev, nextGen, now, now]);
|
|
171
|
+
}
|
|
172
|
+
return structuredClone({ ...clean, rev: nextRev, gen: nextGen });
|
|
173
|
+
}
|
|
174
|
+
isDupKeyError(e) {
|
|
175
|
+
return this.isDupKey(e);
|
|
176
|
+
}
|
|
166
177
|
retrying(attempt) {
|
|
167
178
|
return retryOnDeadlock(this.db.dialect, attempt, (e) => this.isDupKey(e));
|
|
168
179
|
}
|
|
169
180
|
async deleteBySession(sessionId) {
|
|
170
|
-
const sealed = await this.retrying(() => this.tx((conn) =>
|
|
181
|
+
const sealed = await this.retrying(() => this.tx((conn) => this.eraseOn(conn, sessionId)));
|
|
182
|
+
this.discloseErased(sessionId, sealed);
|
|
183
|
+
}
|
|
184
|
+
eraseOn(conn, sessionId) {
|
|
185
|
+
return eraseSessionPolicyRowsOn(conn, this.db.dialect, [sessionId]);
|
|
186
|
+
}
|
|
187
|
+
discloseErased(sessionId, sealed) {
|
|
171
188
|
if (sealed.length > 0)
|
|
172
189
|
this.discloseSealed(sessionId, sealed);
|
|
173
190
|
}
|
|
@@ -206,13 +223,13 @@ export class SqlSessionPolicyStore {
|
|
|
206
223
|
}
|
|
207
224
|
}
|
|
208
225
|
export class TiDBSessionPolicyStore extends SqlSessionPolicyStore {
|
|
209
|
-
constructor(pool,
|
|
210
|
-
super(mysqlDriver(pool),
|
|
226
|
+
constructor(pool, root, logger) {
|
|
227
|
+
super(mysqlDriver(pool), root, logger);
|
|
211
228
|
}
|
|
212
229
|
}
|
|
213
230
|
export class PgSessionPolicyStore extends SqlSessionPolicyStore {
|
|
214
|
-
constructor(pool,
|
|
215
|
-
super(pgDriver(pool),
|
|
231
|
+
constructor(pool, root, logger) {
|
|
232
|
+
super(pgDriver(pool), root, logger);
|
|
216
233
|
}
|
|
217
234
|
}
|
|
218
235
|
//# sourceMappingURL=session-policy-store-sql.js.map
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* 不对别的表的行加锁,也不在 EvalPlanQual 里复判它们。所以是**两句**:店先 `FOR UPDATE` 锁根行(删除 / 重登记 / 改属主都要先拿这把锁),
|
|
14
14
|
* 再在**同一连接**上交出语句面,部署用新语句锁定读(PG 新快照、TiDB 当前读)。钩里的写随店的事务提交或回滚(店拒、或任何一步抛,一起撤)。
|
|
15
15
|
*
|
|
16
|
+
* ## 建行钩(服务扩展,包里没有)
|
|
17
|
+
* 带种类的登记(`registerWithReceipt` 的第三参;会话种类的律在 `session-kind.ts`):店在一只事务里 INSERT、锁定读回化身,读回的 = 本次铸的(这一笔真建了行)⇒
|
|
18
|
+
* {@link SessionWriteHooks.birth} 在同一事务里记下种类;别的(已在的行 / 并发登记里输的那一只)⇒ 零写。包随店搬家时这一组同是向引擎的请托。
|
|
19
|
+
*
|
|
16
20
|
* ## 键控 fork(服务扩展,包里没有)
|
|
17
21
|
* 调用方给目标 id 与键的 fork(`forkTo`)是本服务的扩展:键是部署格,在建目标行的同一事务里由 {@link SessionWriteHooks.keyedFork} 记下;撞上已在的目标时
|
|
18
22
|
* 读它的属主与键。包随店搬家时这一组是向引擎的请托(带目标 id 与键的 fork,或钩表多一组)。
|
|
@@ -61,5 +65,12 @@ export interface SessionWriteHooks {
|
|
|
61
65
|
}): Promise<void>;
|
|
62
66
|
targetOf(sessionId: string): Promise<ForkTargetReading | undefined>;
|
|
63
67
|
};
|
|
68
|
+
/** 建行钩(服务扩展,与键控 fork 同族):带种类的登记里,本事务的 INSERT 真建了这一行(锁定读回的化身 = 本次铸的)⇒ 在同一事务里记下它的种类(部署格的值,座原样转交、不解读;律在部署那一侧)。 */
|
|
69
|
+
readonly birth: {
|
|
70
|
+
record(txn: SessionTxStatements, born: {
|
|
71
|
+
readonly rootSessionId: string;
|
|
72
|
+
readonly kind: string;
|
|
73
|
+
}): Promise<void>;
|
|
74
|
+
};
|
|
64
75
|
}
|
|
65
76
|
//# sourceMappingURL=session-write-hooks.d.ts.map
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
import { type AcquiredSession, type PlacedSessionRow, type SessionHead, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry } from "@sema-agent/core";
|
|
14
14
|
import type { Pool } from "mysql2/promise";
|
|
15
15
|
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
16
|
-
import { type RegisterReceipt, type WithdrawJudgeVerdict, type WithdrawOutcome } from "../session-incarnation.js";
|
|
16
|
+
import { type RegisterReceipt, type SessionFace, type WithdrawJudgeVerdict, type WithdrawOutcome } from "../session-incarnation.js";
|
|
17
17
|
import type { SessionTxStatements, SessionWriteHooks } from "./session-write-hooks.js";
|
|
18
18
|
import { type PlacementAcquireOpts } from "../plugins/session-placement.js";
|
|
19
|
-
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
19
|
+
import { type EntriesStreamOptions, type StagedCommitOptions, type StagingHandle, type SyncRelation } from "../session-sync-kernel.js";
|
|
20
20
|
import { type ForkPlacement, type ForkTarget, type ForkToOutcome } from "../plugins/session-fork.js";
|
|
21
21
|
/**
|
|
22
22
|
* **条件撤回的店那一半**(两方言一形;TiDB 孪生与 PG 孪生 `pg-session-storage.ts` 同调;撤回的规则在 `plugins/session-birth.ts` 头注)。
|
|
@@ -75,7 +75,7 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
75
75
|
* 出生回执面(`plugins/session-birth.ts`):与 {@link register} 同一笔 INSERT;候选化身 = 这一次 INSERT 入参里铸的那一只(不复读)。
|
|
76
76
|
* 已在的行上 INSERT 是 no-op 臂 ⇒ 行上的化身不是候选 ⇒ 撤回在锁内比对时判「留」—— 出生事实只在那里判,这里不分新旧行。
|
|
77
77
|
*/
|
|
78
|
-
registerWithReceipt(sessionId: string, owner: string | null): Promise<RegisterReceipt>;
|
|
78
|
+
registerWithReceipt(sessionId: string, owner: string | null, kind?: string): Promise<RegisterReceipt>;
|
|
79
79
|
/** 条件删除(`plugins/session-birth.ts` 的撤回;本连接开事务 → 店与部署两半同一语句面 → 撤回才提交,否则回滚)。 */
|
|
80
80
|
deleteSessionIf(sessionId: string, cond: SessionDeleteCondition): Promise<WithdrawOutcome>;
|
|
81
81
|
/**
|
|
@@ -88,6 +88,8 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
88
88
|
* **永不被重写**(first-write 不可变,义务 5 的写侧半场)。
|
|
89
89
|
* [ref]:化身与属主同一条 first-write 律 —— 新行铸一只(`mintSessionIncarnation`),已存在的行 no-op 臂一个字节不动。 */
|
|
90
90
|
private insertMeta;
|
|
91
|
+
/** {@link insertMeta} 那一句,发在给定的连接上(带种类的登记在自己的事务连接上发,与锁定读、建行钩同一事务)。 */
|
|
92
|
+
private insertMetaOn;
|
|
91
93
|
/**
|
|
92
94
|
* [ref] 义务 6 的廉价探针 —— 一行 PK SELECT:`undefined` = 普通会话或无此行。
|
|
93
95
|
* (release 的冷缓存腿、包装店的暖命中准入判、诊断面共用这一只。)
|
|
@@ -194,10 +196,7 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
194
196
|
* (按文本投影 + `decodeSessionEventPayload` 恰好解一次码,坏体当场拒). The check is eager (the method is async) because a generator body is lazy — it can't decide
|
|
195
197
|
* `null` vs. iterable on its own first pull.
|
|
196
198
|
*/
|
|
197
|
-
exportEntriesStream(sessionId: string, opts
|
|
198
|
-
afterSeq?: number;
|
|
199
|
-
batchSize?: number;
|
|
200
|
-
}): Promise<AsyncIterable<SessionTreeEntry> | null>;
|
|
199
|
+
exportEntriesStream(sessionId: string, opts: EntriesStreamOptions): Promise<AsyncIterable<SessionTreeEntry> | null>;
|
|
201
200
|
/**
|
|
202
201
|
* 2c session-sync — IMPORT a verbatim entry log (from another backend's {@link exportEntries}) into a
|
|
203
202
|
* (typically NEW) `sessionId`. This is the WRITE half of {@link fork}, surfaced as `SessionRepo.importEntries`,
|
|
@@ -269,5 +268,57 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
269
268
|
get size(): number;
|
|
270
269
|
dispose(): void;
|
|
271
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* 2c session-sync P1d-β (PUSH streaming, §15 + §4) — a TiDB {@link StagingHandle}: stream entry batches into a SHADOW
|
|
273
|
+
* `session_event` partition (the staging id, NO meta row → invisible to wake), then atomically SWAP them into the real
|
|
274
|
+
* session id. Server memory is bounded by the in-flight batch (one txn per batch), not the whole log. The swap is a
|
|
275
|
+
* row-key RENAME (`UPDATE session_event SET session_id`) — metadata-only, NO payload re-copy — so the commit cost is
|
|
276
|
+
* the rename + a DELETE of the (purged) dst rows, in ONE txn; the session_meta row is locked (or, for a fresh push, claimed) first and
|
|
277
|
+
* settled last, and COMMIT is the sole commit point.
|
|
278
|
+
*/
|
|
279
|
+
/** 暂存句柄(会话同步的推送腿逐批写、提交、撤销;由本店的 `stageImport` 交出)。导出只为组合座给它封口(店边界)。 */
|
|
280
|
+
export declare class TiDBStagingHandle implements StagingHandle {
|
|
281
|
+
private readonly pool;
|
|
282
|
+
private readonly hooks;
|
|
283
|
+
readonly realSessionId: string;
|
|
284
|
+
readonly stagingId: string;
|
|
285
|
+
/** Running DENSE seq counter for the staging rows. Advanced ONLY for a logically-NEW entry id (tracked in `seen`), so
|
|
286
|
+
* a re-streamed entry (Phase-B resume) is skipped and the staged seqs stay dense 0..N-1 (NO holes that would collide
|
|
287
|
+
* with a future F2 leaf_seq append on the committed session). */
|
|
288
|
+
private nextSeq;
|
|
289
|
+
/** Entry ids already staged by THIS handle — a resume re-streams from the start, so dedup keeps seqs dense + idempotent. */
|
|
290
|
+
private readonly seen;
|
|
291
|
+
constructor(pool: Pool, hooks: SessionWriteHooks, realSessionId: string, stagingId: string);
|
|
292
|
+
/** Append a batch to the staging id, re-seq'd DENSE from the running counter (skipping ids already staged), IDEMPOTENTLY
|
|
293
|
+
* (ON DUPLICATE KEY UPDATE on the `(session_id, entry_id)` uk is the durable backstop too). One txn per batch
|
|
294
|
+
* (bounded). NO meta row is written (the staging id stays invisible). ts is the entry's own ISO timestamp (verbatim). */
|
|
295
|
+
appendBatch(entries: SessionTreeEntry[]): Promise<void>;
|
|
296
|
+
/**
|
|
297
|
+
* The ATOMIC swap (ONE txn), §15 commit:
|
|
298
|
+
* 0. OWNER CAS FIRST ([ref] — one gate predicate, two phases): the session's owner cell must still be the one the import's gate
|
|
299
|
+
* judged (`opts.expectedOccupant`: owner ∧ incarnation). Like every other writer of a session (append, replace, delete) the staged commit takes the
|
|
300
|
+
* session_meta row FIRST, then the logs — one lock order. A present cell: one conditioned UPDATE (current read; takes the row lock
|
|
301
|
+
* until COMMIT); it bumps `leaf_seq` only as the CAS's always-changing column (a same-value UPDATE can read 0 affected rows on the
|
|
302
|
+
* MySQL protocol depending on driver flags), and step 4 overwrites that column with the real value in the same txn. An absent cell
|
|
303
|
+
* (a fresh push) cannot be locked, so it is CLAIMED: insert this commit's row now (a no-op on an existing key), then a locking read
|
|
304
|
+
* must show the incarnation this commit minted — anything else is a row somebody else created. Either miss ⇒ ROLLBACK → throw
|
|
305
|
+
* StagedCommitOccupantChangedError (route → 409), before anything about the row is read or reported (no placement, no relation,
|
|
306
|
+
* no entry ids).
|
|
307
|
+
* 1. IN-COMMIT RE-CLASSIFY (closes the Phase-A plan→commit TOCTOU): read the dst's CURRENT entry ids + the staged
|
|
308
|
+
* ids under `FOR UPDATE` (row-locks serialize a concurrent commit/run) → classifySyncRelationshipByIds. A flip
|
|
309
|
+
* to fork/stale WITHOUT `resolution:"overwrite-dst"` → ROLLBACK → throw SyncConflictError (route → 409).
|
|
310
|
+
* 2. DELETE the dst's existing session_event rows (the replaceEntries purge).
|
|
311
|
+
* 3. UPDATE session_event SET session_id = real WHERE session_id = staging (the row-key RENAME — metadata only).
|
|
312
|
+
* 4. UPDATE session_meta(owner, leaf_id=replayedLeaf, leaf_seq=N) on the row step 0 locked or claimed; COMMIT is the sole commit point.
|
|
313
|
+
* A crash before COMMIT: the DELETE+rename roll back (dst untouched) and the staging rows keep no meta = invisible
|
|
314
|
+
* orphans (reaped by sweepStagingSessions). `owner` is the re-stamped importing principal (§9).
|
|
315
|
+
*/
|
|
316
|
+
commit(owner: string | null, opts: StagedCommitOptions): Promise<{
|
|
317
|
+
relation: SyncRelation["relation"];
|
|
318
|
+
face: SessionFace;
|
|
319
|
+
}>;
|
|
320
|
+
/** Drop the staging rows (Phase B aborted before commit). Idempotent (a missing staging id deletes 0). */
|
|
321
|
+
abort(): Promise<void>;
|
|
322
|
+
}
|
|
272
323
|
export {};
|
|
273
324
|
//# sourceMappingURL=tidb-session-store.d.ts.map
|
|
@@ -6,7 +6,7 @@ import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "../plugins/retention-dec
|
|
|
6
6
|
import { headFromRow, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
7
7
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
8
8
|
import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertCells, SESSION_DURABILITY, SESSION_PLACEMENTS, } from "../plugins/session-placement.js";
|
|
9
|
-
import { classifySyncRelationshipByIds, SyncConflictError,
|
|
9
|
+
import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOccupantChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
10
10
|
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue, placedSessionOwnerOf } from "../plugins/session-ownership.js";
|
|
11
11
|
import { forkRowCells, selectForkRows } from "@sema-agent/core";
|
|
12
12
|
import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf, sameSourceGeneration } from "../plugins/session-fork.js";
|
|
@@ -104,14 +104,33 @@ export class TiDBSessionStore {
|
|
|
104
104
|
async register(sessionId, owner) {
|
|
105
105
|
await this.registerWithReceipt(sessionId, owner);
|
|
106
106
|
}
|
|
107
|
-
async registerWithReceipt(sessionId, owner) {
|
|
107
|
+
async registerWithReceipt(sessionId, owner, kind) {
|
|
108
108
|
if (!isSessionOwnerValue(owner)) {
|
|
109
109
|
if ((await this.headOf(sessionId)) !== undefined)
|
|
110
110
|
return {};
|
|
111
111
|
assertFirstOwnerWritable(sessionId, owner);
|
|
112
112
|
}
|
|
113
113
|
const incarnation = mintSessionIncarnation();
|
|
114
|
-
|
|
114
|
+
if (kind === undefined) {
|
|
115
|
+
await this.insertMeta(sessionId, owner, undefined, incarnation);
|
|
116
|
+
return { minted: { sessionId, owner, incarnation } };
|
|
117
|
+
}
|
|
118
|
+
const conn = await this.pool.getConnection();
|
|
119
|
+
try {
|
|
120
|
+
await conn.beginTransaction();
|
|
121
|
+
await this.insertMetaOn(conn, sessionId, owner, undefined, incarnation);
|
|
122
|
+
const [rows] = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = ? FOR UPDATE", [sessionId]);
|
|
123
|
+
if (rows[0]?.incarnation === incarnation)
|
|
124
|
+
await this.hooks.birth.record(statementsOn(conn), { rootSessionId: sessionId, kind });
|
|
125
|
+
await conn.commit();
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
await conn.rollback().catch(() => undefined);
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
conn.release();
|
|
133
|
+
}
|
|
115
134
|
return { minted: { sessionId, owner, incarnation } };
|
|
116
135
|
}
|
|
117
136
|
async deleteSessionIf(sessionId, cond) {
|
|
@@ -137,9 +156,12 @@ export class TiDBSessionStore {
|
|
|
137
156
|
await this.insertMeta(sessionId, placement === undefined ? null : placedSessionOwnerOf(sessionId, placement), placement);
|
|
138
157
|
}
|
|
139
158
|
async insertMeta(sessionId, owner, placement, incarnation = mintSessionIncarnation()) {
|
|
159
|
+
await this.insertMetaOn(this.pool, sessionId, owner, placement, incarnation);
|
|
160
|
+
}
|
|
161
|
+
async insertMetaOn(on, sessionId, owner, placement, incarnation) {
|
|
140
162
|
const now = new Date();
|
|
141
163
|
const placed = placementInsertCells(placement, now);
|
|
142
|
-
await
|
|
164
|
+
await on.query(`INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, ${placed.columns}) ` +
|
|
143
165
|
"VALUES (?,?,?,?,NULL,0,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE leaf_seq = leaf_seq", [sessionId, owner, now, now, incarnation, ...placed.values]);
|
|
144
166
|
}
|
|
145
167
|
async placementOf(sessionId) {
|
|
@@ -202,7 +224,7 @@ export class TiDBSessionStore {
|
|
|
202
224
|
}
|
|
203
225
|
throw err instanceof SessionError
|
|
204
226
|
? err
|
|
205
|
-
: new SessionError("storage", `placed session ${sessionId} could not be deleted
|
|
227
|
+
: new SessionError("storage", `placed session ${sessionId} could not be deleted`, err instanceof Error ? err : undefined);
|
|
206
228
|
}
|
|
207
229
|
finally {
|
|
208
230
|
conn.release();
|
|
@@ -298,11 +320,13 @@ export class TiDBSessionStore {
|
|
|
298
320
|
if (!metaRows[0])
|
|
299
321
|
return null;
|
|
300
322
|
const pool = this.pool;
|
|
301
|
-
const batchSize = opts
|
|
323
|
+
const batchSize = opts.batchSize ?? 500;
|
|
302
324
|
async function* gen() {
|
|
303
|
-
let cursor = opts
|
|
325
|
+
let cursor = opts.afterSeq ?? -1;
|
|
304
326
|
for (;;) {
|
|
305
327
|
const [rows] = await pool.query(`SELECT seq, ${jsonColumnText("mysql", "payload")} FROM session_event WHERE session_id = ? AND seq > ? ORDER BY seq ASC LIMIT ?`, [sessionId, cursor, batchSize]);
|
|
328
|
+
if (rows.length > 0)
|
|
329
|
+
await opts.recheck();
|
|
306
330
|
for (const r of rows)
|
|
307
331
|
yield decodeSessionEventPayload(r.payload, sessionId, r.seq);
|
|
308
332
|
if (rows.length < batchSize)
|
|
@@ -426,7 +450,7 @@ export class TiDBSessionStore {
|
|
|
426
450
|
this.pending.clear();
|
|
427
451
|
}
|
|
428
452
|
}
|
|
429
|
-
class TiDBStagingHandle {
|
|
453
|
+
export class TiDBStagingHandle {
|
|
430
454
|
pool;
|
|
431
455
|
hooks;
|
|
432
456
|
realSessionId;
|
|
@@ -466,11 +490,11 @@ class TiDBStagingHandle {
|
|
|
466
490
|
await conn.beginTransaction();
|
|
467
491
|
const now = new Date();
|
|
468
492
|
const minted = mintSessionIncarnation();
|
|
469
|
-
if (opts.
|
|
470
|
-
const [cas] = await conn.query("UPDATE session_meta SET leaf_seq = leaf_seq + 1 WHERE session_id = ? AND owner <=> ?", [this.realSessionId, opts.
|
|
493
|
+
if (opts.expectedOccupant !== undefined) {
|
|
494
|
+
const [cas] = await conn.query("UPDATE session_meta SET leaf_seq = leaf_seq + 1 WHERE session_id = ? AND owner <=> ? AND incarnation = ?", [this.realSessionId, opts.expectedOccupant.owner, opts.expectedOccupant.incarnation]);
|
|
471
495
|
if (cas.affectedRows !== 1) {
|
|
472
496
|
await conn.rollback();
|
|
473
|
-
throw new
|
|
497
|
+
throw new StagedCommitOccupantChangedError(this.realSessionId);
|
|
474
498
|
}
|
|
475
499
|
}
|
|
476
500
|
else {
|
|
@@ -478,7 +502,7 @@ class TiDBStagingHandle {
|
|
|
478
502
|
const [claimed] = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = ? FOR UPDATE", [this.realSessionId]);
|
|
479
503
|
if (String(claimed[0]?.incarnation) !== minted) {
|
|
480
504
|
await conn.rollback();
|
|
481
|
-
throw new
|
|
505
|
+
throw new StagedCommitOccupantChangedError(this.realSessionId);
|
|
482
506
|
}
|
|
483
507
|
}
|
|
484
508
|
settleHookVerdict(await this.hooks.beforeCommit(statementsOn(conn), { op: "import", rootSessionId: this.realSessionId }));
|
|
@@ -490,7 +514,7 @@ class TiDBStagingHandle {
|
|
|
490
514
|
throw new SessionError("placement_refused", `session ${this.realSessionId} belongs to the ${String(dstMeta[0].placement_kind)} placement partition — a session-sync staged commit cannot overwrite a delegation transcript`);
|
|
491
515
|
}
|
|
492
516
|
const srcIds = stagedRows.map((r) => String(r.entry_id));
|
|
493
|
-
const dstIds = opts.
|
|
517
|
+
const dstIds = opts.expectedOccupant !== undefined ? dstRows.map((r) => String(r.entry_id)) : null;
|
|
494
518
|
const rel = classifySyncRelationshipByIds(srcIds, dstIds);
|
|
495
519
|
if ((rel.relation === "fork" || rel.relation === "stale") && opts.resolution !== "overwrite-dst") {
|
|
496
520
|
await conn.rollback();
|
|
@@ -2,6 +2,7 @@ import type { DeploymentShapeInput } from "./deployment-shape.js";
|
|
|
2
2
|
import type { RunAcceptance } from "./run-acceptance.js";
|
|
3
3
|
import type { SessionWithdrawFace } from "./plugins/session-birth.js";
|
|
4
4
|
import type { SessionBirth, StoreKeptWord } from "./session-incarnation.js";
|
|
5
|
+
import type { BindingReceiptSink } from "./session-bindings.js";
|
|
5
6
|
/** 收场词(闭集;加词 ⇒ {@link SubmissionSeat.close} 的穷尽 switch 编译红)。 */
|
|
6
7
|
export type SubmissionEnd = "accepted" | "record" | "refused";
|
|
7
8
|
/** 「留」的词:店内七词 + 座内四词(`withdraw_failed` = 撤回事务抛了,按留答)。 */
|
|
@@ -25,7 +26,7 @@ export interface SubmissionSeatDeps {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
/** 一次提交的出口座(提交门在授权之前铸;三条提交腿只喂它、收它)。 */
|
|
28
|
-
export interface SubmissionSeat {
|
|
29
|
+
export interface SubmissionSeat extends BindingReceiptSink {
|
|
29
30
|
/** 授权器登记新行时交来的候选与店的撤回面(只有新行臂调;调多次以第一次为准)。 */
|
|
30
31
|
noteBirth(birth: SessionBirth, face: SessionWithdrawFace): void;
|
|
31
32
|
/** 提交门铸好 spec 之后挂上受理座(闩的唯一读者在 {@link close})。 */
|
|
@@ -36,7 +37,7 @@ export interface SubmissionSeat {
|
|
|
36
37
|
markRecord(): void;
|
|
37
38
|
/** 执行环境为这次提交留下了按会话键的持久物。 */
|
|
38
39
|
noteEnvWitness(): void;
|
|
39
|
-
/**
|
|
40
|
+
/** 此刻判不判得出「什么都不撤」而不必收场(闩上 / record / 没有候选也没有回执 / 有见证也没有回执)。 */
|
|
40
41
|
readonly keepsWithoutWithdraw: boolean;
|
|
41
42
|
/** 收场(幂等、先到者定;不抛)。 */
|
|
42
43
|
close(): Promise<SubmissionVerdict>;
|
package/dist/submission-exit.js
CHANGED
|
@@ -4,6 +4,7 @@ import { retireSessionId } from "./deleted-session-ids.js";
|
|
|
4
4
|
import { seatText } from "./trace/exit-seat.js";
|
|
5
5
|
export function createSubmissionSeat(deps) {
|
|
6
6
|
let birth;
|
|
7
|
+
let bindings;
|
|
7
8
|
let acceptance;
|
|
8
9
|
let ownTaskId;
|
|
9
10
|
let recorded = false;
|
|
@@ -16,12 +17,15 @@ export function createSubmissionSeat(deps) {
|
|
|
16
17
|
let verdict;
|
|
17
18
|
switch (end) {
|
|
18
19
|
case "accepted":
|
|
20
|
+
bindings?.face.settleSessionBindings(bindings.receipt);
|
|
19
21
|
verdict = "kept_accepted";
|
|
20
22
|
break;
|
|
21
23
|
case "record":
|
|
24
|
+
bindings?.face.settleSessionBindings(bindings.receipt);
|
|
22
25
|
verdict = "kept_record";
|
|
23
26
|
break;
|
|
24
27
|
case "refused":
|
|
28
|
+
bindings?.face.withdrawSessionBindings(bindings.receipt);
|
|
25
29
|
verdict = await withdrawOrKeep();
|
|
26
30
|
break;
|
|
27
31
|
default: {
|
|
@@ -60,6 +64,10 @@ export function createSubmissionSeat(deps) {
|
|
|
60
64
|
if (birth === undefined)
|
|
61
65
|
birth = { birth: b, face };
|
|
62
66
|
},
|
|
67
|
+
noteBindings(receipt, face) {
|
|
68
|
+
if (bindings === undefined)
|
|
69
|
+
bindings = { receipt, face };
|
|
70
|
+
},
|
|
63
71
|
attachAcceptance(a) {
|
|
64
72
|
acceptance = a;
|
|
65
73
|
},
|
|
@@ -77,7 +85,7 @@ export function createSubmissionSeat(deps) {
|
|
|
77
85
|
witnessed = true;
|
|
78
86
|
},
|
|
79
87
|
get keepsWithoutWithdraw() {
|
|
80
|
-
return birth === undefined || witnessed
|
|
88
|
+
return endNow() !== "refused" || (bindings === undefined && (birth === undefined || witnessed));
|
|
81
89
|
},
|
|
82
90
|
close() {
|
|
83
91
|
if (closing === undefined) {
|
package/dist/task-cwd.d.ts
CHANGED
|
@@ -115,8 +115,8 @@ export declare function effectiveHostWorkspace(sessionCwd: string | undefined, c
|
|
|
115
115
|
* 为什么组合腿要它:梯子的每一级是在引擎**现铸的会话**里冷跑(core `runCascade` 剥掉 spec 的 `sessionId`),验证门的验证器腿的 spec 不带会话 ——
|
|
116
116
|
* 工厂按腿的会话查登记表(第 1 臂)查不到提交登记在根会话上的那一枚,不交就落第 3 臂(临时目录):用户让梯子在项目里改文件,改到了别处;验证器验的是一只空目录。
|
|
117
117
|
* 跑在根会话上的腿(验证门的实现腿 / 修复腿)第 1 臂先命中,同一个值。
|
|
118
|
-
* 没登记(
|
|
119
|
-
* 登记粘在会话上(`
|
|
118
|
+
* 没登记(这条腿的会话绑定没有工作目录:非 host / device 车道、多租户、根会话的提交从没带过 `cwd`、嵌入方装配器没铸事实)⇒ 空对象 —— 每一条腿照旧落临时目录、收场由托管表删。
|
|
119
|
+
* 登记粘在会话上(会话绑定的 `cwd` 一格 = 这条腿跑在的那一枚 —— 请求带的 ?? 项目继承来的 ?? 会话上已登记的 —— 受理之后写回会话,`session-bindings.ts`):同一会话之前带过、这次不带 ⇒ 交的仍是那一枚 —— 与第 1 臂对同一会话上普通 run 的读法一致。
|
|
120
120
|
*/
|
|
121
121
|
export declare function compositionLegRoot(registeredCwd: string | undefined): Pick<RunInternals, "parentCwd">;
|
|
122
122
|
/** {@link launchWorkspaceOf} 读到的那一格出自哪里(闭集;起服那一行日志与随包文档的 `workspaceFrom` 词)。 */
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -122,11 +122,12 @@ export declare function planReviewDecisionExitsPlan(decision: "approve" | "edit"
|
|
|
122
122
|
* 新体 `{ ...body, permissionMode: entry.effective }`,调用方的对象不改;bundle 的 `settings.permissions.defaultMode` 不动 —— 顶层赢过它
|
|
123
123
|
* ({@link effectivePermissionMode} 的优先序),再改它就是同一语义的第二个写者。披露这一折的通告(`entry-mode-clamp-notice.ts`)读的也是同一份 `entry`。
|
|
124
124
|
*
|
|
125
|
-
*
|
|
126
|
-
* `buildCheckpointCtx` 持久化的体
|
|
127
|
-
*
|
|
128
|
-
* (`http/
|
|
129
|
-
*
|
|
125
|
+
* 调用点两个,都是受理判官的入口一步(`permission-mode-admission.ts`,同一只 `bodyEntry`):提交口 `stampSubmissionEntryMode`(`http/admission.ts` 的 `prepareSpec` 调它:
|
|
126
|
+
* 值形门全部判完之后、`resolveSpec` 之前 —— 那一份体就是首跑装配读的体,也是 `buildCheckpointCtx` 持久化的体)与续跑口 `stampReplayEntryMode`(`http/resume-legs.ts`
|
|
127
|
+
* 的 `replayCtxOf`:停驻 run 的存储体被重放之前过一次**现**地板,地板的出口词原样;钳过的体随 CAS 赢点写回)。装配腿不读地板;每一腿读一次地板、写进体,
|
|
128
|
+
* run 的模式词仍只有存储体一个源。另外两个写者是 `plan_review` 写回(`http/resume-legs.ts` `drivePlanReview`,判据 {@link planReviewDecisionExitsPlan};
|
|
129
|
+
* 写下的是 plan 地板的出口词)与在飞 run 的切档口写回(`http/routes/run-permission-mode.ts`,写的是同一只判官准入的词)。
|
|
130
|
+
* 热改 `AUTONOMY` 对之后的提交、切档与停驻 run 的下一次续跑都生效。地板的属主是 `runtime-governance.ts` 的 `autonomyModeFloor`。
|
|
130
131
|
*/
|
|
131
132
|
export declare function stampEntryPermissionMode<B extends {
|
|
132
133
|
permissionMode?: unknown;
|
package/dist/terminal.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { terminalProjection, type PausedCause, type TaskStatus, type TerminalCause } from "@sema-agent/core";
|
|
2
|
+
import { StoreBoundaryFault, type StoreFaultClass } from "./plugins/store-fault.js";
|
|
2
3
|
/**
|
|
3
4
|
* 这次失败带的**机器码**(`terminal.failed.code`);非失败终局 ⇒ `undefined`。
|
|
4
5
|
*
|
|
@@ -129,9 +130,12 @@ export declare function mapPauseSeats<T>(result: T, f: (seat: SeatObject, keys:
|
|
|
129
130
|
*
|
|
130
131
|
* ── 折码的运维线索:一句定形字段,两只入口各说一只词 ──
|
|
131
132
|
* 字段 `original=<码 | <not-a-code>> outward=<对外词> messageBytes=<n> at=<观看点>`,只有闭集词、数字与码({@link isCodeToken});码形座里装着文字的、长得像码的秘密同理只报 `<not-a-code>`。
|
|
132
|
-
*
|
|
133
|
-
*
|
|
133
|
+
* 失败句**不上 wire**({@link outwardStoreFailure});店起源的失败到这里时句子已是店边界换过的对外句({@link storeBoundaryFault}:库驱动原句 —— 自由文本,可以有连接串 / 口令 / 库账号 / 表名 ——
|
|
134
|
+
* 只进店边界那一行 `at=store`),写入口那一行的末尾格 `message=<JSON 串>` 对它们带的就是对外句;不经店边界的抛出物(fs / 网络 / 第三方码)带的仍是它自己的句子 ——
|
|
135
|
+
* 都经出口座的一句话行(`operator-log-text`:凭据形换记号、剥控制字符、有界),读座的再观看不带(同一行存量行每读一次就重放一次)。`at` 说这是哪一次观看,**一个入口一只词**(类型给定,不靠记得传):
|
|
134
136
|
* · 本函数 = 结果**第一次**离开引擎(店的写口 / 出口铸体)⇒ `at=exit`,每折一次一行 `[terminal] outward_failure_code_folded <字段>[ message=<句>]` —— 每次都是一件新的失败;
|
|
137
|
+
* · {@link storeBoundaryFault} = 失败**离开本服务的店**(SQL 店的出口)⇒ `at=store`,每次一行,多两格 `site=<店>.<方法> class=<判官词>`,`message=` 是原错与其 `cause` 链各层的句子(逐层过座);
|
|
138
|
+
* 查库的原话读这一行,按 `original=` 与时间对 `at=exit` / `request_error` 行;
|
|
135
139
|
* · {@link sealStoredResult} = 存量行的**再观看**(读座必填)⇒ 走 fail-open 纪律(登记 tag `server.terminal.stored-result-code-folded`):
|
|
136
140
|
* 逐读计数 + 逐读探针行 + 每进程一次 warn,detail 即上面那句字段。读面每次轮询 / 每个观看者 / 每次重连都重放同一批行 ——
|
|
137
141
|
* 计数让「旧失败被读了多少次」看得见,一次 warn 让日志不随读次放大;去重归记录器一只,不另造表。
|
|
@@ -148,12 +152,39 @@ export declare function sealResultForExit<T extends {
|
|
|
148
152
|
export declare function sealStoredResult<T extends {
|
|
149
153
|
readonly terminal?: unknown;
|
|
150
154
|
}>(result: T, seat: StoredResultReadSeat): T;
|
|
155
|
+
/**
|
|
156
|
+
* 一个折掉的码在出口上的对外句(纯函数、无副作用)—— 出口四入口的**唯一**读法:{@link outwardStoreFailure}(封口 / 抛出臂,带运维线索)、{@link outwardPlane}
|
|
157
|
+
* (投影出口,纯读、不出线索)与 {@link persistedPlaneOf} 的旧形臂。句子已是「某个类别句 + 本码尾注」⇒ 原样(店边界换过的句子,词在原错上判,见 {@link storeBoundaryFault});
|
|
158
|
+
* 否则 = 判官读 `{ code }` 的类别句 + 尾注(拼法 `outwardStoreSentence`,一只拼法两种输入)。任何一张脸上折过码的 `errorMessage` 都不可能是驱动的原句,不论这条因由封过口没有、存的是哪一代形。
|
|
159
|
+
* 店边界之前落盘的存量子代句的一次性改写(M53,`store-fault-sentence-m53.ts`)读同一只。
|
|
160
|
+
*/
|
|
161
|
+
export declare function outwardStoreMessageOf(code: string, message: unknown): string;
|
|
151
162
|
/**
|
|
152
163
|
* 腿**抛出**的错落进 run 行 `error` 列时的对外句(没有终局结果、没有因由的那一臂:`runs.ts` 后台腿的兜底腿与它的孪生 `resume-legs.ts` 兜底腿)—— 与终局因由是**同一只**
|
|
153
164
|
* 铸点({@link outwardStoreFailure}):抛出物自带的 `code`({@link errorCodeOf},字符串)出了本仓词法 ⇒ 同码同句;没有码 / 码在词法内(本仓与引擎自己的错、普通 `Error`)⇒ 文本逐字,非库错误照旧。
|
|
154
165
|
* `message` 是调用方已取出的抛出文本({@link errorText} 的产物,不抛);本函数不抛(读码口不抛)。
|
|
155
166
|
*/
|
|
156
167
|
export declare function outwardThrownMessage(err: unknown, message: string): string;
|
|
168
|
+
/**
|
|
169
|
+
* 🔴 **店边界 —— 驱动原句越出 SQL 店的唯一读者**。律:一只 SQL 店的任何出口(抛出值、包装错误的 `cause`、抛出或返回值里的失败文本、交给调用方的可调用对象)
|
|
170
|
+
* 只带「原码 + 稳定句」({@link StoreBoundaryFault});驱动的原句在这里记一次运维日志,不随错误走。店的出口在 `plugins/store-boundary.ts` 封口(逐生产者、编译期穷尽的出口表),
|
|
171
|
+
* 店里自己拼句 / 拼值 / 包 `cause` 的地方先读本函数、再用它交回的形。交回:
|
|
172
|
+
* · 已是边界形 ⇒ 同一引用(嵌套店:最里层出口换过、记过,外层同一引用放行、不再记);
|
|
173
|
+
* · typed 失败(顶层自带词法内的码:`SessionError("storage")`、`store.row_too_large` …)⇒ 同一引用,它的类、码与句子原样;它 `cause` 链上的驱动错**就地**换成边界形
|
|
174
|
+
* (typed 层往下走,第一只不是 typed 而带驱动证据的那一段整只换;那一段的原句照样记一行 `at=store`);
|
|
175
|
+
* · 调用方出身(`error-origin.ts` 的 `caller`:店把调用方钩子的抛出物原样还给调用方)⇒ 同一引用、不记 —— 它不是店的失败;
|
|
176
|
+
* · 不触发扳机 ⇒ 同一引用(不带码 / 码在词法内、不带驱动出身记号的错照旧);
|
|
177
|
+
* · 触发 ⇒ 一行运维日志(`at=store site=<店>.<方法> class=<判官词>`,`message=` 是原错与其 `cause` 链各层的句子,逐层过出口座)+ 计数,交回新铸的边界形。
|
|
178
|
+
* 扳机两条(任一):① **码出词法**:码在场且 {@link outwardFailureCode} 折它 —— fail-closed,词法外的码一律换句(驱动、套接字、对象存储、fs 都算);
|
|
179
|
+
* ② **驱动出身**:原错或其 `cause` 链上任一层带池座打的记号(`plugins/error-origin.ts` 的 `driver`)—— 补词法看不见的无码驱动错。
|
|
180
|
+
* 链读法一律深 ≤ {@link CAUSE_CHAIN_DEPTH};链比这更深 ⇒ 当作带驱动证据(fail-closed:看不到底的链不放行)。
|
|
181
|
+
* 码的读法({@link driverCodeOf}):沿 `cause` 链(深 ≤ 8)取第一个非空串 `code`,`"unknown"` 只作兜底(与 core 的码链读法同读);一条串码都没有、而某一层带整数 `errno`
|
|
182
|
+
* ⇒ 码 = 那个 `errno` 的十进制串 —— 这是本仓的规则(引擎不读 errno):只带 errno 的驱动错(TiDB 自有号,mysql2 的码表里没有符号)于是进同一只扳机、说同一句,出口照折。
|
|
183
|
+
* 判官(`storeFaultClassOf`)在**原错**上判一次(连接类整句、errno 臂、1005 内层号都只在原错上读得到),词随形带走。不抛。
|
|
184
|
+
* `site` = 封口调用点的字面店词 + 方法名(源码里的标识符,不是外部输入)。
|
|
185
|
+
*/
|
|
186
|
+
export declare function storeBoundaryFault<E>(err: E, site: string): E | StoreBoundaryFault;
|
|
187
|
+
export declare function setStoreBoundaryObserver(fn: ((site: string, faultClass: StoreFaultClass) => void) | undefined): void;
|
|
157
188
|
/**
|
|
158
189
|
* **投影出口的唯一属主**([ref])—— core 的 `terminalProjection` + 对外码归一,一次。
|
|
159
190
|
*
|