@sema-agent/server 7.141.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 (171) hide show
  1. package/MIGRATION.md +215 -0
  2. package/USAGE.md +67 -12
  3. package/dist/approval-ask-audit-store.d.ts +54 -38
  4. package/dist/approval-ask-audit-store.js +58 -39
  5. package/dist/approval-card.d.ts +8 -8
  6. package/dist/approval-crash-convergence.d.ts +17 -7
  7. package/dist/approval-crash-convergence.js +94 -22
  8. package/dist/approval-deny-reasons.d.ts +34 -30
  9. package/dist/approval-deny-reasons.js +21 -6
  10. package/dist/approval-policy-settlement.d.ts +1 -1
  11. package/dist/approval-reconciler.d.ts +33 -1
  12. package/dist/approval-reconciler.js +43 -40
  13. package/dist/auth-bridge.d.ts +9 -6
  14. package/dist/auth-bridge.js +9 -3
  15. package/dist/boot/adoption.js +2 -1
  16. package/dist/boot/budget-tracing.d.ts +16 -0
  17. package/dist/boot/budget-tracing.js +10 -6
  18. package/dist/boot/device-lane.d.ts +1 -1
  19. package/dist/boot/execution-env.d.ts +24 -4
  20. package/dist/boot/execution-env.js +26 -23
  21. package/dist/boot/execution-lane.js +2 -1
  22. package/dist/boot/exit-boundary.js +5 -4
  23. package/dist/boot/memory-consolidation.js +2 -1
  24. package/dist/boot/parked-revive-gate.js +2 -1
  25. package/dist/boot/permission-rules-audit.d.ts +0 -28
  26. package/dist/boot/permission-rules-audit.js +2 -1
  27. package/dist/boot/read-deny-anchor-audit.js +2 -1
  28. package/dist/boot/reapers.js +4 -3
  29. package/dist/boot/resolve-spec.js +11 -2
  30. package/dist/boot/retention-lane.js +5 -3
  31. package/dist/boot/runner-deps.js +16 -13
  32. package/dist/boot/runtime-caps.js +3 -2
  33. package/dist/boot/session-faces.js +4 -3
  34. package/dist/boot/shutdown.js +3 -2
  35. package/dist/boot/stage-04-stores.js +3 -2
  36. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  37. package/dist/boot/stage-05-execution-env.js +8 -6
  38. package/dist/boot/stage-06-runners.d.ts +1 -1
  39. package/dist/boot/stage-06-runners.js +2 -1
  40. package/dist/boot/stage-07-capability-layer.d.ts +1 -1
  41. package/dist/boot/stage-07-capability-layer.js +2 -1
  42. package/dist/boot/stage-08-reapers.d.ts +1 -1
  43. package/dist/boot/stage-08-reapers.js +3 -2
  44. package/dist/boot/stage-09-leader.d.ts +1 -1
  45. package/dist/boot/stage-10-http-server.d.ts +1 -1
  46. package/dist/boot/stage-10-http-server.js +5 -4
  47. package/dist/boot/stores.js +3 -2
  48. package/dist/boot/web-admin.js +17 -17
  49. package/dist/boot/webfetch-summarize-lane.js +2 -1
  50. package/dist/boot/workflow-orchestration.js +3 -2
  51. package/dist/capabilities/builtin-tools.d.ts +2 -1
  52. package/dist/capabilities/builtin-tools.js +2 -4
  53. package/dist/capabilities/composite-members.d.ts +2 -1
  54. package/dist/capabilities/composite-members.js +2 -1
  55. package/dist/capability-facts.d.ts +24 -0
  56. package/dist/capability-facts.js +23 -0
  57. package/dist/config-catalog.js +1 -0
  58. package/dist/config-types.d.ts +15 -4
  59. package/dist/config.d.ts +6 -0
  60. package/dist/config.js +17 -3
  61. package/dist/execution-lane-caps.d.ts +2 -1
  62. package/dist/fleet/fleet-bus.d.ts +6 -3
  63. package/dist/hooks/hook-runner.d.ts +6 -0
  64. package/dist/hooks/hook-runner.js +30 -1
  65. package/dist/http/dispatch.js +4 -2
  66. package/dist/http/execution-context.d.ts +2 -2
  67. package/dist/http/idempotency.d.ts +1 -1
  68. package/dist/http/principal-gate.d.ts +3 -1
  69. package/dist/http/principal-gate.js +17 -8
  70. package/dist/http/resume-legs.js +5 -9
  71. package/dist/http/route-table.js +2 -0
  72. package/dist/http/routes/attachments.js +18 -0
  73. package/dist/http/routes/capabilities.js +5 -2
  74. package/dist/http/routes/memory-policy.js +26 -6
  75. package/dist/http/routes/runs.js +3 -3
  76. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  77. package/dist/http/routes/session-memory-sync.js +157 -0
  78. package/dist/http/routes/sessions-list.d.ts +2 -1
  79. package/dist/http/routes/tasks.js +7 -5
  80. package/dist/http/run-read-face.d.ts +7 -0
  81. package/dist/http/run-read-face.js +9 -3
  82. package/dist/http/send.d.ts +5 -0
  83. package/dist/http/send.js +9 -2
  84. package/dist/http/server.d.ts +14 -8
  85. package/dist/http/server.js +2 -4
  86. package/dist/http/submit-replay.d.ts +4 -3
  87. package/dist/http/submit-replay.js +2 -2
  88. package/dist/http/sync-submit-body.d.ts +21 -4
  89. package/dist/http/sync-submit-body.js +13 -0
  90. package/dist/identity-source.d.ts +21 -2
  91. package/dist/identity-source.js +6 -0
  92. package/dist/memory-incident-sink.d.ts +19 -6
  93. package/dist/memory-incident-sink.js +8 -4
  94. package/dist/memory-sync-client.d.ts +21 -21
  95. package/dist/memory-sync-client.js +50 -42
  96. package/dist/memory-sync-identity.d.ts +24 -0
  97. package/dist/memory-sync-identity.js +57 -0
  98. package/dist/memory-sync.d.ts +7 -1
  99. package/dist/memory-sync.js +6 -3
  100. package/dist/observability/fail-open.d.ts +27 -10
  101. package/dist/observability/fail-open.js +20 -7
  102. package/dist/observability/run-terminal.d.ts +92 -34
  103. package/dist/observability/run-terminal.js +167 -162
  104. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  105. package/dist/plugins/approval-ask-store-file.js +48 -7
  106. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  107. package/dist/plugins/approval-ask-store-memory.js +9 -8
  108. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  109. package/dist/plugins/approval-ask-store-sql.js +49 -19
  110. package/dist/plugins/file-run-store.d.ts +6 -1
  111. package/dist/plugins/file-run-store.js +11 -1
  112. package/dist/plugins/local-session-store.d.ts +36 -7
  113. package/dist/plugins/local-session-store.js +76 -32
  114. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  115. package/dist/plugins/local-task-attachment-store.js +67 -43
  116. package/dist/plugins/memory-key-guards.d.ts +3 -3
  117. package/dist/plugins/memory-run-store.d.ts +6 -1
  118. package/dist/plugins/memory-run-store.js +11 -1
  119. package/dist/plugins/one-time-migrations.d.ts +27 -1
  120. package/dist/plugins/one-time-migrations.js +57 -3
  121. package/dist/plugins/remote-env-e2b.d.ts +23 -4
  122. package/dist/plugins/remote-env-e2b.js +111 -28
  123. package/dist/plugins/remote-env-k8s.d.ts +31 -5
  124. package/dist/plugins/remote-env-k8s.js +86 -19
  125. package/dist/plugins/remote-env-local-docker.d.ts +15 -3
  126. package/dist/plugins/remote-env-local-docker.js +40 -12
  127. package/dist/plugins/run-store-sql.d.ts +9 -1
  128. package/dist/plugins/run-store-sql.js +9 -1
  129. package/dist/plugins/store-backend.d.ts +10 -7
  130. package/dist/plugins/store-backend.js +1 -0
  131. package/dist/plugins/store-contracts.d.ts +26 -1
  132. package/dist/plugins/store-contracts.js +3 -0
  133. package/dist/plugins/task-attachment-store.d.ts +112 -14
  134. package/dist/plugins/task-attachment-store.js +221 -28
  135. package/dist/plugins/workspace-fill.d.ts +60 -0
  136. package/dist/plugins/workspace-fill.js +106 -0
  137. package/dist/project-bundle.d.ts +38 -0
  138. package/dist/project-bundle.js +609 -0
  139. package/dist/run-local.js +2 -8
  140. package/dist/runs.js +1 -2
  141. package/dist/sandbox-egress-policy.d.ts +4 -1
  142. package/dist/sandbox-egress-policy.js +3 -1
  143. package/dist/sandbox-git-plane.d.ts +31 -12
  144. package/dist/sandbox-git-plane.js +24 -4
  145. package/dist/security.d.ts +9 -0
  146. package/dist/security.js +8 -0
  147. package/dist/session-error-wire.d.ts +24 -0
  148. package/dist/session-error-wire.js +7 -0
  149. package/dist/task-settings.d.ts +10 -2
  150. package/dist/task-settings.js +15 -2
  151. package/dist/terminal.d.ts +39 -19
  152. package/dist/terminal.js +65 -22
  153. package/dist/tool-approval.d.ts +4 -3
  154. package/dist/tool-approval.js +12 -14
  155. package/dist/trace/core-keyset-guard.d.ts +123 -6
  156. package/dist/trace/engine-notice-wire.d.ts +8 -2
  157. package/dist/trace/engine-notice-wire.js +4 -2
  158. package/dist/trace/exit-seat.d.ts +2 -1
  159. package/dist/trace/ledger-sink.d.ts +8 -4
  160. package/dist/trace/ledger-sink.js +2 -2
  161. package/dist/trace/project.d.ts +18 -1
  162. package/dist/trace/project.js +16 -0
  163. package/dist/trace/self-minted-wire.d.ts +251 -2
  164. package/dist/trace/self-minted-wire.js +82 -0
  165. package/dist/trace/wire-projection-faces.d.ts +1 -1
  166. package/dist/trace/wire-projection-faces.js +1 -1
  167. package/dist/web-admin-client/apply-effective.js +21 -1
  168. package/dist/web-admin-client/effective-keys.js +2 -0
  169. package/dist/web-admin-client/types.d.ts +4 -0
  170. package/package.json +1 -1
  171. package/wire-changes/server@7.142.0.json +639 -0
@@ -7,6 +7,7 @@ import { FileWorkflowCompletionInbox, InMemoryWorkflowCompletionInbox, buildWork
7
7
  import { FileWorkflowNotifyJournalStore, JournalingWorkflowRunStore, WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
8
8
  import { SubagentSteerRegistry } from "../orchestration/subagent-steer.js";
9
9
  import { WorkflowAgentRegistry } from "../orchestration/workflow-agent-steer.js";
10
+ import { seatText } from "../trace/exit-seat.js";
10
11
  export function createWorkflowOrchestration(ctx) {
11
12
  const { config, logger, metrics, localRoot, backend, getRunStore } = ctx;
12
13
  const workflowBootAtMs = Date.now();
@@ -43,12 +44,12 @@ export function createWorkflowOrchestration(ctx) {
43
44
  await workflowCompletionInbox.enqueue(buildWorkflowCompleteInboxEntry(route, p, Date.now()));
44
45
  }
45
46
  catch (err) {
46
- logger.warn("workflow_completion_enqueue_failed", { runId: p.runId, err: String(err) });
47
+ logger.warn("workflow_completion_enqueue_failed", { runId: p.runId, err: seatText("operator-log-text", String(err)) });
47
48
  }
48
49
  };
49
50
  const workflowNotifyGate = baseWorkflowRunStore && workflowNotifyJournal
50
51
  ? new WorkflowNotifyGate(workflowNotifyJournal, baseWorkflowRunStore, deliverWorkflowCompletion, {
51
- onError: (stage, runId, err) => logger.warn("workflow_notify_failed", { stage, runId, err: String(err) }),
52
+ onError: (stage, runId, err) => logger.warn("workflow_notify_failed", { stage, runId, err: seatText("operator-log-text", String(err)) }),
52
53
  })
53
54
  : undefined;
54
55
  const fleetBus = new FleetEventBus();
@@ -37,7 +37,8 @@ export declare function createSubagentExtraTools(deployment?: SubagentExtraToolS
37
37
  /**
38
38
  * `onExtraToolsError` 收到的错误 → 运维行 `subagent_extra_tools_error` 的字段:句子,加上错误**自带**的码与 `detail.reason`(在场才铸)。
39
39
  * core 7.46.0 [ref] 被拒的 spawn(产物不在名册里)带 `config.extra_tool_names` + `undeclared_name` / `undeclared_origin`;部署工厂自己抛的错多半两位都不带。
40
- * 本仓不按码分支、不重铸 —— 只把错误说的话原样过境到运维面(修前只记句子,码与原因词丢在这一跳)。
40
+ * 本仓不按码分支、不重铸 —— 只把错误说的话过境到运维面(修前只记句子,码与原因词丢在这一跳)。码是别人写的串(引擎的 `config.extra_tool_names`,或部署工厂自己抛的错):
41
+ * 与运维日志里其余各处同一只判官 —— 是码才带 `code`,其余键缺席({@link codeFieldOf})。
41
42
  */
42
43
  export declare function extraToolsErrorFields(err: unknown): Record<string, unknown>;
43
44
  //# sourceMappingURL=builtin-tools.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  import { NOW_TOOL_NAME } from "./host-tool-names.js";
3
+ import { codeFieldOf } from "../terminal.js";
3
4
  export function nowTool() {
4
5
  return {
5
6
  name: NOW_TOOL_NAME,
@@ -21,12 +22,9 @@ export function createSubagentExtraTools(deployment) {
21
22
  };
22
23
  }
23
24
  export function extraToolsErrorFields(err) {
24
- const fields = { err: err instanceof Error ? err.message : String(err) };
25
+ const fields = { err: err instanceof Error ? err.message : String(err), ...codeFieldOf(err) };
25
26
  if (err === null || typeof err !== "object")
26
27
  return fields;
27
- const code = Reflect.get(err, "code");
28
- if (typeof code === "string")
29
- fields.code = code;
30
28
  const detail = Reflect.get(err, "detail");
31
29
  const reason = detail !== null && typeof detail === "object" ? Reflect.get(detail, "reason") : undefined;
32
30
  if (typeof reason === "string")
@@ -45,7 +45,7 @@ export declare const COMPOSITE_CAPABILITY_MEMBERS: {
45
45
  readonly centerWiring: readonly ["effectivePull", "effectiveRevision", "lease", "leaseHealth", "failMode", "usageReport", "issuer", "worker", "caller"];
46
46
  readonly webSearch: readonly ["backend"];
47
47
  readonly background: readonly ["listFace", "exitFaces"];
48
- readonly taskSettings: readonly ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "tightenOnlySeats", "approvalFrameFamilies"];
48
+ readonly taskSettings: readonly ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "httpHookAllowlists", "tightenOnlySeats", "approvalFrameFamilies"];
49
49
  readonly settings: readonly ["permissions"];
50
50
  readonly permissionModeAuto: readonly ["accepted", "classifierSeat", "entitlementSource", "armed", "reason", "model"];
51
51
  readonly writeProtection: readonly ["armed", "rows", "replaced"];
@@ -56,6 +56,7 @@ export declare const COMPOSITE_CAPABILITY_MEMBERS: {
56
56
  readonly workspaceSync: readonly ["persist", "keep", "timeoutMs", "cache"];
57
57
  readonly userAttachments: readonly ["entries", "kinds", "supplies", "limits"];
58
58
  readonly credentialPlane: readonly ["sandboxGit"];
59
+ readonly memorySyncServe: readonly ["identity"];
59
60
  };
60
61
  type CompositeTable = typeof COMPOSITE_CAPABILITY_MEMBERS;
61
62
  /** 值的**具名成员对象臂**(标量 / 字面词 / `false` / `null` / `undefined` / 数组 / 函数臂剥掉)。 */
@@ -6,7 +6,7 @@ export const COMPOSITE_CAPABILITY_MEMBERS = {
6
6
  centerWiring: ["effectivePull", "effectiveRevision", "lease", "leaseHealth", "failMode", "usageReport", "issuer", "worker", "caller"],
7
7
  webSearch: ["backend"],
8
8
  background: ["listFace", "exitFaces"],
9
- taskSettings: ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "tightenOnlySeats", "approvalFrameFamilies"],
9
+ taskSettings: ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "httpHookAllowlists", "tightenOnlySeats", "approvalFrameFamilies"],
10
10
  settings: ["permissions"],
11
11
  permissionModeAuto: ["accepted", "classifierSeat", "entitlementSource", "armed", "reason", "model"],
12
12
  writeProtection: ["armed", "rows", "replaced"],
@@ -17,6 +17,7 @@ export const COMPOSITE_CAPABILITY_MEMBERS = {
17
17
  workspaceSync: ["persist", "keep", "timeoutMs", "cache"],
18
18
  userAttachments: ["entries", "kinds", "supplies", "limits"],
19
19
  credentialPlane: ["sandboxGit"],
20
+ memorySyncServe: ["identity"],
20
21
  };
21
22
  export function gateCompositeMembers(body) {
22
23
  return body;
@@ -65,6 +65,9 @@ export declare const CAPABILITY_FACT_WHY: {
65
65
  readonly memory_origin_faces_absent: "the memory engine's origin marking faces are not wired";
66
66
  readonly consolidation_valve_closed: "the memory consolidation valve is closed (MEMORY_CONSOLIDATION_DRIVER)";
67
67
  readonly memory_optout_grant_absent: "no memory-capture opt-out grant store is wired (SQL backends only)";
68
+ readonly memory_db_plane_absent: "this is not a DB memory plane (MEMORY_ENGINE_BACKEND=pg|mysql): the central memory faces are not wired";
69
+ readonly memory_sync_identity_undeclared: "the DB memory plane declares no sync identity (MEMORY_SYNC_SERVE_IDENTITY), so it serves no sync round";
70
+ readonly memory_sync_leg_absent: "no memory sync leg runs here (a file memory engine with MEMORY_SYNC_URL)";
68
71
  readonly outcome_ledger_unqueryable: "the outcome sink has no query face (File sink)";
69
72
  readonly config_catalog_disabled: "the config catalog is switched off (CONFIG_CATALOG_ENABLED=false)";
70
73
  readonly rule_store_absent: "no permission-rule store is wired";
@@ -272,6 +275,27 @@ export declare function sharedMemoryRead(d: Pick<Deps, "sharedMemoryStore" | "or
272
275
  provider: Store<"sharedMemoryStore">;
273
276
  directory: Store<"orgMemoryDirectory">;
274
277
  }>;
278
+ /** 中心侧记忆导出读(`GET /v1/memory/export`):DB 记忆平面的导出读面在场(只在 DB 记忆平面装配)。门读本事实,不读 `MEMORY_ENGINE_BACKEND`。 */
279
+ export declare function memoryExport(d: Pick<Deps, "memoryExport">): Fact<"memory_db_plane_absent", {
280
+ read: Store<"memoryExport">;
281
+ }>;
282
+ /**
283
+ * 中心侧同步口(`POST /v1/memory/sync/:scope`):DB 记忆平面的同步执行位在场 ∧ 本中心声明了同步身份(`MEMORY_SYNC_SERVE_IDENTITY`)。
284
+ * 见证 = 执行位 + 那份声明 —— 能力位 `memorySyncServe` 的 `{ identity }` 逐字投影它,入站 `peer` 与它按串比:一处声明,两个读者读同一个值。
285
+ */
286
+ export declare function memorySyncServe(d: Pick<Deps, "memorySync" | "config">): Fact<"memory_db_plane_absent" | "memory_sync_identity_undeclared", {
287
+ perform: Store<"memorySync">;
288
+ identity: string;
289
+ }>;
290
+ /**
291
+ * 会话记忆同步触发口(`POST /v1/sessions/:id/memory/sync`):本机同步腿在场 ∧ 持久 run 账面(读集 = 会话最近一条已结腿的记忆观测;收割栅栏读会话的 run 认领)
292
+ * ∧ 会话头读(属主门与 `sourceRevision` = 同一次头读的叶子)。
293
+ */
294
+ export declare function sessionMemorySync(d: Pick<Deps, "memorySyncLeg" | "runStore" | "sessionStorage">): Fact<"memory_sync_leg_absent" | "run_store_absent" | "session_head_face_absent", {
295
+ leg: Store<"memorySyncLeg">;
296
+ runStore: Store<"runStore">;
297
+ sessionStorage: HeadReadingSessionStore;
298
+ }>;
275
299
  /** 跨主体投递门在座(`POST /v1/principal-inbox/messages` 的可用性门与能力位 `principalInboxDelivery` 同读这一只)。门的装配本身是
276
300
  * 一道合取(收件箱消费方声明 ∧ 耐久 mailbox ∧ org 成员目录 ∧ 配额店,缺一拒启 —— `boot/stage-06-runners.ts`),
277
301
  * 所以这里只判「门在不在」:在 ⟺ 那四件都在。 */
@@ -45,6 +45,9 @@ export const CAPABILITY_FACT_WHY = {
45
45
  memory_origin_faces_absent: "the memory engine's origin marking faces are not wired",
46
46
  consolidation_valve_closed: "the memory consolidation valve is closed (MEMORY_CONSOLIDATION_DRIVER)",
47
47
  memory_optout_grant_absent: "no memory-capture opt-out grant store is wired (SQL backends only)",
48
+ memory_db_plane_absent: "this is not a DB memory plane (MEMORY_ENGINE_BACKEND=pg|mysql): the central memory faces are not wired",
49
+ memory_sync_identity_undeclared: "the DB memory plane declares no sync identity (MEMORY_SYNC_SERVE_IDENTITY), so it serves no sync round",
50
+ memory_sync_leg_absent: "no memory sync leg runs here (a file memory engine with MEMORY_SYNC_URL)",
48
51
  outcome_ledger_unqueryable: "the outcome sink has no query face (File sink)",
49
52
  config_catalog_disabled: "the config catalog is switched off (CONFIG_CATALOG_ENABLED=false)",
50
53
  rule_store_absent: "no permission-rule store is wired",
@@ -286,6 +289,26 @@ export function sharedMemoryRead(d) {
286
289
  return no("org_memory_directory_absent");
287
290
  return yes({ provider: d.sharedMemoryStore, directory: d.orgMemoryDirectory });
288
291
  }
292
+ export function memoryExport(d) {
293
+ return d.memoryExport !== undefined ? yes({ read: d.memoryExport }) : no("memory_db_plane_absent");
294
+ }
295
+ export function memorySyncServe(d) {
296
+ if (d.memorySync === undefined)
297
+ return no("memory_db_plane_absent");
298
+ const identity = d.config.memorySyncServeIdentity;
299
+ return identity !== undefined ? yes({ perform: d.memorySync, identity }) : no("memory_sync_identity_undeclared");
300
+ }
301
+ export function sessionMemorySync(d) {
302
+ if (d.memorySyncLeg === undefined)
303
+ return no("memory_sync_leg_absent");
304
+ const runs = durableRuns(d);
305
+ if (!runs.available)
306
+ return runs;
307
+ const owned = sessionOwnership(d);
308
+ if (!owned.available)
309
+ return owned;
310
+ return yes({ leg: d.memorySyncLeg, runStore: runs.runStore, sessionStorage: owned.sessionStorage });
311
+ }
289
312
  export function principalInboxDelivery(d) {
290
313
  return d.orgDeliveryGate !== undefined ? yes({ gate: d.orgDeliveryGate }) : no("org_delivery_gate_absent");
291
314
  }
@@ -273,6 +273,7 @@ export const CONFIG_CATALOG = [
273
273
  r("MEMORY_SYNC_MAX_PULL_ENTRIES", "memory", "number", "TOC 同步单轮 pull 条数帽(设了必须 ≥1 整数)", { derivedDefaultNote: "缺席 ⇒ 键缺席,core 默认" }),
274
274
  r("MEMORY_SYNC_MAX_PUSH_ENTRIES", "memory", "number", "TOC 同步单轮 push 条数帽(设了必须 ≥1 整数)", { derivedDefaultNote: "缺席 ⇒ 键缺席,core 默认" }),
275
275
  r("MEMORY_SYNC_SCOPE", "memory", "string", "TOC 同步 scope 覆写(本服务铸的项目键族 `userrepo:` / `repo:` 拒启:项目层由同步腿按属主自己选)", { derivedDefaultNote: "缺席 ⇒ = MEMORY_SCOPE,仅当它已是 typed 键;非 typed 键拒启(S-964)" }),
276
+ r("MEMORY_SYNC_SERVE_IDENTITY", "memory", "url", "记忆同步中心(DB 记忆平面)声明的同步身份 = 客户端用来到达本中心同步口的基址;必须已是规范形(小写协议与主机、无缺省端口、无尾斜杠、无查询串 / 片段 / 凭据),否则拒启并给出规范形;能力位 `memorySyncServe.identity` 逐字公开,入站 `peer` 按串与它比", { derivedDefaultNote: "缺席 ⇒ 本中心不服务同步(501 feature.memory_sync_serve_disabled)", configKey: "memorySyncServeIdentity" }),
276
277
  r("MEMORY_SYNC_TOKEN", "memory", "secret", "TOC 同步中心面的 Bearer(URL 设了它必须在,半配拒启)", { danger: SEC }),
277
278
  r("MEMORY_SYNC_URL", "memory", "url", "TOC 同步中心地址(设了=开;file 记忆形专用,DB 后端配了拒启;同步覆盖同步层与同属主的项目层 `userrepo:<属主>/…` ⇒ 同步层与 MEMORY_SCOPE 同属主时单用户任务照常按项目键控并随同步推拉;不同属主 ⇒ 写家落全局层,启动通知 `memory_project_scope_yields_to_sync`)"),
278
279
  r("METRICS_TOKEN", "observability", "secret", "/metrics 只读令牌", { danger: SEC }),
@@ -22,7 +22,7 @@ import type { GitApiKind } from "./git-api-kind.js";
22
22
  import type { CustomPkgSource } from "./sandbox-pkg-source.js";
23
23
  import type { MemoryEngineBackend, StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
24
24
  import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
25
- import type { AtMention } from "@sema-agent/settings-schema";
25
+ import type { AtMention, SandboxGitConfig } from "@sema-agent/settings-schema";
26
26
  import type { ModsDeclaration } from "./mods-declaration.js";
27
27
  /** [ref]:记忆向量面的 embedder 坐标(env `MEMORY_EMBEDDER_*` 解析结果;单一属主 = 本类型,
28
28
  * `plugins/memory-embedder.ts` 的工厂直接消费它,不复制形状)。语义与三问见
@@ -761,6 +761,10 @@ export interface ServiceConfigFlat {
761
761
  maxPushEntries?: number;
762
762
  maxPullEntries?: number;
763
763
  };
764
+ /** [ref] R2:中心(DB 记忆平面)声明的**同步身份** —— 客户端用来到达本中心同步口的基址(env `MEMORY_SYNC_SERVE_IDENTITY`)。
765
+ * 一处声明、三个读者:能力面 `memorySyncServe.identity` 逐字公开;中心入站 `POST /v1/memory/sync/:scope` 的 `peer` 按串与它比(不等 ⇒ 409,零写);
766
+ * 缺席 ⇒ 本中心不服务同步(501 `feature.memory_sync_serve_disabled`)。必须已是规范形(铸点 `memory-sync-identity.ts`),否则拒启 —— 无条件解析。 */
767
+ memorySyncServeIdentity?: string;
764
768
  /** [ref](黑板 [ref]/[ref] 两裁):记忆检索的**向量面**接线 —— OpenAI 兼容 `/embeddings` 端点。
765
769
  * env 四键(+ 可选 key):`MEMORY_EMBEDDER_ENDPOINT` / `MEMORY_EMBEDDER_MODEL` / `MEMORY_EMBEDDER_DIM`
766
770
  * (+ `MEMORY_EMBEDDER_TIMEOUT_MS` / `MEMORY_EMBEDDER_API_KEY`)。三问:
@@ -928,6 +932,13 @@ export interface ServiceConfigFlat {
928
932
  * ([ref] 起诊断面**有了** `executionLane` 段,但它就是能力面那一只投影 `{provider, toolsOnThisHost, isolated?}`,**不带**本键。)
929
933
  */
930
934
  requireExecutionLane: boolean;
935
+ /**
936
+ * 沙箱 git 平面的有效配置块(本部署经凭据代理路由哪些 git origin;契约 `SandboxGitConfig`:规范形 origin ≤ 16、两两不同、`connectUrl` 必 https、空表合法)。
937
+ * **唯一来源 = 中心有效配置**(`/effective` 的 `sandboxGit`,web-admin 按部署登记册投影;无 env 旋钮,本地配置源没有这一块);唯一判官 = `stageEffective`
938
+ * 的那一次 `SandboxGitConfig.safeParse`,`commitStaged` 整只重赋值 —— 缺席或判不过 ⇒ 键**删**(不写 undefined;判不过 = 当作没有任何 origin,契约 §12.8)。
939
+ * 热:读者(平面座 `sandbox-git-plane.ts` 的每一次读)逐次现读这一键,不捕获对象本身。缺席 = 没有这一块(与契约「可选、无默认」同一种缺席读法,不另设 null)。
940
+ */
941
+ sandboxGit?: SandboxGitConfig;
931
942
  remoteExec?: {
932
943
  provider: "e2b";
933
944
  apiKey: string;
@@ -1038,7 +1049,7 @@ export interface ServiceConfigFlat {
1038
1049
  * 域 = `[1, 86400]` 的十进制正整数,坏值**拒启**([ref] A 档;解析器 `parseDeviceEnrollTokenTtlEnv`)。
1039
1050
  * 缺席 ⇒ 签发点的属主默认 900(不在 config 层抄第二份默认,与本族其余旋钮同姿势)。 */
1040
1051
  enrollTokenTtlSec?: number;
1041
- /** `DEVICE_RELAY_URL` = 设备入口的运维声明,**必填**(缺席 / 空串 ⇒ 配置期拒起;解析器 `parseDeviceRelayUrlEnv`):
1052
+ /** `DEVICE_RELAY_URL` = 设备入口的运维声明,**必填**(缺席 / 空串 / 只含空白 ⇒ 配置期拒起:契约包车道表 device 行的必需键,同所有车道的必需键判官 `laneMissingEnv`;值的文法与凭据形由解析器 `parseDeviceRelayUrlEnv` 拒):
1042
1053
  * `{ kind: "url" }` = 设备连不到本引擎自己的地址(引擎在中继 / 网关之后)时该拨的那一个 ws(s) URL,**逐字**上能力面 `deviceExecutor.relayUrl`
1043
1054
  * (唯一消费点 `http/routes/capabilities.ts`);`{ kind: "none" }`(字面 `none`)= 没有中继,设备按本引擎地址 + `deviceExecutor.wsPath` 拨号,能力面不带 `relayUrl`。
1044
1055
  * 必填的理由:「设备拨哪里」只有运维知道;缺席曾被读成「直拨」,于是撤掉中继声明而根会话行上还有设备绑定时,设备改拨引擎地址(入口已收窄则拨不到)、
@@ -2319,7 +2330,7 @@ export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" |
2319
2330
  * 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
2320
2331
  export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAskRetentionMs" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalGrant" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "autoModeClassifierFallback" | "autoModeClassifierTimeoutMs" | "crossSessionInbound" | "crossSessionDialogExpiry" | "principalInboxAssistant">;
2321
2332
  /** 组:memory(记忆面 + TOC 同步腿)。 */
2322
- export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
2333
+ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memorySyncServeIdentity" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
2323
2334
  /** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 web-admin 腿(无 env 标量形),故 env 解析
2324
2335
  * 函数不产出它,但它与 `autonomy` 是同一根治理棒的两半,归本组。
2325
2336
  * [ref] 的三件部署治理声明(`compliancePosture`/`lockedConfigKeys`/`retentionPolicy`)同归本组:
@@ -2328,7 +2339,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
2328
2339
  * config 这一层它只是一句声明。 */
2329
2340
  export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "operatorSingleUser" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "authBridgeIssuer" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
2330
2341
  /** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
2331
- export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "remoteExecHomeDir" | "requireExecutionLane" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sandboxPkgSourceCustom" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "lspEnvAllow" | "imageBakes" | "readFace" | "readFaceSource" | "taskWriteFaceEscalation" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readDenyFloorRequired" | "sandboxReadFloor" | "agentConfigDir" | "configLkgPath" | "configLkgEnabled">;
2342
+ export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "sandboxGit" | "remoteExec" | "remoteExecHomeDir" | "requireExecutionLane" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sandboxPkgSourceCustom" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "lspEnvAllow" | "imageBakes" | "readFace" | "readFaceSource" | "taskWriteFaceEscalation" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readDenyFloorRequired" | "sandboxReadFloor" | "agentConfigDir" | "configLkgPath" | "configLkgEnabled">;
2332
2343
  /** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
2333
2344
  export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "drainSecondSignalMinGapMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "engineOrphanLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
2334
2345
  /** 组:observability(可观测)。 */
package/dist/config.d.ts CHANGED
@@ -422,6 +422,12 @@ export declare const FLEET_ENV_REFUSAL_CODES: {
422
422
  readonly member_unfenced: "config.fleet_member_unfenced";
423
423
  };
424
424
  export type FleetEnvRefusalCode = (typeof FLEET_ENV_REFUSAL_CODES)[keyof typeof FLEET_ENV_REFUSAL_CODES];
425
+ /**
426
+ * `MEMORY_SYNC_SERVE_IDENTITY`(中心声明的同步身份)的**唯一**解析口。**无条件解析**(运维旋钮律,同 `FLEET_ADVERTISE_ADDRESS`):未设 / 空串 ⇒ 缺席;
427
+ * 设了就必须**已是**同步身份的规范形(铸点 `memory-sync-identity.ts` 的「声明的身份」座位)—— 运维写下的就是能力面公开的、中心入站按串比的那一串,
428
+ * 没有第二种拼法 ⇒ 不是规范形就拒启并给出规范形(不静默规范化);带凭据 / 查询 / 片段 / 非 http(s) / 超宽同样拒启,拒句不引路径与凭据。
429
+ */
430
+ export declare function parseMemorySyncServeIdentity(raw: string | undefined): string | undefined;
425
431
  /** 一只 fleet 成员的围栏车道:续租打的中心端点(车道对象原样,含活性基址)+ 代际。 */
426
432
  export interface FleetFenceLane {
427
433
  readonly center: CenterEndpoint & {
package/dist/config.js CHANGED
@@ -3,13 +3,14 @@ import { DEFAULT_AT_MENTION, DEFAULT_MODEL_REF, mintCapabilityKeys } from "./mod
3
3
  import { homedir } from "node:os";
4
4
  import { isAbsolute, join, posix as posixPath } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { compileAllowList, inheritEnvPolicyProblem, BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, principalInboxConsumerOf } from "@sema-agent/core";
6
+ import { compileAllowList, inheritEnvPolicyProblem, BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, principalInboxConsumerOf, carriesCredential } from "@sema-agent/core";
7
7
  import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES, WEB_ADMIN_ENV, retiredEnvRefusal, stripControlAndBidi } from "@sema-agent/settings-schema";
8
8
  import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
9
9
  import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
10
10
  import { buildRuleListJudgeOptions } from "./permission-rule-list.js";
11
11
  import { isV2ScopeKey, memoryReplicationCoverage, replicatesProjectLayersOf } from "./memory-scope.js";
12
12
  import { memorySyncPeerOf } from "./memory-sync-client.js";
13
+ import { buildMemorySyncIdentityBootRefusal, declaredMemorySyncIdentityFlaw } from "./memory-sync-identity.js";
13
14
  import { assertProjectLayerOwnable } from "./project-identity.js";
14
15
  import { projectLayerOwnerOf } from "./project-layer-key.js";
15
16
  import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
@@ -1756,6 +1757,9 @@ function parseMemoryDomain(ctx) {
1756
1757
  if (scope === undefined) {
1757
1758
  throw new Error("MEMORY_SYNC_URL is set but no sync scope is derivable (memory engine off or multi-tenant without an explicit scope) — set MEMORY_SYNC_SCOPE, or run the single-user file memory engine with a typed MEMORY_SCOPE (e.g. user:<tenant>) so it provides one");
1758
1759
  }
1760
+ if (carriesCredential(scope)) {
1761
+ throw new Error(`${memorySyncScopeRaw !== undefined ? "MEMORY_SYNC_SCOPE" : "MEMORY_SCOPE"} (the memory sync layer's name) holds a credential-shaped part — the layer name travels verbatim in every sync request path and report, so it is refused, never cleaned: rename the layer`);
1762
+ }
1759
1763
  assertProjectLayerOwnable(scope, memorySyncScopeRaw !== undefined ? "MEMORY_SYNC_SCOPE" : "MEMORY_SCOPE");
1760
1764
  memorySync = {
1761
1765
  url: memorySyncUrl,
@@ -1793,6 +1797,7 @@ function parseMemoryDomain(ctx) {
1793
1797
  memoryScope,
1794
1798
  ...((v) => (v !== undefined ? { memoryPersistenceCapable: v } : {}))(parseMemoryPersistenceCapable(process.env.MEMORY_PERSISTENCE_CAPABLE)),
1795
1799
  ...(memorySync ? { memorySync } : {}),
1800
+ ...((v) => (v !== undefined ? { memorySyncServeIdentity: v } : {}))(parseMemorySyncServeIdentity(process.env.MEMORY_SYNC_SERVE_IDENTITY)),
1796
1801
  ...((v) => (v !== undefined ? { memoryEmbedder: v } : {}))(parseMemoryEmbedder({ memoryEngineBackend, memoryEngineEnabled })),
1797
1802
  memoryOrgAdmissionMode,
1798
1803
  ...(process.env.MEMORY_DELEGATION_EVIDENCE !== undefined
@@ -2305,6 +2310,15 @@ function parseFleetScope(raw) {
2305
2310
  }
2306
2311
  return fleetScopeOf(raw);
2307
2312
  }
2313
+ export function parseMemorySyncServeIdentity(raw) {
2314
+ if (raw === undefined || raw === "")
2315
+ return undefined;
2316
+ const flaw = declaredMemorySyncIdentityFlaw(raw);
2317
+ if (flaw !== undefined) {
2318
+ throw new Error(buildMemorySyncIdentityBootRefusal("MEMORY_SYNC_SERVE_IDENTITY", raw, flaw, "clients authenticate with MEMORY_SYNC_TOKEN; set MEMORY_SYNC_SERVE_IDENTITY to the bare base address clients reach this center's sync face at"));
2319
+ }
2320
+ return raw;
2321
+ }
2308
2322
  function parseFleetAdvertiseAddress(raw) {
2309
2323
  const name = "FLEET_ADVERTISE_ADDRESS";
2310
2324
  if (raw === undefined || raw === "")
@@ -2464,7 +2478,7 @@ const APPROVAL_GROUP_KEYS = [
2464
2478
  ];
2465
2479
  const MEMORY_GROUP_KEYS = [
2466
2480
  "memoryEngineEnabled", "memoryEngineDir", "memoryEngineRemoteLaneAllowed", "memoryEngineBackend", "memoryScope",
2467
- "memoryPersistenceCapable", "memorySync", "memoryEmbedder", "memoryOrgAdmissionMode", "memoryDelegationEvidence", "memoryProvenance", "memoryCapturePolicy", "memoryConsolidationDriver", "memoryOrgDirectoryJson", "memoryOrgGrantTtlMs", "memoryOrgUnavailableBackoffMs",
2481
+ "memoryPersistenceCapable", "memorySync", "memorySyncServeIdentity", "memoryEmbedder", "memoryOrgAdmissionMode", "memoryDelegationEvidence", "memoryProvenance", "memoryCapturePolicy", "memoryConsolidationDriver", "memoryOrgDirectoryJson", "memoryOrgGrantTtlMs", "memoryOrgUnavailableBackoffMs",
2468
2482
  "projectMemoryEnabled", "syncImportLeaseStaleSec",
2469
2483
  ];
2470
2484
  const AUTH_GROUP_KEYS = [
@@ -2474,7 +2488,7 @@ const AUTH_GROUP_KEYS = [
2474
2488
  "principalJwtMaxTtlSec", "authBridgeIssuer", "bindHost", "bindHostSource",
2475
2489
  ];
2476
2490
  const ORCHESTRATION_GROUP_KEYS = [
2477
- "remoteExec", "remoteExecHomeDir", "requireExecutionLane", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
2491
+ "sandboxGit", "remoteExec", "remoteExecHomeDir", "requireExecutionLane", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
2478
2492
  "selfOrchestrationModels", "selfOrchestrationWorkerIsolation", "forkEnabled", "experimentalObserverAgents",
2479
2493
  "schedulerEnabled", "schedulerSessionWakeup", "schedulerSessionLifetime", "schedulerStorePath", "planModeEnabled", "hooksTimeoutMs", "workflowRunStoreBackend",
2480
2494
  "workflowOrphanGraceMs", "workflowJournalRetentionMs", "workflowRunRetentionMs", "usageWindows", "workflowAgentsReadOnly",
@@ -138,7 +138,8 @@ export declare function executionLaneRow(lane: RemoteExecProvider): ExecutionLan
138
138
  * 一条车道**必需**的 env 键名 = 正本表行的 `requiredEnv ∪ requiredSecretEnv`(次序:非密坐标在前、密钥在后 —— 与 7.113.0 本仓手写表逐字同)。
139
139
  * 读者:`config.ts` 的 [ref] 拒启门(点了名却缺件 ⇒ 点名缺哪几键)、部署面门(`test/deploy-lanes-contract.test.ts`:`lanes.json` 每行登记
140
140
  * 本车道必需键)、`scripts/gen-deploy-lanes.ts`(从同一张正本生成 `lanes.json` 的必需键列,以及根 / review compose 与 sema-up 栈 compose 里的必需键透传)。
141
- * device 在正本表(15.2.0)上没有必需 env;它的必填键 `DEVICE_RELAY_URL` 的缺席暂由 `config.ts` `parseDeviceRelayUrlEnv` 拒(过渡:表行收进它即删那一臂)。
141
+ * 每条车道的必需键都在这一张表上、缺席都由下面的 {@link laneMissingEnv} 一只判官判 —— device 行也是(契约包 16.0.0 起 `requiredEnv: ["DEVICE_RELAY_URL"]`):缺席 / 空串 / 只含空白在 `declaredExecutionLane` 的必需 env 门被拒,
142
+ * 走到 `config.ts` 的设备入口解析器 `parseDeviceRelayUrlEnv` 时值一定在,它只判值的文法与凭据形。没有哪个必需键另有一臂缺席判官。
142
143
  */
143
144
  export declare function laneRequiredEnv(lane: RemoteExecProvider): readonly string[];
144
145
  /**
@@ -369,8 +369,10 @@ export interface HookNonBlockingFailure {
369
369
  * · `matcher_over_budget` —— 这条 hook 所在组的 `matcher`(或条目自己的 `if` 条件)在这次事件的这个名字上求值没在预算内结束(灾难回溯;[ref])。
370
370
  * **不是 hook 脚本坏了,是它的选中条件不可求值**:server 按「选中」处理(守卫不被跳过,条目照跑),此帧说的是「这只 matcher 在这个名字上跑不完」。
371
371
  * **一组一帧**(`if` 一条目一帧),`hookName` / `entryType` 指认组的第一个条目,`detail` 点名 matcher、名字、额度与组里的条目数;无 `exitCode` / `stderr`。
372
+ * · `url_not_allowed` —— http 条目被 http hook 名单拦下(`settings.allowedHttpHookUrls`,契约包判官答拦:没命中 / 空名单 / 名单坏形),**一个字节都没发**(7.142.0 起)。
373
+ * 不是端点坏了,是配置不许它出门;`detail` 点名 `settings.hooks.<event>`、名单键与规则词(`url` / `empty` / `malformed`)和出路;无 `exitCode` / `stderr`。
372
374
  */
373
- reason: "exit_nonzero" | "timeout" | "spawn_failed" | "bad_json" | "matcher_over_budget";
375
+ reason: "exit_nonzero" | "timeout" | "spawn_failed" | "bad_json" | "matcher_over_budget" | "url_not_allowed";
374
376
  /** hook 身份(CC `OG`/`AEe` 锚:`statusMessage` 优先,否则 command / url / prompt),已脱敏+有界。
375
377
  * ⚠️ http 条目取 URL 时**剥掉 query 与 fragment**([ref] codex R1-F1:端点凭据最常见的挂法是
376
378
  * `?token=…`,而通用脱敏器只认 `user:pass@` 形)。要更友好的名字,作者有 `statusMessage`。 */
@@ -411,9 +413,10 @@ export interface HookManagedUnrun {
411
413
  * · `spawn_failed` / `timeout` —— 起不来 / 超时被收;
412
414
  * · `exit_nonzero` —— 退出码非 0 非 2;`bad_json` —— 看着是 JSON 却读不成(含 http 条目 2xx 回了非 JSON 体、Stop × prompt 的判词读不出);`decision_out_of_set` —— `permissionDecision` 不在 allow / deny / ask 里;
413
415
  * · `evidence_unavailable` —— Stop × prompt 条目因会话证据面不足(或运维关了 transcript)被滤掉没跑;
414
- * · `rewrite_unsettled` —— 一个托管相(插件相 / 策略相)改写了输入、按新输入把那一相重判一遍后仍交回不同的值(不收敛;「改写」按值判,恒等回显不算)。
416
+ * · `rewrite_unsettled` —— 一个托管相(插件相 / 策略相)改写了输入、按新输入把那一相重判一遍后仍交回不同的值(不收敛;「改写」按值判,恒等回显不算);
417
+ * · `url_not_allowed` —— http 条目被 http hook 名单拦下、没发出(7.142.0 起;同一次的 `hook_non_blocking_failure{url_not_allowed}` 照发)。
415
418
  */
416
- reason: "matcher_invalid" | "llm_carrier_unavailable" | "llm_cap" | "powershell_unsupported" | "if_unevaluable" | "budget_exhausted" | "spawn_failed" | "timeout" | "exit_nonzero" | "bad_json" | "decision_out_of_set" | "evidence_unavailable" | "rewrite_unsettled";
419
+ reason: "matcher_invalid" | "llm_carrier_unavailable" | "llm_cap" | "powershell_unsupported" | "if_unevaluable" | "budget_exhausted" | "spawn_failed" | "timeout" | "exit_nonzero" | "bad_json" | "decision_out_of_set" | "evidence_unavailable" | "rewrite_unsettled" | "url_not_allowed";
417
420
  /** 那一组的来源词(`managedPolicy` 或 `managedPlugin:<name>`;名只留痕)。 */
418
421
  source: string;
419
422
  /** hook 身份(同 `hook_non_blocking_failure`:`statusMessage` 优先,否则 command / url〔剥 query〕/ prompt),已脱敏 + 有界。 */
@@ -1,5 +1,6 @@
1
1
  import { type HookEntry, type HookEntryType, type HookEventName, type HooksDroppedPiece, type HooksDroppedPieceCode, type HooksRejection, type HooksRejectionCode } from "@sema-agent/settings-schema/hooks";
2
2
  import { type ConfigPath, type UrlCredential } from "@sema-agent/settings-schema";
3
+ import { type HttpHookAllowlistLayer } from "@sema-agent/settings-schema";
3
4
  import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
4
5
  import type { EngineDollar, ModRegistration } from "@sema-agent/core";
5
6
  import type { Logger } from "../observability/logger.js";
@@ -514,6 +515,11 @@ export interface HookRunnerContext {
514
515
  * 的 TaskStream.steer(下个 turn 边界注入);返回 false=没有活流可投(任务已结束/挂起/别副本)——
515
516
  * 调用方记账不重试(挂起腿的 pending-steer park 是后续切片)。缺席 ⇒ asyncRewake 降级纯 async(记账)。 */
516
517
  wake?: (text: string) => Promise<boolean>;
518
+ /**
519
+ * http hook 两张名单(`allowedHttpHookUrls` / `httpHookAllowedEnvVars`)的**设置层**,交契约包判官原样读(见 {@link runHttpHook})。必填:每个装配点都要说这条 run 有哪些层 ——
520
+ * `[]` = 没有层写名单(不限,与引擎外的参照实现「名单未设 ⇒ 不限」同读)。今天唯一的层来源是请求的 `settings`(`task-settings.ts` 的 `httpHookAllowlist`,源 `flagSettings`)。
521
+ */
522
+ httpHookAllowlistLayers: readonly HttpHookAllowlistLayer[];
517
523
  }
518
524
  /** 阶段三b 模型调用载体契约(prompt=直连一次 completion;agent=runTask 收敛后的 final text)。
519
525
  * 返回 ok/text 或 ok:false/error——载体自己兜超时(timeoutMs 是硬顶,载体可更严),绝不 throw。 */
@@ -4,6 +4,7 @@ import vm from "node:vm";
4
4
  import { hostShell, resolveHostShell, adoptHostForegroundChild, killTreeSoft, killTreeHard } from "../plugins/host-platform.js";
5
5
  import { classifyHooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_ENTRY_TYPES, HOOK_EVENTS, HOOK_EVENT_OWNER, HOOK_EVENT_MATCHER_FIELD, hookMatcherForm, hookMatcherSelects, interpolateHookEnvVars, } from "@sema-agent/settings-schema/hooks";
6
6
  import { findUrlCredential, formatConfigPath, outgoingPath } from "@sema-agent/settings-schema";
7
+ import { httpHookEnvVarsAllowed, httpHookUrlVerdict } from "@sema-agent/settings-schema";
7
8
  import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
8
9
  import { describeToolCatalog } from "@sema-agent/core";
9
10
  import { hangClassic } from "./classic-stations.js";
@@ -511,6 +512,7 @@ export function hookIfMatches(cond, payload) {
511
512
  return "unevaluable";
512
513
  }
513
514
  }
515
+ const SERVER_WORKSPACE_TRUST = "no_trust_step";
514
516
  const MAX_LLM_ENTRIES_PER_EVENT = 4;
515
517
  const DEFAULT_PROMPT_HOOK_TIMEOUT_SECONDS = 30;
516
518
  const DEFAULT_AGENT_HOOK_TIMEOUT_SECONDS = 120;
@@ -996,8 +998,29 @@ async function runLlmHook(entry, payload, ctx, extra) {
996
998
  clearTimeout(hardTopTimer);
997
999
  }
998
1000
  }
1001
+ function httpHookWithheldSentence(event, block) {
1002
+ const field = `settings.${block.list}`;
1003
+ const [why, exit] = (() => {
1004
+ switch (block.rule) {
1005
+ case "url":
1006
+ return [`its url matches no pattern in ${field}`, `add a pattern that matches the url to ${field}, or point the hook at a url the list allows`];
1007
+ case "empty":
1008
+ return [`${field} is an empty list, so no http hook may run`, `add a pattern that matches the url to ${field}`];
1009
+ case "malformed":
1010
+ return [`${field} is not an array of strings, so it is read as allowing no url`, `write ${field} as an array of url pattern strings`];
1011
+ default: {
1012
+ const unhandled = block.rule;
1013
+ throw new Error(`httpHookWithheldSentence: unhandled rule ${String(unhandled)}`);
1014
+ }
1015
+ }
1016
+ })();
1017
+ return `settings.hooks.${event}: http hook not sent — ${why} (rule: ${block.rule}); ${exit}`;
1018
+ }
999
1019
  const MAX_BLANKED_REFERENCES_LOGGED = 16;
1000
1020
  async function runHttpHook(entry, payload, ctx, event) {
1021
+ const block = httpHookUrlVerdict(entry.url, ctx.httpHookAllowlistLayers, SERVER_WORKSPACE_TRUST);
1022
+ if (block !== undefined)
1023
+ return { code: null, stdout: "", stderr: "", timedOut: false, withheld: block };
1001
1024
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
1002
1025
  let hookChildVars;
1003
1026
  try {
@@ -1006,7 +1029,7 @@ async function runHttpHook(entry, payload, ctx, event) {
1006
1029
  catch (e) {
1007
1030
  return { code: null, stdout: "", stderr: "", timedOut: false, spawnError: String(e) };
1008
1031
  }
1009
- const interpolated = interpolateHookEnvVars(entry.headers ?? {}, entry.allowedEnvVars ?? [], hookChildVars);
1032
+ const interpolated = interpolateHookEnvVars(entry.headers ?? {}, httpHookEnvVarsAllowed(entry.allowedEnvVars ?? [], ctx.httpHookAllowlistLayers, SERVER_WORKSPACE_TRUST), hookChildVars);
1010
1033
  if (interpolated.blanked.length > 0) {
1011
1034
  ctx.logger.warn("hook_http_header_env_blanked", {
1012
1035
  event,
@@ -1210,6 +1233,12 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1210
1233
  ctx.logger.warn("hook_command_killed", { event, type: entry.type, killed: true, signal: run.aborted.signal });
1211
1234
  return { kind: "halted" };
1212
1235
  }
1236
+ if (run.withheld !== undefined) {
1237
+ const { list, rule, sources } = run.withheld;
1238
+ ctx.logger.warn("hook_http_withheld", { event, url: entry.type === "http" ? buildDisplaySafeUrl(entry.url) : null, list, rule, sources: [...sources] });
1239
+ noteHookFailure(ctx, { event, entry, reason: "url_not_allowed", detail: httpHookWithheldSentence(event, run.withheld), ...(toolName !== undefined ? { toolName } : {}) });
1240
+ return { kind: "unrun", reason: "url_not_allowed" };
1241
+ }
1213
1242
  if (run.spawnError) {
1214
1243
  ctx.logger.warn(entry.type === "http" ? "hook_http_request_failed" : entry.type === "prompt" || entry.type === "agent" ? "hook_llm_failed" : "hook_command_spawn_failed", { event, type: entry.type, error: seatText("operator-log-error", run.spawnError) });
1215
1244
  if (!run.noticed)
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { setSsoPrincipal, setSsoScope, setSsoRole, isDestructiveSessionWrite, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
2
+ import { setSsoPrincipal, setSsoScope, setSsoRole, setSsoPrincipalInc, isDestructiveSessionWrite, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
3
3
  import { publicStoreProbeError } from "../store-live-probe.js";
4
4
  import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
5
5
  import { admitBridgeIdentity } from "../auth-bridge.js";
@@ -227,6 +227,8 @@ export function createDispatch(ctx) {
227
227
  setSsoScope(req, identity.scope);
228
228
  if (identity.role)
229
229
  setSsoRole(req, identity.role);
230
+ if (identity.principalInc !== undefined)
231
+ setSsoPrincipalInc(req, identity.principalInc);
230
232
  deps.logger?.info?.("auth_bridge_principal_accepted", { principal: identity.principal, scope: identity.scope });
231
233
  }
232
234
  }
@@ -236,7 +238,7 @@ export function createDispatch(ctx) {
236
238
  if (await dispatchSegment(PUBLIC_ROUTES, req, res, url, search, method, allow.pre, ctx))
237
239
  return;
238
240
  const identitySource = verifiedIdentitySource(req, deps.config);
239
- admitRunIdentity(req, identitySource);
241
+ admitRunIdentity(req, identitySource, deps.logger);
240
242
  let source = null;
241
243
  const anyServiceAuth = hasServiceAuth(deps.config);
242
244
  if (anyServiceAuth) {
@@ -85,8 +85,8 @@ export type ExecutionContext = {
85
85
  export type RootedExecutionContext = Extract<ExecutionContext, {
86
86
  readonly root: string;
87
87
  }>;
88
- /** 装配器(`resolveSpec`)收的那一半:根与入口形(它不判放置、不选车道)。 */
89
- export type ExecutionRoot = Pick<RootedExecutionContext, "kind" | "root">;
88
+ /** 装配器(`resolveSpec`)收的那一半:根、入口形与解析器选定的车道(它不判放置、不选车道 —— `lane` 是解析器那一次的读数,装配器只读:项目包的车道门)。 */
89
+ export type ExecutionRoot = Pick<RootedExecutionContext, "kind" | "root" | "lane">;
90
90
  /** 解析器读的部署件(全部取自同一只 `ServiceDeps`)。 */
91
91
  export interface ExecutionContextDeps {
92
92
  readonly config: ServiceConfig;
@@ -117,7 +117,7 @@ export declare function inflightKeyOf(idem: RunIdemBinding, face: SubmitReplayFa
117
117
  * 再走 `/v1/tasks`,合流到的是前者的那份结局。回执若铸在同步腿的体里,这条**确实产了帧**的 run 在非流式面上
118
118
  * 就成了「键缺席」,而 present-iff 的读法会把它读成「这台引擎不给回执」。挂在 outcome 上 = 它是**这条 run 的
119
119
  * 属性**(哪条腿跑的都一样),由各腿自己的应答构造器在送出那一刻按自己的 wire 形挂。
120
- * 落定之后的同键重放不走这里:它读行,体是 run 行读面 + `replay: true`(`http/submit-replay.ts`)—— 回执是起手那一帧
120
+ * 落定之后的同键重放不走这里:它读行,体是首答的信封 + `replay: true`(`http/submit-replay.ts` → `http/sync-submit-body.ts`)—— 回执是起手那一帧
121
121
  * 的活捕获、不落行,所以重放体上**不带** `wiringManifest`(`replay: true` 就是那一形的判别位)。
122
122
  *
123
123
  * 🔴 **租户面安全(别在复审里当成漏)**:合流的那份回执是按**第一位调用方**的 `explicitOperatorOk` 投的,
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import type { ServiceConfig } from "../config-types.js";
3
3
  import { type DurableOwnerKey, type Principal, type SessionCaller, type SessionOwnerCell } from "../security.js";
4
4
  import { type IdentitySource, type RunIdentity } from "../identity-source.js";
5
+ import type { Logger } from "../observability/logger.js";
5
6
  /** Coerce a possibly-repeated request header to a single string (or undefined). */
6
7
  export declare function headerStr(v: string | string[] | undefined): string | undefined;
7
8
  export declare function safeEqual(a: string, b: string): boolean;
@@ -28,8 +29,9 @@ export declare function verifiedIdentitySource(req: IncomingMessage, config: Ser
28
29
  /**
29
30
  * 🔴 **run 行身份元组的唯一铸点**(律见 `identity-source.ts` 头注;唯一调用点 = `dispatch.ts` 的凭据门,机器门钉着)。`source` = 凭据门那一次
30
31
  * {@link verifiedIdentitySource} 的答案 —— 这里不再判来源,只把来源连同它的 scope / 化身号定成元组,挂在这个请求上;建 run 行的点经 {@link runIdentityOf} 读它。
32
+ * `logger` 收律 4 的告警行(令牌声明装不进列;凭据门交 `deps.logger`)。
31
33
  */
32
- export declare function admitRunIdentity(req: IncomingMessage, source: IdentitySource | undefined): void;
34
+ export declare function admitRunIdentity(req: IncomingMessage, source: IdentitySource | undefined, logger: Pick<Logger, "warn"> | undefined): void;
33
35
  /**
34
36
  * 建 run 行时写进行里的身份元组 —— 每个 `createRun` 调用点(或替它建行的腿的调用方)读这一只,原样写入(机器门:`src/http/**` 每处 `createRun` 都写明 `identity`)。
35
37
  * 只在**受理请求验过的主体就是这一行的属主**时交出凭据门铸下的元组(提交面恒成立:行的属主就是这个主体);别人替属主按下的那一跑(唤醒、决定投递铸的
@@ -1,7 +1,7 @@
1
1
  import { timingSafeEqual } from "node:crypto";
2
2
  import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
3
- import { PRINCIPAL_TOKEN_HEADER, verifiedPrincipal, ssoVerifiedPrincipal, ssoVerifiedScope, ownerlessRowOpen, mintPrincipal } from "../security.js";
4
- import { IDENTITY_SOURCES, RUN_IDENTITY_SCOPE_MAX_CHARS } from "../identity-source.js";
3
+ import { PRINCIPAL_TOKEN_HEADER, verifiedPrincipal, ssoVerifiedPrincipal, ssoVerifiedPrincipalInc, ssoVerifiedScope, ownerlessRowOpen, mintPrincipal } from "../security.js";
4
+ import { IDENTITY_SOURCES, runIdentityClaimOverflow } from "../identity-source.js";
5
5
  import { sendError } from "./send.js";
6
6
  export function headerStr(v) {
7
7
  return Array.isArray(v) ? v[0] : v;
@@ -60,19 +60,28 @@ export function verifiedIdentitySource(req, config) {
60
60
  return IDENTITY_SOURCES.find((kind) => identitySourceVerified(kind, req, config));
61
61
  }
62
62
  const ADMITTED_RUN_IDENTITY = new WeakMap();
63
- export function admitRunIdentity(req, source) {
64
- ADMITTED_RUN_IDENTITY.set(req, mintRunIdentity(req, source));
65
- }
66
- function mintRunIdentity(req, source) {
63
+ export function admitRunIdentity(req, source, logger) {
64
+ ADMITTED_RUN_IDENTITY.set(req, mintRunIdentity(req, source, logger));
65
+ }
66
+ function mintRunIdentity(req, source, logger) {
67
+ const fits = (claim, value) => {
68
+ const overflow = runIdentityClaimOverflow(claim, value);
69
+ if (overflow !== undefined)
70
+ logger?.warn("run_identity_claim_unrecorded", { claim: overflow.claim, chars: overflow.chars, max: overflow.max });
71
+ return overflow === undefined;
72
+ };
67
73
  switch (source) {
68
74
  case "service_token":
69
75
  case "direct_door_jwt":
70
76
  return { source, scope: null, principalInc: null };
71
77
  case "sso": {
72
78
  const scope = ssoVerifiedScope(req);
73
- if (scope === undefined || [...scope].length > RUN_IDENTITY_SCOPE_MAX_CHARS)
79
+ if (scope === undefined)
80
+ return null;
81
+ if (!fits("scope", scope))
74
82
  return null;
75
- return { source, scope, principalInc: null };
83
+ const principalInc = ssoVerifiedPrincipalInc(req);
84
+ return { source, scope, principalInc: principalInc !== undefined && fits("principalInc", principalInc) ? principalInc : null };
76
85
  }
77
86
  case undefined:
78
87
  return null;