@sema-agent/server 7.142.0 → 7.143.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 (258) hide show
  1. package/MIGRATION.md +442 -0
  2. package/USAGE.md +41 -24
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +7 -5
  5. package/dist/approval-ask-audit-store.js +12 -6
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +124 -25
  9. package/dist/approval-card.js +103 -23
  10. package/dist/approval-queue-kind.d.ts +27 -0
  11. package/dist/approval-queue-kind.js +6 -0
  12. package/dist/approval-reconciler.d.ts +8 -8
  13. package/dist/approval-reconciler.js +3 -3
  14. package/dist/audit.js +4 -2
  15. package/dist/boot/budget-tracing.d.ts +1 -1
  16. package/dist/boot/governance-seams.d.ts +26 -41
  17. package/dist/boot/governance-seams.js +61 -28
  18. package/dist/boot/late-binds.d.ts +7 -4
  19. package/dist/boot/late-binds.js +3 -1
  20. package/dist/boot/leader.js +2 -4
  21. package/dist/boot/memory-consolidation.d.ts +0 -8
  22. package/dist/boot/memory-consolidation.js +3 -3
  23. package/dist/boot/reapers.js +5 -4
  24. package/dist/boot/resolve-spec.d.ts +1 -1
  25. package/dist/boot/resolve-spec.js +8 -8
  26. package/dist/boot/runner-deps.d.ts +10 -7
  27. package/dist/boot/runner-deps.js +5 -17
  28. package/dist/boot/shutdown.d.ts +9 -0
  29. package/dist/boot/shutdown.js +16 -2
  30. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  31. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  32. package/dist/boot/stage-03-store-backend.js +1 -2
  33. package/dist/boot/stage-04-stores.d.ts +5 -2
  34. package/dist/boot/stage-04-stores.js +3 -5
  35. package/dist/boot/stage-05-execution-env.d.ts +5 -2
  36. package/dist/boot/stage-06-runners.d.ts +5 -2
  37. package/dist/boot/stage-06-runners.js +1 -3
  38. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  39. package/dist/boot/stage-07-capability-layer.js +5 -1
  40. package/dist/boot/stage-08-reapers.d.ts +5 -2
  41. package/dist/boot/stage-09-leader.d.ts +5 -2
  42. package/dist/boot/stage-10-http-server.d.ts +5 -2
  43. package/dist/boot/stage-10-http-server.js +9 -1
  44. package/dist/boot/stores.d.ts +5 -2
  45. package/dist/boot/stores.js +7 -7
  46. package/dist/boot/web-admin.d.ts +5 -1
  47. package/dist/boot/web-admin.js +31 -19
  48. package/dist/capabilities/hands-lane.d.ts +9 -39
  49. package/dist/capabilities/hands-lane.js +8 -13
  50. package/dist/capabilities/mcp-probe.d.ts +3 -2
  51. package/dist/capabilities/scenarios.d.ts +11 -1
  52. package/dist/capabilities/scenarios.js +1 -0
  53. package/dist/capabilities/skills.d.ts +1 -17
  54. package/dist/capabilities/skills.js +0 -8
  55. package/dist/capability-facts.d.ts +8 -8
  56. package/dist/capability-facts.js +4 -4
  57. package/dist/card-void.d.ts +6 -9
  58. package/dist/config-catalog.js +1 -1
  59. package/dist/config-dir-trust.js +58 -29
  60. package/dist/config-invariants.d.ts +7 -1
  61. package/dist/config-invariants.js +7 -0
  62. package/dist/config-provider.d.ts +10 -0
  63. package/dist/config-provider.js +15 -4
  64. package/dist/config-root-migration.d.ts +13 -0
  65. package/dist/config-root-migration.js +99 -0
  66. package/dist/config-types.d.ts +5 -5
  67. package/dist/config.d.ts +4 -3
  68. package/dist/config.js +1 -1
  69. package/dist/delegated-session-owner.d.ts +5 -3
  70. package/dist/delegated-session-owner.js +1 -1
  71. package/dist/device-ws-protocol.d.ts +1 -0
  72. package/dist/device-ws-protocol.js +2 -1
  73. package/dist/env-model-table-gate.d.ts +53 -0
  74. package/dist/env-model-table-gate.js +53 -0
  75. package/dist/fleet/fleet-bus.d.ts +28 -52
  76. package/dist/fleet/fleet-bus.js +35 -100
  77. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  78. package/dist/fleet/fleet-reconciler.js +1 -1
  79. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  80. package/dist/fleet/subagent-tail-bus.js +15 -28
  81. package/dist/hooks/hook-runner.js +28 -7
  82. package/dist/host-decision.d.ts +54 -20
  83. package/dist/host-decision.js +42 -14
  84. package/dist/http/admission.d.ts +1 -1
  85. package/dist/http/admission.js +7 -3
  86. package/dist/http/approval-sweeps.js +11 -8
  87. package/dist/http/caller-trust.d.ts +1 -1
  88. package/dist/http/durable-run-leg.js +2 -2
  89. package/dist/http/engine-refusal.d.ts +16 -0
  90. package/dist/http/engine-refusal.js +17 -0
  91. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  92. package/dist/http/memory-compliance-failure-reply.js +4 -6
  93. package/dist/http/owner-access.d.ts +11 -0
  94. package/dist/http/owner-access.js +13 -0
  95. package/dist/http/resume-legs.d.ts +21 -7
  96. package/dist/http/resume-legs.js +71 -49
  97. package/dist/http/resume-restatement.d.ts +39 -0
  98. package/dist/http/resume-restatement.js +25 -0
  99. package/dist/http/route-ctx.d.ts +35 -10
  100. package/dist/http/route-ctx.js +1 -1
  101. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  102. package/dist/http/routes/approvals-assistant.js +142 -31
  103. package/dist/http/routes/capabilities.js +11 -2
  104. package/dist/http/routes/memory-policy.d.ts +2 -2
  105. package/dist/http/routes/memory-policy.js +1 -1
  106. package/dist/http/routes/notify-wake.js +23 -25
  107. package/dist/http/routes/rules.js +48 -13
  108. package/dist/http/routes/runs.js +9 -8
  109. package/dist/http/routes/session-memory-sync.d.ts +34 -6
  110. package/dist/http/routes/session-memory-sync.js +27 -10
  111. package/dist/http/routes/session-sync.js +13 -20
  112. package/dist/http/routes/sessions.d.ts +2 -2
  113. package/dist/http/routes/sessions.js +22 -23
  114. package/dist/http/routes/tasks.js +27 -49
  115. package/dist/http/server.d.ts +13 -7
  116. package/dist/http/server.js +2 -2
  117. package/dist/http/stored-body-replay.js +15 -4
  118. package/dist/http/wire-types.d.ts +17 -7
  119. package/dist/leader/leader.js +5 -2
  120. package/dist/leader/wire.d.ts +7 -1
  121. package/dist/leader/wire.js +6 -5
  122. package/dist/leg-facts.d.ts +37 -0
  123. package/dist/leg-facts.js +15 -0
  124. package/dist/memory-bundle-engine.d.ts +1 -4
  125. package/dist/memory-bundle-engine.js +2 -2
  126. package/dist/memory-operator-faces.d.ts +30 -66
  127. package/dist/memory-operator-faces.js +17 -88
  128. package/dist/memory-scope-stores.d.ts +38 -40
  129. package/dist/memory-scope-stores.js +51 -36
  130. package/dist/memory-scope.d.ts +0 -9
  131. package/dist/memory-scope.js +0 -9
  132. package/dist/memory-sync-client.js +14 -3
  133. package/dist/memory-sync.d.ts +20 -4
  134. package/dist/memory-sync.js +23 -4
  135. package/dist/model-plane-face.d.ts +13 -7
  136. package/dist/model-plane-face.js +2 -1
  137. package/dist/observability/fail-open.d.ts +20 -4
  138. package/dist/observability/fail-open.js +17 -4
  139. package/dist/observability/run-terminal.js +4 -0
  140. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  141. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  142. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  143. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  144. package/dist/orchestration/workflow-park-decision.js +5 -3
  145. package/dist/org-delivery-gate.d.ts +11 -0
  146. package/dist/org-delivery-gate.js +19 -1
  147. package/dist/parked-decide.d.ts +13 -9
  148. package/dist/parked-decide.js +7 -7
  149. package/dist/parked-revive-surface.js +1 -1
  150. package/dist/peer-inbound-face.d.ts +127 -0
  151. package/dist/peer-inbound-face.js +158 -0
  152. package/dist/permission-rule-list.d.ts +13 -9
  153. package/dist/permission-rule-list.js +7 -3
  154. package/dist/plugins/approval-ask-store-file.js +18 -6
  155. package/dist/plugins/approval-ask-store-memory.js +8 -6
  156. package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
  157. package/dist/plugins/approval-ask-store-sql.js +30 -11
  158. package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
  159. package/dist/plugins/checkpoint-store-sql.js +9 -7
  160. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  161. package/dist/plugins/file-history-store-sql.js +24 -19
  162. package/dist/plugins/file-run-store.d.ts +29 -0
  163. package/dist/plugins/file-run-store.js +97 -8
  164. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  165. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  166. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  167. package/dist/plugins/local-checkpoint-store.js +2 -2
  168. package/dist/plugins/local-session-store.d.ts +6 -2
  169. package/dist/plugins/local-session-store.js +8 -2
  170. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  171. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  172. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  173. package/dist/plugins/mailbox-store-sql.js +76 -7
  174. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  175. package/dist/plugins/memory-sync-store-pg.js +21 -0
  176. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  177. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  178. package/dist/plugins/one-time-migrations.d.ts +32 -2
  179. package/dist/plugins/one-time-migrations.js +106 -3
  180. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  181. package/dist/plugins/org-delivery-quota-store.js +2 -12
  182. package/dist/plugins/park-commit-watch.d.ts +1 -1
  183. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  184. package/dist/plugins/permission-rule-store-file.js +15 -2
  185. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  186. package/dist/plugins/permission-rule-store-sql.js +35 -2
  187. package/dist/plugins/remote-env-device.d.ts +8 -11
  188. package/dist/plugins/remote-env-device.js +6 -3
  189. package/dist/plugins/remote-env-k8s.d.ts +37 -24
  190. package/dist/plugins/remote-env-k8s.js +50 -17
  191. package/dist/plugins/retention-store-sql.d.ts +2 -2
  192. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  193. package/dist/plugins/sandbox-read-floor.js +2 -7
  194. package/dist/plugins/sql-errors.d.ts +10 -1
  195. package/dist/plugins/sql-errors.js +13 -0
  196. package/dist/plugins/store-backend.d.ts +23 -28
  197. package/dist/plugins/store-backend.js +9 -1
  198. package/dist/plugins/store-contracts.d.ts +29 -5
  199. package/dist/plugins/store-contracts.js +4 -1
  200. package/dist/plugins/task-attachment-store.d.ts +2 -2
  201. package/dist/plugins/task-attachment-store.js +37 -52
  202. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  203. package/dist/plugins/tool-result-store-sql.js +23 -2
  204. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  205. package/dist/plugins/wiring-section-codec.js +55 -0
  206. package/dist/plugins/workspace-fill.d.ts +1 -1
  207. package/dist/rule-entry-list.d.ts +3 -2
  208. package/dist/rule-entry-list.js +5 -1
  209. package/dist/rules-consent.d.ts +28 -5
  210. package/dist/rules-consent.js +40 -28
  211. package/dist/run-local.js +9 -5
  212. package/dist/runs.d.ts +12 -8
  213. package/dist/runs.js +6 -7
  214. package/dist/runtime-governance.d.ts +1 -1
  215. package/dist/server-secret-env.d.ts +4 -0
  216. package/dist/session-error-wire.d.ts +12 -0
  217. package/dist/session-error-wire.js +6 -0
  218. package/dist/session-incarnation.d.ts +1 -1
  219. package/dist/session-notify-door.d.ts +106 -0
  220. package/dist/session-notify-door.js +93 -0
  221. package/dist/session-sync.d.ts +9 -14
  222. package/dist/session-sync.js +5 -5
  223. package/dist/session-workspace.d.ts +3 -13
  224. package/dist/session-workspace.js +0 -9
  225. package/dist/standing-answer-grant.d.ts +5 -0
  226. package/dist/standing-answer-grant.js +10 -0
  227. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  228. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  229. package/dist/task-mcp.d.ts +1 -1
  230. package/dist/task-settings.d.ts +11 -8
  231. package/dist/task-settings.js +31 -11
  232. package/dist/tool-approval.d.ts +52 -34
  233. package/dist/tool-approval.js +66 -57
  234. package/dist/trace/core-keyset-guard.d.ts +11 -8
  235. package/dist/trace/engine-notice-wire.d.ts +1 -1
  236. package/dist/trace/engine-notice-wire.js +3 -1
  237. package/dist/trace/exit-seat.d.ts +7 -1
  238. package/dist/trace/exit-seat.js +1 -0
  239. package/dist/trace/ledger-sink.d.ts +2 -2
  240. package/dist/trace/model-segment.d.ts +55 -25
  241. package/dist/trace/model-segment.js +101 -33
  242. package/dist/trace/project.d.ts +7 -4
  243. package/dist/trace/project.js +6 -0
  244. package/dist/trace/redact.d.ts +61 -9
  245. package/dist/trace/redact.js +1490 -468
  246. package/dist/trace/self-minted-wire.d.ts +510 -38
  247. package/dist/trace/self-minted-wire.js +165 -17
  248. package/dist/trace/wire-projection-faces.d.ts +1 -1
  249. package/dist/trace/wire-projection-faces.js +1 -1
  250. package/dist/verifier-pause-card.d.ts +33 -5
  251. package/dist/verifier-pause-card.js +15 -10
  252. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  253. package/dist/web-admin-client/apply-effective.js +90 -10
  254. package/dist/workspace-space.d.ts +19 -3
  255. package/dist/workspace-space.js +8 -4
  256. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  257. package/package.json +3 -2
  258. package/wire-changes/server@7.143.0.json +1167 -0
@@ -1,5 +1,6 @@
1
1
  import { lstat, readFile, readdir, readlink, realpath, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { CONFIG_ROOT_DOCUMENTS } from "@sema-agent/settings-schema";
3
4
  import { LOCAL_READER_DOCUMENTS } from "./config-d-migration.js";
4
5
  import { recordFailOpen } from "./observability/fail-open.js";
5
6
  import { HOST_PLATFORM } from "./plugins/host-platform.js";
@@ -244,35 +245,44 @@ async function confirmUnreached(lexical) {
244
245
  return { kind: "unresolvable", path: lexical };
245
246
  }
246
247
  const documentFiles = () => LOCAL_READER_DOCUMENTS.map((d) => `${d}.json`);
247
- async function reachDir(lexical, ctx) {
248
+ const rootDocumentFiles = () => CONFIG_ROOT_DOCUMENTS.map((d) => `${d}.json`);
249
+ async function reach(lexical, want, ctx) {
248
250
  const top = await lstatStep("/");
249
251
  if (!isStats(top))
250
252
  return "finding" in top ? top.finding : undefined;
251
253
  const rootVerdict = await judgeComponent("/", top, ctx);
252
254
  if (typeof rootVerdict !== "string")
253
255
  return rootVerdict;
254
- const step = await walk({ start: "/", stats: top, open: rootVerdict === "open", hops: 0 }, lexical.split("/"), "dir", ctx);
256
+ const step = await walk({ start: "/", stats: top, open: rootVerdict === "open", hops: 0 }, lexical.split("/"), want, ctx);
255
257
  if ("finding" in step)
256
258
  return step.finding;
257
259
  return step.reached ? step : confirmUnreached(lexical);
258
260
  }
259
- async function judgePosix(configDir, ctx) {
260
- const dir = await reachDir(configDir, ctx);
261
- if (dir === undefined || "kind" in dir)
262
- return dir;
263
- for (const file of documentFiles()) {
264
- const doc = await walk({ start: dir.physical, stats: dir.stats, open: dir.open, hops: dir.hops }, [file], "file", ctx);
261
+ async function judgeDocumentsIn(at, lexical, files, ctx) {
262
+ for (const file of files) {
263
+ const doc = await walk({ start: at.physical, stats: at.stats, open: at.open, hops: at.hops }, [file], "file", ctx);
265
264
  if ("finding" in doc)
266
265
  return doc.finding;
267
266
  if (!doc.reached) {
268
- const unseen = await confirmUnreached(path.join(configDir, file));
267
+ const unseen = await confirmUnreached(path.join(lexical, file));
269
268
  if (unseen !== undefined)
270
269
  return unseen;
271
270
  }
272
271
  }
273
272
  return undefined;
274
273
  }
275
- async function judgeWin32(configDir) {
274
+ async function judgePosix(root, configDir, ctx) {
275
+ for (const file of rootDocumentFiles()) {
276
+ const doc = await reach(path.join(root, file), "file", ctx);
277
+ if (doc !== undefined && "kind" in doc)
278
+ return doc;
279
+ }
280
+ const dir = await reach(configDir, "dir", ctx);
281
+ if (dir === undefined || "kind" in dir)
282
+ return dir;
283
+ return judgeDocumentsIn(dir, configDir, documentFiles(), ctx);
284
+ }
285
+ async function judgeWin32(root, configDir) {
276
286
  const resolveStep = async (p) => {
277
287
  try {
278
288
  return await realpath(p);
@@ -286,26 +296,44 @@ async function judgeWin32(configDir) {
286
296
  return { kind: "unreadable", path: p, errno: code ?? String(e) };
287
297
  }
288
298
  };
289
- const dir = await resolveStep(configDir);
290
- if (dir === undefined)
299
+ const judgeDocs = async (dir, files) => {
300
+ let present = 0;
301
+ for (const file of files) {
302
+ const doc = await resolveStep(path.join(dir, file));
303
+ if (doc === undefined)
304
+ continue;
305
+ if (typeof doc !== "string")
306
+ return doc;
307
+ const st = await stat(doc);
308
+ if (!st.isFile())
309
+ return { kind: "not-regular", path: doc, type: typeOf(st) };
310
+ if (st.size > CONFIG_DOCUMENT_MAX_BYTES)
311
+ return { kind: "too-large", path: doc, bytes: st.size };
312
+ present += 1;
313
+ }
314
+ return present;
315
+ };
316
+ const rootAt = await resolveStep(root);
317
+ if (rootAt === undefined)
291
318
  return undefined;
292
- if (typeof dir !== "string")
293
- return dir;
294
- if (!(await stat(dir)).isDirectory())
319
+ if (typeof rootAt !== "string")
320
+ return rootAt;
321
+ if (!(await stat(rootAt)).isDirectory())
295
322
  return undefined;
296
- for (const file of documentFiles()) {
297
- const doc = await resolveStep(path.join(dir, file));
298
- if (doc === undefined)
299
- continue;
300
- if (typeof doc !== "string")
301
- return doc;
302
- const st = await stat(doc);
303
- if (!st.isFile())
304
- return { kind: "not-regular", path: doc, type: typeOf(st) };
305
- if (st.size > CONFIG_DOCUMENT_MAX_BYTES)
306
- return { kind: "too-large", path: doc, bytes: st.size };
323
+ const rootDocs = await judgeDocs(rootAt, rootDocumentFiles());
324
+ if (typeof rootDocs !== "number")
325
+ return rootDocs;
326
+ const dir = await resolveStep(path.join(rootAt, path.basename(configDir)));
327
+ if (dir !== undefined && typeof dir !== "string")
328
+ return dir;
329
+ const dirIsDir = dir !== undefined && (await stat(dir)).isDirectory();
330
+ if (dirIsDir) {
331
+ const docs = await judgeDocs(dir, documentFiles());
332
+ if (typeof docs !== "number")
333
+ return docs;
307
334
  }
308
- recordFailOpen(WIN32_UNVERIFIED_TAG, `config.d=${dir}`);
335
+ if (dirIsDir || rootDocs > 0)
336
+ recordFailOpen(WIN32_UNVERIFIED_TAG, dirIsDir ? `config.d=${dir}` : `config root=${rootAt}`);
309
337
  return undefined;
310
338
  }
311
339
  export function localConfigDirOf(root) {
@@ -314,14 +342,15 @@ export function localConfigDirOf(root) {
314
342
  export async function judgeLocalConfigDir(input) {
315
343
  const host = input.host ?? processHost();
316
344
  const configDir = localConfigDirOf(input.root);
345
+ const root = path.dirname(configDir);
317
346
  if (host.platform === "win32")
318
- return judgeWin32(configDir);
347
+ return judgeWin32(root, configDir);
319
348
  const owners = new Set([0]);
320
349
  if (host.euid !== undefined)
321
350
  owners.add(host.euid);
322
351
  if (input.declaredOwnerUid !== undefined)
323
352
  owners.add(input.declaredOwnerUid);
324
- return judgePosix(configDir, { owners, mountinfo: host.mountinfo ?? processMountinfo });
353
+ return judgePosix(root, configDir, { owners, mountinfo: host.mountinfo ?? processMountinfo });
325
354
  }
326
355
  const octal = (mode) => `0${(mode & 0o7777).toString(8).padStart(3, "0")}`;
327
356
  export function buildConfigDirUntrustedSentence(f, ctx) {
@@ -26,6 +26,7 @@
26
26
  * 不引 config.ts,故 config.ts / runs.ts 都可以安全引它。
27
27
  */
28
28
  import type { ConfigProviderWord, ServiceConfigFlat } from "./config-types.js";
29
+ import { type EnvTableReplacementInput } from "./env-model-table-gate.js";
29
30
  /**
30
31
  * 活体心跳周期(ms)——`runs.ts` 的 `HEARTBEAT_MS` 就是它(那边 re-export,单源在此)。
31
32
  *
@@ -34,7 +35,7 @@ import type { ConfigProviderWord, ServiceConfigFlat } from "./config-types.js";
34
35
  */
35
36
  export declare const LIVENESS_HEARTBEAT_MS = 30000;
36
37
  /** 闭集:新增一条不变量必须在这里加词(拼错 = 编译红,而不是运行期悄悄不判)。 */
37
- export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "operator-single-user" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "anthropic-credential-exclusive" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "require-execution-lane" | "center-one-identity" | "workflow-park-decision-domain" | "remote-config-provider-url" | "retention-lane-complete";
38
+ export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "operator-single-user" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "anthropic-credential-exclusive" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "env-table-replacement-acknowledged" | "require-execution-lane" | "center-one-identity" | "workflow-park-decision-domain" | "remote-config-provider-url" | "retention-lane-complete";
38
39
  /** 表读的键集(取自平铺配置型 ⇒ 键名/类型永不与真配置漂)。 */
39
40
  export type CrossInvariantView = Readonly<Pick<ServiceConfigFlat, "runStaleSec" | "authToken" | "authTokens" | "allowUnauthedWrites" | "imageBakes" | "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals" | "operatorSingleUser" | "sensitiveWritePatterns" | "leaderEnabled" | "selfOrchestrationEnabled" | "workflowRunStoreBackend" | "remoteExec" | "requireExecutionLane" | "dbBackend" | "tidb" | "pg" | "envModel" | "gatewayBaseUrl" | "gatewayFallbackUrls" | "anthropic" | "models" | "modelRefusals" | "tiers" | "authBridgeIssuer" | "webAdmin" | "configProvider" | "retentionPolicy" | "retentionSweep">>;
40
41
  export interface CrossInvariantInput {
@@ -42,6 +43,11 @@ export interface CrossInvariantInput {
42
43
  readonly live: CrossInvariantView;
43
44
  /** 候选(stage 期 = 现役 + 本批 staged 赋值)。 */
44
45
  readonly next: CrossInvariantView;
46
+ /**
47
+ * 一份**模型面候选**随身带的、不在配置键里的事实(它从哪条配置腿来、本地腿上配置根的 env 模型表决定在同一拍读作什么、引擎铸的 env 表)——
48
+ * 只有模型面 stage 带(它是装模型表的唯一写口);全表扫描(限额批的候选视图)不装模型表、不带。读它的不变量:`env-table-replacement-acknowledged`。
49
+ */
50
+ readonly candidate?: EnvTableReplacementInput;
45
51
  }
46
52
  export interface CrossInvariant {
47
53
  readonly id: CrossInvariantId;
@@ -4,6 +4,7 @@ import { modelRouteUrlRefusal } from "./model-route-url.js";
4
4
  import { anthropicCredentialRefusal } from "./model-route-gap.js";
5
5
  import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
6
6
  import { defaultModelRefusal, refusedNameMintedRefusal } from "./model-entry-refusal.js";
7
+ import { judgeEnvTableReplacement } from "./env-model-table-gate.js";
7
8
  import { WEB_ADMIN_ENV, httpUrlFlaws } from "@sema-agent/settings-schema";
8
9
  import { authBridgeIssuerOf } from "./auth-bridge.js";
9
10
  import { buildDisplaySafeUrl } from "./trace/redact.js";
@@ -318,6 +319,12 @@ export const CONFIG_CROSS_INVARIANTS = [
318
319
  bootAnchor: { file: "src/boot/stage-02-web-admin-pull.ts", marker: "CROSS-INVARIANT:refused-names-not-minted" },
319
320
  check: ({ live, next }) => (next.models === live.models ? undefined : refusedNameMintedRefusal(next)),
320
321
  },
322
+ {
323
+ id: "env-table-replacement-acknowledged",
324
+ keys: ["models"],
325
+ restartReason: "env-model-table",
326
+ check: ({ candidate }) => (candidate === undefined ? undefined : judgeEnvTableReplacement(candidate)?.reason),
327
+ },
321
328
  {
322
329
  id: "center-one-identity",
323
330
  keys: ["authBridgeIssuer", "webAdmin"],
@@ -1,5 +1,7 @@
1
1
  import { type DroppedConfigEntry, type UnreadConfigFile, type UnreadConfigKey } from "./web-admin-client/read-warnings.js";
2
2
  import { FileConfigStore } from "@sema-agent/settings-schema/node";
3
+ import { type EnvModelsDecision } from "@sema-agent/settings-schema";
4
+ import { type CatalogSource } from "./env-model-table-gate.js";
3
5
  import { type ConfigDocumentRefusal, type EffectiveConfig as AgentConfigEffective, type EffectiveReadWarning } from "@sema-agent/settings-schema";
4
6
  import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, type EffectiveConfig } from "./web-admin-client/facade.js";
5
7
  import type { CenterEndpoint } from "./web-admin-client/center-request.js";
@@ -28,6 +30,8 @@ import type { ConfigProviderWord } from "./config-types.js";
28
30
  * (与域判形失败同一条回落链、同一行拒判);缺席 = 没有。远端腿:wire 上只有墓碑一种,那一键不进 effective、其余域照读、版本照读;本地腿:契约的单读口
29
31
  * `FileConfigStore.read()` 交 `refused` ⇒ 被拒文档 = 那一读的拒单原样,什么域内容都没装配 —— 候选是空的 effective(被整拒,不会被应用),版本 = 那一读的契约版本(按文档内容寻址)。 */
30
32
  /** `unreadKeys`:本地读腿读了那份文件、却结构上不读其中一键(`prompts.json` 的 `workers`)—— 同 `unreadFiles`,不进候选门,调用方一键一行 `config_unread_key`。 */
33
+ /** `catalogSource`:这份候选**从哪条配置腿来**(必填;`env-model-table-gate.ts`)—— 本地腿另带配置根上 env 模型表决定在**同一拍**的读数(契约的唯一读者
34
+ * `readEnvModelsDecision`;本地 etag 并入同一份读数的身份)。调用方原样交给 `applyEffective` / `logEffectiveDiff`,模型面 stage 的本地腿确认闸只读它。 */
31
35
  export type FetchEffectiveResult = {
32
36
  effective: EffectiveConfig;
33
37
  etag?: string;
@@ -41,6 +45,7 @@ export type FetchEffectiveResult = {
41
45
  refusedDocuments?: ConfigDocumentRefusal[];
42
46
  readWarnings: readonly EffectiveReadWarning[];
43
47
  credentialIdentity?: string;
48
+ catalogSource: CatalogSource;
44
49
  } | null;
45
50
  /**
46
51
  * 引号段 = **一条规则**:先过 `redactSecrets`;文本里没有引号字符(`"` / `'`)⇒ 原样返回;有 ⇒ 从**第一个**引号到**最后一个**引号(含两端,
@@ -161,6 +166,11 @@ export declare class RemoteConfigProvider implements ConfigProvider {
161
166
  */
162
167
  /** 本地读腿对契约文件店只有一种读法 —— 单读口 `read()`(一次读整棵 `config.d`,交一个结局)。注入缝(测试)按这一形给;严读 / 容错档位不在这条腿的类型里。 */
163
168
  export type LocalConfigReader = Pick<FileConfigStore, "read">;
169
+ /**
170
+ * 本地配置源的 etag(纯字符串,唯一拼法):契约文件店的内容版本 ⊕ 配置根上决定读数的身份(`envModelsDecisionTag`:`absent` / `d:<digest>` / `io:<errno>`)。
171
+ * 只改决定、不改目录 ⇒ etag 也变(下一拍重判);运行记录不进 etag(只在起服读)。
172
+ */
173
+ export declare function buildLocalConfigEtag(version: number, envModels: EnvModelsDecision): string;
164
174
  export declare class LocalConfigProvider implements ConfigProvider {
165
175
  #private;
166
176
  readonly kind: "local";
@@ -1,7 +1,9 @@
1
1
  import { readMcpSourceLabel } from "./mcp-source-label.js";
2
2
  import { createHmac, randomBytes } from "node:crypto";
3
3
  import { splitFoldedReadWarnings, withDroppedCatalogEntries } from "./web-admin-client/read-warnings.js";
4
- import { FileConfigStore } from "@sema-agent/settings-schema/node";
4
+ import { FileConfigStore, readEnvModelsDecision } from "@sema-agent/settings-schema/node";
5
+ import { envModelsDecisionTag } from "@sema-agent/settings-schema";
6
+ import { REMOTE_CATALOG_SOURCE } from "./env-model-table-gate.js";
5
7
  import { assertLocalConfigDirTrusted } from "./config-dir-trust.js";
6
8
  import { danglingRefs, emptyEffective, findSkillContent, promptArtifactFor, renderRefIssue, skillContentHash, } from "@sema-agent/settings-schema";
7
9
  import { redactSecrets } from "./trace/redact.js";
@@ -52,10 +54,13 @@ export class RemoteConfigProvider {
52
54
  async fetchEffective(etag) {
53
55
  const fn = this.deps.fetchEffective ?? remoteFetchEffective;
54
56
  const r = await fn(this.cc, etag, undefined, this.cc.worker);
55
- if (r === null || (r.domainErrors === undefined && r.droppedEntries === undefined && r.refusedDocuments === undefined))
56
- return r;
57
+ if (r === null)
58
+ return null;
59
+ if (r.domainErrors === undefined && r.droppedEntries === undefined && r.refusedDocuments === undefined)
60
+ return { ...r, catalogSource: REMOTE_CATALOG_SOURCE };
57
61
  return {
58
62
  ...r,
63
+ catalogSource: REMOTE_CATALOG_SOURCE,
59
64
  ...(r.domainErrors !== undefined ? { domainErrors: r.domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })) } : {}),
60
65
  ...(r.droppedEntries !== undefined ? { droppedEntries: r.droppedEntries.map(redactDroppedEntry) } : {}),
61
66
  ...(r.refusedDocuments !== undefined ? { refusedDocuments: r.refusedDocuments.map(redactRefusedDocument) } : {}),
@@ -73,6 +78,9 @@ export class RemoteConfigProvider {
73
78
  }
74
79
  }
75
80
  }
81
+ export function buildLocalConfigEtag(version, envModels) {
82
+ return `${String(version)}+${envModelsDecisionTag(envModels)}`;
83
+ }
76
84
  export class LocalConfigProvider {
77
85
  kind = "local";
78
86
  skillBodyCacheDir = undefined;
@@ -88,7 +96,9 @@ export class LocalConfigProvider {
88
96
  async fetchEffective(etag) {
89
97
  await assertLocalConfigDirTrusted({ root: this.root, declaredOwnerUid: this.#declaredOwnerUid });
90
98
  const read = await this.#store.read();
91
- const tag = String(read.kind === "refused" ? read.version : read.effective.version);
99
+ const envModels = await readEnvModelsDecision(this.root);
100
+ const catalogSource = { kind: "local", root: this.root, envModels };
101
+ const tag = buildLocalConfigEtag(read.kind === "refused" ? read.version : read.effective.version, envModels);
92
102
  if (etag !== undefined && etag === tag)
93
103
  return null;
94
104
  const readWarnings = [...read.warnings];
@@ -98,6 +108,7 @@ export class LocalConfigProvider {
98
108
  ...faces,
99
109
  etag: tag,
100
110
  readWarnings,
111
+ catalogSource,
101
112
  ...(folded.unreadFiles.length > 0 ? { unreadFiles: folded.unreadFiles } : {}),
102
113
  };
103
114
  }
@@ -0,0 +1,13 @@
1
+ import { type EnvModelsMigration } from "@sema-agent/settings-schema/node";
2
+ import type { ConfigDirMigrationLog } from "./config-d-migration.js";
3
+ /** 一次起服的输入:配置根、目标引擎的 env(契约只读两槽、只用来点名被顶替的 env 模型)、env 表是不是占位(严格名册布防 —— 与模型面闸同一只谓词)。 */
4
+ export interface EnvModelsMigrationInput {
5
+ readonly root: string;
6
+ readonly env: Readonly<Record<string, string | undefined>>;
7
+ readonly envTableIsPlaceholder: boolean;
8
+ }
9
+ /** 契约的迁移体调一次(本仓不改写它的判序,只调度)。 */
10
+ export declare function runEnvModelsMigration(input: EnvModelsMigrationInput): Promise<EnvModelsMigration>;
11
+ /** 结局的**唯一**说法(两个入口同调)。 */
12
+ export declare function logEnvModelsMigration(log: ConfigDirMigrationLog, input: EnvModelsMigrationInput, m: EnvModelsMigration): void;
13
+ //# sourceMappingURL=config-root-migration.d.ts.map
@@ -0,0 +1,99 @@
1
+ import { formatConfigPath } from "@sema-agent/settings-schema";
2
+ import { migrateEnvModelsDecision } from "@sema-agent/settings-schema/node";
3
+ import { envModelsDocumentPath } from "./env-model-table-gate.js";
4
+ const markerWriteFailureAnnounced = new Set();
5
+ export function runEnvModelsMigration(input) {
6
+ return migrateEnvModelsDecision({ root: input.root, env: input.env });
7
+ }
8
+ export function logEnvModelsMigration(log, input, m) {
9
+ const dir = input.root;
10
+ switch (m.kind) {
11
+ case "already_ran":
12
+ case "not_minter":
13
+ return;
14
+ case "marker_unreadable":
15
+ log.warn("config_env_models_marker_unreadable", {
16
+ dir,
17
+ reason: m.reason,
18
+ note: "the SS-M11 run marker on this config root does not read — it counts as run, so no decision is minted for anyone; an absent decision reads as kept",
19
+ });
20
+ return;
21
+ case "marker_write_failed":
22
+ if (markerWriteFailureAnnounced.has(dir))
23
+ return;
24
+ markerWriteFailureAnnounced.add(dir);
25
+ log.warn("config_dir_migration_failed", {
26
+ dir,
27
+ migration: "SS-M11",
28
+ reason: m.error,
29
+ ...(m.errno !== undefined ? { errno: m.errno } : {}),
30
+ wayOut: m.wayOut,
31
+ note: "the SS-M11 run marker could not be created, so no decision was minted — an absent decision reads as kept (a decision written by hand still applies); said once per process",
32
+ });
33
+ return;
34
+ case "recorded":
35
+ logMint(log, input, m.mint, m.createdRoot, m.decision.kind);
36
+ return;
37
+ default: {
38
+ const unhandled = m;
39
+ throw new Error(`SS-M11 migration: unknown result ${JSON.stringify(unhandled)}`);
40
+ }
41
+ }
42
+ }
43
+ function logMint(log, input, mint, createdRoot, decisionBefore) {
44
+ const dir = input.root;
45
+ const decisionFile = envModelsDocumentPath(dir);
46
+ switch (mint.kind) {
47
+ case "minted":
48
+ log.info("config_dir_migrated", {
49
+ dir,
50
+ receipts: [{ migration: mint.receipt.migration, document: mint.receipt.document, path: formatConfigPath(mint.receipt.path), appliedAs: mint.receipt.appliedAs }],
51
+ written: [decisionFile],
52
+ removed: [],
53
+ createdRoot,
54
+ migration: "SS-M11",
55
+ minted: true,
56
+ note: "settings-schema one-time migration SS-M11 recorded that a catalog managing the model plane replaces the env model table on this config root (the reading before the decision existed); it runs once per config root",
57
+ });
58
+ if (mint.replacedEnvModels.length > 0 && !input.envTableIsPlaceholder) {
59
+ log.warn("config_env_models_replaced_by_migration", {
60
+ dir,
61
+ envModels: mint.replacedEnvModels.map((e) => ({ id: e.id, env: e.env })),
62
+ decisionFile,
63
+ note: `the local catalog keeps replacing these env models (as before this version). To serve the env models instead, delete ${decisionFile} or write it as {"envModels":"kept"}; a running process keeps the catalog it serves until the next start`,
64
+ });
65
+ }
66
+ return;
67
+ case "exists":
68
+ case "skipped":
69
+ log.info("config_dir_migrated", {
70
+ dir,
71
+ receipts: [],
72
+ written: [],
73
+ removed: [],
74
+ createdRoot,
75
+ migration: "SS-M11",
76
+ minted: false,
77
+ why: mint.kind === "exists" ? "decision_written_meanwhile" : mint.why,
78
+ decisionBefore,
79
+ note: "settings-schema one-time migration SS-M11 ran on this config root and minted nothing; an absent decision reads as kept from now on",
80
+ });
81
+ return;
82
+ case "failed":
83
+ case "models_unreadable":
84
+ log.warn("config_env_models_mint_failed", {
85
+ dir,
86
+ cause: mint.kind === "failed" ? "decision_write_failed" : "models_unreadable",
87
+ reason: mint.error,
88
+ ...(mint.errno !== undefined ? { errno: mint.errno } : {}),
89
+ wayOut: mint.wayOut,
90
+ note: "SS-M11 did not mint the decision and will not try again on this config root (its run marker is in place) — an absent decision reads as kept, so a local catalog that manages the model plane is refused while env models are declared",
91
+ });
92
+ return;
93
+ default: {
94
+ const unhandled = mint;
95
+ throw new Error(`SS-M11 migration: unknown mint outcome ${JSON.stringify(unhandled)}`);
96
+ }
97
+ }
98
+ }
99
+ //# sourceMappingURL=config-root-migration.js.map
@@ -748,7 +748,7 @@ export interface ServiceConfigFlat {
748
748
  * git pull 落下的文件都还认),它只是不 harvest;而 declared-false 的部署连收编都不做。 */
749
749
  memoryPersistenceCapable?: boolean;
750
750
  /** 142-S2.5-W1: TOC 同步 client 腿 —— file memory 形态把本地记忆盘与中心(DB 权威,
751
- * POST /v1/memory/sync/:scope)做一轮一 RTT 双向同步。env 三键:`MEMORY_SYNC_URL`(设了=开;缺省
751
+ * POST /v2/memory/sync/:scope)做一轮一 RTT 双向同步。env 三键:`MEMORY_SYNC_URL`(设了=开;缺省
752
752
  * undefined=纯本地现状零变化)/`MEMORY_SYNC_TOKEN`(Bearer)/`MEMORY_SYNC_SCOPE`(可选,缺省 =
753
753
  * memoryScope 本身**仅当它已是 v2 typed key**;非 typed 键 ⇒ 拒启,[ref] —— 旧 formatUserScope 派生臂铸出的是本部署
754
754
  * 从不写的层,见 loadConfig)。半配置 fail-loud
@@ -762,7 +762,7 @@ export interface ServiceConfigFlat {
762
762
  maxPullEntries?: number;
763
763
  };
764
764
  /** [ref] R2:中心(DB 记忆平面)声明的**同步身份** —— 客户端用来到达本中心同步口的基址(env `MEMORY_SYNC_SERVE_IDENTITY`)。
765
- * 一处声明、三个读者:能力面 `memorySyncServe.identity` 逐字公开;中心入站 `POST /v1/memory/sync/:scope` 的 `peer` 按串与它比(不等 ⇒ 409,零写);
765
+ * 一处声明、三个读者:能力面 `memorySyncServe.identity` 逐字公开;中心入站 `POST /v2/memory/sync/:scope` 的 `peer` 按串与它比(不等 ⇒ 409,零写);
766
766
  * 缺席 ⇒ 本中心不服务同步(501 `feature.memory_sync_serve_disabled`)。必须已是规范形(铸点 `memory-sync-identity.ts`),否则拒启 —— 无条件解析。 */
767
767
  memorySyncServeIdentity?: string;
768
768
  /** [ref](黑板 [ref]/[ref] 两裁):记忆检索的**向量面**接线 —— OpenAI 兼容 `/embeddings` 端点。
@@ -1330,9 +1330,9 @@ export interface ServiceConfigFlat {
1330
1330
  * env `LOCKED_CONFIG_KEYS`(csv;词表 = core 的 `LOCKED_KEY_REGISTRY` 闭集,未知键 boot 拒 =
1331
1331
  * `config.locked_registry`「不解析的安全控件不得半开」)。
1332
1332
  *
1333
- * ⚠️ 本仓的两条**装配相容性**判据在 boot/governance-seams.ts(`assertLockAssemblable`):
1334
- * `toolPolicy` 锁与本仓「每条腿自铸 spec.toolPolicy」的装配形结构性冲突,`mcp` 锁与「部署自带 MCP
1335
- * 基线」冲突 —— 两者都在启动期拒,而不是让每条任务腿在 prepare 期各撞一次 `config.locked_key`。
1333
+ * ⚠️ 本仓的**装配相容性**判据在 boot/governance-seams.ts(`assertGovernanceAssemblable`,部署 MCP / A2A 基线物化之后判一次):
1334
+ * `toolPolicy` 锁与本仓「每条腿自铸 spec.toolPolicy」的装配形结构性冲突,`mcp` / `a2a` 锁与「部署自带 MCP 服务器 /
1335
+ * A2A 对端」冲突 —— 都在启动期拒,而不是让每条任务腿在 prepare 期各撞一次 `config.locked_key`。
1336
1336
  */
1337
1337
  lockedConfigKeys?: readonly LockedKey[];
1338
1338
  /**
package/dist/config.d.ts CHANGED
@@ -264,10 +264,11 @@ export declare function applyAutoCompactWindow(m: Model, explicit?: number): "ex
264
264
  /**
265
265
  * **严格名册布防**的唯一判据(boot 就绪门 `boot/web-admin.ts` 与装配第 2 段的路由端点判官同调这一只)。
266
266
  *
267
- * 布防 = 远程 registry ∧ 非 dryRun ∧ `CONFIG_REQUIRE_ROSTER=true`(严格 `"true"`,直读 env、不进 ServiceConfig —— 纯
267
+ * 布防 = 声明了中心(`webAdmin` 在场)∧ 非 dryRun ∧ `CONFIG_REQUIRE_ROSTER=true`(严格 `"true"`,直读 env、不进 ServiceConfig —— 纯
268
268
  * boot 期姿势旋钮,原判据逐字搬来,零语义变化)。布防时运维声明 env MODEL_ID 是**占位**:名册落地前计费提交 503、
269
- * `/health` ready:false,落地后整张模型表被中心目录换装。dryRun 是 compare-only(名册从不被应用),local / 纯 env
270
- * 部署没有「中心」可等 ⇒ 都不布防。
269
+ * `/health` ready:false,落地后整张模型表被中心目录换装。判据**不看配置源形态**:`CONFIG_PROVIDER=local` 而同时声明了中心与
270
+ * `CONFIG_REQUIRE_ROSTER=true` 的部署同样布防,env 表同样按占位读(路由判官、本地腿的 env 模型表闸都不判它)。没有声明中心的
271
+ * local / 纯 env 部署没有「中心」可等 ⇒ 不布防;dryRun 是 compare-only(名册从不被应用)⇒ 不布防。
271
272
  */
272
273
  export declare function isRosterGateArmed(webAdmin: ServiceConfig["webAdmin"]): boolean;
273
274
  /**
package/dist/config.js CHANGED
@@ -1742,7 +1742,7 @@ function parseMemoryDomain(ctx) {
1742
1742
  }
1743
1743
  else {
1744
1744
  if (memoryEngineBackend !== "file") {
1745
- throw new Error(`MEMORY_SYNC_URL is a FILE-memory (TOC client) knob, but MEMORY_ENGINE_BACKEND=${memoryEngineBackend} — the DB memory plane IS the central sync authority (it serves POST /v1/memory/sync/:scope) and must not also run the client leg; unset MEMORY_SYNC_* here`);
1745
+ throw new Error(`MEMORY_SYNC_URL is a FILE-memory (TOC client) knob, but MEMORY_ENGINE_BACKEND=${memoryEngineBackend} — the DB memory plane IS the central sync authority (it serves POST /v2/memory/sync/:scope) and must not also run the client leg; unset MEMORY_SYNC_* here`);
1746
1746
  }
1747
1747
  if (memorySyncToken === undefined) {
1748
1748
  throw new Error("MEMORY_SYNC_URL is set but MEMORY_SYNC_TOKEN is not — refusing to start half-configured (the central sync face requires a bearer token)");
@@ -136,18 +136,20 @@ export interface PlacedRowWriter {
136
136
  */
137
137
  export declare function createPlacedSessionRow(sessionId: string, placement: PlacementSources, io: PlacedRowWriter, log: OrphanOwnerLog): Promise<void>;
138
138
  /**
139
- * 停驻挂点的**见证**:调用方交出的委派树根 + 调用方判断那一刻读到的根行住客(化身;`null` = 那一读里根行不在)。请求腿 = 鉴权解析的会话与受理时那一读;
140
- * 赎回腿 = 行上的根与写口放行的那一位住客(认领之前解析器那一读)。
139
+ * 停驻挂点的**见证**:调用方交出的委派树根 + 调用方判断那一刻读到的根行住客(属主与化身,同一读;化身 `null` = 那一读里根行不在,此时属主恒 `null`)。
140
+ * 请求腿 = 鉴权解析的会话与受理时那一读;赎回腿 = 行上的根与写口放行的那一位住客(认领之前解析器那一读)。
141
+ * 读者:停驻挂点({@link assertRootStillWitnessed},只比化身)、会话行记录座(化身)、后台子代完成 relay(属主与化身,会话通知门的目标住客)。
141
142
  */
142
143
  export interface DelegationRootWitness {
143
144
  readonly sessionId: string;
145
+ readonly owner: string | null;
144
146
  readonly incarnation: string | null;
145
147
  }
146
148
  /**
147
149
  * 「根行的化身已换 ⇒ 拒」在停驻挂点的那一道(挂点的见证是更早的一读,不是这一读):此刻根行在、而住客不是见证的那一位(见证那一读里不在,或是另一位)⇒ 抛。
148
150
  * 根行此刻不在 ⇒ 不抛(根真没了 ⇒ 律给哨兵,不是「换了住客」)。这是**判断**的一部分;判断之后、写之前根的变化不改变写进去的值(律:属主 = 判断时见证到的)。
149
151
  */
150
- export declare function assertRootStillWitnessed(sessionId: string, witness: DelegationRootWitness | undefined, judged: JudgedOwner): void;
152
+ export declare function assertRootStillWitnessed(sessionId: string, witness: Pick<DelegationRootWitness, "incarnation"> | undefined, judged: JudgedOwner): void;
151
153
  /** 记录委派树的宿主店多交的两只口(三只持久会话店与缓存包装层;无 repo 的内存店两只都没有 ⇒ 没有可按的树 ⇒ 不认领)。 */
152
154
  export interface DelegationTreeStore {
153
155
  /** 本行落库的 placement 元组(`undefined` = 本行不是 placed 行 ⇒ 不记委派树)。 */
@@ -92,7 +92,7 @@ export async function settleDelegatedOwner(host, sessionId, owner, tree) {
92
92
  throw refuse();
93
93
  judged = await readDelegatedOwner(sessionId, recorded, sourceFacesVia(host.headOf));
94
94
  if (tree.root !== null)
95
- assertRootStillWitnessed(sessionId, { sessionId: tree.root, incarnation: tree.rootIncarnation }, judged);
95
+ assertRootStillWitnessed(sessionId, { incarnation: tree.rootIncarnation }, judged);
96
96
  expected = judged.inherited.owner;
97
97
  if (face.owner === null && expected !== null) {
98
98
  claimed = await trees.claimOwnerless(sessionId, face.incarnation, expected);
@@ -75,6 +75,7 @@ export declare const DEVICE_OUTCOME_ERROR_CODES_BY_REVISION: {
75
75
  readonly 1: readonly ["aborted", "already_exists", "auth_failed", "callback_error", "invalid", "is_directory", "not_directory", "not_found", "not_supported", "outcome_unknown", "permission_denied", "precondition_failed", "shell_unavailable", "spawn_error", "suspended", "target_unavailable", "timeout", "transport_lost", "unknown"];
76
76
  readonly 2: readonly ["liveness_no_output"];
77
77
  readonly 3: readonly ["name_too_long"];
78
+ readonly 4: readonly ["symlink_loop"];
78
79
  };
79
80
  /** 本包认得的协议修订号(= 增量表的行键)。 */
80
81
  export type DeviceProtocolRevision = keyof typeof DEVICE_OUTCOME_ERROR_CODES_BY_REVISION;
@@ -1,7 +1,7 @@
1
1
  import { createPublicKey, randomBytes, randomInt, verify as edVerify } from "node:crypto";
2
2
  import { MAX_TIMER_DELAY_MS, inWholeNumberBounds, wholeNumberBoundsRule } from "./config-types.js";
3
3
  export const DEVICE_WS_PATH = "/v1/device/ws";
4
- export const DEVICE_PROTOCOL_VERSION = 3;
4
+ export const DEVICE_PROTOCOL_VERSION = 4;
5
5
  export const DEVICE_MIN_PROTOCOL_VERSION = 1;
6
6
  export const DEVICE_HELLO_DOMAIN_TAG = "sema.device.hello.v1";
7
7
  export const DEVICE_WS_LIMITS = {
@@ -67,6 +67,7 @@ export const DEVICE_OUTCOME_ERROR_CODES_BY_REVISION = {
67
67
  ],
68
68
  2: ["liveness_no_output"],
69
69
  3: ["name_too_long"],
70
+ 4: ["symlink_loop"],
70
71
  };
71
72
  export function deviceOutcomeErrorCodesAt(revision) {
72
73
  if (!Number.isInteger(revision) || revision < 1 || revision > DEVICE_PROTOCOL_VERSION) {
@@ -0,0 +1,53 @@
1
+ /**
2
+ * 本地配置腿上「一份管模型面的目录能不能顶替已声明的 env 模型表」—— **唯一判官**与它的拒句;以及候选随身带的那一份事实 {@link CatalogSource}。
3
+ *
4
+ * ## 律(一条)
5
+ * 一份候选的模型面被组拒 `config.catalog_replaces_env_models`,当且仅当四件同时成立:
6
+ * ① 候选来自**本地配置腿**({@link CatalogSource} 的 `local` 臂;中心腿不守卫 —— 中心发布的目录照旧整表顶替 env 表);
7
+ * ② 候选**管**模型面(模型面 stage 已走过「一条启用条目也没有 ⇒ 不管」那条捷径 —— 本判官只在它之后被问);
8
+ * ③ env 模型表**已声明**且不是占位(`MODEL_ID` 铸出了条目;严格名册布防时 env 表是占位、不算);
9
+ * ④ 配置根上的决定读数**不是** `replaced`(缺席 / `kept` / 读不懂 —— 运行记录在场之后缺席 ≡ `kept`,读不懂的不许读成确认)。
10
+ * 拒 ⇒ 模型面这一组停在上一代(起服:env 表;刷新拍:在服的那一代),其余组照落;拒句点名被顶替的 env 模型、决定文件的路径与两条出路。
11
+ *
12
+ * 执法点:模型面 stage 经登记条 `config-invariants.ts` 的 `env-table-replacement-acknowledged` 调本判官(候选的事实随 `CrossInvariantInput.candidate` 交来)。
13
+ *
14
+ * ## 一个读者
15
+ * 决定文件的字节只有契约的 `readEnvModelsDecision` 打开:`LocalConfigProvider.fetchEffective` 在读候选的**同一拍**调它,读数随候选交出
16
+ * (`FetchEffectiveResult.catalogSource`;本地 etag 由同一份读数的身份 `envModelsDecisionTag` 并入)。本模块与模型面 stage 只读这份读数,不开文件。
17
+ * 「env 表已声明」读的是引擎**自己铸的那张** env 表(模型面 stage 的 env 快照),不再读一遍 env。
18
+ */
19
+ import { CATALOG_REPLACES_ENV_MODELS_CODE, type EnvModelTableEntry, type EnvModelsDecision } from "@sema-agent/settings-schema";
20
+ /**
21
+ * 一份目录候选**从哪条配置腿来**(以及本地腿上,配置根的 env 模型表决定在**同一拍**读作什么)。必填、没有缺省腿:安全轴上「忘了说是哪条腿」
22
+ * 不许静默落成某一种语义(与 `ApplyLeg` 同一条纪律)。铸点:`LocalConfigProvider.fetchEffective`(`local` 臂,带 `root` 与那一读的决定)、
23
+ * `RemoteConfigProvider.fetchEffective` 与盘上 LKG 读回(`remote` 臂 —— LKG 只在中心腿启用)。
24
+ */
25
+ export type CatalogSource = {
26
+ readonly kind: "local";
27
+ readonly root: string;
28
+ readonly envModels: EnvModelsDecision;
29
+ } | {
30
+ readonly kind: "remote";
31
+ };
32
+ /** 中心腿的那一份(冻结;中心腿不守卫,没有可带的事实)。 */
33
+ export declare const REMOTE_CATALOG_SOURCE: CatalogSource;
34
+ /** 判官的拒决(码与句出自同一次判决)。 */
35
+ export interface EnvTableReplacementRefusal {
36
+ readonly code: typeof CATALOG_REPLACES_ENV_MODELS_CODE;
37
+ readonly reason: string;
38
+ }
39
+ /** 判官的输入(纯数据)。 */
40
+ export interface EnvTableReplacementInput {
41
+ /** 引擎自己铸的那张 env 表的条目(槽序;空 = env 表没声明)。 */
42
+ readonly envTable: readonly EnvModelTableEntry[];
43
+ /** 严格名册布防(`isRosterGateArmed`):env 表是占位、从不被派发 ⇒ 对本闸等于没声明。 */
44
+ readonly envTableIsPlaceholder: boolean;
45
+ readonly source: CatalogSource;
46
+ }
47
+ /**
48
+ * **唯一判官**(见文件头的律;② 由调用方的位置保证)。通过 ⇒ `undefined`。两只闭集都穷举:腿加一臂 / 决定加一词不补 ⇒ 编译红。
49
+ */
50
+ export declare function judgeEnvTableReplacement(input: EnvTableReplacementInput): EnvTableReplacementRefusal | undefined;
51
+ /** 配置根上决定文件的路径(文件名取契约的文档名,不手抄)。 */
52
+ export declare function envModelsDocumentPath(root: string): string;
53
+ //# sourceMappingURL=env-model-table-gate.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { CATALOG_REPLACES_ENV_MODELS_CODE, ENV_MODELS_DOCUMENT } from "@sema-agent/settings-schema";
2
+ import path from "node:path";
3
+ export const REMOTE_CATALOG_SOURCE = Object.freeze({ kind: "remote" });
4
+ export function judgeEnvTableReplacement(input) {
5
+ const { source } = input;
6
+ switch (source.kind) {
7
+ case "remote":
8
+ return undefined;
9
+ case "local":
10
+ break;
11
+ default: {
12
+ const unhandled = source;
13
+ throw new Error(`judgeEnvTableReplacement: unknown catalog source ${JSON.stringify(unhandled)}`);
14
+ }
15
+ }
16
+ if (input.envTable.length === 0 || input.envTableIsPlaceholder)
17
+ return undefined;
18
+ const decision = source.envModels;
19
+ switch (decision.kind) {
20
+ case "replaced":
21
+ return undefined;
22
+ case "absent":
23
+ case "kept":
24
+ case "unreadable":
25
+ return { code: CATALOG_REPLACES_ENV_MODELS_CODE, reason: envTableReplacementSentence(input.envTable, decision, envModelsDocumentPath(source.root)) };
26
+ default: {
27
+ const unhandled = decision;
28
+ throw new Error(`judgeEnvTableReplacement: unknown decision ${JSON.stringify(unhandled)}`);
29
+ }
30
+ }
31
+ }
32
+ export function envModelsDocumentPath(root) {
33
+ return path.join(root, `${ENV_MODELS_DOCUMENT}.json`);
34
+ }
35
+ function decisionPhrase(decision) {
36
+ switch (decision.kind) {
37
+ case "absent":
38
+ return "is absent (an absent decision reads as kept)";
39
+ case "kept":
40
+ return 'says "kept"';
41
+ case "unreadable":
42
+ return decision.reason === "io" ? `cannot be read (${decision.errno}) — an unreadable decision is never read as a confirmation` : `does not read (${decision.reason}) — an unreadable decision is never read as a confirmation`;
43
+ }
44
+ }
45
+ function envTableReplacementSentence(envTable, decision, documentPath) {
46
+ const named = envTable.map((e) => `${e.env} "${e.id}"`).join(", ");
47
+ const keys = envTable.map((e) => e.env).join(" / ");
48
+ return (`[${CATALOG_REPLACES_ENV_MODELS_CODE}] config.d/models.json manages the model plane, which would replace the env model table (${named}); ` +
49
+ `the config root's decision ${documentPath} ${decisionPhrase(decision)}, so this catalog's model plane is not adopted and the model plane in force keeps serving ` +
50
+ `(the env models, unless an earlier catalog generation was adopted). To let local catalogs replace the env models on this config root, write ${documentPath} as {"envModels":"replaced"} ` +
51
+ `(a supported writer does this after showing what is replaced), or unset ${keys}.`);
52
+ }
53
+ //# sourceMappingURL=env-model-table-gate.js.map