@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
3
3
|
export const ADOPTION_LOG_TABLE = "adoption_log";
|
|
4
|
-
/** 弧的阶段(183 §3.2 根级状态机的 form b 投影)。**单调**,只增不减。 */
|
|
5
4
|
export const ADOPTION_PHASE = {
|
|
6
|
-
/** ② 意图已落库(行在,尚未动任何数据行)。 */
|
|
7
5
|
INTENT: 2,
|
|
8
|
-
/** ③ 身份轴重绑腿全部完成(与腿的 UPDATE **同一个事务**)。 */
|
|
9
6
|
REBOUND: 3,
|
|
10
|
-
/** ④ 解析切换 / 配置清单产出。form b 的 server 半场:配置账已物化进 `configs`。 */
|
|
11
7
|
CONFIGS: 4,
|
|
12
|
-
/** ⑤ 承运腿。**form b 恒零承运**(数据本来就在 SQL)—— 阶段位仍显式推进,好让 form a 上车时挂得上。 */
|
|
13
8
|
CARRIED: 5,
|
|
14
|
-
/** ⑥ 永久终态。 */
|
|
15
9
|
TERMINAL: 6,
|
|
16
10
|
};
|
|
17
|
-
/** MySQL 协议方言的建表语句(展开进 tidb-pool 的中央 `SCHEMA_STATEMENTS`,与 approval-ask 同姿势:
|
|
18
|
-
* 跟着中央 ensureSchema 在 named-lock 的那条 conn 上建,不绕开 DDL 串行化)。 */
|
|
19
11
|
export const TIDB_ADOPTION_LOG_STATEMENTS = [
|
|
20
12
|
`CREATE TABLE IF NOT EXISTS ${ADOPTION_LOG_TABLE} (
|
|
21
13
|
adoption_id VARCHAR(64) NOT NULL,
|
|
@@ -38,7 +30,6 @@ export const TIDB_ADOPTION_LOG_STATEMENTS = [
|
|
|
38
30
|
KEY idx_adoption_log_state (state)
|
|
39
31
|
) COLLATE utf8mb4_bin`,
|
|
40
32
|
];
|
|
41
|
-
/** PostgreSQL 孪生(由 `ensurePgSchema` 在 advisory lock 的那条 client 上调用)。 */
|
|
42
33
|
export async function ensurePgAdoptionLogSchema(q) {
|
|
43
34
|
await q(`CREATE TABLE IF NOT EXISTS ${ADOPTION_LOG_TABLE} (
|
|
44
35
|
adoption_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -76,54 +67,16 @@ function toRow(r) {
|
|
|
76
67
|
updatedAtMs: Number(r.updated_at_ms),
|
|
77
68
|
};
|
|
78
69
|
}
|
|
79
|
-
/** PG advisory lock 的 32 位对象键(类键固定,对象键 = 名字的 sha256 前 4 字节转有符号 int4)。 */
|
|
80
70
|
const PG_ADOPTION_LOCK_CLASS = 4907;
|
|
81
71
|
function pgLockObjectKey(name) {
|
|
82
72
|
return createHash("sha256").update(name).digest().readInt32BE(0);
|
|
83
73
|
}
|
|
84
|
-
/**
|
|
85
|
-
* 弧锁的名字。**定长**(前缀 14 + 32 位十六进制 = 46 字符)。
|
|
86
|
-
*
|
|
87
|
-
* 🔴 为什么必须 hash 而不是把 principal 直接拼进去(codex R3-F3,亲核属实):TiDB/MySQL 的 `GET_LOCK`
|
|
88
|
-
* 锁名上限是 **64 字符**,而请求面收的 principal 到 190 —— 超过 50 字符的源身份会让取锁**在弧动起来
|
|
89
|
-
* 之前**就失败,而那时意图行已经落库,于是每一次重发、每一次副本 boot 续跑都确定性地再撞一次同一堵墙。
|
|
90
|
-
* (PG 那条腿本来就走 hash 出来的 int4 对象键,这里只是把 MySQL 腿补齐到同一姿势。)
|
|
91
|
-
*/
|
|
92
74
|
export function adoptionLockName(fromPrincipal) {
|
|
93
75
|
return `sema_adoption:${createHash("sha256").update(fromPrincipal).digest("hex").slice(0, 32)}`;
|
|
94
76
|
}
|
|
95
|
-
/**
|
|
96
|
-
* 把弧锁名限定到**一个库**的键空间(A-010.13,验真后修)。
|
|
97
|
-
*
|
|
98
|
-
* 🔴 病:两条腿的锁键空间**不等价**。MySQL/TiDB 的 `GET_LOCK` 名字是**服务器实例全局**的
|
|
99
|
-
* (`performance_schema.metadata_locks` 里就是一个进程级命名空间,与你连的是哪个 schema 无关);
|
|
100
|
-
* PG 的 advisory lock 是**每数据库**的(`pg_locks` 的 database 列参与身份)。而锁名此前只 hash 了
|
|
101
|
-
* `fromPrincipal`、前缀写死 `sema_adoption:` —— 于是同一台 MySQL 上并存的两套部署
|
|
102
|
-
* (`aiagent_prod` / `aiagent_staging`,同一个源身份)会**互相抢同一把锁**:一边正在跑弧,另一边
|
|
103
|
-
* 取不到锁、按「在飞」回一份 `stalled` 回执 —— 一个与它自己的库状态完全对不上的答复。切到 PG 同样
|
|
104
|
-
* 两套部署却各跑各的。同一份代码、同一份配置,两个后端上语义不同 = 方言不等价。
|
|
105
|
-
*
|
|
106
|
-
* 🔴 姿势:限定符 = **当前数据库名**(MySQL `DATABASE()` / PG `current_database()`),与 principal
|
|
107
|
-
* 一起进 hash 原像。于是 MySQL 腿被收窄到与 PG 腿同一个键空间语义(每库一把),而不是把 PG 放宽到
|
|
108
|
-
* 实例全局(放宽的那个方向会让**同库**的两副本以为各自持锁,那是真丢互斥,方向错得多)。
|
|
109
|
-
*
|
|
110
|
-
* 🔴 为什么仍然 hash 而不是 `${db}/${name}` 直接拼:`GET_LOCK` 的名字上限是 **64 字符**,而库名没有
|
|
111
|
-
* 本仓能保证的上限。拼接形会让一个长库名的部署在**取锁那一步**才失败,而那时意图行已经落库 ——
|
|
112
|
-
* 每一次重发、每一次 boot 续跑都确定性地再撞一次同一堵墙(与 `adoptionLockName` 自己头注里那条
|
|
113
|
-
* codex R3-F3 是同一个病)。hash 后定长 46 字符,与库名长度无关。
|
|
114
|
-
* 分隔符取 **NUL**(源码里写成转义 `\u0000`,不落裸控制字节 —— 那会让 grep 把整个文件当二进制):
|
|
115
|
-
* 库名可以含 `/`、空格这类字符(MySQL 反引号标识符 / PG 双引号标识符都允许),拿它们当分隔符时
|
|
116
|
-
* `(db="a", name="b/c")` 与 `(db="a/b", name="c")` 会铸出**同一个原像** —— 弱分隔符拼接的经典撞形。
|
|
117
|
-
* NUL 在两种引擎的标识符里都不合法,所以它是真的不可能出现在任何一段里。
|
|
118
|
-
*/
|
|
119
77
|
export function qualifyAdoptionLockName(database, name) {
|
|
120
78
|
return `sema_adoption:${createHash("sha256").update(`${database}\u0000${name}`, "utf8").digest("hex").slice(0, 32)}`;
|
|
121
79
|
}
|
|
122
|
-
/**
|
|
123
|
-
* 收编日志店(单文件双方言,SqlDriver 形——checkpoint-store / approval-ask-store 同款)。
|
|
124
|
-
*
|
|
125
|
-
* 每条语句的两方言文本**并排写在调用点**(A12 判据:方言差异必须显式,不许用占位符编号循环拼)。
|
|
126
|
-
*/
|
|
127
80
|
export class SqlAdoptionLogStore {
|
|
128
81
|
db;
|
|
129
82
|
constructor(db) {
|
|
@@ -132,15 +85,9 @@ export class SqlAdoptionLogStore {
|
|
|
132
85
|
q(tidb, pg) {
|
|
133
86
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
134
87
|
}
|
|
135
|
-
/** 池级只读执行面(phase 0 的 preflight 扫描用——它**零写**,不需要也不该占一条事务连接)。
|
|
136
|
-
* 写路径一律走 {@link rebindTransaction};把驱动整个暴露出去会让调用方能绕开 phase CAS。 */
|
|
137
88
|
get reader() {
|
|
138
89
|
return this.db;
|
|
139
90
|
}
|
|
140
|
-
/**
|
|
141
|
-
* 发起腿的 **insert-or-fetch 原子形**。返回读回的行(可能是别人插的)+ 是否本次插入。
|
|
142
|
-
* 调用方据 `row.toPrincipal !== toPrincipal` 判「同 from 异 to」⇒ typed 409。
|
|
143
|
-
*/
|
|
144
91
|
async ensureAdoption(row) {
|
|
145
92
|
const emptyLegs = "[]";
|
|
146
93
|
const res = await this.db.query(this.q(`INSERT IGNORE INTO ${ADOPTION_LOG_TABLE} (${COLS}) VALUES (?, ?, ?, ?, 'in_flight', ?, ?, NULL, NULL, NULL, ?, ?)`, `INSERT INTO ${ADOPTION_LOG_TABLE} (${COLS}) VALUES ($1, $2, $3, $4, 'in_flight', $5, $6, NULL, NULL, NULL, $7, $8) ON CONFLICT (from_principal) DO NOTHING`), [row.adoptionId, row.fromPrincipal, row.toPrincipal, ADOPTION_PHASE.INTENT, emptyLegs, emptyLegs, row.nowMs, row.nowMs]);
|
|
@@ -158,29 +105,15 @@ export class SqlAdoptionLogStore {
|
|
|
158
105
|
const res = await exec.query(this.q(`SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE from_principal = ?`, `SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE from_principal = $1`), [fromPrincipal]);
|
|
159
106
|
return res.rows[0] ? toRow(res.rows[0]) : null;
|
|
160
107
|
}
|
|
161
|
-
/** I6 server 同族:每副本 boot 必查的在飞名单(**响亮**,不静默跳过)。 */
|
|
162
108
|
async listInFlight(exec = this.db) {
|
|
163
109
|
const res = await exec.query(this.q(`SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE state = 'in_flight' ORDER BY created_at_ms ASC`, `SELECT ${COLS} FROM ${ADOPTION_LOG_TABLE} WHERE state = 'in_flight' ORDER BY created_at_ms ASC`), []);
|
|
164
110
|
return res.rows.map(toRow);
|
|
165
111
|
}
|
|
166
|
-
/**
|
|
167
|
-
* 迁移事务:腿的全部 UPDATE **与** phase CAS 落在**同一个事务**里。
|
|
168
|
-
*
|
|
169
|
-
* 🔴 为什么必须同事务(而不是「先迁完再推 phase」):两句分开就留下一个窗 —— 腿提交了、phase 没推,
|
|
170
|
-
* 崩溃后接棒副本重跑腿(幂等,零行变化)却把**首次的行数读数**丢了,回执里的 `legs` 会变成一串 0。
|
|
171
|
-
* 同事务之后这个窗根本不存在:要么「行迁了且 phase=3 且 legs 读数在」,要么什么都没发生。
|
|
172
|
-
* 任何异常(含唯一键违例 = 跨轴组合冲突)⇒ ROLLBACK ⇒ **源/目的地两侧字节零变更**。
|
|
173
|
-
*/
|
|
174
112
|
async rebindOn(conn, adoptionId, expectPhase, nextPhase, nowMs, work, encodeLegs) {
|
|
175
113
|
try {
|
|
176
114
|
await conn.begin();
|
|
177
115
|
const legs = await work(conn);
|
|
178
|
-
const cas = await conn.query(
|
|
179
|
-
// 🔴 谓词里必须带 `state = 'in_flight'`(codex R2-F1..F3 的 F3):被拒的行**故意**停在 phase INTENT
|
|
180
|
-
// (「两侧字节零变更」要求它一步都别走),所以只判 phase 的 CAS 对一条已 rejected 的行是**满足**的
|
|
181
|
-
// —— 一个在锁上排队的过期调用方于是能把一条永久拒了的弧推去迁数据。终态永久性是库级不变量,
|
|
182
|
-
// 这道守卫就该在库里,不在调用方的 if 里。
|
|
183
|
-
this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, legs = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ? AND state = 'in_flight'`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, legs = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5 AND state = 'in_flight'`), [nextPhase, encodeLegs(legs), nowMs, adoptionId, expectPhase]);
|
|
116
|
+
const cas = await conn.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, legs = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ? AND state = 'in_flight'`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, legs = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5 AND state = 'in_flight'`), [nextPhase, encodeLegs(legs), nowMs, adoptionId, expectPhase]);
|
|
184
117
|
if (cas.affected !== 1) {
|
|
185
118
|
await conn.rollback();
|
|
186
119
|
return { ok: false, reason: "phase_moved" };
|
|
@@ -193,20 +126,11 @@ export class SqlAdoptionLogStore {
|
|
|
193
126
|
await conn.rollback();
|
|
194
127
|
}
|
|
195
128
|
catch (rollbackErr) {
|
|
196
|
-
// 🔴 不空吞(#191 静默降级门):回滚本身失败通常意味着连接已经废了,原始异常才是要上抛的那个,
|
|
197
|
-
// 但「回滚没成功」是运维必须看见的事 —— 它决定了库里可能留着一段未提交/已废弃的事务状态。
|
|
198
129
|
console.warn("[adoption-log] rollback after a failed rebind transaction also failed:", rollbackErr);
|
|
199
130
|
}
|
|
200
131
|
throw err;
|
|
201
132
|
}
|
|
202
133
|
}
|
|
203
|
-
/**
|
|
204
|
-
* 终态**之后**的迟到行清扫事务(无 phase 变更 —— phase 已经是终态,而终态是永久的)。
|
|
205
|
-
*
|
|
206
|
-
* 🔴 它不是「再收编一次」:腿本身幂等,清扫只是把收编**结束后**又落到旧身份下的行(配置未随迁的
|
|
207
|
-
* 部署会持续制造它们)一并搬过去,并让 `current.residualSourceRows` 有个能归零的动作。
|
|
208
|
-
* `immutableReport` 一个字节都不动(史实与现势分家,183 §6)。
|
|
209
|
-
*/
|
|
210
134
|
async sweepOn(conn, work) {
|
|
211
135
|
try {
|
|
212
136
|
await conn.begin();
|
|
@@ -219,52 +143,29 @@ export class SqlAdoptionLogStore {
|
|
|
219
143
|
await conn.rollback();
|
|
220
144
|
}
|
|
221
145
|
catch (rollbackErr) {
|
|
222
|
-
console.warn("[adoption-log] rollback after a failed sweep transaction also failed:", rollbackErr);
|
|
146
|
+
console.warn("[adoption-log] rollback after a failed sweep transaction also failed:", rollbackErr);
|
|
223
147
|
}
|
|
224
148
|
throw err;
|
|
225
149
|
}
|
|
226
150
|
}
|
|
227
|
-
/** 单调 CAS 推进(无数据写的阶段;`false` = 别人已经推过了/相位不符 ⇒ 调用方重读行)。 */
|
|
228
151
|
async advancePhase(adoptionId, expectPhase, nextPhase, nowMs, exec = this.db) {
|
|
229
152
|
const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ?`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, updated_at_ms = $2 WHERE adoption_id = $3 AND phase = $4`), [nextPhase, nowMs, adoptionId, expectPhase]);
|
|
230
153
|
return res.affected === 1;
|
|
231
154
|
}
|
|
232
|
-
/** phase 4:配置账物化(与 phase 推进同一条语句,免得账落了相位没推)。 */
|
|
233
155
|
async putConfigs(adoptionId, expectPhase, nextPhase, configsJson, nowMs, exec = this.db) {
|
|
234
156
|
const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, configs = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ?`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, configs = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5`), [nextPhase, configsJson, nowMs, adoptionId, expectPhase]);
|
|
235
157
|
return res.affected === 1;
|
|
236
158
|
}
|
|
237
|
-
/**
|
|
238
|
-
* ⑥ 终态:`report` 一次写入,`state='adopted'`。
|
|
239
|
-
* 🔴 `AND report IS NULL` 是 I4(永久终态 + 不可变史实)的**数据库强形**:即使调用序漂了,快照也
|
|
240
|
-
* 不可能被第二次写盖掉 —— 「逐字节恒同」于是不依赖调用方自觉。
|
|
241
|
-
*/
|
|
242
159
|
async finalizeAdopted(adoptionId, expectPhase, reportJson, nowMs, exec = this.db) {
|
|
243
160
|
const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET phase = ?, state = 'adopted', report = ?, updated_at_ms = ? WHERE adoption_id = ? AND phase = ? AND report IS NULL`, `UPDATE ${ADOPTION_LOG_TABLE} SET phase = $1, state = 'adopted', report = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND phase = $5 AND report IS NULL`), [ADOPTION_PHASE.TERMINAL, reportJson, nowMs, adoptionId, expectPhase]);
|
|
244
161
|
return res.affected === 1;
|
|
245
162
|
}
|
|
246
|
-
/** 目的地冲突 ⇒ rejected 终态(源/目的地两侧字节零变更;phase 停在 INTENT)。 */
|
|
247
163
|
async finalizeRejected(adoptionId, code, detail, nowMs, exec = this.db) {
|
|
248
164
|
const res = await exec.query(this.q(`UPDATE ${ADOPTION_LOG_TABLE} SET state = 'rejected', reject_code = ?, reject_detail = ?, updated_at_ms = ? WHERE adoption_id = ? AND state = 'in_flight'`, `UPDATE ${ADOPTION_LOG_TABLE} SET state = 'rejected', reject_code = $1, reject_detail = $2, updated_at_ms = $3 WHERE adoption_id = $4 AND state = 'in_flight'`), [code, detail, nowMs, adoptionId]);
|
|
249
165
|
return res.affected === 1;
|
|
250
166
|
}
|
|
251
|
-
/**
|
|
252
|
-
* 本连接所在**数据库**的名字 = 弧锁的键空间限定符(A-010.13,理由见 {@link qualifyAdoptionLockName})。
|
|
253
|
-
*
|
|
254
|
-
* 在**交出来的那条连接**上问(不向池要第二条 —— 弧全程单连接,见 `AdoptionLogStore.getById` 的注),
|
|
255
|
-
* 结果缓存在店上:库名在一条驱动的生命周期里不会变,而每次取锁多打一个往返是白付的。
|
|
256
|
-
*
|
|
257
|
-
* 读不出来 ⇒ **响亮抛**,不回落到未限定的旧名字:那正是本条要消灭的形,静默回落等于「修了个寂寞」
|
|
258
|
-
* 且只在多部署共库的那台机器上才发作(安全轴禁静默 fail-open 的同族判据)。
|
|
259
|
-
*/
|
|
260
167
|
lockKeyspace;
|
|
261
168
|
resolveLockKeyspace(conn) {
|
|
262
|
-
// 🔴 缓存的是**这一次尝试**,而失败的那一次必须从缓存里消失(codex 对抗复审 R1 [medium],验真后修)。
|
|
263
|
-
// 病:`??=` 把 promise 本身钉住,于是**一次瞬时**的 `SELECT DATABASE()` 失败(连接抖动、库短暂不可
|
|
264
|
-
// 达)会被永久缓存 —— 此后每一条弧都复用那个已拒的 promise、连库都不再问一次。而意图行是在取锁
|
|
265
|
-
// **之前**就落库的,所以首条弧永远停在 in_flight、每次 boot 续跑再撞同一堵墙,整台副本的收编面
|
|
266
|
-
// 直到重启为止都是死的。一次抖动升级成一次停机,方向完全反了。
|
|
267
|
-
// 修:只有**解析成功**的读数留在缓存里;拒绝时清掉,下一次真的重问。
|
|
268
169
|
const attempt = (this.lockKeyspace ??= (async () => {
|
|
269
170
|
const res = await conn.query(this.q("SELECT DATABASE() AS db", "SELECT current_database() AS db"));
|
|
270
171
|
const db = res.rows[0]?.db;
|
|
@@ -273,29 +174,15 @@ export class SqlAdoptionLogStore {
|
|
|
273
174
|
}
|
|
274
175
|
return db;
|
|
275
176
|
})());
|
|
276
|
-
// 清缓存要**认准这一次**(`=== attempt`):否则一次迟到的失败会把别人刚缓存好的成功读数抹掉。
|
|
277
177
|
return attempt.catch((err) => {
|
|
278
178
|
if (this.lockKeyspace === attempt)
|
|
279
179
|
this.lockKeyspace = undefined;
|
|
280
180
|
throw err;
|
|
281
181
|
});
|
|
282
182
|
}
|
|
283
|
-
/**
|
|
284
|
-
* 收编弧的 advisory 锁(183 I1 的 form b 形:「与任何活写互斥」在 SQL 侧 = 一次只有一个副本在推这条弧)。
|
|
285
|
-
*
|
|
286
|
-
* **非阻塞取 + 有界轮询**(tidb-pool 的 `acquireEnsureSchemaLock` 同款判据:阻塞式 GET_LOCK 会把并发
|
|
287
|
-
* 调用方全部park 在 TiDB 的悲观锁行上,既耗它的重试预算又饿死应用自己的 FOR UPDATE 路)。取不到 ⇒
|
|
288
|
-
* 返回 `undefined`,调用方按「在飞」应答 —— **不假装成功,也不无限等**。
|
|
289
|
-
*
|
|
290
|
-
* 🔴 入参 `name` 是**逻辑**锁名(`adoptionLockName(fromPrincipal)`);真正发给引擎的是它被
|
|
291
|
-
* {@link qualifyAdoptionLockName} 限定到本库之后的形(A-010.13:两方言键空间不等价)。限定发生在
|
|
292
|
-
* **这里**而不是调用方,因为「键空间是每库还是每实例」是**存储层**的知识,协议层(runner)不该知道。
|
|
293
|
-
*/
|
|
294
183
|
async withLock(name, fn, attempts = 20, sleepMs = 100) {
|
|
295
184
|
const conn = await this.db.connect();
|
|
296
185
|
let held = false;
|
|
297
|
-
// 🔴 在 try **之外**声明:释放腿(finally)必须用**取锁时用过的那一个**名字。第一版把它 const 在
|
|
298
|
-
// try 里,于是 finally 只能回头用未限定的 `name` —— 取的是 A、放的是 B,锁会一直持到连接回收。
|
|
299
186
|
let lockName = "";
|
|
300
187
|
try {
|
|
301
188
|
lockName = qualifyAdoptionLockName(await this.resolveLockKeyspace(conn), name);
|
|
@@ -308,8 +195,6 @@ export class SqlAdoptionLogStore {
|
|
|
308
195
|
}
|
|
309
196
|
if (!held)
|
|
310
197
|
return undefined;
|
|
311
|
-
// 🔴 把**这一条**连接交出去:整条弧(读 / 事务 / CAS)都跑在它上面,弧内绝不再向池要第二条
|
|
312
|
-
// ——`connectionLimit=1` 的部署上那第二条永远等不到(codex R3-F2)。
|
|
313
198
|
return await fn(conn);
|
|
314
199
|
}
|
|
315
200
|
finally {
|
|
@@ -318,8 +203,6 @@ export class SqlAdoptionLogStore {
|
|
|
318
203
|
await conn.query(this.q("SELECT RELEASE_LOCK(?) AS released", "SELECT pg_advisory_unlock($1, $2) AS released"), this.db.dialect === "tidb" ? [lockName] : [PG_ADOPTION_LOCK_CLASS, pgLockObjectKey(lockName)]);
|
|
319
204
|
}
|
|
320
205
|
catch (releaseErr) {
|
|
321
|
-
// 锁在连接关闭时会自动释放,所以释放失败不该盖掉真正的结果 —— 但同样不空吞:
|
|
322
|
-
// 释放失败意味着这把弧锁会一直持到连接回收,下一个副本要多等一会儿,那是要能看见的。
|
|
323
206
|
console.warn("[adoption-log] releasing the adoption arc lock failed (it auto-frees when the connection closes):", releaseErr);
|
|
324
207
|
}
|
|
325
208
|
}
|
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `InMemoryApprovalAskStore` —— #151 车1,`ApprovalAskStore` 的语义真源(twin 对拆基准)+ LOCAL
|
|
3
|
-
* 车道占位(File 形不在本车,见设计定稿 §7)。单进程内两个 `Map`,方法体全同步(不含 `await` 中断点)——
|
|
4
|
-
* Node 单线程下这就是天然原子:`Promise.all` 发起的并发调用不会在方法体内部交错执行,与
|
|
5
|
-
* `approval-ask-store-sql.ts` 的 CAS UPDATE 语义逐字段一致(SQL twin 对拆套件的基准就是这份实现)。
|
|
6
|
-
*
|
|
7
|
-
* 行为口径与 `SqlApprovalAskStore` 逐条对齐:
|
|
8
|
-
* - ensureAsk 幂等 upsert(ask_id 已在 ⇒ 返回既有行,不改写;batch 行若不存在则以 OPEN 补齐)。
|
|
9
|
-
* - transitionAsk 先过 machine 层 `canAskTransition`(非法转移 throw,不返回 false)。
|
|
10
|
-
* - decideAsk/expireAsk/bindBatch/abortBatch 的双/三步判定顺序与 SQL twin 的事务步骤一一对应
|
|
11
|
-
* (哪一步先判、判什么、失败时是否触碰另一半状态——照抄,不是「反正内存里原子就随便」)。
|
|
12
|
-
* - resolveProvisional 故意不经 canAskTransition(同 SQL twin 头注:这是版本化补偿的例外通道)。
|
|
13
|
-
*/
|
|
14
1
|
import { canAskTransition } from "../approval-ask-machine.js";
|
|
15
2
|
import { assertIdempotencyKeyShape, ApprovalAskReadAbortedError } from "./approval-ask-store-sql.js";
|
|
16
|
-
/** patch 里真出现的字段搬到行上(未出现的字段维持原值)——`transitionAsk`/`resolveProvisional` 共用。 */
|
|
17
3
|
function applyPatch(row, patch) {
|
|
18
4
|
if (patch.decision !== undefined)
|
|
19
5
|
row.decision = patch.decision;
|
|
@@ -50,12 +36,8 @@ export class InMemoryApprovalAskStore {
|
|
|
50
36
|
});
|
|
51
37
|
}
|
|
52
38
|
const existing = this.asks.get(row.askId);
|
|
53
|
-
// 幂等命中:行是**别人**插的 ⇒ `inserted: false`(收尾权限判别位,语义见 `EnsureAskResult` 顶注)。
|
|
54
39
|
if (existing)
|
|
55
40
|
return { row: { ...existing }, inserted: false };
|
|
56
|
-
// 🔴 车4 §12-E:`idempotency_key` 已归**回决专用**,`NewAskRow` 的同名字段随之摘除 ⇒ 铸行不再有
|
|
57
|
-
// 唯一性可撞(列恒 NULL)。原先在这里镜像 UNIQUE 的那段扫描也随之下车;撞键判定搬到 `decideAsk`
|
|
58
|
-
// (SQL twin 那侧是 `(task_id, idempotency_key)` 索引在库层拒 ⇒ typed `idempotency_conflict`)。
|
|
59
41
|
const created = {
|
|
60
42
|
askId: row.askId,
|
|
61
43
|
taskId: row.taskId,
|
|
@@ -78,7 +60,7 @@ export class InMemoryApprovalAskStore {
|
|
|
78
60
|
gateToken: null,
|
|
79
61
|
gateBoundCallId: null,
|
|
80
62
|
gateBoundInputHash: null,
|
|
81
|
-
idempotencyKey: null,
|
|
63
|
+
idempotencyKey: null,
|
|
82
64
|
cardJson: row.cardJson,
|
|
83
65
|
schemaVersion: row.schemaVersion,
|
|
84
66
|
expiresAtMs: row.expiresAtMs,
|
|
@@ -103,7 +85,7 @@ export class InMemoryApprovalAskStore {
|
|
|
103
85
|
}
|
|
104
86
|
async decideAsk(askId, batchId, decision) {
|
|
105
87
|
if (decision.idempotencyKey != null)
|
|
106
|
-
assertIdempotencyKeyShape(decision.idempotencyKey);
|
|
88
|
+
assertIdempotencyKeyShape(decision.idempotencyKey);
|
|
107
89
|
const now = Date.now();
|
|
108
90
|
const batch = this.batches.get(batchId);
|
|
109
91
|
if (!batch || batch.state !== "OPEN") {
|
|
@@ -114,10 +96,6 @@ export class InMemoryApprovalAskStore {
|
|
|
114
96
|
if (!ask || ask.state !== "STREAM_PENDING") {
|
|
115
97
|
return { ok: false, reason: "ask_not_pending", row: ask ? { ...ask } : null };
|
|
116
98
|
}
|
|
117
|
-
// 🔴 UNIQUE `(task_id, idempotency_key)` 的内存镜像(车4 §12-E)——SQL twin 靠索引在库层拒并把方言
|
|
118
|
-
// 错误翻成 typed 臂;这里必须同答,否则这个店就不是 SQL twin 的语义真源。判定顺序也照抄 SQL:
|
|
119
|
-
// 批 CAS(已过)→ ask CAS(已过)→ 才轮到撞键 ⇒ 一条非 STREAM_PENDING 的 ask 拿到的仍是
|
|
120
|
-
// `ask_not_pending` 而不是 `idempotency_conflict`(哪个先判决定了输者看到的 reason)。
|
|
121
99
|
if (decision.idempotencyKey != null) {
|
|
122
100
|
for (const [id, r] of this.asks) {
|
|
123
101
|
if (id !== askId && r.taskId === ask.taskId && r.idempotencyKey === decision.idempotencyKey) {
|
|
@@ -139,9 +117,6 @@ export class InMemoryApprovalAskStore {
|
|
|
139
117
|
}
|
|
140
118
|
async expireAsk(askId, batchId) {
|
|
141
119
|
const now = Date.now();
|
|
142
|
-
// 批行存在性先判(镜像 SQL twin 死锁修后的步⓪,§6-4c 钉):批行缺席=ensureAsk 从未为它建批
|
|
143
|
-
// (错配 batchId 的编程错误形),如实不赢——不在半空中把 ask 铸成 PARKING(它真正的批还 OPEN,
|
|
144
|
-
// 状态会被污染)。批 state 不设谓词,与 SQL twin 一致(ROUTING_UNBOUND 兄弟先到属合法续走)。
|
|
145
120
|
const batch = this.batches.get(batchId);
|
|
146
121
|
if (!batch)
|
|
147
122
|
return { won: false, voidedSiblings: [] };
|
|
@@ -170,15 +145,12 @@ export class InMemoryApprovalAskStore {
|
|
|
170
145
|
async bindBatch(batchId, askId, gate) {
|
|
171
146
|
const now = Date.now();
|
|
172
147
|
const batch = this.batches.get(batchId);
|
|
173
|
-
// 判别式失败臂逐条对齐 SQL twin(见 `BindResult` 头注):批行缺席 = "MISSING";批态不对 ⇒ 回真实态
|
|
174
|
-
// (ABORTED / ROUTING_BOUND 两条处置完全相反,调用方靠它分臂)。
|
|
175
148
|
if (!batch)
|
|
176
149
|
return { ok: false, batchState: "MISSING" };
|
|
177
150
|
if (batch.state !== "ROUTING_UNBOUND" || batch.boundAskId !== null) {
|
|
178
151
|
return { ok: false, batchState: batch.state, ...(batch.boundAskId !== null ? { boundAskId: batch.boundAskId } : {}) };
|
|
179
152
|
}
|
|
180
153
|
const ask = this.asks.get(askId);
|
|
181
|
-
// ask 不在 PARKING ⇒ SQL twin 整事务回滚,批停在 ROUTING_UNBOUND;这里同样一个字节都还没改。
|
|
182
154
|
if (!ask || ask.state !== "PARKING")
|
|
183
155
|
return { ok: false, batchState: batch.state };
|
|
184
156
|
batch.state = "ROUTING_BOUND";
|
|
@@ -203,7 +175,6 @@ export class InMemoryApprovalAskStore {
|
|
|
203
175
|
return { ok: true, voidedSiblings };
|
|
204
176
|
}
|
|
205
177
|
async deferReconcile(askId, expectedState, expectedRev, nowMs) {
|
|
206
|
-
// 纯 touch,**不碰 state**,故意不经 canAskTransition(同态转移在转移表上无出边——见接口注)。
|
|
207
178
|
const row = this.asks.get(askId);
|
|
208
179
|
if (!row || row.state !== expectedState || row.rev !== expectedRev)
|
|
209
180
|
return false;
|
|
@@ -231,15 +202,12 @@ export class InMemoryApprovalAskStore {
|
|
|
231
202
|
return voided;
|
|
232
203
|
}
|
|
233
204
|
async listByState(state, limit) {
|
|
234
|
-
// 排序键与 SQL twin 逐字同形:`updated_at_ms ASC, created_at_ms ASC`(队列轮转,见 SQL 侧头注)。
|
|
235
205
|
return [...this.asks.values()]
|
|
236
206
|
.filter((r) => r.state === state)
|
|
237
207
|
.sort((a, b) => a.updatedAtMs - b.updatedAtMs || a.createdAtMs - b.createdAtMs)
|
|
238
208
|
.slice(0, limit)
|
|
239
209
|
.map((r) => ({ ...r }));
|
|
240
210
|
}
|
|
241
|
-
/** `signal` 的 twin 语义(车3 刀 3b):同步实现里没有「在飞」这回事,所以能做也只需做**入口复核** ——
|
|
242
|
-
* 已 abort 就一条都不读、如实抛,与 SQL twin 的第①条语义逐字一致(第②条在这里天然平凡成立)。 */
|
|
243
211
|
async listPendingByTask(taskId, signal) {
|
|
244
212
|
if (signal?.aborted === true)
|
|
245
213
|
throw new ApprovalAskReadAbortedError("listPendingByTask");
|
|
@@ -251,7 +219,6 @@ export class InMemoryApprovalAskStore {
|
|
|
251
219
|
async listPendingBySession(sessionId, owner, signal) {
|
|
252
220
|
if (signal?.aborted === true)
|
|
253
221
|
throw new ApprovalAskReadAbortedError("listPendingBySession");
|
|
254
|
-
// 租户门与 SQL twin 逐字同语义:`owner` 必填,`null` 只匹配 owner 为 null 的行(不是「不过滤」)。
|
|
255
222
|
return [...this.asks.values()]
|
|
256
223
|
.filter((r) => r.sessionId === sessionId && r.state === "STREAM_PENDING" && r.owner === owner)
|
|
257
224
|
.sort((a, b) => a.createdAtMs - b.createdAtMs)
|
|
@@ -262,8 +229,7 @@ export class InMemoryApprovalAskStore {
|
|
|
262
229
|
return r ? { ...r } : null;
|
|
263
230
|
}
|
|
264
231
|
async getByIdempotencyKey(taskId, key) {
|
|
265
|
-
assertIdempotencyKeyShape(key);
|
|
266
|
-
// 键的两维都要匹配(UNIQUE 已改 `(task_id, idempotency_key)`)——租户门是读口自己的责任。
|
|
232
|
+
assertIdempotencyKeyShape(key);
|
|
267
233
|
for (const r of this.asks.values()) {
|
|
268
234
|
if (r.taskId === taskId && r.idempotencyKey === key)
|
|
269
235
|
return { ...r };
|
|
@@ -271,7 +237,6 @@ export class InMemoryApprovalAskStore {
|
|
|
271
237
|
return null;
|
|
272
238
|
}
|
|
273
239
|
async resolveProvisional(askId, from, to, patch) {
|
|
274
|
-
// 🔴 与 SQL twin 同注:故意不经 canAskTransition——版本化补偿的例外通道。
|
|
275
240
|
const row = this.asks.get(askId);
|
|
276
241
|
if (!row || !row.provisional || row.state !== from)
|
|
277
242
|
return false;
|