@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,204 +1,32 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #307 件6(core 5.46.0 提货批,design/336 §2.3-2)—— **external-origin 标记不可变律**的 SQL 孪生实现。
|
|
3
|
-
*
|
|
4
|
-
* ## 这条法是什么、为什么落在本仓
|
|
5
|
-
*
|
|
6
|
-
* design/336 slice 1 给「已暴露会话」的普通记忆写打上引擎铸的 `frontmatter.origin` 标记(taint +
|
|
7
|
-
* 成因闭集 + 铸造时刻),取代 design/178 那种「整条隔离」的全禁。标记的全部价值来自**不可洗白**:
|
|
8
|
-
* 一旦某个 id 的**已提交**状态带了标记,之后**任何**写法(update / 裸 add / `guard:"absent"` add /
|
|
9
|
-
* 同批 delete+re-add)都必须把它 DEEP-EQUAL 地带下去,否则拒绝。合法出口只有一个 —— **已提交的**
|
|
10
|
-
* 墓碑(上一批真的 delete 掉了),那之后这个 id 开始一段未标记的新生命。
|
|
11
|
-
*
|
|
12
|
-
* core 5.46.0 把这条法写进了**记忆后端契约试剂盒**
|
|
13
|
-
* (`dist/core/memory-engine/memory-backend-contract.js`,新增一条必跑条目),而该契约**压在本仓身上**:
|
|
14
|
-
* core 只养 File backend,Pg / TiDB 两方言归 server。提货当天真双库实跑的红读数(A-002.21 教训的
|
|
15
|
-
* 直接兑现,不是自我声明「本批无 SQL 面」):
|
|
16
|
-
* `[engine=tidb] contract case "design/336: origin marker round-trips; …":
|
|
17
|
-
* an origin-stripping update must not apply — 1 !== 0`
|
|
18
|
-
* ⇒ 两只 SQL 孪生此前**没有**这条法:一条被标记为外部来源的记忆条目,可以被后续一次普通 update
|
|
19
|
-
* 悄悄抹掉标记(洗白),而 File backend 会拒。这是安全轴上的真缺口,不是「新契约还没接」。
|
|
20
|
-
*
|
|
21
|
-
* ## 为什么是一个共享模块而不是两处各写一遍
|
|
22
|
-
*
|
|
23
|
-
* 本仓的成文教训(`key-resolver.ts` 顶注「链只有一条,重复实现就会长出半条链的偏差」;
|
|
24
|
-
* `memory-key-guards.ts` 同族先例):两支方言各写一份判据,迟早在某一支上漏掉一个 op 拼写 ——
|
|
25
|
-
* 而漏掉的那一支**没有任何报错面**,表现只是「这台机器上的标记可以被洗掉」。判据函数与批次基线
|
|
26
|
-
* 记录器都在这里,两支后端只负责在自己的 I/O 点上调用。
|
|
27
|
-
*
|
|
28
|
-
* ## 判据的两半(缺一不可)
|
|
29
|
-
*
|
|
30
|
-
* ① **判据本身**({@link originWhitewashRefusal}):`committedOriginOf` / `originEquals` 都用 core 的
|
|
31
|
-
* 导出实现,**不重写** —— 它们要处理 `frontmatter.origin` 与「保留在 `extra` 里的 origin 形字节块」
|
|
32
|
-
* 两种承载形(legacy / 外来),自己写一份近似的等于给洗白留一条后门。
|
|
33
|
-
* ② **批次基线**({@link rememberBatchOrigin}):两支 SQL 后端逐条 patch 直打真库(没有 File backend
|
|
34
|
-
* 那种「先规划整批再落盘」的阶段),所以同批 `delete` 之后再 `add` 时,库里已经没有那一行了 ——
|
|
35
|
-
* 只看当下的库会把它误判成合法新生命。基线 map 按**本批第一次触碰**记下该 id 的已提交 origin,
|
|
36
|
-
* 之后同批的任何 op 都按那一份判。⇒「同批 delete+re-add 拒 / 已提交墓碑后 add 放」这条分界
|
|
37
|
-
* (core 契约逐字要求的那条)才成立。
|
|
38
|
-
*/
|
|
39
1
|
import { ambiguousOriginRepresentation, committedDistilledOf, committedOriginOf, distilledEquals, originEquals } from "@sema-agent/core";
|
|
40
|
-
/**
|
|
41
|
-
* #319 增量(sema-comms [4768] 勘误 + core 仓 `1a5ebaa1`,conformance 30→31)—— **歧义 origin 表示**
|
|
42
|
-
* 的拒绝门,与不可变律同批、同一个写路径上。
|
|
43
|
-
*
|
|
44
|
-
* ## 病
|
|
45
|
-
*
|
|
46
|
-
* 同一条目**同时**携带 typed `frontmatter.origin` 与一个**冲突的** origin 形 `extra` 块(或多个彼此
|
|
47
|
-
* 冲突的 extra 块)时:不可变律的比较器走 `committedOriginOf`,而它在 typed 在场时优先读 typed ——
|
|
48
|
-
* 于是那枚 typed 满足「标记原样带下去」,冲突的块却照样进了存储。任何一次**重解析**(File 的投影
|
|
49
|
-
* 往返 / sync 对拍 / bundle 校验)都可能答出**另一枚**标记 ⇒ 成员改写的载具。不可变律本身没漏,
|
|
50
|
-
* 漏的是「根本不该收下这种表示」这一步。
|
|
51
|
-
*
|
|
52
|
-
* ## 钥匙在 DISAGREEMENT,不在载体数
|
|
53
|
-
*
|
|
54
|
-
* 载体彼此**一致**的多载形(typed 旁边一个逐成员相等的 extra 形)是合法的 legacy 承载,必须照收 ——
|
|
55
|
-
* 那正是序列化闭合(同一条记录在 File 投影里往返一圈就长这样)。「见到 extra 就拒」会把正常的
|
|
56
|
-
* legacy 条目全部打死。
|
|
57
|
-
*
|
|
58
|
-
* ## 判据禁手抄
|
|
59
|
-
*
|
|
60
|
-
* 谓词一律用 core 导出的 {@link ambiguousOriginRepresentation}:它要解 `extra` 里的 origin 形块
|
|
61
|
-
* (跨块字段组合、解析期块重排都算歧义),自己写一份近似的等于给洗白开后门 —— 这条在 #307 就写死过。
|
|
62
|
-
* core 5.47.0 起该谓词进公开导出面([4698] 请托兑现),本仓的「撤除条件钉」也因此在 5.47 提货当天
|
|
63
|
-
* 准时翻红,本批就是那条钉指定的兑现动作。
|
|
64
|
-
*
|
|
65
|
-
* ## 文案与 File 逐字同
|
|
66
|
-
*
|
|
67
|
-
* 拒绝文案照抄 core `file-backend.ts` 的 `planOne`(亲读 `1a5ebaa1` 前后两版确认未变),因为
|
|
68
|
-
* 契约按 `/representation conflict|malformed patch refused/` 匹配,而**跨后端同一输入必须同一答复**
|
|
69
|
-
* —— 消费方按 reason 分类处置,两支方言各写各的文案就等于让同一件事在不同部署上分叉。
|
|
70
|
-
*
|
|
71
|
-
* ## 判位
|
|
72
|
-
*
|
|
73
|
-
* 与 File 同precedence:**表示合法性先于状态比较** —— 排在 id/键形守卫之后、add/update 分支
|
|
74
|
-
* (guard 算术、跨 scope 拒、白洗判据、rev CAS)之前,且在**任何 I/O 之前**(零写拒)。
|
|
75
|
-
*/
|
|
76
2
|
export function ambiguousOriginRefusal(fm) {
|
|
77
3
|
if (!ambiguousOriginRepresentation(fm))
|
|
78
4
|
return undefined;
|
|
79
5
|
return "external-origin marker representation conflict: the entry carries conflicting or duplicated origin representations (malformed patch refused)";
|
|
80
6
|
}
|
|
81
|
-
/**
|
|
82
|
-
* #319(core 5.47/5.48 契约冻结面,design/336 §5.2)—— **头面 exposure 事实**的同一份归一化。
|
|
83
|
-
*
|
|
84
|
-
* 契约逐字:`listHeaders` 与 `search` 两个头面都要携带 `exposure`,且**同一条归一化**
|
|
85
|
-
* (`committedOriginOf`:typed `frontmatter.origin` 与 legacy 的 `extra` origin 形字节块两种承载,
|
|
86
|
-
* 一次判、两面同答)。这是**数据事实**不是模式:后端恒报库里存着什么,读侧怎么处置(不透明句柄/
|
|
87
|
-
* 横幅/两带序)归引擎层的 provenance 模式。缺席 ⇔ 没标记(不等于「证明干净」)。
|
|
88
|
-
*
|
|
89
|
-
* 为什么与不可变律同住一个模块:两者读的是**同一个** committed origin。判据(白洗拒)与事实
|
|
90
|
-
* (exposure 携带)分家写,迟早在某一支方言上一边认得 extra 形、另一边只认 typed 形 —— 那时
|
|
91
|
-
* 「洗不掉的标记」在检索面**看不见**,而两支后端谁都不会报错。
|
|
92
|
-
*/
|
|
93
7
|
export function exposureCarriage(fm) {
|
|
94
8
|
return committedOriginOf(fm) !== undefined ? { exposure: "external" } : {};
|
|
95
9
|
}
|
|
96
|
-
/**
|
|
97
|
-
* 两带序的带号:未标记 = 0(**先**),已标记 = 1。
|
|
98
|
-
*
|
|
99
|
-
* 契约的载荷理由(core r1-9,逐字转述):两带序必须落在**截断之前** —— 一页 limit 全是带标条目时,
|
|
100
|
-
* 排在 limit+1 位的干净条目会被饿死,而工具层**无法**从一页已截断的结果里把它捞回来。
|
|
101
|
-
* `exposureBands` 缺席/false ⇒ 单带序,与 pre-336 逐字节同(`provenance:"off"` 的读面)。
|
|
102
|
-
*/
|
|
103
10
|
export function exposureBandOf(header) {
|
|
104
11
|
return header.exposure === "external" ? 1 : 0;
|
|
105
12
|
}
|
|
106
|
-
/**
|
|
107
|
-
* `committedOriginOf` 的**下推形**:同一条判据的 SQL 写法,给「排序与截断都发生在 SQL 里」的那一档
|
|
108
|
-
* (Pg 的原生 pgvector 档)用。
|
|
109
|
-
*
|
|
110
|
-
* ## 为什么非要下推(不能只在进程里排)
|
|
111
|
-
*
|
|
112
|
-
* 原生档 `ORDER BY … LIMIT n` 是**库里**排完就截断的:进程拿到的已经是一页 n 行,被截掉的那条干净
|
|
113
|
-
* 条目在页里根本不存在,重排救不回来 —— 而「带序必须先于截断」正是这条契约的载荷所在。所以带号
|
|
114
|
-
* 必须进 ORDER BY,也就必须有一份 SQL 侧的判据。
|
|
115
|
-
*
|
|
116
|
-
* ## 为什么写成逐元素的行首正则(codex 对抗复审 R1-[high] 的直接产物)
|
|
117
|
-
*
|
|
118
|
-
* 初版图省事写的是 `frontmatter->>'extra' LIKE '%origin:%'`,自称「只多认不漏认」。**错了**,两个方向
|
|
119
|
-
* 都不成立:`['origin :']`(冒号前有空格)是 core 认的合法承载形,而 LIKE 漏掉它 —— 一条**带标记**的
|
|
120
|
-
* 条目被排进干净带,占掉的正是干净条目的位置;反过来 `['prefix origin: suffix']` 被 LIKE 误认,一条
|
|
121
|
-
* **干净**条目沉进后带、可能被截断饿死。两个方向的结局是同一个:干净条目被饿死,而这正是本条款要拒的。
|
|
122
|
-
*
|
|
123
|
-
* 现形逐元素跑 core 那条行首正则(`/^\s*origin\s*:/`)。真库实测 10 种拼写(裸 / 冒号前空格 / 前导
|
|
124
|
-
* 空格 / 制表 / 全角空格 U+3000 / 行中出现 / originx: / 非 origin 行 …)与 JS 判据**逐条同答**。
|
|
125
|
-
*
|
|
126
|
-
* ## 这份「第二实现」的同步义务
|
|
127
|
-
*
|
|
128
|
-
* 两份实现同表一个语义 = 本仓明令的危险形,所以它带**机器对表**:
|
|
129
|
-
* `test/memory-engine-db-integration.test.ts` 的钉6 拿同一组拼写在**真 PG** 上让本谓词与
|
|
130
|
-
* `committedOriginOf` 逐条对答,不一致当场红(离线格做不了这件事:被测的正是 PG 自己的正则引擎)。
|
|
131
|
-
* core 改那条正则时,红的是对表格 —— 不是某台部署上悄悄跑偏的排序。
|
|
132
|
-
*
|
|
133
|
-
* ## 字符类为什么是**穷举码点**而不是 `[[:space:]]`(codex 第二轮 R2-[medium],采纳)
|
|
134
|
-
*
|
|
135
|
-
* PG 的 POSIX 字符类**跟着数据库 LC_CTYPE 走**:同一条 SQL 在 C locale 与某个 UTF-8 locale 下认的
|
|
136
|
-
* 空白集合可以不一样(例如 U+0085 NEL —— JS 的 `\s` **不**含它)。而本谓词的正确性判据是「与 JS 的
|
|
137
|
-
* `\s` 逐字同」,一条依赖部署 locale 的判据等于把同一条记忆在两台机器上排进不同的带 —— 而带号错了
|
|
138
|
-
* 就会在 LIMIT 那一刀上不可恢复地截掉本该入页的干净条目。所以这里**不用**任何 locale 敏感构造,把
|
|
139
|
-
* ECMAScript `\s` 的码点逐个写死(PG 的 ARE 支持 `\uXXXX` 转义,已在真库上验过);对表格里同时钉了
|
|
140
|
-
* U+0085 这条**反例**(JS 不认 ⇒ SQL 也必须不认),locale 一旦重新混进来它当场红。
|
|
141
|
-
*/
|
|
142
|
-
/** ⚠️ 本常量**只准写 ASCII**:那些 unicode 空白必须以 `\\uXXXX` 的**字面反斜杠形**交给 PG 的 ARE
|
|
143
|
-
* 自己解析(已在真库上验过)。把真字符敲进源码会同时踩两条线 —— 源码卫生门(不可见字节)与
|
|
144
|
-
* U+2028/U+2029(它们在 JS 源码里就是换行,会把这一行劈开)。 */
|
|
145
13
|
const SQL_JS_WHITESPACE_CLASS = "[\\u0009\\u000a\\u000b\\u000c\\u000d\\u0020\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]";
|
|
146
|
-
/** core `/^\s*origin\s*:/`(`extraOriginCarriers` 的开块判据)的 SQL 孪生。 */
|
|
147
14
|
export const ORIGIN_CARRIER_LINE_SQL_REGEX = `^${SQL_JS_WHITESPACE_CLASS}*origin${SQL_JS_WHITESPACE_CLASS}*:`;
|
|
148
|
-
/**
|
|
149
|
-
* 「这一行带 committed origin 标记」的 SQL 布尔式(两种承载形同判,与 {@link exposureCarriage} 同源)。
|
|
150
|
-
* `column` = 该表 frontmatter 列的表达式(调用方自己保证它是列名/限定名,**不接受外来串**)。
|
|
151
|
-
*/
|
|
152
15
|
export function originMarkedSqlPredicate(column) {
|
|
153
16
|
return (`(${column}->'origin' IS NOT NULL OR (jsonb_typeof(${column}->'extra') = 'array' AND EXISTS (` +
|
|
154
17
|
`SELECT 1 FROM jsonb_array_elements_text(${column}->'extra') AS origin_line(line) ` +
|
|
155
18
|
`WHERE origin_line.line ~ '${ORIGIN_CARRIER_LINE_SQL_REGEX}')))`);
|
|
156
19
|
}
|
|
157
|
-
/** 新建一只批次基线(每次 `applyPatches` 一只;跨批次**不得**复用 —— 已提交的墓碑正是靠「换批清零」放行)。 */
|
|
158
20
|
export function createBatchCommittedBaseline() {
|
|
159
21
|
return new Map();
|
|
160
22
|
}
|
|
161
|
-
/**
|
|
162
|
-
* 记下(或取回)某 id 在**本批开始时**的已提交 frontmatter。
|
|
163
|
-
*
|
|
164
|
-
* `committedFm` = 这一次 I/O 真的从库里读到的已提交 frontmatter(没有行就传 `undefined`)。
|
|
165
|
-
* **首次触碰写入,之后只读** —— 同批里后面的 op 看到的必须还是批开始那一份,否则本批自己的写会把
|
|
166
|
-
* 基线推走(delete 之后 re-add 就又变成「库里没有 ⇒ 未标记」)。
|
|
167
|
-
*/
|
|
168
23
|
export function rememberBatchCommitted(baseline, id, committedFm) {
|
|
169
24
|
if (baseline.has(id))
|
|
170
25
|
return baseline.get(id);
|
|
171
26
|
baseline.set(id, committedFm);
|
|
172
27
|
return committedFm;
|
|
173
28
|
}
|
|
174
|
-
/**
|
|
175
|
-
* 不可变律的判据。返回 `undefined` = 放行;返回字符串 = 该字符串就是 `PatchReport.conflicts[].reason`。
|
|
176
|
-
*
|
|
177
|
-
* 文案与 core File backend 的两处**逐字同形**(`external-origin marker whitewash refused: the
|
|
178
|
-
* {add,update} strips or rewrites the origin marker of a marked entry (malformed patch refused)`):
|
|
179
|
-
* 契约试剂盒按 `/malformed patch refused/` 匹配,而跨后端同一输入必须给同一个答复 —— 消费方(引擎的
|
|
180
|
-
* harvest 腿、sync 对拍)按 reason 分类处置,两支方言各写各的文案就等于让同一件事在不同部署上分叉。
|
|
181
|
-
*
|
|
182
|
-
* 未标记的已提交条目(`committed === undefined`)一律放行:本法只约束**已经**带标记的那些 id,
|
|
183
|
-
* 「给未标记条目加标记」是引擎写路径自己的事(server 不铸 origin,见台账的 skip 行)。
|
|
184
|
-
*/
|
|
185
|
-
/** `NotePatch.op` 的运行期闭集(与 core 的联合逐字同)。 */
|
|
186
29
|
const PATCH_OPS = new Set(["add", "update", "delete"]);
|
|
187
|
-
/**
|
|
188
|
-
* 未知 op 拼写的**前置**拒绝(codex R1-[high],验真后采纳;**既有**缺口,不是本批引入)。
|
|
189
|
-
*
|
|
190
|
-
* 病:两支 SQL 后端的 `applyOne` 形状是「`if (op === "add") {…}` → 落到 update/delete 段」,而段内
|
|
191
|
-
* 的两道白洗守卫(repo_file provenance / 本模块的 origin 不可变律)都写成 `if (patch.op === "update")`。
|
|
192
|
-
* ⇒ 一个**词表外**的 op(`"upsert"` / 打错的字 / 将来 core 加的新词)会:跳过两道守卫 → 走到 update
|
|
193
|
-
* 的 SQL → 把行改掉 → 在 `report.applied` 里报成一次 update。TypeScript 的联合型是**编译期**的,
|
|
194
|
-
* 拦不住任何运行期调用方(sync 腿、嵌入方自建的调用点、future core)。
|
|
195
|
-
*
|
|
196
|
-
* 今日可达性(如实):本仓唯一的调用点 `src/memory-sync.ts` 自己铸 op(基线有 ⇒ update、无 ⇒ add),
|
|
197
|
-
* 词表外的值到不了。所以这条是**加固**而不是在修一次已发生的事故 —— 但它落在安全轴(白洗守卫的
|
|
198
|
-
* 有效性完全依赖「op 只有三种」这个未被检查的前提),按 CLAUDE.md #157「闭集 + 不可避免的运行期
|
|
199
|
-
* miss 臂必须说出来」补一道显式拒绝:未知 op **零写**,报 `malformed patch refused`(与本仓既有的
|
|
200
|
-
* id-mismatch 拒绝同族文案,消费方按同一条正则分类)。
|
|
201
|
-
*/
|
|
202
30
|
export function unknownPatchOpRefusal(op) {
|
|
203
31
|
if (PATCH_OPS.has(op))
|
|
204
32
|
return undefined;
|
|
@@ -212,15 +40,6 @@ export function originWhitewashRefusal(op, committedFm, next) {
|
|
|
212
40
|
return undefined;
|
|
213
41
|
return `external-origin marker whitewash refused: the ${op} strips or rewrites the origin marker of a marked entry (malformed patch refused)`;
|
|
214
42
|
}
|
|
215
|
-
/**
|
|
216
|
-
* #337(core 5.55.0 conformance c36/c37,design/339)—— **distilled 血统不可变律**的判据,与 origin
|
|
217
|
-
* 律同族同基线同判位:一旦某 id 的已提交状态带 `frontmatter.distilled` 块(consolidation 蒸馏产物的
|
|
218
|
-
* 血统:planId/at/carrierRev/inputs),任何 update / 裸 add / guard add / 同批 delete+re-add 都必须把
|
|
219
|
-
* 它 DEEP-EQUAL 地带下去,否则拒 —— 合法出口只有已提交的墓碑(c36 逐字)。判序:**先于 rev CAS**
|
|
220
|
-
* (c37:骑着 stale baseRev 的 strip 也必须答 malformed refusal,不是 rev mismatch)。
|
|
221
|
-
* 判据用 core 导出的 {@link committedDistilledOf} / {@link distilledEquals},不手抄(F1b);文案与
|
|
222
|
-
* core File backend 两处逐字同形,契约按 `/malformed patch refused/` 匹配。
|
|
223
|
-
*/
|
|
224
43
|
export function distilledWhitewashRefusal(op, committedFm, next) {
|
|
225
44
|
const committed = committedFm !== undefined ? committedDistilledOf(committedFm) : undefined;
|
|
226
45
|
if (committed === undefined)
|
|
@@ -229,12 +48,6 @@ export function distilledWhitewashRefusal(op, committedFm, next) {
|
|
|
229
48
|
return undefined;
|
|
230
49
|
return `distilled lineage whitewash refused: the ${op} strips or rewrites the distilled block of a consolidation product (malformed patch refused)`;
|
|
231
50
|
}
|
|
232
|
-
/**
|
|
233
|
-
* #337(conformance c38)—— **distilled 事实的头面携带**,与 {@link exposureCarriage} 同住同因:
|
|
234
|
-
* `listHeaders` 与 `search` 两个头面都携带 `{ carrierRev, supersedes }` 投影(supersedes = inputs 中
|
|
235
|
-
* `superseded === true` 的行,形状与 core File backend 的 header 投影逐字同);无块条目两面都不携带。
|
|
236
|
-
* 蒸馏产物的「取代了哪些输入行」必须在检索面可见,否则被取代的旧行与蒸馏产物在读侧无从判序。
|
|
237
|
-
*/
|
|
238
51
|
export function distilledCarriage(fm) {
|
|
239
52
|
const d = fm.distilled;
|
|
240
53
|
if (d === undefined)
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory resume-at anchor map — the LOCAL (DB-less) twin of {@link TiDBResumeAnchorStore}/{@link PgResumeAnchorStore}
|
|
3
|
-
* (E18 resume-at; clay seamless local↔cloud). The shell holds the live E2 `eventId` (core never persists it);
|
|
4
|
-
* core's `TaskSpec.resumeAt` takes a persisted `SessionTreeEntry.id`. This store is the service-owned translation
|
|
5
|
-
* ("a deployment that holds eventIds owns the eventId→entryId map", core types). resume-at works LOCALLY (it needs
|
|
6
|
-
* only the session tree + this map, no cloud-only checkpoint), so this is PRESENT on the local backend (a singleton,
|
|
7
|
-
* like MemoryRunStore) — not omitted like the cloud-only stores. Process-local + ephemeral (lost on restart, same
|
|
8
|
-
* blast radius as MemoryRunStore; the file-backed follow-on persists it alongside run history).
|
|
9
|
-
*/
|
|
10
1
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
11
2
|
export class MemoryResumeAnchorStore {
|
|
12
3
|
bySession = new Map();
|
|
13
|
-
/** Record/overwrite the anchor for one settled message boundary (idempotent upsert; parity with the SQL ON DUPLICATE/CONFLICT). */
|
|
14
4
|
async put(sessionId, eventId, entryId, owner) {
|
|
15
5
|
let m = this.bySession.get(sessionId);
|
|
16
6
|
if (!m) {
|
|
@@ -19,15 +9,10 @@ export class MemoryResumeAnchorStore {
|
|
|
19
9
|
}
|
|
20
10
|
m.set(eventId, { entryId, owner });
|
|
21
11
|
}
|
|
22
|
-
/** Resolve a shell-supplied eventId → the persisted SessionTreeEntry.id for TaskSpec.resumeAt; undefined = unknown
|
|
23
|
-
* anchor (the route 4xx's before handing core a bad id). owner null-safe guard = the SQL `owner <=> ?` defense-in-depth. */
|
|
24
12
|
async resolve(sessionId, eventId, owner) {
|
|
25
13
|
const e = this.bySession.get(sessionId)?.get(eventId);
|
|
26
14
|
return e && ownerEq(e.owner, owner) ? e.entryId : undefined;
|
|
27
15
|
}
|
|
28
|
-
/** 2c session-sync: ALL anchors for a session (every eventId→entryId + its owner), UNFILTERED — a cross-backend
|
|
29
|
-
* session EXPORT bundles the whole anchor map so the importer can re-key owner to the target principal (§9). Twin of
|
|
30
|
-
* the SQL/file stores; kept on all three backends so the ResumeAnchorStore union shares a single contract. */
|
|
31
16
|
async listBySession(sessionId) {
|
|
32
17
|
const m = this.bySession.get(sessionId);
|
|
33
18
|
if (!m)
|
|
@@ -37,8 +22,6 @@ export class MemoryResumeAnchorStore {
|
|
|
37
22
|
out.push({ eventId, entryId: e.entryId, owner: e.owner });
|
|
38
23
|
return out;
|
|
39
24
|
}
|
|
40
|
-
/** E21 (§0.5 delete) — purge ALL anchors for a session, scoped by session_id ALONE (the route already proved session
|
|
41
|
-
* ownership; a per-row owner guard would orphan rows stored under a divergent per-run principal). Idempotent (count). */
|
|
42
25
|
async deleteBySession(sessionId) {
|
|
43
26
|
const removed = this.bySession.get(sessionId)?.size ?? 0;
|
|
44
27
|
this.bySession.delete(sessionId);
|
|
@@ -1,27 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory async run registry + replayable event log — the LOCAL (DB-less) twin of {@link TiDBRunStore}/
|
|
3
|
-
* {@link PgRunStore} (clay 2026-06-25: the seamless local↔cloud switch — a locally-run HTTP service must serve the
|
|
4
|
-
* SAME contract a cloud worker does, incl. `/v1/runs` async runs + the trace event log). Behaviourally identical to
|
|
5
|
-
* the SQL twins (the shared `storeBehaviorSuite` keeps them in lock-step), but process-local + ephemeral: a restart
|
|
6
|
-
* loses run history (file-backed durability is the immediate follow-on; the conversation HISTORY + checkpoints DO
|
|
7
|
-
* persist via core's FileStorageBackend). Single-process, so the cross-replica/durable reapers degrade — see below.
|
|
8
|
-
*
|
|
9
|
-
* Mirrors the SQL semantics exactly: the `task_active` single-active-run claim (a Map keyed by sessionId), the
|
|
10
|
-
* NON-terminal suspend/needs_review parks that KEEP the claim, the markResuming CAS that resets cancel/preempt, the
|
|
11
|
-
* owner null-safe guard on the flag/heartbeat paths (`<=>` → JS `(a??null)===(b??null)`), and the keyset list order.
|
|
12
|
-
*/
|
|
13
1
|
import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
|
|
14
|
-
/** Null-safe owner equality — the JS twin of the SQL `owner <=> ?` guard (a legacy null-owner run matches owner=null). */
|
|
15
2
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
16
3
|
export class MemoryRunStore {
|
|
17
4
|
runs = new Map();
|
|
18
|
-
/** sessionId → taskId — the single-active-run claim (the `task_active` table). */
|
|
19
5
|
active = new Map();
|
|
20
|
-
/** taskId → events (kept seq-sorted on read). */
|
|
21
6
|
events = new Map();
|
|
22
|
-
/** [868] the checkpoint-table stand-in (see {@link RunStoreCheckpointProbe}); absent ⇒ reapers NO-OP. */
|
|
23
7
|
checkpointProbe;
|
|
24
|
-
/** [868] wire the checkpoint probe (backend assembly, where checkpoint + run store are built together). */
|
|
25
8
|
setCheckpointProbe(probe) {
|
|
26
9
|
this.checkpointProbe = probe;
|
|
27
10
|
}
|
|
@@ -42,7 +25,6 @@ export class MemoryRunStore {
|
|
|
42
25
|
updatedAt: r.updatedAt.toISOString(),
|
|
43
26
|
};
|
|
44
27
|
}
|
|
45
|
-
/** Create a run iff the session has no active run (the task_active unique-key claim). */
|
|
46
28
|
async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
|
|
47
29
|
const claimed = this.active.get(sessionId);
|
|
48
30
|
if (claimed !== undefined)
|
|
@@ -97,12 +79,8 @@ export class MemoryRunStore {
|
|
|
97
79
|
}
|
|
98
80
|
async appendEvent(taskId, seq, type, data) {
|
|
99
81
|
const list = this.events.get(taskId) ?? [];
|
|
100
|
-
// Mirror the (task_id, seq) PK: a duplicate seq is a caller bug (callers use ++seq); ignore a re-append of the
|
|
101
|
-
// same seq rather than corrupt the log (the SQL twin would throw ER_DUP_ENTRY — callers never hit it).
|
|
102
82
|
if (list.some((e) => e.seq === seq))
|
|
103
83
|
return;
|
|
104
|
-
// Deep-clone `data` on WRITE so the stored event is isolated from later caller mutation — the SQL twin
|
|
105
|
-
// JSON.stringify's it into the column, giving the same write-isolation (parity oracle).
|
|
106
84
|
list.push({ seq, type, data: data == null ? null : structuredClone(data), ts: new Date().toISOString() });
|
|
107
85
|
this.events.set(taskId, list);
|
|
108
86
|
}
|
|
@@ -116,19 +94,14 @@ export class MemoryRunStore {
|
|
|
116
94
|
}
|
|
117
95
|
async getEvents(taskId, afterSeq) {
|
|
118
96
|
const list = this.events.get(taskId) ?? [];
|
|
119
|
-
// Deep-clone `data` on READ so each call yields a fresh, mutation-isolated object — the SQL twin re-parses the
|
|
120
|
-
// JSON column per read (parity oracle); a shallow `{...e}` would alias `data` into the stored Map (review footgun).
|
|
121
97
|
return list
|
|
122
98
|
.filter((e) => e.seq > afterSeq)
|
|
123
99
|
.sort((a, b) => a.seq - b.seq)
|
|
124
100
|
.map((e) => ({ ...e, data: e.data == null ? null : structuredClone(e.data) }));
|
|
125
101
|
}
|
|
126
|
-
/** [888] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
|
|
127
|
-
* [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
|
|
128
102
|
async setTerminal(taskId, status, result, error) {
|
|
129
103
|
const r = this.runs.get(taskId);
|
|
130
104
|
if (!r) {
|
|
131
|
-
// [2255]③ file 孪生同判据(parity oracle):行缺席时终局动词仍放锁(反查 active)。
|
|
132
105
|
for (const [sessionId, tid] of this.active)
|
|
133
106
|
if (tid === taskId) {
|
|
134
107
|
this.active.delete(sessionId);
|
|
@@ -143,11 +116,9 @@ export class MemoryRunStore {
|
|
|
143
116
|
r.errorCode = result?.errorCode ?? null;
|
|
144
117
|
r.updatedAt = new Date();
|
|
145
118
|
}
|
|
146
|
-
// Release the single-active-run claim (DELETE FROM task_active WHERE task_id = ?).
|
|
147
119
|
if (this.active.get(r.sessionId) === taskId)
|
|
148
120
|
this.active.delete(r.sessionId);
|
|
149
121
|
}
|
|
150
|
-
/** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard). */
|
|
151
122
|
async setSuspended(taskId) {
|
|
152
123
|
const r = this.runs.get(taskId);
|
|
153
124
|
if (r && (r.status === "running" || r.status === "suspended")) {
|
|
@@ -155,7 +126,6 @@ export class MemoryRunStore {
|
|
|
155
126
|
r.updatedAt = new Date();
|
|
156
127
|
}
|
|
157
128
|
}
|
|
158
|
-
/** design/80 D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review. */
|
|
159
129
|
async setNeedsReview(taskId) {
|
|
160
130
|
const r = this.runs.get(taskId);
|
|
161
131
|
if (r && (r.status === "running" || r.status === "needs_review")) {
|
|
@@ -163,7 +133,6 @@ export class MemoryRunStore {
|
|
|
163
133
|
r.updatedAt = new Date();
|
|
164
134
|
}
|
|
165
135
|
}
|
|
166
|
-
/** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags. */
|
|
167
136
|
async markResuming(taskId) {
|
|
168
137
|
const r = this.runs.get(taskId);
|
|
169
138
|
if (!r || (r.status !== "suspended" && r.status !== "needs_review"))
|
|
@@ -181,33 +150,27 @@ export class MemoryRunStore {
|
|
|
181
150
|
const r = this.runs.get(taskId);
|
|
182
151
|
return r ? this.toRecord(r) : undefined;
|
|
183
152
|
}
|
|
184
|
-
/** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
|
|
185
153
|
async listRuns(opts) {
|
|
186
|
-
// 夜测 N1(2026-07-09,LOW):a non-date cursor.createdAt made the four backends diverge (PG threw a raw
|
|
187
|
-
// DB error; the other three returned an empty page). HTTP's decodeCursor pre-validates, so this is
|
|
188
|
-
// defence-in-depth for direct store callers — normalize ALL backends to the empty page.
|
|
189
154
|
if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
|
|
190
155
|
return [];
|
|
191
156
|
const cursorAt = opts.cursor ? Date.parse(opts.cursor.createdAt) : undefined;
|
|
192
157
|
const rows = [...this.runs.values()]
|
|
193
158
|
.filter((r) => (opts.status ? r.status === opts.status : true))
|
|
194
|
-
.filter((r) => (opts.jobId !== undefined ? r.jobId === opts.jobId : true))
|
|
195
|
-
.filter((r) => (opts.source !== undefined ? r.source === opts.source : true))
|
|
196
|
-
.filter((r) => (opts.owner !== undefined ? r.owner === opts.owner : true))
|
|
159
|
+
.filter((r) => (opts.jobId !== undefined ? r.jobId === opts.jobId : true))
|
|
160
|
+
.filter((r) => (opts.source !== undefined ? r.source === opts.source : true))
|
|
161
|
+
.filter((r) => (opts.owner !== undefined ? r.owner === opts.owner : true))
|
|
197
162
|
.filter((r) => {
|
|
198
163
|
if (cursorAt === undefined)
|
|
199
164
|
return true;
|
|
200
165
|
const t = r.createdAt.getTime();
|
|
201
|
-
return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId);
|
|
166
|
+
return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId);
|
|
202
167
|
})
|
|
203
168
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
204
169
|
return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
|
|
205
170
|
}
|
|
206
|
-
/** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
|
|
207
171
|
async listSessions(opts) {
|
|
208
172
|
const bySession = new Map();
|
|
209
173
|
for (const r of this.runs.values()) {
|
|
210
|
-
// exact owner filter (SQL `owner = ?`); "" is a valid exact owner (B10). `includeUnowned` = [4198] 窄臂第三条件(SQL `OR owner IS NULL`)。
|
|
211
174
|
if (opts.owner !== undefined && r.owner !== opts.owner && !(opts.includeUnowned === true && r.owner === null))
|
|
212
175
|
continue;
|
|
213
176
|
const arr = bySession.get(r.sessionId) ?? [];
|
|
@@ -215,12 +178,11 @@ export class MemoryRunStore {
|
|
|
215
178
|
bySession.set(r.sessionId, arr);
|
|
216
179
|
}
|
|
217
180
|
const cursorAt = opts.cursor ? Date.parse(opts.cursor.lastActivityAt) : undefined;
|
|
218
|
-
const qlc = opts.q?.toLowerCase();
|
|
181
|
+
const qlc = opts.q?.toLowerCase();
|
|
219
182
|
const summaries = [];
|
|
220
183
|
for (const [sessionId, arr] of bySession) {
|
|
221
184
|
if (qlc && !arr.some((r) => r.objectivePreview?.toLowerCase().includes(qlc)))
|
|
222
185
|
continue;
|
|
223
|
-
// rn=1 = latest run (createdAt DESC, taskId DESC) → preview/status/owner.
|
|
224
186
|
const sorted = [...arr].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
225
187
|
const latest = sorted[0];
|
|
226
188
|
const times = arr.map((r) => r.createdAt.getTime());
|
|
@@ -234,8 +196,8 @@ export class MemoryRunStore {
|
|
|
234
196
|
runCount: arr.length,
|
|
235
197
|
objectivePreview: latest.objectivePreview,
|
|
236
198
|
lastStatus: latest.status,
|
|
237
|
-
lastRunId: latest.taskId,
|
|
238
|
-
title: null,
|
|
199
|
+
lastRunId: latest.taskId,
|
|
200
|
+
title: null,
|
|
239
201
|
});
|
|
240
202
|
}
|
|
241
203
|
return summaries
|
|
@@ -248,19 +210,8 @@ export class MemoryRunStore {
|
|
|
248
210
|
.sort((a, b) => Date.parse(b.lastActivityAt) - Date.parse(a.lastActivityAt) || (a.sessionId < b.sessionId ? 1 : a.sessionId > b.sessionId ? -1 : 0))
|
|
249
211
|
.slice(0, opts.limit);
|
|
250
212
|
}
|
|
251
|
-
/**
|
|
252
|
-
* E21 (§0.5 delete) — purge the runs ledger for one session, scoped by `session_id` ALONE (per-row owner is the
|
|
253
|
-
* submitting principal and diverges from the session owner — full 判据 see the SQL twin's head-note, F-A);
|
|
254
|
-
* aborts (returns {active}) if a run is live.
|
|
255
|
-
*
|
|
256
|
-
* `expectedSessionOwner` 是 SQL 双生的**化身围栏**入参(事务内复核 `session_meta.owner`,防「A 验权后 A' 删完、
|
|
257
|
-
* 别人用同一 uuid 重新登记」那一形)。本孪生**执行不了**它:local 车道的会话表在另一只店里,这只账本没有
|
|
258
|
-
* session_meta 可读 —— 参数只作契约对齐(签名不齐会让 RunStore 联合类型的调用点直接不可调)。local 的替代
|
|
259
|
-
* 围栏是两条:①协调器在本调用**紧前**复读一次 canonical owner(不齐即整条中止);②数据根的独占 BootLock
|
|
260
|
-
* 保证一个数据根恒一个进程,交错只可能发生在同进程的 await 缝里。这条不对称是**明写**的,不是遗漏。
|
|
261
|
-
*/
|
|
262
213
|
async deleteBySession(sessionId, expectedSessionOwner) {
|
|
263
|
-
void expectedSessionOwner;
|
|
214
|
+
void expectedSessionOwner;
|
|
264
215
|
const claimed = this.active.get(sessionId);
|
|
265
216
|
if (claimed !== undefined)
|
|
266
217
|
return { active: claimed };
|
|
@@ -274,7 +225,6 @@ export class MemoryRunStore {
|
|
|
274
225
|
}
|
|
275
226
|
return { removed };
|
|
276
227
|
}
|
|
277
|
-
/** 用量扫窗——in-memory 腿:遍历窗内行,投影同 usage-analytics 纯函数(SQL 孪生 parity)。 */
|
|
278
228
|
async usageScan(fromMs, toMs, opts) {
|
|
279
229
|
const limit = Math.min(opts?.limit ?? USAGE_SCAN_LIMIT, USAGE_SCAN_LIMIT);
|
|
280
230
|
const all = [];
|
|
@@ -295,14 +245,13 @@ export class MemoryRunStore {
|
|
|
295
245
|
for (const r of this.runs.values()) {
|
|
296
246
|
if (r.createdAt.getTime() < sinceMs)
|
|
297
247
|
continue;
|
|
298
|
-
const key = JSON.stringify([r.source, r.status]);
|
|
248
|
+
const key = JSON.stringify([r.source, r.status]);
|
|
299
249
|
const cur = counts.get(key) ?? { source: r.source, status: r.status, count: 0 };
|
|
300
250
|
cur.count += 1;
|
|
301
251
|
counts.set(key, cur);
|
|
302
252
|
}
|
|
303
253
|
return [...counts.values()];
|
|
304
254
|
}
|
|
305
|
-
/** Fail stale `running` rows (crashed-run backstop) + release task_active for any now-terminal row. */
|
|
306
255
|
async reapStale(olderThanMs) {
|
|
307
256
|
const cutoff = Date.now() - olderThanMs;
|
|
308
257
|
let reaped = 0;
|
|
@@ -314,20 +263,15 @@ export class MemoryRunStore {
|
|
|
314
263
|
reaped += 1;
|
|
315
264
|
}
|
|
316
265
|
}
|
|
317
|
-
// Release the claim for TERMINAL rows only (suspended/needs_review intentionally keep it).
|
|
318
266
|
this.releaseTerminalClaims();
|
|
319
267
|
return reaped;
|
|
320
268
|
}
|
|
321
|
-
/** [868] shared tail of both suspended-run reapers: flip one parked row → failed('approval.expired'), the
|
|
322
|
-
* in-memory twin of the SQL UPDATE (`error`/`error_code` set directly — no result blob to derive from). */
|
|
323
269
|
failExpiredPark(r) {
|
|
324
270
|
r.status = "failed";
|
|
325
271
|
r.error = "approval expired before decision";
|
|
326
272
|
r.errorCode = "approval.expired";
|
|
327
273
|
r.updatedAt = new Date();
|
|
328
274
|
}
|
|
329
|
-
/** Release the claim for TERMINAL rows only — suspended/needs_review keep it (the `DELETE ta … WHERE tr.status
|
|
330
|
-
* NOT IN ('running','suspended','needs_review')` twin, shared by reapStale + the [868] suspended reapers). */
|
|
331
275
|
releaseTerminalClaims() {
|
|
332
276
|
for (const [sessionId, taskId] of [...this.active]) {
|
|
333
277
|
const r = this.runs.get(taskId);
|
|
@@ -335,22 +279,12 @@ export class MemoryRunStore {
|
|
|
335
279
|
this.active.delete(sessionId);
|
|
336
280
|
}
|
|
337
281
|
}
|
|
338
|
-
/**
|
|
339
|
-
* [868] Durable F4 expiry — the local twin of TiDBRunStore.reapSuspended, driven by the injected checkpoint
|
|
340
|
-
* probe instead of a SQL JOIN (formerly a NO-OP, which was one of the five closed doors in the "suspended run
|
|
341
|
-
* locks the session forever" incident). Semantics verbatim from the SQL twin: suspended ∧ updatedAt < cutoff
|
|
342
|
-
* ∧ NOT probe.hasPending(session) → failed 'approval expired before decision' / error_code 'approval.expired'
|
|
343
|
-
* + release the task_active claim. The NOT-pending clause is the design/80 D-D adversarial-blocker guard
|
|
344
|
-
* (SQL 孪生现在的真身:`run-store-sql.ts` 的 `reapSuspended`;`tidb-run-store.ts` 该壳文件已删(5.0.0)): a row whose checkpoint is STILL pending belongs to the checkpoint-state
|
|
345
|
-
* sweeps (SLA deny-sweep / failSuspendedWithExpiredCheckpoint) — time-reaping it here would orphan a model
|
|
346
|
-
* leg on a now-unlocked session. Probe absent ⇒ honest NO-OP (never kill a park without the guard).
|
|
347
|
-
*/
|
|
348
282
|
async reapSuspended(olderThanMs) {
|
|
349
283
|
const probe = this.checkpointProbe;
|
|
350
284
|
if (!probe) {
|
|
351
285
|
this.releaseTerminalClaims();
|
|
352
286
|
return 0;
|
|
353
|
-
}
|
|
287
|
+
}
|
|
354
288
|
const cutoff = Date.now() - olderThanMs;
|
|
355
289
|
let reaped = 0;
|
|
356
290
|
for (const r of this.runs.values()) {
|
|
@@ -358,10 +292,7 @@ export class MemoryRunStore {
|
|
|
358
292
|
continue;
|
|
359
293
|
const snap = r.updatedAt.getTime();
|
|
360
294
|
if (await probe.hasPending(r.sessionId))
|
|
361
|
-
continue;
|
|
362
|
-
// codex H2:probe 的 await 让出事件循环——markResuming(approve 恢复)/cancel/新一轮 park 可能已改行。
|
|
363
|
-
// 复核与改写之间无 await(Node 单线程 ⇒ 原子);状态或 updatedAt 变了就放手,绝不覆盖一条已被别腿
|
|
364
|
-
// 认领的行(SQL 孪生靠单条条件 UPDATE 天然原子,这里的 snap 复核就是它的本地等价)。
|
|
295
|
+
continue;
|
|
365
296
|
if (r.status !== "suspended" || r.updatedAt.getTime() !== snap)
|
|
366
297
|
continue;
|
|
367
298
|
this.failExpiredPark(r);
|
|
@@ -370,19 +301,12 @@ export class MemoryRunStore {
|
|
|
370
301
|
this.releaseTerminalClaims();
|
|
371
302
|
return reaped;
|
|
372
303
|
}
|
|
373
|
-
/**
|
|
374
|
-
* [868] design/80 §3 inv#3 crash-safe backstop — the local twin of TiDBRunStore.failSuspendedWithExpiredCheckpoint
|
|
375
|
-
* (checkpoint-STATE-driven, not time-driven; runs UNCONDITIONALLY every reaper tick, no APPROVAL_TIMEOUT_SEC gate).
|
|
376
|
-
* Semantics verbatim from the SQL twin: (suspended ∨ needs_review) ∧ probe.hasExpired(session) ∧ NOT
|
|
377
|
-
* probe.hasPending(session) → same fail + claim release. The NOT-pending clause covers a re-suspended session
|
|
378
|
-
* that minted a NEW pending gate (wq64gmm5e: an abandoned needs_review park leaks exactly like a suspended one).
|
|
379
|
-
*/
|
|
380
304
|
async failSuspendedWithExpiredCheckpoint() {
|
|
381
305
|
const probe = this.checkpointProbe;
|
|
382
306
|
if (!probe) {
|
|
383
307
|
this.releaseTerminalClaims();
|
|
384
308
|
return 0;
|
|
385
|
-
}
|
|
309
|
+
}
|
|
386
310
|
let reaped = 0;
|
|
387
311
|
for (const r of this.runs.values()) {
|
|
388
312
|
if (r.status !== "suspended" && r.status !== "needs_review")
|
|
@@ -393,7 +317,6 @@ export class MemoryRunStore {
|
|
|
393
317
|
continue;
|
|
394
318
|
if (await probe.hasPending(r.sessionId))
|
|
395
319
|
continue;
|
|
396
|
-
// codex H2 同款 snap 复核(两个 await 窗都盖住):行被别腿改过就放手。
|
|
397
320
|
if (r.status !== parkedAs || r.updatedAt.getTime() !== snap)
|
|
398
321
|
continue;
|
|
399
322
|
this.failExpiredPark(r);
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory SessionPolicyStore + the service's `deleteBySession` extension (E21 purge / 2c overwrite-dst wipe).
|
|
3
|
-
*
|
|
4
|
-
* WHY THIS EXISTS (and not core's `InMemorySessionPolicyStore`): core's in-memory/file policy stores implement only
|
|
5
|
-
* the core interface (getRules/putRules/listBySession) — they have NO deletion seam, so neither the E21 session purge
|
|
6
|
-
* nor the 2c `overwrite-dst` subsystem wipe can remove policy rows on a backend that uses them (the SQL twins carry
|
|
7
|
-
* `deleteBySession` as a service extension; see session-policy-store-sql.ts). This store is the delete-capable
|
|
8
|
-
* in-memory twin. The seam itself is also requested upstream (core: optional `deleteBySession?` on the interface +
|
|
9
|
-
* the File/InMemory impls) — if core lands it, this class can retire in favor of core's.
|
|
10
|
-
*
|
|
11
|
-
* BYTE-MATCH DISCIPLINE: getRules/putRules/listBySession replicate core's `InMemorySessionPolicyStore` semantics
|
|
12
|
-
* exactly (same key shape, same CAS `conflict` on expectedRev mismatch, same tighten-only `loosen_forbidden` gate via
|
|
13
|
-
* core's exported `normalizeRules`/`loosenReasons`/`stripRev`, same rev start/bump). Any divergence is a bug here.
|
|
14
|
-
*/
|
|
15
1
|
import { normalizeRules, loosenReasons, stripRev, SessionPolicyError, } from "@sema-agent/core";
|
|
16
2
|
export class MemorySessionPolicyStore {
|
|
17
3
|
map = new Map();
|
|
@@ -48,9 +34,6 @@ export class MemorySessionPolicyStore {
|
|
|
48
34
|
}
|
|
49
35
|
return out;
|
|
50
36
|
}
|
|
51
|
-
/** Purge ALL policy rows for a session, across every principal. Scoped by session_id alone — the caller
|
|
52
|
-
* (E21 route / importSession overwrite-dst) already proved ownership. Shape = core 1.423's optional
|
|
53
|
-
* `SessionPolicyStore.deleteBySession?` seam (`Promise<void>` — the count was incidental; nothing consumed it). */
|
|
54
37
|
async deleteBySession(sessionId) {
|
|
55
38
|
for (const k of [...this.map.keys()]) {
|
|
56
39
|
const [sid] = JSON.parse(k);
|