@sema-agent/server 7.140.0 → 7.142.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.
Files changed (296) hide show
  1. package/MIGRATION.md +505 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +142 -25
  5. package/dist/approval-ask-audit-store.d.ts +54 -38
  6. package/dist/approval-ask-audit-store.js +58 -39
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +39 -24
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval-crash-convergence.d.ts +17 -7
  12. package/dist/approval-crash-convergence.js +94 -22
  13. package/dist/approval-deny-reasons.d.ts +34 -30
  14. package/dist/approval-deny-reasons.js +21 -6
  15. package/dist/approval-policy-settlement.d.ts +1 -1
  16. package/dist/approval-reconciler.d.ts +33 -1
  17. package/dist/approval-reconciler.js +43 -40
  18. package/dist/approval.d.ts +15 -0
  19. package/dist/approval.js +10 -0
  20. package/dist/audit.d.ts +4 -2
  21. package/dist/audit.js +2 -2
  22. package/dist/auth-bridge.d.ts +9 -6
  23. package/dist/auth-bridge.js +9 -3
  24. package/dist/boot/adoption.js +2 -1
  25. package/dist/boot/budget-tracing.d.ts +16 -0
  26. package/dist/boot/budget-tracing.js +10 -6
  27. package/dist/boot/device-lane.d.ts +11 -0
  28. package/dist/boot/device-lane.js +16 -0
  29. package/dist/boot/engine-lease.d.ts +24 -3
  30. package/dist/boot/engine-lease.js +9 -2
  31. package/dist/boot/execution-env.d.ts +24 -4
  32. package/dist/boot/execution-env.js +34 -27
  33. package/dist/boot/execution-lane.js +2 -1
  34. package/dist/boot/exit-boundary.js +5 -4
  35. package/dist/boot/memory-boundary.d.ts +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +1 -1
  37. package/dist/boot/memory-consolidation.js +3 -2
  38. package/dist/boot/parked-revive-gate.js +2 -1
  39. package/dist/boot/permission-rules-audit.d.ts +0 -28
  40. package/dist/boot/permission-rules-audit.js +2 -1
  41. package/dist/boot/read-deny-anchor-audit.js +2 -1
  42. package/dist/boot/reapers.d.ts +2 -2
  43. package/dist/boot/reapers.js +4 -3
  44. package/dist/boot/resolve-spec.js +18 -6
  45. package/dist/boot/retention-lane.js +5 -3
  46. package/dist/boot/runner-deps.js +16 -13
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.d.ts +1 -1
  49. package/dist/boot/session-faces.js +8 -6
  50. package/dist/boot/shutdown.js +8 -2
  51. package/dist/boot/stage-04-stores.js +3 -2
  52. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  53. package/dist/boot/stage-05-execution-env.js +11 -7
  54. package/dist/boot/stage-06-runners.d.ts +1 -3
  55. package/dist/boot/stage-06-runners.js +3 -2
  56. package/dist/boot/stage-07-capability-layer.d.ts +1 -2
  57. package/dist/boot/stage-07-capability-layer.js +10 -11
  58. package/dist/boot/stage-08-reapers.d.ts +1 -2
  59. package/dist/boot/stage-08-reapers.js +3 -2
  60. package/dist/boot/stage-09-leader.d.ts +1 -2
  61. package/dist/boot/stage-10-http-server.d.ts +1 -2
  62. package/dist/boot/stage-10-http-server.js +6 -4
  63. package/dist/boot/stores.js +13 -6
  64. package/dist/boot/web-admin.js +18 -17
  65. package/dist/boot/webfetch-summarize-lane.js +2 -1
  66. package/dist/boot/workflow-orchestration.js +3 -2
  67. package/dist/capabilities/builtin-tools.d.ts +2 -1
  68. package/dist/capabilities/builtin-tools.js +2 -4
  69. package/dist/capabilities/composite-members.d.ts +2 -1
  70. package/dist/capabilities/composite-members.js +2 -1
  71. package/dist/capabilities/scenarios.d.ts +1 -1
  72. package/dist/capability-facts.d.ts +33 -0
  73. package/dist/capability-facts.js +32 -0
  74. package/dist/card-void.d.ts +38 -0
  75. package/dist/card-void.js +23 -0
  76. package/dist/config-catalog.js +12 -8
  77. package/dist/config-provider.d.ts +13 -13
  78. package/dist/config-provider.js +19 -33
  79. package/dist/config-types.d.ts +39 -10
  80. package/dist/config.d.ts +6 -0
  81. package/dist/config.js +72 -49
  82. package/dist/delegated-session-owner.d.ts +21 -19
  83. package/dist/delegated-session-owner.js +1 -1
  84. package/dist/device-store.d.ts +7 -0
  85. package/dist/device-store.js +3 -0
  86. package/dist/execution-config-root.d.ts +22 -0
  87. package/dist/execution-config-root.js +35 -0
  88. package/dist/execution-lane-caps.d.ts +50 -2
  89. package/dist/execution-lane-caps.js +90 -13
  90. package/dist/execution-placement.d.ts +3 -0
  91. package/dist/execution-placement.js +7 -0
  92. package/dist/fleet/drain-entry.d.ts +3 -0
  93. package/dist/fleet/fleet-bus.d.ts +6 -3
  94. package/dist/hooks/hook-runner.d.ts +6 -0
  95. package/dist/hooks/hook-runner.js +30 -1
  96. package/dist/hosted-posture.d.ts +8 -0
  97. package/dist/hosted-posture.js +4 -0
  98. package/dist/http/admission.js +3 -3
  99. package/dist/http/caller-trust.d.ts +3 -2
  100. package/dist/http/caller-trust.js +2 -1
  101. package/dist/http/dispatch.js +8 -2
  102. package/dist/http/execution-context.d.ts +2 -2
  103. package/dist/http/idempotency.d.ts +1 -1
  104. package/dist/http/live-frame.d.ts +24 -0
  105. package/dist/http/live-frame.js +16 -0
  106. package/dist/http/principal-gate.d.ts +3 -1
  107. package/dist/http/principal-gate.js +17 -8
  108. package/dist/http/resume-legs.js +42 -29
  109. package/dist/http/route-table.js +4 -0
  110. package/dist/http/routes/approvals-assistant.js +1 -0
  111. package/dist/http/routes/attachments.js +18 -0
  112. package/dist/http/routes/capabilities.js +10 -6
  113. package/dist/http/routes/diagnostics.d.ts +0 -19
  114. package/dist/http/routes/diagnostics.js +16 -2
  115. package/dist/http/routes/memory-bundle.d.ts +2 -2
  116. package/dist/http/routes/memory-bundle.js +1 -1
  117. package/dist/http/routes/memory-compliance.js +1 -1
  118. package/dist/http/routes/memory-policy.js +27 -7
  119. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  120. package/dist/http/routes/run-permission-mode.js +170 -0
  121. package/dist/http/routes/runs.js +3 -3
  122. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  123. package/dist/http/routes/session-memory-sync.js +157 -0
  124. package/dist/http/routes/session-sync.d.ts +3 -6
  125. package/dist/http/routes/session-sync.js +7 -2
  126. package/dist/http/routes/sessions-list.d.ts +2 -1
  127. package/dist/http/routes/sessions.js +5 -4
  128. package/dist/http/routes/tasks.js +70 -71
  129. package/dist/http/run-read-face.d.ts +7 -0
  130. package/dist/http/run-read-face.js +10 -4
  131. package/dist/http/send.d.ts +6 -1
  132. package/dist/http/send.js +10 -3
  133. package/dist/http/server.d.ts +20 -7
  134. package/dist/http/server.js +2 -4
  135. package/dist/http/sse-log.d.ts +3 -8
  136. package/dist/http/stored-body-replay.d.ts +3 -3
  137. package/dist/http/stored-body-replay.js +3 -1
  138. package/dist/http/submit-replay.d.ts +4 -3
  139. package/dist/http/submit-replay.js +2 -2
  140. package/dist/http/sync-submit-body.d.ts +21 -4
  141. package/dist/http/sync-submit-body.js +13 -0
  142. package/dist/identity-source.d.ts +21 -2
  143. package/dist/identity-source.js +6 -0
  144. package/dist/instruction-source-notice.d.ts +7 -0
  145. package/dist/instruction-source-notice.js +6 -6
  146. package/dist/mcp-assertion-header.d.ts +4 -0
  147. package/dist/mcp-assertion-header.js +5 -0
  148. package/dist/memory-incident-sink.d.ts +19 -6
  149. package/dist/memory-incident-sink.js +8 -4
  150. package/dist/memory-posture.d.ts +1 -1
  151. package/dist/memory-scope.d.ts +4 -4
  152. package/dist/memory-scope.js +1 -1
  153. package/dist/memory-sync-client.d.ts +35 -23
  154. package/dist/memory-sync-client.js +122 -52
  155. package/dist/memory-sync-identity.d.ts +24 -0
  156. package/dist/memory-sync-identity.js +57 -0
  157. package/dist/memory-sync.d.ts +7 -1
  158. package/dist/memory-sync.js +6 -3
  159. package/dist/observability/fail-open.d.ts +57 -12
  160. package/dist/observability/fail-open.js +50 -9
  161. package/dist/observability/metrics.js +0 -1
  162. package/dist/observability/run-terminal.d.ts +93 -31
  163. package/dist/observability/run-terminal.js +175 -171
  164. package/dist/observability/secret-env-scrub.d.ts +20 -1
  165. package/dist/observability/secret-env-scrub.js +14 -0
  166. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  167. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  168. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  169. package/dist/orchestration/workflow-notify-journal.js +2 -1
  170. package/dist/permission-mode-admission.d.ts +68 -0
  171. package/dist/permission-mode-admission.js +23 -0
  172. package/dist/permission-rule-list.d.ts +55 -6
  173. package/dist/permission-rule-list.js +52 -5
  174. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  175. package/dist/plugins/approval-ask-store-file.js +48 -7
  176. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  177. package/dist/plugins/approval-ask-store-memory.js +9 -8
  178. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  179. package/dist/plugins/approval-ask-store-sql.js +49 -19
  180. package/dist/plugins/caching-session-store.d.ts +6 -0
  181. package/dist/plugins/caching-session-store.js +3 -0
  182. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  183. package/dist/plugins/checkpoint-store-sql.js +74 -3
  184. package/dist/plugins/device-store-sql.d.ts +5 -2
  185. package/dist/plugins/device-store-sql.js +4 -1
  186. package/dist/plugins/execution-placement-sql.d.ts +11 -1
  187. package/dist/plugins/execution-placement-sql.js +12 -1
  188. package/dist/plugins/file-run-store.d.ts +7 -2
  189. package/dist/plugins/file-run-store.js +12 -2
  190. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  191. package/dist/plugins/fork-routing-session-store.js +2 -61
  192. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  193. package/dist/plugins/k8s-exec-protocol.js +6 -1
  194. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  195. package/dist/plugins/local-checkpoint-store.js +22 -0
  196. package/dist/plugins/local-session-store.d.ts +260 -39
  197. package/dist/plugins/local-session-store.js +582 -57
  198. package/dist/plugins/local-session-window.d.ts +17 -3
  199. package/dist/plugins/local-session-window.js +7 -13
  200. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  201. package/dist/plugins/local-task-attachment-store.js +67 -43
  202. package/dist/plugins/memory-key-guards.d.ts +3 -3
  203. package/dist/plugins/memory-run-store.d.ts +7 -2
  204. package/dist/plugins/memory-run-store.js +12 -2
  205. package/dist/plugins/one-time-migrations.d.ts +68 -2
  206. package/dist/plugins/one-time-migrations.js +120 -4
  207. package/dist/plugins/pg-pool.js +3 -2
  208. package/dist/plugins/pg-session-storage.d.ts +2 -0
  209. package/dist/plugins/pg-session-storage.js +3 -2
  210. package/dist/plugins/remote-env-e2b.d.ts +47 -7
  211. package/dist/plugins/remote-env-e2b.js +242 -46
  212. package/dist/plugins/remote-env-k8s.d.ts +51 -6
  213. package/dist/plugins/remote-env-k8s.js +234 -44
  214. package/dist/plugins/remote-env-local-docker.d.ts +40 -14
  215. package/dist/plugins/remote-env-local-docker.js +127 -33
  216. package/dist/plugins/run-store-sql.d.ts +11 -3
  217. package/dist/plugins/run-store-sql.js +13 -4
  218. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  219. package/dist/plugins/sandbox-read-floor.js +608 -0
  220. package/dist/plugins/session-ownership.d.ts +2 -1
  221. package/dist/plugins/session-placement.d.ts +17 -4
  222. package/dist/plugins/session-placement.js +2 -1
  223. package/dist/plugins/session-store.d.ts +18 -20
  224. package/dist/plugins/session-store.js +4 -12
  225. package/dist/plugins/store-backend.d.ts +41 -7
  226. package/dist/plugins/store-backend.js +22 -4
  227. package/dist/plugins/store-contracts.d.ts +35 -3
  228. package/dist/plugins/store-contracts.js +3 -0
  229. package/dist/plugins/task-attachment-store.d.ts +112 -14
  230. package/dist/plugins/task-attachment-store.js +221 -28
  231. package/dist/plugins/tidb-pool.js +2 -1
  232. package/dist/plugins/tidb-session-store.d.ts +4 -1
  233. package/dist/plugins/tidb-session-store.js +3 -2
  234. package/dist/plugins/workspace-fill.d.ts +60 -0
  235. package/dist/plugins/workspace-fill.js +106 -0
  236. package/dist/project-bundle.d.ts +38 -0
  237. package/dist/project-bundle.js +609 -0
  238. package/dist/run-cancel-context.d.ts +34 -0
  239. package/dist/run-cancel-context.js +7 -0
  240. package/dist/run-local.js +2 -8
  241. package/dist/runs.d.ts +39 -12
  242. package/dist/runs.js +69 -55
  243. package/dist/sandbox-egress-policy.d.ts +4 -1
  244. package/dist/sandbox-egress-policy.js +3 -1
  245. package/dist/sandbox-git-plane.d.ts +31 -12
  246. package/dist/sandbox-git-plane.js +24 -4
  247. package/dist/security.d.ts +11 -2
  248. package/dist/security.js +8 -0
  249. package/dist/server-secret-env.d.ts +2 -1
  250. package/dist/server-secret-env.js +2 -4
  251. package/dist/session-error-wire.d.ts +46 -4
  252. package/dist/session-error-wire.js +23 -0
  253. package/dist/session-incarnation.d.ts +9 -3
  254. package/dist/session-sync-kernel.d.ts +42 -1
  255. package/dist/session-sync-kernel.js +17 -0
  256. package/dist/session-sync.js +16 -4
  257. package/dist/sql-engine-coordinates.d.ts +49 -0
  258. package/dist/sql-engine-coordinates.js +27 -0
  259. package/dist/standing-answer-grant.d.ts +31 -2
  260. package/dist/standing-answer-grant.js +14 -2
  261. package/dist/store-posture.d.ts +1 -1
  262. package/dist/task-a2a.d.ts +1 -1
  263. package/dist/task-mcp.d.ts +1 -1
  264. package/dist/task-settings.d.ts +62 -27
  265. package/dist/task-settings.js +105 -29
  266. package/dist/terminal.d.ts +97 -20
  267. package/dist/terminal.js +107 -18
  268. package/dist/tool-approval.d.ts +4 -3
  269. package/dist/tool-approval.js +12 -14
  270. package/dist/trace/core-keyset-guard.d.ts +196 -2
  271. package/dist/trace/engine-notice-wire.d.ts +8 -2
  272. package/dist/trace/engine-notice-wire.js +4 -2
  273. package/dist/trace/exit-seat.d.ts +9 -1
  274. package/dist/trace/exit-seat.js +1 -0
  275. package/dist/trace/ledger-sink.d.ts +8 -4
  276. package/dist/trace/ledger-sink.js +2 -2
  277. package/dist/trace/model-segment.d.ts +2 -2
  278. package/dist/trace/project.d.ts +18 -1
  279. package/dist/trace/project.js +16 -0
  280. package/dist/trace/redact.js +323 -90
  281. package/dist/trace/self-minted-wire.d.ts +419 -4
  282. package/dist/trace/self-minted-wire.js +145 -0
  283. package/dist/trace/wire-projection-faces.d.ts +1 -1
  284. package/dist/trace/wire-projection-faces.js +1 -1
  285. package/dist/verifier-pause-card.d.ts +8 -0
  286. package/dist/verifier-pause-card.js +19 -0
  287. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  288. package/dist/web-admin-client/apply-effective.js +22 -2
  289. package/dist/web-admin-client/candidate-gate.d.ts +1 -1
  290. package/dist/web-admin-client/effective-keys.js +2 -0
  291. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  292. package/dist/web-admin-client/types.d.ts +4 -0
  293. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  294. package/package.json +2 -2
  295. package/wire-changes/server@7.141.0.json +367 -0
  296. package/wire-changes/server@7.142.0.json +639 -0
@@ -1,4 +1,4 @@
1
- import { type MemorySyncClientResult, type MemorySyncTransport } from "@sema-agent/core";
1
+ import { type memorySyncReportOf, type MemorySyncTransport } from "@sema-agent/core";
2
2
  import type { ServiceConfig } from "./config-types.js";
3
3
  import type { MemoryScopeStores } from "./memory-scope-stores.js";
4
4
  import { type OneTimeMigrationWindow } from "./plugins/one-time-migrations.js";
@@ -8,19 +8,13 @@ export interface MemorySyncLog {
8
8
  warn(msg: string, fields?: Record<string, unknown>): void;
9
9
  }
10
10
  /**
11
- * 中心基址 → 同步对端的身份(规范化基址)。本仓唯一的铸点:`config.ts` 读 env 时调它拒启(引擎关着也拒),transport 建时再调它铸身份 —— 同一只纯函数,同一个答案。
12
- * 两道判,各归各的属主,本函数不另写规则:
13
- * ① 地址的形 —— 这只地址是「后面要拼路径的基址」座位(post 把同步路径拼在它后面),判官与拒句就是那一座位的那一只({@link pathBaseUrlRefusal}:层级式 http(s),
14
- * 不带 userinfo / query / fragment;拒句只点名规则、值只到 origin);
15
- * ② 身份里有没有凭据 —— 判官是引擎包根的 `carriesCredential`(引擎同步客户端判 `transport.peer` 的同一只:任何 userinfo,或同主体出口脱敏会遮掉的任何一段,
16
- * 例如路径段里的令牌形),判的就是规范化之后的身份 —— 引擎第一轮判的那个值。身份随每个请求体与落盘游标走,所以带凭据的地址拒而不清洗
17
- * (清洗过的身份是另一个身份;令牌只走 `MEMORY_SYNC_TOKEN`);拒句不引地址的任何部分。
18
- * 规范化 = WHATWG URL 的 `origin`(协议与主机小写、缺省端口省掉)+ 路径去尾斜杠;规范化之后装不进中心侧 `peer` 列({@link MEMORY_SYNC_PEER_COLUMN_CHARS},
19
- * 每一轮都会被中心拒)⇒ 同样拒。
11
+ * 客户端腿的同步地址 `MEMORY_SYNC_URL` → 同步对端的身份(规范化基址)。判与铸只在 `memory-sync-identity.ts` 的唯一铸点({@link readMemorySyncIdentity});
12
+ * 本函数是那只铸点的「客户端腿」座位:值可以不是规范形(铸成规范形用),有缺陷 ⇒ 抛(拒句不引地址的路径与凭据)。`config.ts` 读 env 时调它拒启(引擎关着也拒),
13
+ * transport 建时再调它铸身份 —— 同一只纯函数,同一个答案。
20
14
  */
21
15
  export declare function memorySyncPeerOf(url: string): string;
22
16
  /**
23
- * 起服日志行 `memory_sync_enabled` 的字段(两处起服 —— `boot/stores.ts` 与 `run-local.ts` —— 同读这一只)。`peer` 是运维改绑游标时要抄的那个值:
17
+ * 起服日志行 `memory_sync_enabled` 的字段(两处起服 —— `boot/stores.ts` 与 `run-local.ts` —— 同读这一只)。`peer` 是这一腿的对端身份(游标绑的就是它;排障时与停驻行的轴对读):
24
18
  * 它已过 {@link memorySyncPeerOf} 的两道判(带凭据的身份起不来);仍过展示铸点({@link buildDisplaySafeUrl}),日志里的 URL 值一律经它 —— 两只判官
25
19
  * (引擎的出口脱敏与本仓的展示形状律)不是同一份实现,本仓的那一只若遮了引擎放行的一段,这一行带遮记号而不是逐字(响亮,不漏)。
26
20
  */
@@ -46,6 +40,16 @@ export declare function createMemorySyncTransport(opts: {
46
40
  /** 一层的游标文件路径:引擎根 `memoryEngineDir`(= `RunnerDeps.memoryEngineDir` 的那一个值)下 `sync-cursors/<scope 摘要>.json`。一层一只,不随对端变、不随店变。
47
41
  * 落在子目录里:与更早的根下一层文件名 `sync-cursor-<peer>.json` 的空间不相交。 */
48
42
  export declare function memorySyncCursorPath(memoryEngineDir: string, scope: string): string;
43
+ /**
44
+ * ⑦ 停驻层的出路(闭集;今天唯一成员)。`delete_cursor` = 删掉这一层的游标文件,下一轮从空基线重收敛。读面 `memorySync.parked[].wayOut` 与运维行同读这只词;
45
+ * 加一个词 ⇒ {@link wayOutSentence} 的穷尽 switch 编译红(句子先写好,词才上得了 wire)。
46
+ */
47
+ export type MemorySyncWayOut = "delete_cursor";
48
+ /** ⑦ 一层停驻的读数(读面与运维行同读这一只记录;不带盘路径 —— 路径只进运维行)。 */
49
+ export interface MemorySyncParkedLayer {
50
+ readonly scope: string;
51
+ readonly wayOut: MemorySyncWayOut;
52
+ }
49
53
  /** M31 一次的读数(测试按它断言)。`window: "closed"` ⇒ 一行都没读。 */
50
54
  export interface SyncCursorRelocationM31Report {
51
55
  readonly window: "open" | "closed";
@@ -56,7 +60,7 @@ export interface SyncCursorRelocationM31Report {
56
60
  * ⑤ 一次性迁移 M31:把升级前的游标搬到各自那一层的路径(`rename`,字节不变;只搬不改,判归引擎)。`today` = UTC 日历日;窗关 ⇒ 不搬。
57
61
  * · 上一版的分区目录 `sync-cursors/central/<scope 摘要>.json` ⇒ `sync-cursors/<同名>`(两种布局的文件名是同一只 scope 摘要);搬空的目录删掉;
58
62
  * 名字不是摘要形的(写游标的 staging 残片)原地不动。
59
- * · 更早的 `sync-cursor-central.json` ⇒ 它记的那一层的路径;读不成对象 / 没有 scope ⇒ 原地不动、一行 warn(没法说它是哪一层的基线)。
63
+ * · 更早的 `sync-cursor-central.json` ⇒ 它记的那一层的路径;读不成对象 / 没有 scope ⇒ 原地不动、一行 warn(没法说它是哪一层的基线;句子只点名删掉它,⑦ 一出路律)。
60
64
  * · 目标路径上已有游标 ⇒ 不搬、不删,一行 warn(这一层用路径上的那一份;留下的那只没有读者)。
61
65
  * 与同一数据根上并跑的另一个进程(server + run-local)同搬:源已被对方搬走(`ENOENT`)⇒ 跳过。残余(如实):先查目标再 `rename` 之间若有写者在目标路径上
62
66
  * 落下游标,会被盖掉 —— 写者只有本模块的 runner,而 runner 写任何游标之前先跑完本步。别的读写错 ⇒ 抛(这一轮整轮失败、下一轮再搬)。
@@ -65,20 +69,26 @@ export declare function relocateSyncCursorsM31(memoryEngineDir: string, log: Mem
65
69
  readonly today: string;
66
70
  readonly window?: Pick<OneTimeMigrationWindow, "removableAfter">;
67
71
  }): Promise<SyncCursorRelocationM31Report>;
68
- /** 一轮里一层的结果。 */
69
- export interface MemorySyncLayerResult {
70
- readonly scope: string;
71
- readonly result: MemorySyncClientResult;
72
- }
73
- /** 一轮(按层)的结果:成功的各层,按同步序(同步层在前,项目层按键序)。 */
72
+ /**
73
+ * 一层的一次调用如它落定的那样:`{ scope, result }`(`syncMemoryScope` 兑现)或 `{ scope, thrown }`(它或本服务在这一层上抛的)。
74
+ * 型**从引擎报告的入参派生**(`memorySyncReportOf` 的 `layers` 元素;引擎没有单独导出这只型)—— 本服务不手抄一份。
75
+ */
76
+ export type MemorySyncLayerCall = Extract<Parameters<typeof memorySyncReportOf>[0], {
77
+ readonly layers: unknown;
78
+ }>["layers"][number];
79
+ /** 一轮:这一轮每一层的每一次调用,按跑的顺序(同步层在前、项目层按键;一层续轮 = 多条;一层失败 = 那一层最后一条是 `thrown`)。 */
74
80
  export interface MemorySyncRound {
75
- readonly layers: readonly MemorySyncLayerResult[];
81
+ readonly calls: readonly MemorySyncLayerCall[];
76
82
  }
77
83
  export interface MemorySyncRunner {
78
- /** 一轮同步(串行:排在上一轮之后),逐层一轮。任一层 transport/协议错误 ⇒ 其余层照跑,轮末汇总 reject(失败层本地零副作用,
79
- * core 契约);cursor 读写、conflicts warn 都在层内完成。 */
80
- syncOnce(reason: string): Promise<MemorySyncRound>;
81
- /** fire-and-forget 一轮:上一轮在飞(排队/执行中)则跳过(inflight 节流);失败 warn 不上抛。 */
84
+ /** 这一腿的对端身份(transport 的 `peer`;会话触发口拿请求点名的目标与它比)。 */
85
+ readonly peer: string;
86
+ /** 一轮同步(串行:排在上一轮之后),逐层一轮;**从不 reject**(⑧):一层失败那一层以 `{ scope, thrown }` 收尾、别的层照跑,逐层失败各一行运维 warn。
87
+ * `reading`:会话触发的读集 —— 里面被本腿覆盖的项目层并进这一轮(并登记进跟踪表);不被覆盖的层不进轮。 */
88
+ syncOnce(reason: string, opts?: {
89
+ readonly reading?: readonly string[];
90
+ }): Promise<MemorySyncRound>;
91
+ /** fire-and-forget 一轮:上一轮在飞(排队/执行中)则跳过(inflight 节流)。 */
82
92
  trigger(reason: string): void;
83
93
  /** 跟踪一只本层属主的项目层(本地还没有条目也每轮拉它);首次跟踪即排一轮。不是本同步腿覆盖的项目层 ⇒ 抛(调用方的错)。 */
84
94
  track(scope: string): void;
@@ -86,6 +96,8 @@ export interface MemorySyncRunner {
86
96
  exclusive<T>(work: () => Promise<T>): Promise<T>;
87
97
  /** 某一层的 cursor sidecar 路径(boot 日志/测试可见性)。 */
88
98
  cursorPathFor(scope: string): string;
99
+ /** ⑦ 停驻的层(最近一轮被拒在游标上的那些;按轮序:同步层在前、项目层按键)。停驻事实的唯一读口 —— 读面与运维行读同一份。 */
100
+ parked(): readonly MemorySyncParkedLayer[];
89
101
  /** 排队+在飞的轮数(测试的确定性等待面;0 = 完全空闲)。 */
90
102
  pending(): number;
91
103
  }
@@ -1,27 +1,20 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { mkdir, readFile, readdir, rename, rmdir, stat, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
- import { carriesCredential, syncMemoryScope, } from "@sema-agent/core";
4
+ import { syncMemoryScope, } from "@sema-agent/core";
5
5
  import { replicationCovers } from "./memory-scope.js";
6
6
  import { parseProjectLayerKey } from "./project-layer-key.js";
7
- import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
8
7
  import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./plugins/one-time-migrations.js";
9
- import { pathBaseUrlRefusal } from "./web-admin-client/center-request.js";
8
+ import { buildMemorySyncIdentityBootRefusal, readMemorySyncIdentity } from "./memory-sync-identity.js";
10
9
  import { buildDisplaySafeUrl } from "./trace/redact.js";
10
+ import { errorText } from "./observability/err-text.js";
11
11
  export function memorySyncPeerOf(url) {
12
- const refusal = pathBaseUrlRefusal("memory sync: MEMORY_SYNC_URL", url);
13
- if (refusal !== undefined)
12
+ const r = readMemorySyncIdentity(url);
13
+ if ("flaw" in r) {
14
+ const refusal = buildMemorySyncIdentityBootRefusal("MEMORY_SYNC_URL", url, r.flaw, "put the token in MEMORY_SYNC_TOKEN and set MEMORY_SYNC_URL to the central base address itself");
14
15
  throw new Error(refusal);
15
- const u = new URL(url);
16
- const identity = `${u.origin}${u.pathname.replace(/\/+$/, "")}`;
17
- if (carriesCredential(identity)) {
18
- throw new Error("memory sync: MEMORY_SYNC_URL holds a credential (a token-shaped part of its path) — the sync peer identity travels verbatim in every request body and " +
19
- "cursor, so it is refused, never cleaned: put the token in MEMORY_SYNC_TOKEN and set MEMORY_SYNC_URL to the central base address itself");
20
16
  }
21
- if (identity.length > MEMORY_SYNC_PEER_COLUMN_CHARS) {
22
- throw new Error(`memory sync: MEMORY_SYNC_URL normalizes to ${identity.length} characters — the peer identity is the central request's peer, which holds at most ${MEMORY_SYNC_PEER_COLUMN_CHARS}: use a shorter base URL`);
23
- }
24
- return identity;
17
+ return r.identity;
25
18
  }
26
19
  export function buildMemorySyncEnabledFields(peer, scope, cursorPath) {
27
20
  return { peer: buildDisplaySafeUrl(peer), scope, cursorPath };
@@ -53,10 +46,54 @@ function scopeDigest(scope) {
53
46
  export function memorySyncCursorPath(memoryEngineDir, scope) {
54
47
  return join(memoryEngineDir, SYNC_CURSOR_DIR, `${scopeDigest(scope)}.json`);
55
48
  }
56
- function unusableCursor(scope, path, why) {
57
- return new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — the cursor file ${path} cannot be read as a sync cursor (${why}); the layer stays unsynced until the operator picks one of two ways out: ` +
58
- "(1) restore the file from a backup; (2) delete it to accept re-convergence from an empty baseline — entries changed on both sides are reported as conflicts, " +
59
- "and entries deleted on one side since the last round come back from the other side");
49
+ const CURSOR_AXES = { scope: true, peer: true, store: true };
50
+ const CURSOR_FOUND = { absent: true, other: true };
51
+ function wayOutSentence(wayOut, cursorPath) {
52
+ switch (wayOut) {
53
+ case "delete_cursor":
54
+ return (`one way out: delete the cursor file ${cursorPath} — the next round re-converges from an empty baseline: it pulls what the center holds for this layer, ` +
55
+ "pushes what this store holds and pushes no deletes; entries changed on both sides are reported as conflicts, and an entry deleted on one side since the last round comes back from the other side");
56
+ default: {
57
+ const unhandled = wayOut;
58
+ throw new Error(`memory sync: no sentence for way out ${String(unhandled)}`);
59
+ }
60
+ }
61
+ }
62
+ function parkCauseSentence(cause) {
63
+ switch (cause.kind) {
64
+ case "cursor_refused":
65
+ return cause.detail === undefined
66
+ ? "the sync engine refused its cursor"
67
+ : `the sync engine refused its cursor on the ${cause.detail.axis} axis (${cause.detail.found === "absent" ? `the cursor has no ${cause.detail.axis} binding` : `the cursor is bound to another ${cause.detail.axis}`})`;
68
+ case "cursor_unusable":
69
+ return `the cursor file cannot be read as a sync cursor (${cause.why})`;
70
+ default: {
71
+ const unhandled = cause;
72
+ throw new Error(`memory sync: unknown park cause ${JSON.stringify(unhandled)}`);
73
+ }
74
+ }
75
+ }
76
+ class MemorySyncLayerParked extends Error {
77
+ scope;
78
+ cursorPath;
79
+ parkCause;
80
+ wayOut;
81
+ constructor(scope, cursorPath, parkCause, wayOut = "delete_cursor") {
82
+ super(`memory sync: layer ${JSON.stringify(scope)} parked — ${parkCauseSentence(parkCause)}; ${wayOutSentence(wayOut, cursorPath)}`);
83
+ this.scope = scope;
84
+ this.cursorPath = cursorPath;
85
+ this.parkCause = parkCause;
86
+ this.wayOut = wayOut;
87
+ }
88
+ }
89
+ function cursorRefusalOf(err) {
90
+ if (err === null || typeof err !== "object" || err.code !== "config.memory_sync_cursor_mismatch")
91
+ return undefined;
92
+ const d = err.detail;
93
+ const axis = d?.axis;
94
+ const found = d?.found;
95
+ const known = typeof axis === "string" && Object.hasOwn(CURSOR_AXES, axis) && typeof found === "string" && Object.hasOwn(CURSOR_FOUND, found);
96
+ return { detail: known ? { axis, found } : undefined };
60
97
  }
61
98
  function cursorOf(parsed) {
62
99
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
@@ -79,18 +116,18 @@ async function loadLayerBaseline(path, scope) {
79
116
  catch (err) {
80
117
  if (err.code === "ENOENT")
81
118
  return { kind: "none" };
82
- throw new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — cannot read the cursor file ${path} (${String(err)}); the next round reads it again once it is readable`);
119
+ throw new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — cannot read the cursor file ${path} (${errorText(err)}); the next round reads it again once it is readable`);
83
120
  }
84
121
  let parsed;
85
122
  try {
86
123
  parsed = JSON.parse(raw);
87
124
  }
88
125
  catch {
89
- throw unusableCursor(scope, path, "not JSON");
126
+ throw new MemorySyncLayerParked(scope, path, { kind: "cursor_unusable", why: "not JSON" });
90
127
  }
91
128
  const cursor = cursorOf(parsed);
92
129
  if (typeof cursor === "string")
93
- throw unusableCursor(scope, path, cursor);
130
+ throw new MemorySyncLayerParked(scope, path, { kind: "cursor_unusable", why: cursor });
94
131
  return { kind: "cursor", cursor };
95
132
  }
96
133
  async function saveCursor(path, cursor) {
@@ -190,7 +227,7 @@ export async function relocateSyncCursorsM31(memoryEngineDir, log, opts) {
190
227
  const scope = scopeOfLegacyCursor(raw);
191
228
  if (scope === undefined) {
192
229
  left += 1;
193
- log.warn("memory_sync_cursor_not_relocated", { path: legacyFile, note: "a cursor file written by an earlier version that is not a JSON object naming its scope, so no layer can take it as its baseline; restore it from a backup and restart, or delete it" });
230
+ log.warn("memory_sync_cursor_not_relocated", { path: legacyFile, note: "a cursor file written by an earlier version that is not a JSON object naming its scope, so no layer can take it as its baseline and no round reads it; delete it" });
194
231
  }
195
232
  else {
196
233
  await move(legacyFile, memorySyncCursorPath(memoryEngineDir, scope));
@@ -221,7 +258,7 @@ export function createMemorySyncRunner(opts) {
221
258
  const projectLayers = new Set([...(local.supported ? local.scopes : []), ...tracked].filter(isCoveredProjectLayer));
222
259
  return [opts.scope, ...[...projectLayers].sort()];
223
260
  };
224
- const syncLayer = async (scope, reason) => {
261
+ const syncLayer = async (scope, reason, calls) => {
225
262
  const cursorPath = memorySyncCursorPath(memoryEngineDir, scope);
226
263
  const baseline = await loadLayerBaseline(cursorPath, scope);
227
264
  let cursor = baseline.kind === "cursor" ? baseline.cursor : undefined;
@@ -229,15 +266,24 @@ export function createMemorySyncRunner(opts) {
229
266
  let subRound = 0;
230
267
  for (;;) {
231
268
  subRound += 1;
232
- result = await syncMemoryScope({
233
- backend: storeOf(scope),
234
- scope,
235
- transport: opts.transport,
236
- ...(cursor !== undefined ? { cursor } : {}),
237
- ...(opts.now !== undefined ? { now: opts.now } : {}),
238
- ...(opts.maxPushEntries !== undefined ? { maxPushEntries: opts.maxPushEntries } : {}),
239
- ...(opts.maxPullEntries !== undefined ? { maxPullEntries: opts.maxPullEntries } : {}),
240
- });
269
+ try {
270
+ result = await syncMemoryScope({
271
+ backend: storeOf(scope),
272
+ scope,
273
+ transport: opts.transport,
274
+ ...(cursor !== undefined ? { cursor } : {}),
275
+ ...(opts.now !== undefined ? { now: opts.now } : {}),
276
+ ...(opts.maxPushEntries !== undefined ? { maxPushEntries: opts.maxPushEntries } : {}),
277
+ ...(opts.maxPullEntries !== undefined ? { maxPullEntries: opts.maxPullEntries } : {}),
278
+ });
279
+ }
280
+ catch (err) {
281
+ const refusal = cursorRefusalOf(err);
282
+ if (refusal !== undefined)
283
+ throw new MemorySyncLayerParked(scope, cursorPath, { kind: "cursor_refused", detail: refusal.detail });
284
+ throw err;
285
+ }
286
+ calls.push({ scope, result });
241
287
  for (const w of result.warnings ?? [])
242
288
  opts.log.warn("memory_sync_warning", { scope, subRound, warning: w });
243
289
  if (!(result.pushTruncated === true || result.pullTruncated === true))
@@ -251,7 +297,7 @@ export function createMemorySyncRunner(opts) {
251
297
  await saveCursor(cursorPath, result.cursor);
252
298
  }
253
299
  catch (err) {
254
- opts.log.warn("memory_sync_cursor_write_failed", { path: cursorPath, err: String(err) });
300
+ opts.log.warn("memory_sync_cursor_write_failed", { path: cursorPath, err: errorText(err) });
255
301
  }
256
302
  }
257
303
  cursor = result.cursor ?? cursor;
@@ -272,7 +318,7 @@ export function createMemorySyncRunner(opts) {
272
318
  await saveCursor(cursorPath, result.cursor);
273
319
  }
274
320
  catch (err) {
275
- opts.log.warn("memory_sync_cursor_write_failed", { path: cursorPath, err: String(err) });
321
+ opts.log.warn("memory_sync_cursor_write_failed", { path: cursorPath, err: errorText(err) });
276
322
  }
277
323
  }
278
324
  opts.log.info("memory_sync_round", {
@@ -289,26 +335,47 @@ export function createMemorySyncRunner(opts) {
289
335
  return result;
290
336
  };
291
337
  let relocated = false;
338
+ let parkedLayers = [];
339
+ const logLayerFailure = (reason, scope, err) => {
340
+ if (err instanceof MemorySyncLayerParked) {
341
+ const refused = err.parkCause.kind === "cursor_refused" ? err.parkCause.detail : undefined;
342
+ opts.log.warn("memory_sync_layer_parked", { reason, scope, cursorPath: err.cursorPath, ...(refused !== undefined ? { axis: refused.axis, found: refused.found } : {}), wayOut: err.wayOut, note: err.message });
343
+ }
344
+ else {
345
+ opts.log.warn("memory_sync_layer_failed", { reason, scope, cursorPath: memorySyncCursorPath(memoryEngineDir, scope), err: errorText(err) });
346
+ }
347
+ };
292
348
  const round = async (reason) => {
293
- if (!relocated) {
294
- await relocateSyncCursorsM31(memoryEngineDir, opts.log, { today: new Date(now()).toISOString().slice(0, 10) });
295
- relocated = true;
349
+ const calls = [];
350
+ let layers;
351
+ try {
352
+ if (!relocated) {
353
+ await relocateSyncCursorsM31(memoryEngineDir, opts.log, { today: new Date(now()).toISOString().slice(0, 10) });
354
+ relocated = true;
355
+ }
356
+ layers = layersOfRound();
357
+ }
358
+ catch (err) {
359
+ for (const scope of [opts.scope, ...[...tracked].filter(isCoveredProjectLayer).sort()]) {
360
+ calls.push({ scope, thrown: err });
361
+ logLayerFailure(reason, scope, err);
362
+ }
363
+ return { calls };
296
364
  }
297
- const layers = [];
298
- const failures = [];
299
- for (const scope of layersOfRound()) {
365
+ const parkedNow = [];
366
+ for (const scope of layers) {
300
367
  try {
301
- layers.push({ scope, result: await syncLayer(scope, reason) });
368
+ await syncLayer(scope, reason, calls);
302
369
  }
303
370
  catch (err) {
304
- failures.push({ scope, err });
305
- opts.log.warn("memory_sync_layer_failed", { reason, scope, cursorPath: memorySyncCursorPath(memoryEngineDir, scope), err: String(err) });
371
+ calls.push({ scope, thrown: err });
372
+ if (err instanceof MemorySyncLayerParked)
373
+ parkedNow.push({ scope, wayOut: err.wayOut });
374
+ logLayerFailure(reason, scope, err);
306
375
  }
307
376
  }
308
- if (failures.length > 0) {
309
- throw new Error(`memory sync round failed for ${failures.length} layer(s): ${failures.map((f) => `${f.scope}: ${f.err instanceof Error ? f.err.message : String(f.err)}`).join("; ")}`);
310
- }
311
- return { layers };
377
+ parkedLayers = Object.freeze(parkedNow);
378
+ return { calls };
312
379
  };
313
380
  let chain = Promise.resolve();
314
381
  let pending = 0;
@@ -322,11 +389,14 @@ export function createMemorySyncRunner(opts) {
322
389
  });
323
390
  return run;
324
391
  };
325
- const syncOnce = (reason) => exclusive(() => round(reason));
392
+ const syncOnce = (reason, syncOpts) => {
393
+ for (const scope of syncOpts?.reading ?? [])
394
+ if (isCoveredProjectLayer(scope))
395
+ tracked.add(scope);
396
+ return exclusive(() => round(reason));
397
+ };
326
398
  const queueRound = (reason) => {
327
- void syncOnce(reason).catch((err) => {
328
- opts.log.warn("memory_sync_failed", { reason, scope: opts.scope, err: String(err) });
329
- });
399
+ void syncOnce(reason);
330
400
  };
331
401
  const trigger = (reason) => {
332
402
  if (pending > 0)
@@ -342,7 +412,7 @@ export function createMemorySyncRunner(opts) {
342
412
  tracked.add(scope);
343
413
  queueRound("track");
344
414
  };
345
- return { syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(memoryEngineDir, scope), pending: () => pending };
415
+ return { peer: opts.transport.peer, syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(memoryEngineDir, scope), parked: () => parkedLayers, pending: () => pending };
346
416
  }
347
417
  export function startMemorySyncRunner(deps) {
348
418
  const { sync } = deps;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 同步身份的拒因词(闭集,判序):形的四词逐字是契约包 `HttpUrlFlaw`;`credential` = 规范化之后带凭据;`too-long` = 装不进中心 `peer` 列;
3
+ * `not-canonical` = 值本身没缺陷、但不是它自己的规范形(只在「声明的身份」座位出现)。wire 面:`request.field_invalid` 拒体的 `reason`。
4
+ */
5
+ export declare const MEMORY_SYNC_IDENTITY_FLAWS: readonly ["not-http-url", "userinfo", "query", "fragment", "credential", "too-long", "not-canonical"];
6
+ export type MemorySyncIdentityFlaw = (typeof MEMORY_SYNC_IDENTITY_FLAWS)[number];
7
+ /** 铸点的答:身份,或首个缺陷。 */
8
+ export type MemorySyncIdentityReading = {
9
+ readonly identity: string;
10
+ } | {
11
+ readonly flaw: Exclude<MemorySyncIdentityFlaw, "not-canonical">;
12
+ };
13
+ /** 唯一铸点(纯函数)。值 → 规范化身份,或首个缺陷。 */
14
+ export declare function readMemorySyncIdentity(raw: string): MemorySyncIdentityReading;
15
+ /** 「声明的身份」座位(中心声明 / 会话触发的 `target` / 中心入站的 `peer`):值必须**就是**它自己的规范化身份。`undefined` = 合格。 */
16
+ export declare function declaredMemorySyncIdentityFlaw(raw: string): MemorySyncIdentityFlaw | undefined;
17
+ /** 一个拒因词的规则句(穷举;新词不补 = 编译红)。只说规则,不含值。 */
18
+ export declare function memorySyncIdentityRule(flaw: MemorySyncIdentityFlaw): string;
19
+ /**
20
+ * 起服座位(env 键)的拒句:`memory sync: <键> <规则句>[: <出路>]`。形的四词走 URL 形拒句的铸点(值只到 origin);其余三词值不回显 ——
21
+ * `not-canonical` 例外地给出**规范形**(它已过凭据判官,是运维要写下的那一串;仍经展示铸点)。
22
+ */
23
+ export declare function buildMemorySyncIdentityBootRefusal(envKey: string, raw: string, flaw: MemorySyncIdentityFlaw, exit: string): string;
24
+ //# sourceMappingURL=memory-sync-identity.d.ts.map
@@ -0,0 +1,57 @@
1
+ import { carriesCredential } from "@sema-agent/core";
2
+ import { httpUrlRule, pathBaseUrlFlaws } from "@sema-agent/settings-schema";
3
+ import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
4
+ import { buildHttpUrlRefusal } from "./url-refusal.js";
5
+ import { buildDisplaySafeUrl } from "./trace/redact.js";
6
+ export const MEMORY_SYNC_IDENTITY_FLAWS = ["not-http-url", "userinfo", "query", "fragment", "credential", "too-long", "not-canonical"];
7
+ export function readMemorySyncIdentity(raw) {
8
+ const shape = pathBaseUrlFlaws(raw)[0];
9
+ if (shape !== undefined)
10
+ return { flaw: shape };
11
+ const u = new URL(raw);
12
+ const identity = `${u.origin}${u.pathname.replace(/\/+$/, "")}`;
13
+ if (carriesCredential(identity))
14
+ return { flaw: "credential" };
15
+ if (identity.length > MEMORY_SYNC_PEER_COLUMN_CHARS)
16
+ return { flaw: "too-long" };
17
+ return { identity };
18
+ }
19
+ export function declaredMemorySyncIdentityFlaw(raw) {
20
+ const r = readMemorySyncIdentity(raw);
21
+ if ("flaw" in r)
22
+ return r.flaw;
23
+ return r.identity === raw ? undefined : "not-canonical";
24
+ }
25
+ export function memorySyncIdentityRule(flaw) {
26
+ switch (flaw) {
27
+ case "not-http-url":
28
+ case "userinfo":
29
+ case "query":
30
+ case "fragment":
31
+ return httpUrlRule(flaw);
32
+ case "credential":
33
+ return "carries a credential (a token-shaped part of its path) — a sync identity travels verbatim in every request body and cursor and is published on the capability face, so it is refused, never cleaned";
34
+ case "too-long":
35
+ return `is longer than ${MEMORY_SYNC_PEER_COLUMN_CHARS} characters once normalized (the central peer column holds at most ${MEMORY_SYNC_PEER_COLUMN_CHARS})`;
36
+ case "not-canonical":
37
+ return "is not in canonical form (lower-case scheme and host, no default port, no trailing slash)";
38
+ }
39
+ }
40
+ export function buildMemorySyncIdentityBootRefusal(envKey, raw, flaw, exit) {
41
+ switch (flaw) {
42
+ case "not-http-url":
43
+ case "userinfo":
44
+ case "query":
45
+ case "fragment":
46
+ return buildHttpUrlRefusal(`memory sync: ${envKey}`, raw, [flaw]);
47
+ case "credential":
48
+ return `memory sync: ${envKey} holds a credential — ${memorySyncIdentityRule(flaw)}: ${exit}`;
49
+ case "too-long":
50
+ return `memory sync: ${envKey} ${memorySyncIdentityRule(flaw)}: use a shorter base URL`;
51
+ case "not-canonical": {
52
+ const r = readMemorySyncIdentity(raw);
53
+ return `memory sync: ${envKey} ${memorySyncIdentityRule(flaw)} — what is declared is what is published and compared, so it is not normalized for you: set it to ${JSON.stringify("identity" in r ? buildDisplaySafeUrl(r.identity) : "")}`;
54
+ }
55
+ }
56
+ }
57
+ //# sourceMappingURL=memory-sync-identity.js.map
@@ -1,8 +1,10 @@
1
1
  import { type MemoryBackend, type MemoryEntry, type PatchReport } from "@sema-agent/core";
2
2
  import type { MemorySyncStore } from "./plugins/memory-sync-store-pg.js";
3
+ import { type MemorySyncIdentityFlaw } from "./memory-sync-identity.js";
3
4
  /** 一轮同步请求(验型后的干净形状——`parseMemorySyncRequest` 是唯一铸造点)。 */
4
5
  export interface MemorySyncRequest {
5
- /** 对端标识(`CentralSyncCursor.peer`,per-(scope,peer) 游标分区键)= 客户端 transport 的身份(本仓的客户端腿:规范化的中心基址,`memory-sync-client.ts` `memorySyncPeerOf`)。 */
6
+ /** 对端标识(`CentralSyncCursor.peer`,per-(scope,peer) 游标分区键)= 客户端到达本中心用的同步身份(规范化基址)。验型时已过同步身份的唯一铸点
7
+ * (`memory-sync-identity.ts`,「声明的身份」座位:必须已是规范形、不带凭据);路由随后与本中心声明的身份按串比(不等 ⇒ 409,零写)。 */
6
8
  peer: string;
7
9
  /** 客户端游标的共同基线:id → rev at last completed sync round(空 map = 首轮)。 */
8
10
  baseRevs: Record<string, string>;
@@ -48,12 +50,16 @@ export interface MemorySyncResponse {
48
50
  * 同步一轮打满全集也在此内)。deletes 上限同基数放宽(全集删除也要能一轮表达)。 */
49
51
  export declare const MEMORY_SYNC_MAX_ENTRIES = 500;
50
52
  export declare const MEMORY_SYNC_MAX_DELETES = 2000;
53
+ /** 验型的答:干净形,或体的形错(HTTP 422),或 `peer` 不是声明的同步身份(HTTP 400 `request.field_invalid`,`field: "peer"`,拒因词 = 铸点的闭集,不回显值)。 */
51
54
  type ParseResult = {
52
55
  ok: true;
53
56
  value: MemorySyncRequest;
54
57
  } | {
55
58
  ok: false;
56
59
  error: string;
60
+ } | {
61
+ ok: false;
62
+ peerFlaw: MemorySyncIdentityFlaw;
57
63
  };
58
64
  /** Shape-validate + NORMALIZE the request body (picked-not-spread:只挑认识的字段重建,body 上的
59
65
  * 多余键绝不透传进数据面)。`scope` = 路径上已过 owner 门的 scope——每个条目的 scope 必须逐字
@@ -1,6 +1,6 @@
1
1
  import { computeEntryRev, MEMORY_ORIGIN_CAUSES, nextSyncBaseline, reconcileMemoryEntries, screenInboundEntries, } from "@sema-agent/core";
2
2
  import { exportMemoryScope } from "./memory-export.js";
3
- import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
3
+ import { declaredMemorySyncIdentityFlaw } from "./memory-sync-identity.js";
4
4
  export const MEMORY_SYNC_MAX_ENTRIES = 500;
5
5
  export const MEMORY_SYNC_MAX_DELETES = 2000;
6
6
  export function parseMemorySyncRequest(body, scope) {
@@ -8,8 +8,11 @@ export function parseMemorySyncRequest(body, scope) {
8
8
  return { ok: false, error: "body must be a JSON object { peer, baseRevs, entries, deletes }" };
9
9
  const b = body;
10
10
  const peer = b["peer"];
11
- if (typeof peer !== "string" || peer.length === 0 || peer.length > MEMORY_SYNC_PEER_COLUMN_CHARS)
12
- return { ok: false, error: `peer must be a non-empty string of at most ${MEMORY_SYNC_PEER_COLUMN_CHARS} characters` };
11
+ if (typeof peer !== "string")
12
+ return { ok: false, error: "peer must be a string — the sync identity the client reaches this center at" };
13
+ const peerFlaw = declaredMemorySyncIdentityFlaw(peer);
14
+ if (peerFlaw !== undefined)
15
+ return { ok: false, peerFlaw };
13
16
  const rawBase = b["baseRevs"];
14
17
  if (rawBase === null || typeof rawBase !== "object" || Array.isArray(rawBase))
15
18
  return { ok: false, error: "baseRevs must be an object mapping entry id → rev (may be empty on the first round)" };