@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,66 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #264 v2-c —— `POST /v1/memory/{export,import}` 两口的**引擎接线**(core 5.40.0 design/178 v2-c)。
|
|
3
|
-
*
|
|
4
|
-
* ## 为什么需要这个模块(而不是在 main.ts 里直接 new)
|
|
5
|
-
*
|
|
6
|
-
* 本仓**没有**一个长命的 `MemoryEngine` 实例:`boot/stores.ts` 装配出来的 `memoryEngine` 是
|
|
7
|
-
* `{ backend, root }` 二元组,真正的引擎由 **core 的 Runner 每任务现构**
|
|
8
|
-
* (`core/runner/prepare-memory.js`),而且构出来的 `memoryDir` 是**按任务的 scope 平面**决定的
|
|
9
|
-
* (personal / project / dual-root 三形,还要看仓根、项目标记、pinned 目录)。
|
|
10
|
-
* 也就是说「这个部署的那一个记忆引擎」这个东西**在本仓不存在** —— HTTP 面若要一个引擎,必须自己构。
|
|
11
|
-
*
|
|
12
|
-
* ## 为什么自己构是安全的:这两个面是 **backend-scoped**,不碰 `memoryDir`
|
|
13
|
-
*
|
|
14
|
-
* 亲读 core 5.40.0 的 `memory-engine/engine.js`(不信 JSDoc,读实现):
|
|
15
|
-
* · `exportMemoryScopes` 全身只用 `this.backend.exportSnapshotOf` 与 `this.now()`;
|
|
16
|
-
* · `importMemoryBundle` 全身只用 `this.backend.importBundleCommit`(校验段是纯函数 `memoryBundleInvalid`)。
|
|
17
|
-
* 两条路径**都不读** `memoryDir` / `controlDir` —— 它们是**店级**的单锁复合面(一个 epoch 里把五张
|
|
18
|
-
* 店面围起来),不是「某个任务挂载的那棵树」上的操作。因此本模块传给构造器的 `memoryDir` 只是**构造器
|
|
19
|
-
* 的必填座**,对这两条路径无影响。
|
|
20
|
-
*
|
|
21
|
-
* 🔴 这条判断是承重的,所以它有**机器钉**而不是只有这段注释:`test/memory-bundle-engine.test.ts` 用一只
|
|
22
|
-
* 假 backend 断言两条路径确实落到 `exportSnapshotOf` / `importBundleCommit`,且给一个**不存在的**
|
|
23
|
-
* `memoryDir` 也照样工作。core 哪天让这两条路径去读挂载树,那道钉当场红 —— 而不是在某个部署上静默
|
|
24
|
-
* 读错一棵树。
|
|
25
|
-
*
|
|
26
|
-
* ## 能力缺席在**构造期**判定(不挂一个注定失败的口)
|
|
27
|
-
*
|
|
28
|
-
* backend 没有那两个可选面(本仓两只 SQL 记忆孪生就没有)⇒ 本工厂返回 **undefined**,两口整个不挂载,
|
|
29
|
-
* HTTP 面诚实 501。
|
|
30
|
-
*
|
|
31
|
-
* 🔴 首版把这件事当成**运行期**事实(照常挂载,让 core 每次抛 `memory.export_incomplete` / 422),
|
|
32
|
-
* 被 codex 对抗复审驳倒(#277 提货批 [medium],验真后修):`typeof backend.exportSnapshotOf === "function"`
|
|
33
|
-
* 是一次**属性检查**,boot 期就判得出;而且它是**永久**缺席(换后端才会变),不是瞬时状态。照旧挂着
|
|
34
|
-
* 就成了「能力位说 yes、每一次调用都确定性失败」——本仓「says yes ⟺ route works」是结构性承诺,
|
|
35
|
-
* 一个恒假的 yes 会让壳给用户开出一条走不通的迁移流程。
|
|
36
|
-
*
|
|
37
|
-
* ⚠️ 两面是**一个**能力,判据取合取:只有一半也不挂。半挂的形是「导得出、导不进」,而这条产品面的
|
|
38
|
-
* 全部意义是那趟往返。
|
|
39
|
-
*
|
|
40
|
-
* ⚠️ 与**运行期**拒不是一回事,别合并:面齐备但店自陈说不清(chain-degraded)时,core 照样抛
|
|
41
|
-
* `memory.export_incomplete` —— 那是诚实的运行期拒,附录 A 单列一条。绝不在这里回落到「用通用读面拼
|
|
42
|
-
* 一个包」:core 明写那正是它拒绝生产的形(a governance-less export is the laundering shape)。
|
|
43
|
-
*/
|
|
44
1
|
import { MemoryEngine } from "@sema-agent/core";
|
|
45
|
-
/**
|
|
46
|
-
* 在**已装配的记忆后端**上建 bundle 两面。命名照 CLAUDE.md 工厂律:返回的是带行为的活对象 ⇒ `create*`。
|
|
47
|
-
*
|
|
48
|
-
* `memoryDir` = `boot/stores.ts` 的 `memoryEngine.root`(该部署的记忆工作根)。见文件头:这两条路径
|
|
49
|
-
* 不读它,传它是为了不发明第二个「引擎根」的概念 —— 万一 core 日后真用上,它拿到的也是本部署唯一的
|
|
50
|
-
* 那个根,而不是一个本模块凭空编出来的路径。
|
|
51
|
-
*/
|
|
52
2
|
export function createMemoryBundleFaces(store) {
|
|
53
|
-
// 构造期能力判定(见文件头):两个可选面缺任一 ⇒ 整个不挂。判据直接读 backend 的面,不看后端方言名
|
|
54
|
-
// ——「认能力不认实现名」是本仓的既有教训(按部署名判「组件已装」认不出 helm 装的那次)。
|
|
55
3
|
const b = store.backend;
|
|
56
4
|
if (typeof b.exportSnapshotOf !== "function" || typeof b.importBundleCommit !== "function")
|
|
57
5
|
return undefined;
|
|
58
6
|
const engine = new MemoryEngine({ backend: store.backend, memoryDir: store.root });
|
|
59
7
|
return {
|
|
60
8
|
exportScopes: (scopes) => engine.exportMemoryScopes(scopes),
|
|
61
|
-
// 唯一一次窄化,就发生在**引擎边界上**:core 的 d.ts 要 MemoryExportBundle,而它的实装第一件事
|
|
62
|
-
// 就是 JSON 深拷 + `memoryBundleInvalid` 整包校验 —— 也就是说这个形参在运行期本就接受任意值,
|
|
63
|
-
// 由它自己裁。把窄化收在这一行(而不是让它一路污染 HTTP 层的类型)是「谁裁决谁承型」。
|
|
64
9
|
importBundle: (bundle, opts) => engine.importMemoryBundle(bundle, opts),
|
|
65
10
|
};
|
|
66
11
|
}
|
package/dist/memory-export.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
/** All entries of one scope, deterministic id order (both backend read faces ORDER BY id). */
|
|
2
1
|
export async function exportMemoryScope(backend, scope) {
|
|
3
2
|
const headers = await backend.listHeaders([scope]);
|
|
4
3
|
if (headers.length === 0)
|
|
5
4
|
return [];
|
|
6
5
|
const entries = await backend.getByIds(headers.map((h) => h.id));
|
|
7
|
-
// Defense-in-depth: getByIds is id-keyed (no scope filter). The ids came from THIS scope's own
|
|
8
|
-
// headers, but a header-read/entry-read race (concurrent cross-scope re-add) must never leak a
|
|
9
|
-
// foreign scope's entry into an owner-gated export — filter honestly.
|
|
10
6
|
return entries.filter((e) => e.scope === scope);
|
|
11
7
|
}
|
|
12
8
|
//# sourceMappingURL=memory-export.js.map
|
package/dist/memory-posture.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 纯推导(`build*`:产纯数据)。成因的判序是**显式关停 > 租户隔离 > 车道分裂**:
|
|
3
|
-
* operator 自己关的那一形必须报成 `engine_off`,报成隔离/车道会把人指向一个改了也没用的旋钮。
|
|
4
|
-
*/
|
|
5
1
|
export function buildMemoryPosture(facts) {
|
|
6
2
|
const { config, engineWired, vectorMode, remoteLaneDark } = facts;
|
|
7
3
|
const embedder = config.memoryEmbedder;
|
|
@@ -13,8 +9,7 @@ export function buildMemoryPosture(facts) {
|
|
|
13
9
|
? "multi_tenant_file_backend"
|
|
14
10
|
: remoteLaneDark
|
|
15
11
|
? "remote_lane_split"
|
|
16
|
-
:
|
|
17
|
-
// 不会被建出来。仍然给一个词而不是 null:点亮态为假时 `darkReason` 必须有值,才谈得上机器可读。
|
|
12
|
+
:
|
|
18
13
|
"engine_off";
|
|
19
14
|
return {
|
|
20
15
|
enabled: engineWired,
|
|
@@ -23,19 +18,10 @@ export function buildMemoryPosture(facts) {
|
|
|
23
18
|
darkReason,
|
|
24
19
|
multiTenant: config.requirePrincipal === true,
|
|
25
20
|
embedder: embedder !== undefined ? { model: embedder.model, dimensions: embedder.dimensions } : null,
|
|
26
|
-
// #299:键缺席 ⇒ `null`(「本部署没设」),不折成 core 的缺省词 —— 见字段 JSDoc 的诚实纪律。
|
|
27
21
|
delegationEvidence: config.memoryDelegationEvidence ?? null,
|
|
28
|
-
// #307 件4:同一条诚实纪律 —— 键缺席 ⇒ `null`(「本部署没设」),不折成 core 的缺省词 `carry`。
|
|
29
22
|
provenance: config.memoryProvenance ?? null,
|
|
30
23
|
};
|
|
31
24
|
}
|
|
32
|
-
/**
|
|
33
|
-
* 能力面(`GET /v1/capabilities` 的 `memoryEngine` 位)的**窄投影**:点亮 ⇒ 后端形 + 档位;
|
|
34
|
-
* 暗 ⇒ `false`(位在场且假 = 「这台认识这一位、本部署没点亮」,与位整个缺席 = 老 server 两回事)。
|
|
35
|
-
*
|
|
36
|
-
* 刻意**不**外泄 `darkReason` / `multiTenant` / embedder 身份:能力面是任何持凭证调用方都能读的面,
|
|
37
|
-
* 它只需要回答「我能不能用记忆、用的是哪一档」;成因与身份是运维诊断料,归 operator-only 那一面。
|
|
38
|
-
*/
|
|
39
25
|
export function projectMemoryEngineCapability(posture) {
|
|
40
26
|
if (posture === undefined || !posture.enabled)
|
|
41
27
|
return false;
|
package/dist/memory-scope.js
CHANGED
|
@@ -1,124 +1,43 @@
|
|
|
1
|
-
// design/158 N17: the memory-plane resolvers moved VERBATIM out of security.ts (lens4 §4.1 — "budget.ts"'s
|
|
2
|
-
// sibling finding: security.ts's name says auth, but ~110 of its 765 lines were memory-scope config
|
|
3
|
-
// resolution with nothing to do with authentication). security.ts imports memoryScopeFor for its own
|
|
4
|
-
// createAuthorizer; consumers import all four from HERE ([2354] 兼容面全清 — the security.ts compat re-export is gone); index.ts's npm-facing re-export is unaffected (package.json
|
|
5
|
-
// "exports" only ever exposed "." and "./main" — dist/security.js was never a reachable deep-import path).
|
|
6
1
|
import { join } from "node:path";
|
|
7
2
|
import { FileMemoryEngineBackend, resolveMemoryEngineRoot, deriveControlPlaneDir, formatUserScope, formatProjScope } from "@sema-agent/core";
|
|
8
|
-
import { assertMemoryScopeWidth } from "./plugins/memory-key-guards.js";
|
|
9
|
-
/**
|
|
10
|
-
* design/142 §1.2 的 v2 typed scope-key 前缀表 —— **单一属主**(A-002.4:`config.ts` 的
|
|
11
|
-
* MEMORY_SYNC_SCOPE 派生处曾有一份逐字副本,两处咬合用途不同[盖 v2 契约标 vs 判要不要再包一层
|
|
12
|
-
* `formatUserScope`],但判据必须是同一份,否则 core 加第五种 typed 前缀时两处各自漂)。
|
|
13
|
-
*
|
|
14
|
-
* 🔴 表按 core 的 {@link ParsedScopeKey} 判别词**穷举**:`Record<Exclude<kind,"legacy">, …>` 少一格
|
|
15
|
-
* 编译红、多一格也编译红 —— core 新增/改名一种 typed kind,本表在编译期就先红,而不是等某个运行期
|
|
16
|
-
* 分支静默走错。(`legacy` 排除在外正是本谓词要判的反面:无可识别前缀 = 不透明旧键。)
|
|
17
|
-
*/
|
|
3
|
+
import { assertMemoryScopeWidth } from "./plugins/memory-key-guards.js";
|
|
18
4
|
const V2_SCOPE_PREFIX_BY_KIND = {
|
|
19
5
|
user: "user:",
|
|
20
6
|
org: "org:",
|
|
21
7
|
proj: "proj:",
|
|
22
8
|
userproj: "userproj:",
|
|
23
9
|
};
|
|
24
|
-
/** 前缀词表本体(供门/诊断读;判定一律走 {@link isV2ScopeKey},别在别处重写 startsWith 链)。 */
|
|
25
10
|
export const V2_SCOPE_PREFIXES = Object.freeze(Object.values(V2_SCOPE_PREFIX_BY_KIND));
|
|
26
|
-
/**
|
|
27
|
-
* 一个 scope 键是否带 v2 typed 前缀。
|
|
28
|
-
*
|
|
29
|
-
* ⚠️ 判据刻意停在**前缀**层,不升级成 core 的 `parseScopeKey`:后者对「带可识别前缀但结构坏」的键
|
|
30
|
-
* 是 fail-loud 抛错,而本谓词的两个调用点都只是在决定「要不要挂 v2 标 / 要不要再包一层」——把抛点
|
|
31
|
-
* 挪到这里会把 core 的校验时机整体前移(行为面改动,不在本条案的射程内)。坏键仍由 core 在它自己的
|
|
32
|
-
* 校验点 fail-loud。
|
|
33
|
-
*/
|
|
34
11
|
export function isV2ScopeKey(key) {
|
|
35
12
|
return V2_SCOPE_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
36
13
|
}
|
|
37
|
-
/** Resolve the memory scope for a request (design/138 S1 — the file-based memory engine is the only plane).
|
|
38
|
-
*
|
|
39
|
-
* SINGLE-USER (REQUIRE_PRINCIPAL !== "true"): ONE shared scope — `config.memoryScope` (default "local"),
|
|
40
|
-
* regardless of any principal label the caller self-reports (a single-user worker's `anon:*` / ad-hoc
|
|
41
|
-
* principals are surface labels, not isolation identities — keying memory by them would split the one
|
|
42
|
-
* user's memory across surfaces). Explicit `MEMORY_SCOPE` wins (it IS config.memoryScope).
|
|
43
|
-
*
|
|
44
|
-
* MULTI-TENANT (REQUIRE_PRINCIPAL=true): always `undefined` ⇒ no `spec.memory` ⇒ memory is DARK,
|
|
45
|
-
* fail-closed. The engine's file basement carries NO tenant isolation (one shared directory tree), so
|
|
46
|
-
* handing tenants per-principal scopes over it would be a cross-tenant bleed surface; until a
|
|
47
|
-
* tenant-isolated backend exists, multi-tenant memory is off by construction. */
|
|
48
|
-
/** R5(批γ):memory scope 列宽(两方言 VARCHAR(190) 同宽)。principal 上限 190 **字符**在
|
|
49
|
-
* security.ts 把身份轴守住了,但 `formatUserScope`/`formatProjScope` 的段编码(百分号转义)会
|
|
50
|
-
* **膨胀**——非 ASCII principal 编码后可超列宽,此前落裸 SQL 错。产出处响亮拒,错误可分类。
|
|
51
|
-
*
|
|
52
|
-
* #271 件1:定义**搬去** `plugins/memory-key-guards.ts`(列宽与 store 写口同源单一属主),此处
|
|
53
|
-
* 只再导出——旧 import 路径(本文件)逐字不变,`memoryScopeFor` 的产出处消费点也不变。 */
|
|
54
14
|
export { MEMORY_SCOPE_COLUMN_CHARS, assertMemoryScopeWidth } from "./plugins/memory-key-guards.js";
|
|
55
15
|
export function memoryScopeFor(config, principal, projectId) {
|
|
56
16
|
if (config.requirePrincipal === true) {
|
|
57
|
-
// S3-TOB(设计 §1.3):multi-tenant lights up ONLY on a DB backend (scope column = tenant
|
|
58
|
-
// partition) — the scope is the VERIFIED principal, never MEMORY_SCOPE (a shared-env scope here would
|
|
59
|
-
// be a cross-tenant bleed, 复审 F6) and never absent-principal (anonymous run ⇒ no memory).
|
|
60
|
-
// NOTE (复审 F-07): the scope column is data PARTITIONING; tenant AUTHORIZATION is exactly this
|
|
61
|
-
// binding — verified principal in, one tenant's scope out. File backend stays dark (no isolation).
|
|
62
|
-
// 142-S1.5(core 1.268.0 到货即切,免费迁移窗=现网点亮前存量 0):bare principal → the v2 typed
|
|
63
|
-
// key `user:<tenant>` via core's formatUserScope (encodeScopeSegment inside — principal chars are
|
|
64
|
-
// segment-encoded, never truncated/collided). Paired with scopeContract:"v2" on the spec face below
|
|
65
|
-
// (opt-in is EXPLICIT per design/142 §1.2 — no prefix sniffing anywhere).
|
|
66
|
-
// 142-S4(派生 v1=项目盘):a SHAPE-VALIDATED projectId(authorizer 的 PROJECT_ID_REGEX 门)
|
|
67
|
-
// 在场 ⇒ 项目盘 v2 键 `proj:<tenant>/<projectId>`(core formatProjScope;tenant 段 = verified
|
|
68
|
-
// principal,身份永远不来自 body — projectId 只挑盘)。铁律:proj: 键经我方 DB 的场景=无仓
|
|
69
|
-
// project(有仓 project 权威=git,根本不到这条派生线)。userproj: 派生不在 v1(待拍板)。
|
|
70
17
|
if (config.memoryEngineBackend === "pg" || config.memoryEngineBackend === "tidb") {
|
|
71
18
|
if (!principal)
|
|
72
19
|
return undefined;
|
|
73
20
|
const scope = projectId ? formatProjScope(principal, projectId) : formatUserScope(principal);
|
|
74
|
-
assertMemoryScopeWidth(scope);
|
|
21
|
+
assertMemoryScopeWidth(scope);
|
|
75
22
|
return scope;
|
|
76
23
|
}
|
|
77
|
-
return undefined;
|
|
24
|
+
return undefined;
|
|
78
25
|
}
|
|
79
|
-
return config.memoryScope;
|
|
26
|
+
return config.memoryScope;
|
|
80
27
|
}
|
|
81
|
-
/**
|
|
82
|
-
* N0 (通宵测试 2026-07-09,三重坐实 e2b+kata+代码): the file memory engine (core design/138) materializes and
|
|
83
|
-
* harvests via `node:fs` on the WORKER's local fs, while a remote exec lane routes the model's file tools to
|
|
84
|
-
* the SANDBOX fs — the planes never meet: materialized memory is invisible inside the sandbox (the injected
|
|
85
|
-
* instructions burn failing Write turns) and sandbox writes are never harvested (add=0). Until core defines
|
|
86
|
-
* the boundary (engine routed through the ExecutionEnv seam, or host-only by contract — relayed on the team
|
|
87
|
-
* channel), a remote lane forces memory DARK unless the operator explicitly vouches both lanes share one fs
|
|
88
|
-
* (`MEMORY_ENGINE_REMOTE_LANE=allow`, e.g. ssh to a host on the same NFS mount).
|
|
89
|
-
*
|
|
90
|
-
* Pure posture probe (memoryEngineBackendFor consumes it; main.ts logs it): `undefined` = no plane split
|
|
91
|
-
* (no remote lane, or the `host` lane), `dark` = split + fail-closed, `forced` = split + operator override.
|
|
92
|
-
*/
|
|
93
28
|
export function memoryEngineRemoteLanePosture(config) {
|
|
94
29
|
const lane = config.remoteExec?.provider;
|
|
95
30
|
if (lane === undefined || lane === "host")
|
|
96
|
-
return undefined;
|
|
31
|
+
return undefined;
|
|
97
32
|
return { lane, posture: config.memoryEngineRemoteLaneAllowed === true ? "forced" : "dark" };
|
|
98
33
|
}
|
|
99
|
-
|
|
100
|
-
* N0 启动告警的**文案**(纯,可单测;boot/stores.ts 的装配点只负责喂「引擎这一腿真的接上了吗」并打日志)。
|
|
101
|
-
*
|
|
102
|
-
* 为什么要按引擎真身分腿(2026-08-12 复扫,已核真):{@link memoryEngineRemoteLanePosture} 的 `dark` 只对
|
|
103
|
-
* **file** 引擎腿有裁决权 —— 车道门的唯一消费者是 {@link memoryEngineBackendFor}。`MEMORY_ENGINE_BACKEND=pg|tidb`
|
|
104
|
-
* 的两条腿在 boot/stores.ts 里**先于**任何车道判断就被点亮(库是持久真身,与手的文件平面无关),于是同一次
|
|
105
|
-
* 启动会先打 `memory_engine_enabled {enabled:true, backend:"pg"}`,几行之后再打一句「memory dark
|
|
106
|
-
* (fail-closed) … Set MEMORY_ENGINE_REMOTE_LANE=allow」—— 两句直接对撞,而且把运维指向一个在该腿上**不接线**
|
|
107
|
-
* 的旋钮(`memoryEngineRemoteLaneAllowed` 的读者只有本文件与那条日志)。日志是运维唯一能看见的部署事实,
|
|
108
|
-
* 一句谎比没有这句更贵。
|
|
109
|
-
*
|
|
110
|
-
* 返回 `undefined` = 本部署没什么可说的(无平面分裂车道 / 记忆整体关 / 多租户 file 腿的 dark 成因是租户隔离,
|
|
111
|
-
* 已由 `memory_engine_enabled` 的 reason 位报因,不在这里重复)。
|
|
112
|
-
*/
|
|
113
|
-
export function buildMemoryRemoteLaneWarn(config,
|
|
114
|
-
/** 记忆引擎这一腿**真的**接上了吗(`memoryEngine !== undefined`)——装配结果,不在本函数里重算。 */
|
|
115
|
-
engineWired) {
|
|
34
|
+
export function buildMemoryRemoteLaneWarn(config, engineWired) {
|
|
116
35
|
const posture = memoryEngineRemoteLanePosture(config);
|
|
117
36
|
if (posture === undefined || !config.memoryEngineEnabled)
|
|
118
37
|
return undefined;
|
|
119
38
|
if (config.memoryEngineBackend !== "file") {
|
|
120
39
|
if (!engineWired)
|
|
121
|
-
return undefined;
|
|
40
|
+
return undefined;
|
|
122
41
|
return {
|
|
123
42
|
lane: posture.lane,
|
|
124
43
|
effect: `memory ON over a remote lane: the "${config.memoryEngineBackend}" memory plane is durable in the DB and is NOT gated by ` +
|
|
@@ -128,7 +47,7 @@ engineWired) {
|
|
|
128
47
|
};
|
|
129
48
|
}
|
|
130
49
|
if (config.requirePrincipal === true)
|
|
131
|
-
return undefined;
|
|
50
|
+
return undefined;
|
|
132
51
|
return posture.posture === "dark"
|
|
133
52
|
? {
|
|
134
53
|
lane: posture.lane,
|
|
@@ -141,77 +60,22 @@ engineWired) {
|
|
|
141
60
|
"WORKER fs; verify the lane really shares it.",
|
|
142
61
|
};
|
|
143
62
|
}
|
|
144
|
-
/**
|
|
145
|
-
* design/138 S1 wiring gate (pure — unit-testable without booting main): build the file-based memory-engine
|
|
146
|
-
* backend for this deployment, or `undefined` when memory must stay dark.
|
|
147
|
-
*
|
|
148
|
-
* - SINGLE-USER TURNKEY ONLY (`requirePrincipal !== true`): the engine's file basement
|
|
149
|
-
* (`FileMemoryEngineBackend` over one directory tree) has NO tenant isolation, so a multi-tenant
|
|
150
|
-
* deployment gets `undefined` — memory dark, fail-closed (never a shared cross-tenant store).
|
|
151
|
-
* - `MEMORY_ENGINE=off` (`config.memoryEngineEnabled === false`) is the explicit operator kill-switch.
|
|
152
|
-
* - REMOTE EXEC LANE (N0): a plane-splitting lane (any non-`host` remoteExec) forces `undefined` — memory
|
|
153
|
-
* dark, fail-closed — unless `MEMORY_ENGINE_REMOTE_LANE=allow` (see memoryEngineRemoteLanePosture above).
|
|
154
|
-
* - Directory: `<root>/memory` where root = `resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot)`
|
|
155
|
-
* (explicit `MEMORY_ENGINE_DIR` → caller's data root → `AGENT_DATA_DIR` env → `~/.ai-agent`). The
|
|
156
|
-
* FileBackend PINS this directory (core §2.7: `directoryRoot` wins over per-repo derivation), so the
|
|
157
|
-
* single user has ONE memory home across repos/tasks — the CC `~/.claude` posture.
|
|
158
|
-
*
|
|
159
|
-
* Returns the backend + the resolved config root (the root also rides on `RunnerDeps.memoryEngineDir` so
|
|
160
|
-
* core derives the B3 control plane beside it).
|
|
161
|
-
*/
|
|
162
63
|
export function memoryEngineBackendFor(config, fallbackRoot) {
|
|
163
64
|
if (config.requirePrincipal === true || !config.memoryEngineEnabled)
|
|
164
65
|
return undefined;
|
|
165
66
|
if (memoryEngineRemoteLanePosture(config)?.posture === "dark")
|
|
166
|
-
return undefined;
|
|
67
|
+
return undefined;
|
|
167
68
|
const root = resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot);
|
|
168
69
|
const dir = join(root, "memory");
|
|
169
|
-
// 🔴 controlDir EXPLICIT: the backend's DEFAULT control plane derives from the ARGLESS
|
|
170
|
-
// resolveMemoryEngineRoot() — i.e. AGENT_DATA_DIR/~/.ai-agent, IGNORING an explicit MEMORY_ENGINE_DIR. With a
|
|
171
|
-
// custom dir the sidecars (ledger/shadow/quarantine) would land under the DEFAULT root while
|
|
172
|
-
// RunnerDeps.memoryEngineDir points core's engine at the custom root — a split control plane. Pin it beside
|
|
173
|
-
// OUR root using core's own derivation (key = the memory dir, the same shape as the backend's default).
|
|
174
70
|
return { backend: new FileMemoryEngineBackend(dir, { controlDir: deriveControlPlaneDir(root, dir) }), root };
|
|
175
71
|
}
|
|
176
|
-
|
|
177
|
-
* MF-30 memory PAUSE (option B, per-request — clay 2026-06-27): build the `TaskSpec.memory` spec for ONE run from
|
|
178
|
-
* the resolved memory `scope` + the request's `memoryWrite` toggle. `memoryWrite === false` ⇒ `writeScope: null`
|
|
179
|
-
* (design/138 engine read-only: normalizeMemorySpec's writeScope:null — materialize/read still works, harvest
|
|
180
|
-
* commits nothing). Absent/true ⇒ normal read+write. No scope (engine off / multi-tenant dark) ⇒ undefined
|
|
181
|
-
* (memory feature off for this run). Pure (testable in isolation) — main.ts's resolveSpec composes it with the
|
|
182
|
-
* engine-backend presence guard.
|
|
183
|
-
*/
|
|
184
|
-
export function memorySpecForRequest(scope, memoryWrite, defaultScopes,
|
|
185
|
-
/** design/170 件A(#148 件3④):origin 盖章的部署形态维(N2)。`multiTenant=true`(requirePrincipal)
|
|
186
|
-
* ⇒ 登记簿 defaultScopes 的 org 键按 **request** 盖章(条目由调用方 projectId 选定=caller 可影响的
|
|
187
|
-
* 选择器,core 准入门据此过目录判决);单用户 ⇒ 一律 deployment(operator 登记簿条目归 deployment,
|
|
188
|
-
* 否则单用户部署被自家规则整拒——v4 §0 伤害①的成立前提)。缺参=旧调用形,不盖章(整键缺席=
|
|
189
|
-
* core legacy 语义,零迁移)。 */
|
|
190
|
-
originPolicy) {
|
|
72
|
+
export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy) {
|
|
191
73
|
if (!scope)
|
|
192
74
|
return undefined;
|
|
193
|
-
|
|
194
|
-
// the key fail-loud under "v2" (design/142 §1.2). Detection here is NOT sniffing: `user:`/`proj:` keys are
|
|
195
|
-
// OUR memoryScopeFor's own mint (formatUserScope/formatProjScope) and `defaultScopes` entries are the
|
|
196
|
-
// OPERATOR's center-registry declaration (config.projects — 登记簿说明其形态=v2 scope 键串),so the
|
|
197
|
-
// prefix is a self/operator-produced marker, never a guess about caller data; a legacy opaque scope
|
|
198
|
-
// (single-user config.memoryScope) with no defaultScopes stays contract-less byte-identical.
|
|
199
|
-
const isV2 = isV2ScopeKey; // A-002.4:词表单一属主(见文件顶部 V2_SCOPE_PREFIX_BY_KIND)
|
|
200
|
-
// 142-S4 defaultScopes 种子(config.projects[projectId].defaultScopes):额外的 READ 层(去重、去 scope
|
|
201
|
-
// 自身、去空串;保 center 声明顺序)。写路由不变:core normalizeMemorySpec 的 writeScope 缺省=scopes 的
|
|
202
|
-
// 最后一层,所以带种子时必须 EXPLICIT 钉 writeScope=派生 scope(否则 harvest 会写进登记簿的最后一个默认
|
|
203
|
-
// scope — 一个共享层,错)。无种子 = 形状逐字节等价旧版(additive 锁)。
|
|
75
|
+
const isV2 = isV2ScopeKey;
|
|
204
76
|
const extras = (defaultScopes ?? []).filter((s, i, arr) => typeof s === "string" && s.length > 0 && s !== scope && arr.indexOf(s) === i);
|
|
205
|
-
// core 5.1.0([2390]②)删单数 `scope` 键:normalizeMemorySpec 只迭代 `scopes`,单数形 ⇒ undefined ⇒
|
|
206
|
-
// memory 静默整体关闭(全窗复审 D7-F1,HIGH——返回值位不触发多余属性检查,旧形编译全绿)。生产者恒产
|
|
207
|
-
// `scopes: [scope, ...extras]`;writeScope 只在带种子时显式钉(core 默认取 scopes 最后一层,无种子时
|
|
208
|
-
// 最后一层就是派生 scope=正确,不钉保持最小形)。
|
|
209
77
|
const layered = extras.length > 0 ? { scopes: [scope, ...extras], writeScope: scope } : { scopes: [scope] };
|
|
210
78
|
const v2 = isV2(scope) || extras.some(isV2) ? { scopeContract: "v2" } : {};
|
|
211
|
-
// 件A origin 盖章(core 5.13.0 `memory.scopeOrigins` 两格语义):只盖 org 键(非 org 不入准入判决,
|
|
212
|
-
// v4 §1);派生/env 位的 `scope` org 形只可能来自 operator 显式 MEMORY_SCOPE(memoryScopeFor 多租户
|
|
213
|
-
// 恒铸 user: 形)⇒ deployment;登记簿种子按部署形态维分格(见参数注)。零 org 键 ⇒ 整键缺席
|
|
214
|
-
// (core legacy 零迁移;非 org 部署形状逐字节不变=additive 锁)。
|
|
215
79
|
const stamped = {};
|
|
216
80
|
if (originPolicy !== undefined) {
|
|
217
81
|
if (scope.startsWith("org:"))
|
|
@@ -222,7 +86,6 @@ originPolicy) {
|
|
|
222
86
|
}
|
|
223
87
|
}
|
|
224
88
|
const origins = Object.keys(stamped).length > 0 ? { scopeOrigins: stamped } : {};
|
|
225
|
-
// memoryWrite === false(MF-30 pause)wins over the layered writeScope pin — 只读语义在有种子时同样成立。
|
|
226
89
|
return memoryWrite === false ? { ...layered, writeScope: null, ...v2, ...origins } : { ...layered, ...v2, ...origins };
|
|
227
90
|
}
|
|
228
91
|
//# sourceMappingURL=memory-scope.js.map
|
|
@@ -1,29 +1,8 @@
|
|
|
1
|
-
// 142-S2.5-W1 —— TOC 同步 client 腿:包 core `syncMemoryScope`(1.275.1 导出,一轮 =
|
|
2
|
-
// 一 RTT 双向,cursor 保留纪律见 core sync-client.js 头注)。本模块只补 core 刻意留白的部署半场:
|
|
3
|
-
// ① transport:fetch POST `{MEMORY_SYNC_URL 去尾斜杠}{path}`(path 由 core 铸,scope 已
|
|
4
|
-
// encodeURIComponent),headers = Bearer + JSON;非 2xx throw(带 status + body 前 200 字节)。
|
|
5
|
-
// core 契约:transport 在任何本地写之前跑,throw = 本轮零本地副作用。
|
|
6
|
-
// ② cursor 持久化:`join(memoryRoot, "sync-cursor-" + encodeScopeSegment(peer) + ".json")`——
|
|
7
|
-
// 读:不存在 ⇒ undefined(首轮);坏 JSON/形状不符/scope-peer 不匹配 ⇒ warn + 当首轮(core 对
|
|
8
|
-
// 错配 cursor 是 fail-loud throw,这里预检降为首轮:空基线重收敛是协议的幂等安全面)。
|
|
9
|
-
// 写:仅 cursorAdvanced 才写(core 保留纪律的落盘半场),tmp+rename 原子。
|
|
10
|
-
// ③ 串行化 + 节流:syncOnce 按 promise 链串行(两轮并发会在同一文件面上交错写);trigger =
|
|
11
|
-
// fire-and-forget,上一轮在飞(排队中/执行中)则跳过(设计:简单 inflight 布尔)。
|
|
12
|
-
// conflicts = logger.warn 逐条上报(不自动 ladder——败者铸 sibling 的解决动作留给 operator/后续单)。
|
|
13
1
|
import { randomBytes } from "node:crypto";
|
|
14
2
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
15
3
|
import { dirname, join } from "node:path";
|
|
16
4
|
import { encodeScopeSegment, syncMemoryScope, } from "@sema-agent/core";
|
|
17
|
-
/** 缺省对端名(core `syncMemoryScope` 同款缺省;cursor 按 (scope,peer) 分区)。 */
|
|
18
5
|
export const DEFAULT_MEMORY_SYNC_PEER = "central";
|
|
19
|
-
/**
|
|
20
|
-
* fetch transport(core `MemorySyncTransport` 的部署实现)。`url` = 中心 server 基址(尾斜杠剥除,
|
|
21
|
-
* core 传来的 path 以 `/` 开头);`token` 走 Authorization: Bearer(与中心侧 gatedPrincipal 的
|
|
22
|
-
* verified-principal 门对齐)。非 2xx throw(status + body 前 200 字节——中心 4xx/5xx 的 typed error
|
|
23
|
-
* body 就是最好的诊断线索);2xx 返回解码后的 JSON(形状校验在 core parseMemorySyncResponse,职责
|
|
24
|
-
* 不重复)。`timeoutMs`(缺省 60s,AbortSignal.timeout)是活性保护:fire-and-forget 轮挂死会把
|
|
25
|
-
* inflight 节流永久卡住(undici 默认 headers 超时 300s 太钝)。
|
|
26
|
-
*/
|
|
27
6
|
export function createMemorySyncTransport(opts) {
|
|
28
7
|
const base = opts.url.replace(/\/+$/, "");
|
|
29
8
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
@@ -41,13 +20,9 @@ export function createMemorySyncTransport(opts) {
|
|
|
41
20
|
return JSON.parse(text);
|
|
42
21
|
};
|
|
43
22
|
}
|
|
44
|
-
/** cursor 文件路径:`memoryRoot`(= RunnerDeps.memoryEngineDir 的 resolved config root,memory/ 目录
|
|
45
|
-
* 旁)下的 per-peer sidecar。peer 段走 core encodeScopeSegment(文件名安全字符纪律与 scope 目录同)。 */
|
|
46
23
|
export function memorySyncCursorPath(memoryRoot, peer) {
|
|
47
24
|
return join(memoryRoot, `sync-cursor-${encodeScopeSegment(peer)}.json`);
|
|
48
25
|
}
|
|
49
|
-
/** 读 cursor:不存在 ⇒ undefined(首轮);坏 JSON / 形状不符 / (scope,peer) 错配 ⇒ warn + undefined
|
|
50
|
-
* (core 对错配 cursor fail-loud throw——这里预检降为首轮,空基线重收敛幂等安全)。 */
|
|
51
26
|
async function loadCursor(path, scope, peer, log) {
|
|
52
27
|
let raw;
|
|
53
28
|
try {
|
|
@@ -83,22 +58,12 @@ async function loadCursor(path, scope, peer, log) {
|
|
|
83
58
|
log.warn("memory_sync_cursor_invalid", { path, scope, peer, note: "shape/identity mismatch — treating as first round" });
|
|
84
59
|
return undefined;
|
|
85
60
|
}
|
|
86
|
-
/** 写 cursor:tmp + rename 原子(半写的 cursor = 下轮坏 JSON 当首轮,重收敛而非错基线)。 */
|
|
87
61
|
async function saveCursor(path, cursor) {
|
|
88
62
|
await mkdir(dirname(path), { recursive: true });
|
|
89
|
-
// #131-3:staging 名带熵(pid+random)——共享 memoryRoot 的跨进程形(server + run-local 同 data 根)
|
|
90
|
-
// 用固定 `.tmp` 会互抢 staging(一侧 rename 走对方的 tmp ⇒ 对方 ENOENT / 写出对方字节);同仓其余
|
|
91
|
-
// 原子写(config-lkg / local-session-store / skills-mcp / workflow-completion-inbox)全带熵,此处对齐。
|
|
92
63
|
const tmp = `${path}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
93
64
|
await writeFile(tmp, JSON.stringify(cursor), "utf8");
|
|
94
65
|
await rename(tmp, path);
|
|
95
66
|
}
|
|
96
|
-
/**
|
|
97
|
-
* 组装一个 per-(scope,peer) 的同步执行器:cursor 持久化 + core `syncMemoryScope` + conflicts 上报 +
|
|
98
|
-
* 串行化/节流。`backend` = 本地 File authority plane(memoryEngineBackendFor 的产物);`memoryRoot` =
|
|
99
|
-
* resolved config root(cursor sidecar 落在 memory/ 目录旁)。
|
|
100
|
-
*/
|
|
101
|
-
/** S2.5 分批 loop 的轮数上限(fail-loud 警戒,防协议异常时死循环;正常收敛远低于此)。 */
|
|
102
67
|
export const MAX_SYNC_ROUNDS = 32;
|
|
103
68
|
export function createMemorySyncRunner(opts) {
|
|
104
69
|
const peer = opts.peer ?? DEFAULT_MEMORY_SYNC_PEER;
|
|
@@ -107,8 +72,6 @@ export function createMemorySyncRunner(opts) {
|
|
|
107
72
|
let cursor = await loadCursor(cursorPath, opts.scope, peer, opts.log);
|
|
108
73
|
let result;
|
|
109
74
|
let subRound = 0;
|
|
110
|
-
// S2.5 分批续轮:任一半场截断 ⇒ 立即续跑下一子轮(用上一轮返回的 cursor 作基线,协议幂等收敛);
|
|
111
|
-
// MAX_SYNC_ROUNDS 警戒 fail-loud(截断标志异常粘死时不无限烧请求,余量下轮 trigger 自然补)。
|
|
112
75
|
for (;;) {
|
|
113
76
|
subRound += 1;
|
|
114
77
|
result = await syncMemoryScope({
|
|
@@ -131,7 +94,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
131
94
|
}
|
|
132
95
|
if (result.cursorAdvanced && result.cursor !== undefined) {
|
|
133
96
|
try {
|
|
134
|
-
await saveCursor(cursorPath, result.cursor);
|
|
97
|
+
await saveCursor(cursorPath, result.cursor);
|
|
135
98
|
}
|
|
136
99
|
catch (err) {
|
|
137
100
|
opts.log.warn("memory_sync_cursor_write_failed", { path: cursorPath, err: String(err) });
|
|
@@ -140,7 +103,6 @@ export function createMemorySyncRunner(opts) {
|
|
|
140
103
|
cursor = result.cursor ?? cursor;
|
|
141
104
|
opts.log.info("memory_sync_continuation", { reason, scope: opts.scope, peer, subRound, pushTruncated: result.pushTruncated === true, pullTruncated: result.pullTruncated === true });
|
|
142
105
|
}
|
|
143
|
-
// conflicts 逐条 warn(server 半场 + 本地半场聚合,core 绝不隐藏;解决 ladder 不自动跑)。
|
|
144
106
|
for (const c of result.conflicts) {
|
|
145
107
|
opts.log.warn("memory_sync_conflict", {
|
|
146
108
|
scope: opts.scope,
|
|
@@ -152,8 +114,6 @@ export function createMemorySyncRunner(opts) {
|
|
|
152
114
|
...(c.currentRev !== undefined ? { currentRev: c.currentRev } : {}),
|
|
153
115
|
});
|
|
154
116
|
}
|
|
155
|
-
// 保留纪律的落盘半场:仅 cursorAdvanced 才写。写失败只 warn(数据已按 CAS 落定,丢的是基线推进——
|
|
156
|
-
// 下轮按旧基线重放即幂等自愈,与中心侧 putCursor 失败的收敛论证同款),绝不把成功轮报成失败。
|
|
157
117
|
if (result.cursorAdvanced && result.cursor !== undefined) {
|
|
158
118
|
try {
|
|
159
119
|
await saveCursor(cursorPath, result.cursor);
|
|
@@ -176,8 +136,6 @@ export function createMemorySyncRunner(opts) {
|
|
|
176
136
|
});
|
|
177
137
|
return result;
|
|
178
138
|
};
|
|
179
|
-
// 串行链:两轮并发会在同一 File plane / cursor 文件上交错(push 半场读集与 pull 半场落盘互踩)。
|
|
180
|
-
// `pending` = 排队+在飞的轮数(设计的“简单 inflight 布尔”推广到可排队形态:syncOnce 排队,trigger 跳过)。
|
|
181
139
|
let chain = Promise.resolve();
|
|
182
140
|
let pending = 0;
|
|
183
141
|
const syncOnce = (reason) => {
|
|
@@ -192,7 +150,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
192
150
|
};
|
|
193
151
|
const trigger = (reason) => {
|
|
194
152
|
if (pending > 0)
|
|
195
|
-
return;
|
|
153
|
+
return;
|
|
196
154
|
void syncOnce(reason).catch((err) => {
|
|
197
155
|
opts.log.warn("memory_sync_failed", { reason, scope: opts.scope, peer, err: String(err) });
|
|
198
156
|
});
|
package/dist/memory-sync.js
CHANGED
|
@@ -1,49 +1,7 @@
|
|
|
1
|
-
// 142-S2.5 中心侧同步 API 的执行内核(设计 sema-internal server/docs/design/142-S4-DATAPLANE.md §2.5;裁定:
|
|
2
|
-
// **plan 随数据走,core 零运行时半场**——core 的 `reconcileMemoryEntries`/`nextSyncBaseline`/
|
|
3
|
-
// `screenInboundEntries` 是纯函数导出,持数据的一侧(本 server=DB 权威+per-(scope,peer) 游标)直接
|
|
4
|
-
// import 调用算 plan 并执行 CAS;API 面全在 server,core 的承诺=纯函数语义稳定。
|
|
5
|
-
//
|
|
6
|
-
// 协议(POST /v1/memory/sync/:scope,http/server.ts 持路由/鉴权/验型;本模块只吃已验型请求):
|
|
7
|
-
// 请求 = { peer, baseRevs, entries[], deletes[] } —— TOC 本地引擎按**自己的**游标把 push 半场打过来:
|
|
8
|
-
// entries = 自 baseRevs 以来本地新增/变更的条目全量;deletes = 本地删除(tombstone)的 id
|
|
9
|
-
// (CAS 基线 = baseRevs[id],所以 deletes ⊆ baseRevs 键——验型层已保证)。
|
|
10
|
-
// 响应 = { applied[], conflicts[], serverEntries[], serverDeletes[], cursor } —— 一个 RTT 双向:
|
|
11
|
-
// serverEntries/serverDeletes = 中心侧自 baseRevs 以来变过/删过的条目 = 本地的 pull 半场。
|
|
12
|
-
//
|
|
13
|
-
// 与 §2.5 草案的两处**诚实偏离**(按 core JSDoc 实况,不硬凑草案):
|
|
14
|
-
// ① 响应加 `serverDeletes[]`:core MemorySyncPlan.deleteRemote—— 没有删除传播腿,
|
|
15
|
-
// 中心侧删除永远过不去、每轮重摆同一状态 = livelock;草案响应形状缺这条腿。
|
|
16
|
-
// ② 响应加 `cursor`(本轮落库的 per-(scope,peer) 新基线):客户端应用完 pull 半场后持久化为
|
|
17
|
-
// 自己的游标——它自己算不出(不持有中心全集)。
|
|
18
|
-
//
|
|
19
|
-
// 游标推进纪律(nextSyncBaseline 的 JSDoc 假设「POST-round 双方都已应用」,但响应发出时客户端还
|
|
20
|
-
// **没有**应用 pull 半场——直接乐观推进会在客户端应用失败时把它的旧条目误判为「客户端改过」而回灌
|
|
21
|
-
// 中心 = 数据回退):对 pull 半场(plan.push/plan.deleteRemote)的 id **保留旧基线**。两种客户端结局
|
|
22
|
-
// 都收敛且零回退:客户端应用了 ⇒ 下轮 identical revs → no-op+基线推进;没应用 ⇒ 下轮干净重推。
|
|
23
|
-
//
|
|
24
|
-
// 执行序(任务书①-⑤):
|
|
25
|
-
// ① screenInboundEntries 入站门(拒 = 该条入 conflicts 上报,不落库不入队——门拒是终态)+
|
|
26
|
-
// rev 诚实校验(rev = core computeEntryRev 的内容哈希契约;谎报的 rev 会破坏 identical-revs
|
|
27
|
-
// 收敛判定,按条拒入 conflicts,批内其余照常——applyPatches 的 per-patch 非致命姿势);
|
|
28
|
-
// ② 过门条目按 baseRevs[id] CAS applyPatches(基线有 ⇒ update+baseRev;基线无 ⇒ add——core
|
|
29
|
-
// MemorySyncPlan JSDoc 的 CAS 纪律原文:absent from the baseline ⇒ plain add);
|
|
30
|
-
// ③ deletes 同 CAS(plan.deleteLocal 的 {id, baseRev} → delete patch);
|
|
31
|
-
// ④ reconcileMemoryEntries(baseRevs, 中心集, 对端视图) 算双向 plan(对端视图 = push 条目 +
|
|
32
|
-
// 「基线未提及 id 钉在基线 rev」的合成 stub——stub 永远不会被 pull/delete 判定选中,只参与
|
|
33
|
-
// rev 对比);⑤ nextSyncBaseline + 保留纪律 → putCursor。
|
|
34
|
-
//
|
|
35
|
-
// putCursor 失败于 applyPatches 之后 = 补丁已落、游标陈旧:向上抛(路由 500),客户端按同一
|
|
36
|
-
// baseRevs 重放即幂等自愈(identical revs 全程 no-op)——游标写不是本轮正确性的一部分。
|
|
37
1
|
import { computeEntryRev, MEMORY_ORIGIN_CAUSES, nextSyncBaseline, reconcileMemoryEntries, screenInboundEntries, } from "@sema-agent/core";
|
|
38
2
|
import { exportMemoryScope } from "./memory-export.js";
|
|
39
|
-
// ── 验型(HTTP 面的 422 判据;返回 error 字符串而非 throw——validateUserSkills 姿势) ──────────
|
|
40
|
-
/** 一轮 push 的条目数上限 = core REMOTE_HARVEST_MAX_FILES 同款口径(一个 scope 的文件面上限 500;
|
|
41
|
-
* 同步一轮打满全集也在此内)。deletes 上限同基数放宽(全集删除也要能一轮表达)。 */
|
|
42
3
|
export const MEMORY_SYNC_MAX_ENTRIES = 500;
|
|
43
4
|
export const MEMORY_SYNC_MAX_DELETES = 2000;
|
|
44
|
-
/** Shape-validate + NORMALIZE the request body (picked-not-spread:只挑认识的字段重建,body 上的
|
|
45
|
-
* 多余键绝不透传进数据面)。`scope` = 路径上已过 owner 门的 scope——每个条目的 scope 必须逐字
|
|
46
|
-
* 等于它(否则一个 owner-gated 路由就成了跨租户写门)。 */
|
|
47
5
|
export function parseMemorySyncRequest(body, scope) {
|
|
48
6
|
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
49
7
|
return { ok: false, error: "body must be a JSON object { peer, baseRevs, entries, deletes }" };
|
|
@@ -99,12 +57,6 @@ export function parseMemorySyncRequest(body, scope) {
|
|
|
99
57
|
if (fm["extra"] !== undefined && !(Array.isArray(fm["extra"]) && fm["extra"].every((s) => typeof s === "string"))) {
|
|
100
58
|
return { ok: false, error: "entry.frontmatter.extra must be an array of strings when present" };
|
|
101
59
|
}
|
|
102
|
-
// #323([4769]):三只 provenance 载体入白名单——修前这里静默剥掉 origin/trust/provenance,
|
|
103
|
-
// 带标条目过 sync wire 即丢标(污染追踪断链)、rev 恒假冲突(client 以含标载体算 rev),且
|
|
104
|
-
// 「typed origin + 冲突 extra 块」的歧义表示被选择性改写成非歧义形绕过 core 的
|
|
105
|
-
// ambiguousOriginRepresentation 拒绝门(applyPatches 直调路径拒、sync 路径放行=同法两答)。
|
|
106
|
-
// 修=pick+闭集验形(照 core MemoryEntryFrontmatter 单源形),坏形响亮拒;歧义**判定**刻意不在
|
|
107
|
-
// 这里重复——保真透传后 core 受理门(clause 31)与 sync 路径同答([4770] core 已确认零改动)。
|
|
108
60
|
const rawOrigin = fm["origin"];
|
|
109
61
|
if (rawOrigin !== undefined) {
|
|
110
62
|
if (rawOrigin === null || typeof rawOrigin !== "object" || Array.isArray(rawOrigin))
|
|
@@ -120,9 +72,6 @@ export function parseMemorySyncRequest(body, scope) {
|
|
|
120
72
|
}
|
|
121
73
|
if (fm["trust"] !== undefined && fm["trust"] !== "untrusted")
|
|
122
74
|
return { ok: false, error: 'entry.frontmatter.trust must be the literal "untrusted" when present (single-value union — there is no trusted spelling)' };
|
|
123
|
-
// core 5.55.0 第 9 键 distilled(consolidation 产物块):受理为**不透明块**浅验过境——成员级判据
|
|
124
|
-
// 不在这里手抄(F1b 温床),真闸=store add/update 臂的 distilledWhitewashRefusal(committedDistilledOf/
|
|
125
|
-
// distilledEquals);sync 剥掉它=洗白(#323 origin 同病),深验它=第二份 core 结构。
|
|
126
75
|
const rawDistilled = fm["distilled"];
|
|
127
76
|
if (rawDistilled !== undefined && (rawDistilled === null || typeof rawDistilled !== "object" || Array.isArray(rawDistilled))) {
|
|
128
77
|
return { ok: false, error: "entry.frontmatter.distilled must be an object (opaque consolidation block) when present" };
|
|
@@ -152,7 +101,6 @@ export function parseMemorySyncRequest(body, scope) {
|
|
|
152
101
|
...(fm["description"] !== undefined ? { description: fm["description"] } : {}),
|
|
153
102
|
...(fm["type"] !== undefined ? { type: fm["type"] } : {}),
|
|
154
103
|
...(fm["deleted"] !== undefined ? { deleted: fm["deleted"] } : {}),
|
|
155
|
-
// #323:三载体保真重建(pick,不 spread——上面已闭集验形;origin 逐成员重建防原对象带私货键)
|
|
156
104
|
...(rawOrigin !== undefined
|
|
157
105
|
? {
|
|
158
106
|
origin: {
|
|
@@ -203,21 +151,12 @@ export function parseMemorySyncRequest(body, scope) {
|
|
|
203
151
|
}
|
|
204
152
|
return { ok: true, value: { peer, baseRevs, entries, deletes, ...(pull !== undefined ? { pull } : {}) } };
|
|
205
153
|
}
|
|
206
|
-
// ── 执行内核 ─────────────────────────────────────────────────────────────────────────────────
|
|
207
|
-
/** 基线 stub:对端在基线上、本轮未提及(或被门拒钉回基线)的 id 的合成占位。只有 id/rev 参与
|
|
208
|
-
* reconcile 判定;rev === baseRevs[id] ⇒ remoteChanged 恒 false ⇒ 永不被 pull/deleteLocal 选中,
|
|
209
|
-
* 身体永不落任何盘。 */
|
|
210
154
|
function baselineStub(id, rev, scope, slug) {
|
|
211
155
|
return { id, rev, scope, slug: slug ?? `sync-baseline-stub/${id}`, frontmatter: {}, body: "" };
|
|
212
156
|
}
|
|
213
|
-
/**
|
|
214
|
-
* One full sync round for one (scope, peer). `backend` = 中心侧 entry plane(DB 权威),`cursors` =
|
|
215
|
-
* per-(scope,peer) 游标面(sync_cursors)。纯数据参数进(路由已完成鉴权/owner 门/验型),响应形状出。
|
|
216
|
-
*/
|
|
217
157
|
export async function performMemorySync(backend, cursors, scope, req, now = Date.now) {
|
|
218
158
|
const conflicts = [];
|
|
219
|
-
|
|
220
|
-
const rejected = new Map(); // id → reason
|
|
159
|
+
const rejected = new Map();
|
|
221
160
|
for (const e of req.entries) {
|
|
222
161
|
const computed = computeEntryRev(e);
|
|
223
162
|
if (computed !== e.rev)
|
|
@@ -233,23 +172,16 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
|
|
|
233
172
|
conflicts.push({ id, reason, ...(base !== undefined ? { baseRev: base } : {}) });
|
|
234
173
|
}
|
|
235
174
|
const clean = req.entries.filter((e) => !rejected.has(e.id));
|
|
236
|
-
// 中心侧全集(export 面同款读姿势:listHeaders → getByIds → scope 过滤纵深)。
|
|
237
175
|
const central = await exportMemoryScope(backend, scope);
|
|
238
176
|
const centralById = new Map(central.map((e) => [e.id, e]));
|
|
239
|
-
// 对端视图:push 条目 + 基线 stub。门拒的 id 钉回基线(有基线时)——绝不能让「拒收」被误判成
|
|
240
|
-
// 「对端删除」而触发 deleteLocal(中心副本会被误删);新条目被拒 = 视图里干脆没有它。
|
|
241
177
|
const peerView = [...clean];
|
|
242
178
|
const pushedOrDeleted = new Set([...clean.map((e) => e.id), ...req.deletes]);
|
|
243
179
|
for (const [id, rev] of Object.entries(req.baseRevs)) {
|
|
244
180
|
if (pushedOrDeleted.has(id))
|
|
245
181
|
continue;
|
|
246
|
-
// 未提及 = 对端仍在基线(含被门拒钉回的);stub 只有 rev 参与判定。
|
|
247
182
|
peerView.push(baselineStub(id, rev, scope, centralById.get(id)?.slug));
|
|
248
183
|
}
|
|
249
|
-
// ④(判定先行)三方 reconcile:local = 中心,remote = 对端视图。
|
|
250
184
|
const plan = reconcileMemoryEntries(req.baseRevs, central, peerView);
|
|
251
|
-
// ②③ 中心半场执行:pull(采纳对端 push)按 baseRevs CAS;deleteLocal 同 CAS。backend.applyPatches
|
|
252
|
-
// 的 per-id CAS 是并发纵深——本函数读集与落库之间挤进来的写者会让 baseRev 失配 = 冲突上报,绝不盲写。
|
|
253
185
|
const patches = [
|
|
254
186
|
...plan.pull.map((e) => {
|
|
255
187
|
const base = req.baseRevs[e.id];
|
|
@@ -261,8 +193,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
|
|
|
261
193
|
for (const c of report.conflicts) {
|
|
262
194
|
conflicts.push({ id: c.id, reason: c.reason, ...(c.baseRev !== undefined ? { baseRev: c.baseRev } : {}), ...(c.currentRev !== undefined ? { currentRev: c.currentRev } : {}) });
|
|
263
195
|
}
|
|
264
|
-
// plan 冲突(双边自基线各自变过 / 删改相撞):currentRev = 中心当前 rev(中心已删则缺席),
|
|
265
|
-
// 客户端拿它走 §3 ladder(败者铸 sibling)。
|
|
266
196
|
for (const c of plan.conflicts) {
|
|
267
197
|
conflicts.push({
|
|
268
198
|
id: c.id,
|
|
@@ -275,7 +205,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
|
|
|
275
205
|
...(c.local !== undefined ? { currentRev: c.local.rev } : {}),
|
|
276
206
|
});
|
|
277
207
|
}
|
|
278
|
-
// ⑤ 游标:POST-round 中心集(内存推演,只按 applied 口径——CAS 输掉的补丁不算)× 对端视图。
|
|
279
208
|
const appliedByKey = new Set(report.applied.map((a) => `${a.op}\x1f${a.id}`));
|
|
280
209
|
const appliedPatch = new Map();
|
|
281
210
|
for (const p of patches)
|
|
@@ -292,9 +221,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
|
|
|
292
221
|
centralPost.push(p.entry);
|
|
293
222
|
}
|
|
294
223
|
const nextBase = nextSyncBaseline(centralPost, peerView);
|
|
295
|
-
// 保留纪律(头注):pull 半场的 id 保留旧基线——客户端此刻还没应用 serverEntries/serverDeletes,
|
|
296
|
-
// 乐观推进会在「客户端没应用」的下一轮把它的旧条目误判成新改动回灌中心(数据回退)。保留旧基线
|
|
297
|
-
// 两种结局都收敛:应用了 ⇒ identical revs no-op+推进;没应用 ⇒ 干净重推/重删。
|
|
298
224
|
for (const e of plan.push) {
|
|
299
225
|
const base = req.baseRevs[e.id];
|
|
300
226
|
if (base !== undefined)
|
|
@@ -302,11 +228,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
|
|
|
302
228
|
}
|
|
303
229
|
for (const d of plan.deleteRemote)
|
|
304
230
|
nextBase[d.id] = d.baseRev;
|
|
305
|
-
// ⑥ S2.5 pull 分批(契约 settled 2026-07-14):serverEntries 稳定序(id 升序)截 limit;deletes 轻不截。
|
|
306
|
-
// cursor 精确化约束(core 回执):**不得预填未交付条目的 rev**——有基线的 pull id 上面的保留纪律已经
|
|
307
|
-
// 钉回旧值;无基线的新条目若未交付,必须从 nextBase 整个剔除(预填=客户端应用交付子集后持久化整张
|
|
308
|
-
// cursor,下轮把未交付 id 误判「baseline 已知」⇒ 该条目永久漏拉)。已交付的新条目保持 nextSyncBaseline
|
|
309
|
-
// 值(客户端按纪律应用完交付集才持久化 cursor,安全)。
|
|
310
231
|
let serverEntries = plan.push;
|
|
311
232
|
let pullTruncated = false;
|
|
312
233
|
if (req.pull !== undefined && plan.push.length > req.pull.limit) {
|