@sema-agent/server 7.137.0 → 7.138.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 (209) hide show
  1. package/MIGRATION.md +328 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +67 -20
  5. package/dist/approval-card.d.ts +21 -14
  6. package/dist/approval-card.js +1 -0
  7. package/dist/audit.js +1 -0
  8. package/dist/background-agent-name.d.ts +5 -0
  9. package/dist/background-agent-name.js +5 -0
  10. package/dist/boot/budget-tracing.d.ts +1 -12
  11. package/dist/boot/deployment-mods.d.ts +46 -16
  12. package/dist/boot/deployment-mods.js +82 -6
  13. package/dist/boot/governance-seams.d.ts +10 -17
  14. package/dist/boot/governance-seams.js +5 -17
  15. package/dist/boot/leader.d.ts +3 -0
  16. package/dist/boot/leader.js +1 -0
  17. package/dist/boot/org-delivery.js +15 -2
  18. package/dist/boot/resolve-spec.d.ts +3 -0
  19. package/dist/boot/resolve-spec.js +23 -12
  20. package/dist/boot/retention-lane.d.ts +11 -5
  21. package/dist/boot/retention-lane.js +2 -2
  22. package/dist/boot/runner-deps.d.ts +7 -3
  23. package/dist/boot/runner-deps.js +1 -0
  24. package/dist/boot/stage-04-stores.d.ts +1 -12
  25. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  26. package/dist/boot/stage-05-execution-env.js +7 -4
  27. package/dist/boot/stage-06-runners.d.ts +3 -12
  28. package/dist/boot/stage-06-runners.js +5 -2
  29. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  30. package/dist/boot/stage-07-capability-layer.js +2 -2
  31. package/dist/boot/stage-08-reapers.d.ts +3 -12
  32. package/dist/boot/stage-09-leader.d.ts +3 -12
  33. package/dist/boot/stage-09-leader.js +2 -2
  34. package/dist/boot/stage-10-http-server.d.ts +3 -12
  35. package/dist/boot/stage-10-http-server.js +4 -2
  36. package/dist/boot/web-admin.d.ts +5 -2
  37. package/dist/boot/web-admin.js +3 -3
  38. package/dist/brain.js +5 -5
  39. package/dist/budget.d.ts +17 -15
  40. package/dist/budget.js +2 -2
  41. package/dist/capabilities/mcp-probe.d.ts +36 -11
  42. package/dist/capabilities/mcp-probe.js +44 -5
  43. package/dist/capability-facts.d.ts +6 -0
  44. package/dist/capability-facts.js +5 -0
  45. package/dist/checkpoint-identity.d.ts +13 -6
  46. package/dist/config-catalog.js +7 -1
  47. package/dist/config-types.d.ts +15 -7
  48. package/dist/config.js +8 -5
  49. package/dist/deployment-shape.d.ts +6 -6
  50. package/dist/gate-settlement.d.ts +71 -0
  51. package/dist/gate-settlement.js +40 -0
  52. package/dist/hooks/classic-stations.d.ts +1 -0
  53. package/dist/hooks/classic-stations.js +1 -0
  54. package/dist/hooks/command-spawn.d.ts +2 -2
  55. package/dist/hooks/hook-runner.js +14 -14
  56. package/dist/http/admission.js +4 -2
  57. package/dist/http/dispatch.d.ts +4 -0
  58. package/dist/http/dispatch.js +3 -0
  59. package/dist/http/durable-run-leg.js +4 -2
  60. package/dist/http/memory-compliance-failure-reply.js +3 -0
  61. package/dist/http/resume-legs.d.ts +5 -11
  62. package/dist/http/resume-legs.js +136 -91
  63. package/dist/http/route-ctx.d.ts +30 -51
  64. package/dist/http/route-ctx.js +2 -5
  65. package/dist/http/routes/a2a-serve.js +10 -4
  66. package/dist/http/routes/agents-roster.d.ts +1 -1
  67. package/dist/http/routes/agents-roster.js +5 -1
  68. package/dist/http/routes/approvals-assistant.js +6 -6
  69. package/dist/http/routes/capabilities.js +12 -10
  70. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  71. package/dist/http/routes/mcp-reconnect.js +7 -5
  72. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  73. package/dist/http/routes/memory-origin.d.ts +1 -1
  74. package/dist/http/routes/rules.js +3 -3
  75. package/dist/http/routes/runs.js +11 -7
  76. package/dist/http/routes/sessions.js +6 -4
  77. package/dist/http/routes/side-query.js +2 -3
  78. package/dist/http/routes/tasks.js +792 -780
  79. package/dist/http/server.d.ts +31 -25
  80. package/dist/http/wire-types.d.ts +14 -6
  81. package/dist/identity-source.d.ts +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +1 -0
  84. package/dist/leader/wire.d.ts +7 -0
  85. package/dist/leader/wire.js +5 -4
  86. package/dist/mcp-assertion-header.d.ts +51 -40
  87. package/dist/mcp-assertion-header.js +10 -11
  88. package/dist/mcp-authorization-face.d.ts +27 -0
  89. package/dist/mcp-authorization-face.js +15 -0
  90. package/dist/memory-operator-faces.d.ts +13 -4
  91. package/dist/memory-operator-faces.js +1 -17
  92. package/dist/memory-sync-client.js +72 -57
  93. package/dist/memory-sync.d.ts +1 -1
  94. package/dist/model-route-gap.d.ts +6 -22
  95. package/dist/model-route-gap.js +7 -5
  96. package/dist/mods-declaration.d.ts +110 -0
  97. package/dist/mods-declaration.js +109 -0
  98. package/dist/observability/err-text.d.ts +5 -0
  99. package/dist/observability/err-text.js +9 -0
  100. package/dist/observability/fail-open.d.ts +5 -1
  101. package/dist/observability/fail-open.js +5 -1
  102. package/dist/observability/secret-env-scrub.d.ts +1 -1
  103. package/dist/observability/secret-env-scrub.js +1 -1
  104. package/dist/parked-decide.d.ts +15 -2
  105. package/dist/parked-decide.js +14 -8
  106. package/dist/peer-seat.d.ts +150 -0
  107. package/dist/peer-seat.js +239 -0
  108. package/dist/permission-rule-vocab.d.ts +7 -7
  109. package/dist/permission-rule-vocab.js +3 -3
  110. package/dist/plain-string-record.d.ts +38 -0
  111. package/dist/plain-string-record.js +38 -0
  112. package/dist/plugins/background-agent-store-sql.js +6 -3
  113. package/dist/plugins/caching-session-store.d.ts +5 -0
  114. package/dist/plugins/caching-session-store.js +6 -0
  115. package/dist/plugins/checkpoint-store-sql.d.ts +15 -20
  116. package/dist/plugins/checkpoint-store-sql.js +8 -8
  117. package/dist/plugins/file-history-store-sql.js +3 -2
  118. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  119. package/dist/plugins/k8s-pod-reaper.js +3 -3
  120. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  121. package/dist/plugins/local-checkpoint-store.js +0 -1
  122. package/dist/plugins/local-session-store.d.ts +66 -27
  123. package/dist/plugins/local-session-store.js +60 -28
  124. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  125. package/dist/plugins/mailbox-store-sql.js +103 -15
  126. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  127. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  128. package/dist/plugins/one-time-migrations.d.ts +6 -3
  129. package/dist/plugins/one-time-migrations.js +3 -4
  130. package/dist/plugins/permission-rule-store-sql.js +21 -13
  131. package/dist/plugins/pg-session-storage.d.ts +10 -2
  132. package/dist/plugins/pg-session-storage.js +18 -5
  133. package/dist/plugins/remote-env-adb.d.ts +3 -1
  134. package/dist/plugins/remote-env-adb.js +1 -1
  135. package/dist/plugins/remote-env-device.d.ts +3 -1
  136. package/dist/plugins/remote-env-device.js +1 -1
  137. package/dist/plugins/remote-env-e2b.d.ts +2 -0
  138. package/dist/plugins/remote-env-e2b.js +1 -1
  139. package/dist/plugins/remote-env-host.d.ts +3 -1
  140. package/dist/plugins/remote-env-host.js +1 -1
  141. package/dist/plugins/remote-env-k8s.d.ts +3 -1
  142. package/dist/plugins/remote-env-k8s.js +1 -1
  143. package/dist/plugins/remote-env-local-docker.d.ts +2 -0
  144. package/dist/plugins/remote-env-local-docker.js +1 -1
  145. package/dist/plugins/remote-env-ssh.d.ts +3 -1
  146. package/dist/plugins/remote-env-ssh.js +1 -1
  147. package/dist/plugins/retention-store-sql.d.ts +14 -2
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/session-fork.d.ts +10 -13
  150. package/dist/plugins/session-fork.js +5 -25
  151. package/dist/plugins/session-ownership.d.ts +7 -0
  152. package/dist/plugins/session-ownership.js +5 -0
  153. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  154. package/dist/plugins/session-workspace-cell.js +38 -0
  155. package/dist/plugins/store-backend.js +7 -7
  156. package/dist/plugins/tidb-pool.js +6 -0
  157. package/dist/plugins/tidb-session-store.d.ts +17 -2
  158. package/dist/plugins/tidb-session-store.js +16 -5
  159. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  160. package/dist/plugins/tool-result-store-sql.js +8 -7
  161. package/dist/plugins/worktree-isolation.d.ts +3 -2
  162. package/dist/plugins/worktree-isolation.js +1 -7
  163. package/dist/project-identity.d.ts +1 -14
  164. package/dist/project-identity.js +0 -4
  165. package/dist/rules-consent.js +6 -4
  166. package/dist/run-local.js +12 -6
  167. package/dist/runs.d.ts +7 -1
  168. package/dist/runs.js +6 -1
  169. package/dist/sandbox-git-plane.d.ts +2 -2
  170. package/dist/server-secret-env.d.ts +6 -3
  171. package/dist/server-secret-env.js +4 -2
  172. package/dist/session-incarnation.d.ts +4 -5
  173. package/dist/session-incarnation.js +2 -2
  174. package/dist/session-workspace.d.ts +130 -0
  175. package/dist/session-workspace.js +83 -0
  176. package/dist/spec-fields.d.ts +1 -1
  177. package/dist/standing-answer-grant.d.ts +2 -13
  178. package/dist/standing-answer-grant.js +0 -7
  179. package/dist/task-a2a.d.ts +44 -14
  180. package/dist/task-a2a.js +46 -20
  181. package/dist/task-mcp.d.ts +33 -20
  182. package/dist/task-mcp.js +46 -22
  183. package/dist/tool-approval.d.ts +32 -8
  184. package/dist/tool-approval.js +49 -12
  185. package/dist/trace/core-keyset-guard.d.ts +33 -18
  186. package/dist/trace/engine-notice-wire.d.ts +1 -1
  187. package/dist/trace/engine-notice-wire.js +2 -0
  188. package/dist/trace/exit-seat.d.ts +20 -1
  189. package/dist/trace/exit-seat.js +3 -0
  190. package/dist/trace/extension-call-face.d.ts +33 -0
  191. package/dist/trace/extension-call-face.js +39 -0
  192. package/dist/trace/project.d.ts +25 -7
  193. package/dist/trace/project.js +29 -3
  194. package/dist/trace/redact.js +42 -13
  195. package/dist/trace/self-minted-wire.d.ts +129 -10
  196. package/dist/trace/self-minted-wire.js +62 -3
  197. package/dist/trace/wire-projection-faces.d.ts +6 -6
  198. package/dist/trace/wire-projection-faces.js +6 -5
  199. package/dist/user-authored-text.d.ts +2 -2
  200. package/dist/user-authored-text.js +16 -1
  201. package/dist/web-admin-client/center-request.d.ts +3 -2
  202. package/dist/web-admin-client/center-request.js +2 -3
  203. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  204. package/dist/web-admin-client/skills-mcp.js +31 -18
  205. package/dist/web-admin-client/types.d.ts +5 -0
  206. package/package.json +3 -3
  207. package/wire-changes/server@7.138.0.json +446 -0
  208. package/dist/http/mcp-remote-text.d.ts +0 -5
  209. package/dist/http/mcp-remote-text.js +0 -7
@@ -1,31 +1,61 @@
1
+ import { type EngineDollar, type ModAdmissionRecord, type ModRegistration } from "@sema-agent/core";
2
+ import { type ModsDeclaration } from "../mods-declaration.js";
3
+ import type { Logger } from "../observability/logger.js";
4
+ import type { Metrics } from "../observability/metrics.js";
1
5
  /**
2
- * 部署座(`RunnerDeps.mods`)的注册:**铸**({@link createDeploymentMods})与**起服自证**({@link assertDeploymentModsAdmitted})都在这一处。
3
- *
4
- * 起服自证(律三:拒形只有引擎一种读法,本仓只负责「不写」与「启动时自证」):
5
- * 部署座上的注册是本仓自己铸的。它们坏了
6
- * —— 坏名、坏档、缺 `uses.calls`、`on` 越出 `uses`、只选无站的名字、`register` 抛 —— 引擎在每条腿的 prepare 里拒
7
- * (`config.mods_invalid`),那就是每个请求都失败。所以起服时让引擎的 admission 判一次:判官仍是引擎(`admitModChain`),
8
- * 本函数不判形,只把它的拒折成一句起服错误、拒启。两个起服入口(server 第五段、本地 CLI)同调这一只。
6
+ * 部署座注册的**出处标签**(谁供的这张注册)—— 宿主自己的词,引擎原样携带、不判;**全仓唯一写点**。
9
7
  *
10
- * 也拒启的两形(同一个理由:部署注册一张都不该缺席,也不该带病挂上):
11
- * · admission 期的报告(引擎对坏 `budgetMs` 只报告、回落缺省,不拒)—— 部署注册不设 `budgetMs`,报告出现就是铸法坏了;
12
- * · 被 `plugin.register` 判拒的注册(本部署没有这种判官;出现即装配错)。
13
- * 腿外 admission 只建一只总线并跑一次各注册的 `register`(本仓的 `register` 是纯函数),不碰任何腿。
8
+ * `wiring_manifest.mods[]` 的一行上有两个词字段,各自一张闭集、互不相交:`seat` 是引擎的座词(`MOD_SEATS`:`deployment` | `task`,这张注册站在哪一座),
9
+ * `provenance` 是这张表的值(这张注册是谁供的)。两个字段不共用词 —— 同一个词不在一行里指两件事。
10
+ * · `minted` → `server`:本服务自己铸的(拒绝计量 / 工具追踪 / 品牌行);
11
+ * · `declared` → `operator`:运维在部署声明(`MODS_ROOT` / `MODS_PACKAGES`)里点名装进来的包。词取「谁供的」:与 `server` 同一个维度(供给方),
12
+ * 并沿用本仓对这件事的既有称呼「运维声明」。不用 `deployment`:它是座词,同一行上同形不同义。
13
+ * 加一种供给方 = 在这里加一行;取到的词与座词相交 ⇒ 下面的类型门编译红。
14
14
  */
15
- import { type EngineDollar, type ModRegistration } from "@sema-agent/core";
16
- import type { Logger } from "../observability/logger.js";
17
- import type { Metrics } from "../observability/metrics.js";
15
+ export declare const MOD_PROVENANCE: {
16
+ readonly minted: "server";
17
+ readonly declared: "operator";
18
+ };
18
19
  /**
19
- * 部署座上的注册列表 —— 两个起服入口(server 第五段与本地 CLI)同一只铸法,哪几张、什么顺序只写在这里:
20
+ * 本仓自铸的部署注册 —— 哪几张、什么顺序只写在这里(装配口 {@link assembleDeploymentMods} 把它们排在声明的包之前):
20
21
  * · 拒绝计量恒挂、在前;工具追踪(`TOOL_TRACE=true`,交 `traceLogger`)在后。两张都是 `prepend` 档的观察者,引擎按「档 → 部署先于任务 → 座内顺序」
21
22
  * 串行跑观察者、且都在链之前 ⇒ 同一次拒绝先计数、后写追踪行;
22
23
  * · 品牌行(`brandIdentity`:品牌部署 = local / TOC)—— `prompt.compose` 上的一条部署类 overlay(`capabilities/brand-identity.ts`),与两只观察者
23
24
  * 不在同一个事件上,顺序不影响它们。部署开不开品牌只在这一个入参表态。
25
+ * 出处标签:自铸的每一张都带宿主标签 {@link MOD_PROVENANCE}`.minted`,声明的包由装配口给 `.declared` —— 引擎把宿主给的标签原样记进准入记录与每条腿的
26
+ * `wiring_manifest.mods[]`,一条腿的部署座由此分得出「本服务自铸」与「运维声明」两种来源。
24
27
  */
25
28
  export declare function createDeploymentMods(input: {
26
29
  readonly metrics: Pick<Metrics, "inc">;
27
30
  readonly traceLogger?: Logger | undefined;
28
31
  readonly brandIdentity?: boolean | undefined;
29
32
  }): readonly ModRegistration<EngineDollar>[];
30
- export declare function assertDeploymentModsAdmitted(mods: readonly ModRegistration<EngineDollar>[]): Promise<void>;
33
+ /** 起服装上的一只声明包(起服日志行 `mods_package_loaded` 与 `/health` / `/v1/capabilities` 的读数都读这一份)。 */
34
+ export interface LoadedModPackage {
35
+ readonly name: string;
36
+ /** 引擎准入记录里这只包站的档(准入判过的那一份,不是声明里写的)。 */
37
+ readonly tier: ModAdmissionRecord["tier"];
38
+ /** 读包者按相等判过的引擎契约号(包声明的 `contract` 与引擎的 `KERNEL_CONTRACT_VERSION` 相等才装得上)。 */
39
+ readonly contract: number;
40
+ /** 读包者交回的三件字节摘要(`ModPackageRegistration.digest`,按给值记)—— 认得出「装的是哪几份字节」,不是代码完整性凭据。 */
41
+ readonly digest: string;
42
+ }
43
+ /** 装配的结果:部署座的注册(自铸在前、声明在后,整列已过引擎准入)与装上的声明包(只数声明的包,自铸的不算)。 */
44
+ export interface DeploymentModsAssembly {
45
+ readonly registrations: readonly ModRegistration<EngineDollar>[];
46
+ readonly loaded: readonly LoadedModPackage[];
47
+ }
48
+ /** 部署座的唯一装配口(两只起服入口同调;三段与失败见文件头注)。 */
49
+ export declare function assembleDeploymentMods(input: {
50
+ readonly metrics: Pick<Metrics, "inc">;
51
+ readonly traceLogger?: Logger | undefined;
52
+ readonly brandIdentity?: boolean | undefined;
53
+ readonly declaration: ModsDeclaration;
54
+ readonly logger: Pick<Logger, "info">;
55
+ }): Promise<DeploymentModsAssembly>;
56
+ /**
57
+ * 第三段:整列交引擎准入判一次(判官是引擎;本函数不判形,只把它的拒、报告与判拒折成一句 `admission_refused`)。
58
+ * 过了 ⇒ 交回这一次准入的记录(每张注册一行,全部挂上了 —— 有一行被判拒就不会走到这里)。
59
+ */
60
+ export declare function assertDeploymentModsAdmitted(mods: readonly ModRegistration<EngineDollar>[]): Promise<readonly ModAdmissionRecord[]>;
31
61
  //# sourceMappingURL=deployment-mods.d.ts.map
@@ -1,26 +1,102 @@
1
- import { admitModChain } from "@sema-agent/core";
1
+ import { realpathSync, statSync } from "node:fs";
2
+ import { isAbsolute, relative, sep } from "node:path";
3
+ import { admitModChain, KERNEL_CONTRACT_VERSION, loadModPackage } from "@sema-agent/core";
2
4
  import { createBrandIdentityRegistration } from "../capabilities/brand-identity.js";
3
5
  import { createPermissionDeniedMeter, createToolTracer } from "../observability/tool-trace.js";
6
+ import { errorCodeOf, errorText } from "../observability/err-text.js";
7
+ import { sanitizeWireText } from "../trace/engine-notice-wire.js";
8
+ import { DeploymentModsRefusedError } from "../mods-declaration.js";
9
+ export const MOD_PROVENANCE = { minted: "server", declared: "operator" };
4
10
  export function createDeploymentMods(input) {
5
11
  return [
6
12
  createPermissionDeniedMeter(input.metrics),
7
13
  ...(input.traceLogger !== undefined ? [createToolTracer(input.traceLogger)] : []),
8
14
  ...(input.brandIdentity === true ? [createBrandIdentityRegistration()] : []),
9
- ];
15
+ ].map((registration) => ({ ...registration, provenance: MOD_PROVENANCE.minted }));
16
+ }
17
+ export async function assembleDeploymentMods(input) {
18
+ const minted = createDeploymentMods(input);
19
+ const resolved = resolveDeclaredPackages(input.declaration);
20
+ const declared = [];
21
+ for (const { pkg, realDir } of resolved)
22
+ declared.push(await loadDeclaredPackage(pkg, realDir));
23
+ const registrations = [...minted, ...declared];
24
+ const records = await assertDeploymentModsAdmitted(registrations);
25
+ const loaded = resolved.map(({ pkg }, i) => ({ name: pkg.name, tier: admittedRecordOf(records, pkg.name).tier, contract: KERNEL_CONTRACT_VERSION, digest: declared[i].digest }));
26
+ for (const row of loaded)
27
+ input.logger.info("mods_package_loaded", { name: sanitizeWireText(row.name), tier: row.tier, contract: row.contract, digest: row.digest });
28
+ return { registrations, loaded };
29
+ }
30
+ function fsCauseOf(e) {
31
+ const code = errorCodeOf(e);
32
+ return typeof code === "string" && /^E[A-Z0-9]{1,31}$/.test(code) ? code : "unknown";
33
+ }
34
+ function strictlyUnderRoot(root, child) {
35
+ const rel = relative(root, child);
36
+ return rel !== "" && rel.split(sep)[0] !== ".." && !isAbsolute(rel);
37
+ }
38
+ function resolveDeclaredPackages(declaration) {
39
+ if (declaration.packages === undefined)
40
+ return [];
41
+ let realRoot;
42
+ try {
43
+ realRoot = realpathSync(declaration.root);
44
+ }
45
+ catch (e) {
46
+ throw new DeploymentModsRefusedError({ word: "root_unreadable", cause: fsCauseOf(e) });
47
+ }
48
+ return declaration.packages.map((pkg, entry) => {
49
+ let realDir;
50
+ let isDirectory;
51
+ try {
52
+ realDir = realpathSync(pkg.dir);
53
+ isDirectory = statSync(realDir).isDirectory();
54
+ }
55
+ catch (e) {
56
+ throw new DeploymentModsRefusedError({ word: "dir_unreadable", entry, cause: fsCauseOf(e) });
57
+ }
58
+ if (!strictlyUnderRoot(realRoot, realDir))
59
+ throw new DeploymentModsRefusedError({ word: "outside_root", entry });
60
+ if (!isDirectory)
61
+ throw new DeploymentModsRefusedError({ word: "not_a_directory", entry });
62
+ return { pkg, realDir };
63
+ });
64
+ }
65
+ function machineCodeOf(e) {
66
+ const code = errorCodeOf(e);
67
+ return typeof code === "string" && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(code) ? code : undefined;
68
+ }
69
+ async function loadDeclaredPackage(pkg, realDir) {
70
+ let registration;
71
+ try {
72
+ registration = await loadModPackage(realDir, { tier: pkg.tier, provenance: MOD_PROVENANCE.declared, ...("options" in pkg ? { options: pkg.options } : {}) });
73
+ }
74
+ catch (e) {
75
+ throw new DeploymentModsRefusedError({ word: "package_refused", name: pkg.name, code: machineCodeOf(e), reason: errorText(e) });
76
+ }
77
+ if (registration.name !== pkg.name)
78
+ throw new DeploymentModsRefusedError({ word: "name_mismatch", name: pkg.name, packageName: registration.name });
79
+ return registration;
10
80
  }
11
- const REFUSED = "deployment extension registration refused";
12
81
  export async function assertDeploymentModsAdmitted(mods) {
13
82
  const reports = [];
14
83
  let records;
15
84
  try {
16
- ({ records } = await admitModChain({ deployment: mods }, (err) => void reports.push(err.message)));
85
+ ({ records } = await admitModChain({ deployment: mods }, (err) => void reports.push(errorText(err))));
17
86
  }
18
87
  catch (e) {
19
- throw new Error(`${REFUSED}: ${e instanceof Error ? e.message : String(e)}`);
88
+ throw new DeploymentModsRefusedError({ word: "admission_refused", code: machineCodeOf(e), reason: errorText(e) });
20
89
  }
21
90
  const refused = records.flatMap((r) => ("refusedBy" in r ? [`"${r.name}" refused by ${r.refusedBy}: ${r.reason}`] : []));
22
91
  const problems = [...reports, ...refused];
23
92
  if (problems.length > 0)
24
- throw new Error(`${REFUSED}: ${problems.join("; ")}`);
93
+ throw new DeploymentModsRefusedError({ word: "admission_refused", code: undefined, reason: problems.join("; ") });
94
+ return records;
95
+ }
96
+ function admittedRecordOf(records, name) {
97
+ const record = records.find((r) => r.seat === "deployment" && r.name === name);
98
+ if (record === undefined)
99
+ throw new DeploymentModsRefusedError({ word: "admission_refused", code: undefined, reason: `the engine's admission returned no record for ${JSON.stringify(name)}` });
100
+ return record;
25
101
  }
26
102
  //# sourceMappingURL=deployment-mods.js.map
@@ -41,13 +41,13 @@
41
41
  *
42
42
  * ## 件D 的边界(成文,别读成「数据会被删」)
43
43
  *
44
- * 本仓**没有任何** store 声明 `retention:"managed"`(亲验:session/checkpoint/tool-result 三族 SQL 双生
45
- * 与 local 形都没有这一位),托管留存的执行面(调度/重试/副本协调/逐行审计)按 §5.2 是「server 半场四件
46
- * 皆新建」,不在本批。所以本装配点对件D 只做两件事:①把策略递给 core 的启动期校验
47
- * (`assertRetentionCapability` —— 锁着 + 店不能删 ⇒ 拒启,「策略锁着、数据永存」不许发生);②策略在场
48
- * 而没有任何 managed 店时打一条响亮 warn。**不**假装有执行面。
44
+ * 三只被托管的 SQL 店(session / checkpoint / tool-result)的 `retention` 声明读一个事实 —— 留存扫车道开不开(`plugins/retention-store-sql.ts` 的
45
+ * `retentionSweepRuns`):开 ⇒ `managed`,关(缺省)⇒ `none`;local 形的店恒声明 `none`。删除的执行面是扫车道(`boot/retention-lane.ts`)与 SQL 店上的
46
+ * 执行器,不在本装配点。所以本装配点对件D 只做两件事:①把策略与执行器递给 core 的启动期校验
47
+ * (`assertRetentionCapability` —— 配对由引擎一个判官判:店声明 `managed` 而部署不跑执行器、执行器不是执行器,锁不锁都拒;锁着 + 店不能删 ⇒ 拒启,
48
+ * 「策略锁着、数据永存」不许发生);②策略在场而没有任何 managed 店时打一条响亮 warn。**不**假装有执行面。
49
49
  */
50
- import { type LockedKey, type RunnerDeps } from "@sema-agent/core";
50
+ import { type LockedKey, type ManagedRetentionCapability, type RunnerDeps } from "@sema-agent/core";
51
51
  import type { ServiceConfig } from "../config.js";
52
52
  import type { Logger } from "../observability/logger.js";
53
53
  /** 装配产物:三个 core 座席 + 已校验的锁集(同步拒面 `assertRequestMcpUnlocked` 读它)。 */
@@ -72,18 +72,11 @@ export interface GovernanceSeamsCtx {
72
72
  store: object | undefined;
73
73
  }>;
74
74
  /**
75
- * [ref] 车1 —— **本 build 是否真的接了留存执行器**(sweep lane;车2 的 `startRetentionLane()`)。
76
- * 缺席 = `false`。
77
- *
78
- * 🔴 为什么这一位必须存在(codex 交叉复审 R2-[critical],验真后修):core 的
79
- * `assertRetentionCapability` 只读**店的声明位**。[ref] 车1 让三只 SQL 店如实声明了 `"managed"`
80
- * (它们的行确实由托管留存删),于是那道门从此**放行** locked policy —— 可执行器还在车2 手里。
81
- * 两者之间的窗口如果什么都不做,后果正是本仓最忌的那种:部署自述「策略已受管」、审计与能力面
82
- * 也这么说,而**没有任何东西在删数据**;车1 之前那条 `retention_policy_not_executed` 的响亮 warn
83
- * 还会因为"有 managed 店在场"而**静音**。
84
- * ⇒ 判据改成问「有没有东西真会删」,而不是「有没有店说自己被管」。车2 接上 lane 时把这一位传 true。
75
+ * 交给引擎启动检查的留存执行器 —— 本部署跑扫车道 ⇒ 那只执行器(`backend.retention`),不跑 ⇒ `null`(必填:漏递即编译红,
76
+ * 引擎对 `undefined` 也拒启)。「店说 managed」与「有执行器」的配对由引擎一个判官判(引擎 7.58.0:锁不锁都判);
77
+ * 店的词与这一格读同一个事实(`retention-store-sql.ts` 的 `retentionSweepRuns`),本仓不再另判一遍。
85
78
  */
86
- retentionExecutorWired?: boolean;
79
+ retentionExecutor: ManagedRetentionCapability | null;
87
80
  }
88
81
  /**
89
82
  * 件C 的装配相容性判据(纯,可单测)——见文件头「两条判据」。
@@ -49,30 +49,18 @@ export function createGovernanceSeams(ctx) {
49
49
  }
50
50
  const retentionPolicy = config.retentionPolicy;
51
51
  const locked = lockedKeys.has("retentionPolicy");
52
- assertRetentionCapability({ policy: retentionPolicy, locked, stores: retentionStores });
52
+ assertRetentionCapability({ policy: retentionPolicy, locked, stores: retentionStores, executor: ctx.retentionExecutor });
53
53
  if (retentionPolicy !== undefined) {
54
54
  const managed = retentionStores.filter((s) => s.store?.retention === "managed");
55
- const executorWired = ctx.retentionExecutorWired === true;
56
55
  if (managed.length === 0) {
57
56
  logger.warn("retention_policy_not_executed", {
58
57
  maxAgeDays: retentionPolicy.maxAgeDays,
59
- note: "RETENTION_MAX_AGE_DAYS is configured but NO wired store declares retention:\"managed\", and this build ships no " +
60
- "retention scheduler — the policy is a DECLARATION only; nothing deletes data on its account. It is relayed to the " +
61
- "engine for the startup capability check and will become effective when a managed-retention store + scheduler land.",
58
+ note: "RETENTION_MAX_AGE_DAYS is configured but NO wired store declares retention:\"managed\" (the retention sweep lane is off — " +
59
+ "RETENTION_SWEEP_INTERVAL_SEC unset or 0 — or the store medium has no sweep) — the policy is a DECLARATION only; nothing " +
60
+ "deletes data on its account. It is relayed to the engine for the startup capability check; run the sweep lane on a SQL " +
61
+ "backend to make it effective.",
62
62
  });
63
63
  }
64
- else if (!executorWired) {
65
- const detail = `retention policy is configured (maxAgeDays=${retentionPolicy.maxAgeDays}) and ${managed.length} wired store(s) declare ` +
66
- `retention:"managed", but THIS BUILD WIRES NO RETENTION EXECUTOR (the sweep lane) — nothing enumerates domains or calls ` +
67
- `the destructive store methods, so no data is deleted on the policy's account. A "managed" declaration is a statement ` +
68
- `about who OWNS deleting those rows, not proof that a scheduler is running.`;
69
- if (locked) {
70
- throw new Error(`${detail} Refusing to start with the retentionPolicy key LOCKED: a locked policy over a deployment that does not ` +
71
- `actually delete is exactly the "policy locked, data immortal" posture the engine's capability check exists to ` +
72
- `prevent. Unlock the key, remove the policy, or run a build whose retention sweep lane is wired.`);
73
- }
74
- logger.warn("retention_policy_not_executed", { maxAgeDays: retentionPolicy.maxAgeDays, note: detail });
75
- }
76
64
  }
77
65
  return { compliancePostureResolver, lockedConfig, retentionPolicy, lockedKeys };
78
66
  }
@@ -49,6 +49,9 @@ export interface LeaderCtx {
49
49
  * 唯一出口就是它(`runtask.js` 的 `catch (flushErr) => deps.onError`),所以它与 `usageWindowStore`
50
50
  * 席同生共死:挂窗不挂汇 = 治理轴上的静默失败面([ref])。必填键,理由同 `tracer`。 */
51
51
  onError: RunnerDeps["onError"];
52
+ /** 部署座 —— 起服第 5 段装配口 `assembleDeploymentMods` 交回的**同一份**(主车道各 Runner 经共享基座吃的就是它)。leader 车道的每一只 Runner 都要带:
53
+ * 缺席 = 运维声明的 mod 包管不到 leader 派出的腿。必填键,理由同 `tracer`。 */
54
+ deploymentMods: NonNullable<RunnerDeps["mods"]>;
52
55
  /** [ref] 车4 件1:durable store 后端(leader-run 登记表从这里取)。缺席 = 无 DB 的 env-only worker。 */
53
56
  backend?: StoreBackend | undefined;
54
57
  }
@@ -59,6 +59,7 @@ export function createLeaderFace(ctx) {
59
59
  : {}),
60
60
  ...(ctx.promptSource ? { promptSource: ctx.promptSource } : {}),
61
61
  ...(ctx.onError ? { onError: ctx.onError } : {}),
62
+ mods: ctx.deploymentMods,
62
63
  fanoutEnabled: config.leaderFanoutEnabled,
63
64
  ...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
64
65
  ? { routerModel: (process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID) }
@@ -1,5 +1,17 @@
1
1
  import { principalInboxConsumerOf } from "@sema-agent/core";
2
2
  import { createOrgDeliveryGate } from "../org-delivery-gate.js";
3
+ function declarationOf(consumer) {
4
+ switch (consumer.kind) {
5
+ case "assistant":
6
+ return { declared: "PRINCIPAL_INBOX_ASSISTANT declares a principal-inbox consumer", undeclare: "unset PRINCIPAL_INBOX_ASSISTANT" };
7
+ case "terminals":
8
+ return { declared: 'RunnerDeps.principalInboxConsumer declares "terminals" (the scope\'s terminal sessions drain the principal inbox)', undeclare: "drop the RunnerDeps.principalInboxConsumer declaration" };
9
+ default: {
10
+ const unknown = consumer;
11
+ throw new Error(`unknown principal-inbox consumer kind: ${JSON.stringify(unknown)}`);
12
+ }
13
+ }
14
+ }
3
15
  export function mountOrgDeliveryGate(input) {
4
16
  const consumer = principalInboxConsumerOf(input.runnerDeps);
5
17
  if (consumer.kind === "none")
@@ -11,8 +23,9 @@ export function mountOrgDeliveryGate(input) {
11
23
  ...(directory === undefined ? ["an organization membership directory (a web-admin center, or MEMORY_ORG_DIRECTORY_JSON)"] : []),
12
24
  ...(backend === undefined ? ["a store backend for the delivery quota (DB_BACKEND=mysql|pg|local)"] : []),
13
25
  ];
14
- throw new Error(`PRINCIPAL_INBOX_ASSISTANT declares a principal-inbox consumer, so this deployment must mount the organization delivery gate — but it lacks ${missing.join("; ")}. ` +
15
- `Refusing to boot rather than run with a declared inbox that nothing may legitimately write to; provide the missing pieces or unset PRINCIPAL_INBOX_ASSISTANT.`);
26
+ const { declared, undeclare } = declarationOf(consumer);
27
+ throw new Error(`${declared}, so this deployment must mount the organization delivery gate — but it lacks ${missing.join("; ")}. ` +
28
+ `Refusing to boot rather than run with a declared inbox that nothing may legitimately write to; provide the missing pieces or ${undeclare}.`);
16
29
  }
17
30
  input.logger.info("org_delivery_gate_mounted", { consumer: consumer.kind });
18
31
  return createOrgDeliveryGate({ directory, mailbox: mailboxStore, quota: backend.orgDeliveryQuota(), consumer });
@@ -18,6 +18,7 @@ import { type TaskAttachmentStore } from "../plugins/task-attachment-store.js";
18
18
  import { type createPrincipalEntitlementsClient } from "../runtime-caps-resolver.js";
19
19
  import { type OwnerAwareSessionStore } from "../security.js";
20
20
  import type { MemorySyncRunner } from "../memory-sync-client.js";
21
+ import type { PeerDesk } from "../peer-seat.js";
21
22
  import type { RunnerDepsOnAsk } from "./runner-deps.js";
22
23
  type PrincipalCaps = ReturnType<typeof createPrincipalEntitlementsClient>;
23
24
  /** `resolveSpec` 原先从 `main()` 闭包里拿到的全部 boot 局部量。 */
@@ -84,6 +85,8 @@ export interface ResolveSpecCtx {
84
85
  /** [ref] 件C:本部署已校验的锁集(boot/governance-seams.ts 的产物,与 `RunnerDeps.lockedConfig`
85
86
  * 同源一次解析)。空集 = 无锁 = 现行为。本域只用它做**同步拒面**(§4.3),不做第二次裁决。 */
86
87
  lockedKeys: ReadonlySet<import("@sema-agent/core").LockedKey>;
88
+ /** 本进程的对端座 desk(stage-05 建、HTTP 服务的 `peerDesk` 同一只):请求面 MCP / A2A 的胜出草稿在 `opts.lease` 下经它登记,换回声明带的引用 —— 两只漏斗开的租约属于它。 */
89
+ peerDesk: PeerDesk;
87
90
  /** 本部署**装载簿**(stage-07 的出厂技能 + 配置源覆盖之后的终表,场景工厂读的同一份)。本装配器只拿它答「场景交来的这只技能来自哪里」
88
91
  * (run 名册 `wiring_manifest.skills[].source`,`buildRunSkills`);名字在装载簿里唯一(装载器同名即抛、配置源按名覆盖)。 */
89
92
  loadedSkills: readonly LoadedSkill[];
@@ -29,6 +29,7 @@ import { gateExecutionLane } from "../runtime-caps-resolver.js";
29
29
  import { applyRuntimeGovernance } from "../runtime-governance.js";
30
30
  import { HttpError, createCodedHttpError, encodeCheckpointScope } from "../security.js";
31
31
  import { headReadOf, sessionFaceOfHead } from "../session-incarnation.js";
32
+ import { legProjectIdOf, sessionWorkspaceStoreOf, stampLegSessionWitness } from "../session-workspace.js";
32
33
  import { memorySpecForRequest } from "../memory-scope.js";
33
34
  import { createMemoryProjectLayers } from "../memory-project-layer.js";
34
35
  import { inheritEnvFacts, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
@@ -40,6 +41,7 @@ import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
40
41
  import { assertRequestA2aEntries, resolveRequestA2a, snapshotRequestA2a } from "../task-a2a.js";
41
42
  import { assertRequestImageUrls } from "../request-image-urls.js";
42
43
  import { assertRequestMcpEntries, assertRequestMcpUnlocked, resolveRequestMcp } from "../task-mcp.js";
44
+ import { GOVERNANCE_HTTP_STATUS } from "../governance-http-status.js";
43
45
  import { storedToolAxesRereadM19 } from "../stored-request-tool-axes-m19.js";
44
46
  import { storedHooksRereadM20 } from "../stored-request-hooks-m20.js";
45
47
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, REQUEST_SETTINGS_REFUSAL_PREFIX, SETTINGS_RULE_REFUSAL_CODE, SettingsRuleRefusalError, acceptAppendSystemPrompt, applyTaskSettings, createWorkflowAskPolicy, effectivePermissionMode, effectiveThinking, parseTaskSettings, taskHooksHonored, settingsPermissionBucketIssue } from "../task-settings.js";
@@ -54,10 +56,11 @@ import { effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
54
56
  import { withUnattendedLeg } from "../unattended-permission-mode.js";
55
57
  import { fieldConflictFacts } from "../field-conflict.js";
56
58
  export function createResolveSpec(ctx) {
57
- const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, memorySyncRunner, durableEnabled, approvalExemptionStore, checkpointStore, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, loadedSkills, } = ctx;
59
+ const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, memorySyncRunner, durableEnabled, approvalExemptionStore, checkpointStore, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, loadedSkills, peerDesk, } = ctx;
58
60
  const deploymentSkillSources = new Map(loadedSkills.map((k) => [k.spec.name, k.source]));
59
61
  const scenarioGate = createScenarioGate({ config, scenarios, principalCaps, logger }, "enforce");
60
62
  const sessionHeadOf = headReadOf(ownerAware);
63
+ const sessionWorkspace = sessionWorkspaceStoreOf(ownerAware);
61
64
  assertGuardPatternsUsable(config);
62
65
  const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, {
63
66
  approvalSeat: durableEnabled ? "durable" : "absent",
@@ -80,6 +83,13 @@ export function createResolveSpec(ctx) {
80
83
  if (seatRefusal !== undefined)
81
84
  throw createCodedHttpError(501, "capability.checkpoint_store_required", seatRefusal);
82
85
  assertRequestMcpUnlocked(body.mcpServers, lockedKeys);
86
+ const storedToolAxes = opts?.leg === "fresh" ? undefined : storedToolAxesRereadM19(new Date().toISOString().slice(0, 10));
87
+ const a2aPeers = snapshotRequestA2a(body.a2aPeers, { reread: storedToolAxes !== undefined });
88
+ if (lockedKeys.has("a2a") && a2aPeers !== undefined && a2aPeers !== null) {
89
+ throw new HttpError(GOVERNANCE_HTTP_STATUS["config.locked_key"], "`a2aPeers` is administratively locked by this deployment (locked key \"a2a\") — task-supplied A2A peers are refused; locks are config-time and there is no request-time unlock", {
90
+ code: "config.locked_key",
91
+ });
92
+ }
83
93
  if (opts?.leg === "fresh")
84
94
  assertRequestMcpEntries(body.mcpServers);
85
95
  if (opts?.leg === "fresh") {
@@ -142,8 +152,6 @@ export function createResolveSpec(ctx) {
142
152
  }
143
153
  }
144
154
  }
145
- const storedToolAxes = opts?.leg === "fresh" ? undefined : storedToolAxesRereadM19(new Date().toISOString().slice(0, 10));
146
- const a2aPeers = snapshotRequestA2a(body.a2aPeers, { reread: storedToolAxes !== undefined });
147
155
  if (opts?.leg === "fresh")
148
156
  assertRequestA2aEntries(a2aPeers);
149
157
  if (opts?.leg === "fresh")
@@ -167,12 +175,13 @@ export function createResolveSpec(ctx) {
167
175
  hostSemanticsLane: folded.hostSemanticsLane,
168
176
  });
169
177
  const stamped = stampHandsLane(final, gated.hands);
170
- return stampRunSkills(stamped, runSkills.roster);
178
+ return stampLegSessionWitness(stampRunSkills(stamped, runSkills.roster), gated.sessionWitness);
171
179
  };
172
180
  const gateScenarioAndAppend = async (body, auth, opts) => {
173
181
  const scenarioName = await scenarioGate(auth?.principal, body.scenario);
174
182
  gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), executionLaneOf(config.remoteExec?.provider), logger);
175
- const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal, auth?.sessionId !== undefined ? { sessionId: auth.sessionId, delegationRoot: { sessionId: auth.sessionId, incarnation: (sessionHeadOf === undefined ? undefined : sessionFaceOfHead(auth.sessionId, await sessionHeadOf(auth.sessionId)))?.incarnation ?? null } } : {});
183
+ const sessionWitness = auth?.sessionId !== undefined ? { sessionId: auth.sessionId, incarnation: (sessionHeadOf === undefined ? undefined : sessionFaceOfHead(auth.sessionId, await sessionHeadOf(auth.sessionId)))?.incarnation ?? null } : undefined;
184
+ const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal, sessionWitness !== undefined ? { sessionId: sessionWitness.sessionId, delegationRoot: sessionWitness } : {});
176
185
  const acceptedAppend = acceptAppendSystemPrompt(body.appendSystemPrompt, (detail) => logger.warn("append_system_prompt_dropped", { detail, sessionId: auth?.sessionId ?? null }));
177
186
  const objective = typeof body.objective === "string" ? body.objective : "";
178
187
  const parsedSettings = parseTaskSettings(body.settings, opts?.leg === "fresh" || !taskHooksHonored(config) ? {} : { storedHooksReread: storedHooksRereadM20(new Date().toISOString().slice(0, 10)) });
@@ -222,7 +231,7 @@ export function createResolveSpec(ctx) {
222
231
  }
223
232
  }
224
233
  }
225
- return { scenarioName, cap, hands: cap.hands, acceptedAppend, objective, parsedSettings, attachmentNotice };
234
+ return { scenarioName, cap, hands: cap.hands, acceptedAppend, objective, parsedSettings, attachmentNotice, sessionWitness };
226
235
  };
227
236
  const bindSettingsCwdEnvAndModel = (body, auth, gated, opts) => {
228
237
  const { objective, parsedSettings } = gated;
@@ -339,9 +348,10 @@ export function createResolveSpec(ctx) {
339
348
  if (rewindFilesToEntryId === undefined)
340
349
  throw new HttpError(404, `rewindFilesTo: no such message in this session (rewind_files_to.unknown_event) — ${NO_ANCHOR_HINT}; name another prompt`, { code: "rewind_files_to.unknown_event" });
341
350
  }
342
- const s4ProjectId = auth?.resolvedProjectId ?? (typeof body.projectId === "string" && body.projectId ? body.projectId.toLowerCase() : undefined);
343
- const s4DefaultScopes = s4ProjectId ? config.projects[s4ProjectId]?.defaultScopes : undefined;
344
- return { resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes };
351
+ const submittedProjectId = auth?.resolvedProjectId ?? (typeof body.projectId === "string" && body.projectId ? body.projectId.toLowerCase() : undefined);
352
+ const s4DefaultScopes = submittedProjectId ? config.projects[submittedProjectId]?.defaultScopes : undefined;
353
+ const legProjectId = await legProjectIdOf(submittedProjectId, sessionWorkspace, auth?.sessionId);
354
+ return { resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, legProjectId };
345
355
  };
346
356
  const declaresInteractivePosture = (body, req, opts) => {
347
357
  if (opts?.liveStream !== true)
@@ -358,7 +368,7 @@ export function createResolveSpec(ctx) {
358
368
  return deriveAskEffective(askForm.form, "unresolved") === "human_reachable";
359
369
  };
360
370
  const assembleSpecLiteral = (body, auth, req, opts, parts, effMode) => {
361
- const { scenarioName, cap, hands, acceptedAppend, parsedSettings, attachmentNotice, taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked, resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, projectKey, a2aPeers, storedToolAxes, runSkills, } = parts;
371
+ const { scenarioName, cap, hands, acceptedAppend, parsedSettings, attachmentNotice, taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked, resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, legProjectId, projectKey, a2aPeers, storedToolAxes, runSkills, } = parts;
362
372
  const spec = {
363
373
  objective: attachmentNotice ? `${picked.cleanedObjective}\n\n${attachmentNotice}` : picked.cleanedObjective,
364
374
  ...buildPromptSeats(scenarioName, cap.prompt, typeof body.systemPrompt === "string" ? body.systemPrompt : undefined),
@@ -366,6 +376,7 @@ export function createResolveSpec(ctx) {
366
376
  ...(body.outputStyle !== undefined ? { outputStyle: body.outputStyle } : {}),
367
377
  ...(body.projectMemory !== undefined ? { projectMemory: body.projectMemory } : {}),
368
378
  sessionId: auth?.sessionId,
379
+ ...(legProjectId !== undefined ? { projectId: legProjectId } : {}),
369
380
  principal: auth?.principal,
370
381
  images: body.images,
371
382
  ...(body.userAttachments !== undefined ? { userAttachments: body.userAttachments } : {}),
@@ -491,8 +502,8 @@ export function createResolveSpec(ctx) {
491
502
  ...(() => { const v = toolNameListFromBody(body.userInvokedSkills); return v ? { userInvokedSkills: v } : {}; })(),
492
503
  ...(parsedSettings.settings?.skillListingBudgetFraction !== undefined ? { skillListingBudgetFraction: parsedSettings.settings.skillListingBudgetFraction } : {}),
493
504
  ...(parsedSettings.settings?.skillListingMaxDescChars !== undefined ? { skillListingMaxDescChars: parsedSettings.settings.skillListingMaxDescChars } : {}),
494
- mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger, opts?.onNotice !== undefined ? { sessionId: auth?.sessionId, emit: opts.onNotice } : undefined, storedToolAxes),
495
- a2a: resolveRequestA2a(a2aForScenario(config.a2aPeers, scenarioName), a2aPeers, config, logger, opts?.leg === "fresh" ? "fresh" : "resume", storedToolAxes),
505
+ mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, peerDesk, opts.lease, logger, opts?.onNotice !== undefined ? { sessionId: auth?.sessionId, emit: opts.onNotice } : undefined, storedToolAxes),
506
+ a2a: resolveRequestA2a(a2aForScenario(config.a2aPeers, scenarioName), a2aPeers, config, peerDesk, opts.lease, logger, opts?.leg === "fresh" ? "fresh" : "resume", storedToolAxes),
496
507
  toolPolicy: toolPolicyForHands(hands, durableEnabled
497
508
  ? createApprovalBaselinePolicy(config, {
498
509
  kind: "durable",
@@ -155,15 +155,21 @@ export interface RetentionSweepOutcome {
155
155
  /**
156
156
  * lane **自持的** boot 不变式(设计稿 §7,codex F3)—— 与 core 的 `assertRetentionCapability` 是两道门,
157
157
  * 各答各的问题:
158
- * · core 那道问的是「**锁着**的策略会不会盖在一只删不了的店上」——只在 locked 时说话;
159
- * · **本道**问的是「lane 开着的时候,它真的能删吗」——**不看 locked 状态**。
158
+ * · core 那道判**配对**,锁不锁都判(`executor` 必给):声明 `managed` 的店 × 没有执行器(`null`)⇒ 拒;执行器四个契约方法
159
+ * (`listRetentionDomains` / `expireCheckpoints` / `deleteExpiredSessions` / `deleteOrphanToolResults`)缺一 ⇒ 拒;
160
+ * **锁着**的策略盖在声明 `none` 的店上 ⇒ 拒。它不问 lane 开没开、策略在不在,也不拦没锁时「执行器在场 × 店声明 `none`」(部署可以持有执行器而不在某只店上跑);
161
+ * · **本道**问的是「lane 开着的时候,它真的能删吗」——**不看 locked 状态**:策略在场、执行器在场、lane 调的**五**个方法都在
162
+ * (多出的 `previewRetention` 是 audit-only 档的读数口,不在 core 的四方法契约里,引擎的检查覆盖不到它)、每只被 lane 消费的店都声明 `managed`。
163
+ * 「执行器在场」与「契约方法齐」两格和引擎的判官说的是同一件事(各自一句拒句),其余几格是引擎不问的。
160
164
  *
161
- * 为什么后者必须存在:core 的门在 `locked=false` 时整条放行,于是
165
+ * 为什么本道必须存在:引擎的门不拦
162
166
  * ① `RETENTION_SWEEP_INTERVAL_SEC>0` 而 policy 缺席 = 没有有效 cutoff 的 sweep(每 tick 什么都算不出来,
163
167
  * 读数一路 0,看起来像"没有候选");
164
- * ② unlocked policy × 一只 `"none"` 的店照样过检 —— 部署于是**广告了一个不执行的留存面**:能力位说
168
+ * ② 没锁时,执行器在场 × 一只 `"none"` 的店 —— 部署于是**广告了一个不执行的留存面**:能力位说
165
169
  * `{mode,maxAgeDays}`、审计表也在长,而那只店的行谁都没在删。
166
- * 两者都在**启动期**拒(安全控件不得半开),不是让运维从一台"启动成功、什么也没删"的机器上猜。
170
+ * 本仓的 SQL 后端按同一个事实(扫车道开不开,`plugins/retention-store-sql.ts` 的 `retentionSweepRuns`)给它铸的三只店声明词,所以 lane 开着时
171
+ * 这三只店在这一格上恒是 `managed`;这一格防的是声明与 lane 脱钩的店(别的后端、替身)。
172
+ * 这些都在**启动期**拒(安全控件不得半开),不是让运维从一台"启动成功、什么也没删"的机器上猜。
167
173
  *
168
174
  * @param locked 只进文案(让拒启信息能说清"你锁没锁都一样拒");**判据本身不读它** —— 这一格的存在
169
175
  * 就是为了让"不看 locked"这句话在签名上可见,而不是靠注释声明。
@@ -2,6 +2,7 @@ import { recordFailOpen } from "../observability/fail-open.js";
2
2
  export const RETENTION_SWEEP_STUCK_THRESHOLD = 5;
3
3
  export const RETENTION_LEASE_TTL_FACTOR = 2;
4
4
  const DAY_MS = 86_400_000;
5
+ const LANE_EXECUTOR_METHODS = ["listRetentionDomains", "previewRetention", "expireCheckpoints", "deleteExpiredSessions", "deleteOrphanToolResults"];
5
6
  export function assertRetentionLaneWirable(input) {
6
7
  if (input.intervalSec <= 0)
7
8
  return;
@@ -18,8 +19,7 @@ export function assertRetentionLaneWirable(input) {
18
19
  `declares retention:"none" and nothing would delete a row${lockNote}. Run a SQL backend, or set RETENTION_SWEEP_INTERVAL_SEC=0.`);
19
20
  }
20
21
  const executor = input.executor;
21
- const CONTRACT_METHODS = ["listRetentionDomains", "previewRetention", "expireCheckpoints", "deleteExpiredSessions", "deleteOrphanToolResults"];
22
- for (const m of CONTRACT_METHODS) {
22
+ for (const m of LANE_EXECUTOR_METHODS) {
23
23
  if (typeof executor[m] !== "function") {
24
24
  throw new Error(`the wired retention executor is missing \`${m}\` — a store that declares retention:"managed" PROMISES the whole ` +
25
25
  `ManagedRetentionCapability contract, and the sweep lane calls every method of it${lockNote}. Refusing to start with a ` +
@@ -29,6 +29,7 @@ import type { ToolApprovalCoordinator } from "../tool-approval.js";
29
29
  import type { MemoryBackend } from "@sema-agent/core";
30
30
  import type { MemorySyncRunner } from "../memory-sync-client.js";
31
31
  import type { MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
32
+ import type { PeerDesk } from "../peer-seat.js";
32
33
  /**
33
34
  * `RunnerDeps.onAsk` 的装配([ref] 件4:从内联闭包提为具名导出,**纯 testability 重构、零行为变化**)。
34
35
  *
@@ -163,6 +164,8 @@ export interface RunnerDepsCtx {
163
164
  /** core 7.49.0 [ref]:引擎数据根(boot 事实,`boot/stores.ts` 恒铸 —— 记忆引擎开不开都在)。共享基座把它交成每只 Runner 的
164
165
  * `memoryEngineDir`:引擎从它解出每条腿的唯一豁免根(守卫集不再收 `dataRoot`)。开引擎时与 `memoryEngine.root` 同一只。 */
165
166
  engineDataRoot: string;
167
+ /** 对端座的 desk(进程一只,`peer-seat.ts`):共享基座把它的座交成每只 Runner 的 `peerSeat` —— MCP / A2A 声明里的引用只经它在发送时作答。 */
168
+ peerDesk: PeerDesk;
166
169
  /**
167
170
  * [ref](core 7.21.1)—— 记忆整理的**已解析座**(`boot/memory-consolidation.ts` 的
168
171
  * `resolveConsolidationDriverSeat` 产物),在场 **iff** 整理阀门开着(`resolveConsolidationValve` 的判决:
@@ -228,7 +231,8 @@ export interface RunnerDepsCtx {
228
231
  executionEnvFactory: RunnerDeps["executionEnvFactory"];
229
232
  lspManager: RunnerDeps["lspManager"];
230
233
  fleetBus: FleetEventBus;
231
- /** 部署座的 mods 注册(stage-05 `createDeploymentMods` 铸、起服自证过)—— 共享基座 ⇒ 主 / handless / hookAgent / sub 每一只 Runner 同一份。 */
234
+ /** 部署座的 mods 注册(起服第 5 段装配口 `assembleDeploymentMods` 铸:自铸 + 运维声明的包,起服自证过)—— 主 / handless / hookAgent / sub 经本共享基座、
235
+ * leader 车道的路由 / worker / 修复腿经 `LeaderWireConfig.mods`(`boot/leader.ts` 递同一份),每一只 Runner 同一份。 */
232
236
  deploymentMods: NonNullable<RunnerDeps["mods"]>;
233
237
  workflowRunStore: WorkflowRunStore | undefined;
234
238
  workflowJournalStore: ServiceWorkflowJournalStore | undefined;
@@ -241,7 +245,7 @@ export interface RunnerDepsCtx {
241
245
  }
242
246
  /** [ref] A10 留档发现②:main runner `RunnerDeps` 与 main.ts subRunner 字面量之间此前手工重复
243
247
  * 的 ~15 个键,类型标注见 {@link createSharedRunnerDeps} 头注。 */
244
- export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "memoryEngineDir" | "allowedTools" | "crossBoundaryPosture" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "mods" | "toolResultStore" | "checkpointStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice" | "mcpRevocations">;
248
+ export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "memoryEngineDir" | "allowedTools" | "crossBoundaryPosture" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "mods" | "toolResultStore" | "checkpointStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice" | "mcpRevocations" | "peerSeat">;
245
249
  /**
246
250
  * [ref] A10 留档发现②(review 2026-07-29,[ref]§三族A 同源修补的延续):main runner 的
247
251
  * `RunnerDeps` 字面量(下方 `createRunnerDeps`)与 `main.ts` 里 subRunner 的 `new Runner({...})`
@@ -268,7 +272,7 @@ export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDeny
268
272
  * 属性,比再抽一层共享基座更强的同源保证),不重复收纳进这里。
269
273
  */
270
274
  /** 基座真实消费的窄面(Pick)——subRunner 调用点(main.ts)只需凑这 13 个字段,不必造全量 ctx。 */
271
- export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "deploymentMods" | "toolResultStore" | "checkpointStore" | "sessionPolicyStore" | "hands" | "usageWindowStore" | "orgMemoryAdmission" | "sharedMemoryStores" | "logger" | "governanceSeams" | "mcpRevocations" | "metrics" | "engineDataRoot">;
275
+ export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "deploymentMods" | "toolResultStore" | "checkpointStore" | "sessionPolicyStore" | "hands" | "usageWindowStore" | "orgMemoryAdmission" | "sharedMemoryStores" | "logger" | "governanceSeams" | "mcpRevocations" | "metrics" | "engineDataRoot" | "peerDesk">;
272
276
  /** 转发席只需要 `inc` —— 刻意窄到这一个动词,免得把整只 Metrics 拖进这条纯转发腿。 */
273
277
  export interface EngineNoticeMetricsSeat {
274
278
  inc: (name: string, labels?: Record<string, string>) => void;
@@ -131,6 +131,7 @@ export function createSharedRunnerDeps(ctx) {
131
131
  lockedConfig: ctx.governanceSeams.lockedConfig,
132
132
  retentionPolicy: ctx.governanceSeams.retentionPolicy,
133
133
  mcpRevocations: ctx.mcpRevocations ? ctx.mcpRevocations : undefined,
134
+ peerSeat: ctx.peerDesk.seat.answer,
134
135
  };
135
136
  return shared;
136
137
  }
@@ -27,18 +27,7 @@ export declare function runStage04(ctx: Awaited<ReturnType<typeof runStage03>>):
27
27
  ruleConsent: import("../rules-consent.js").RuleConsentLane | undefined;
28
28
  sessionMemoryStatusFace: import("../memory-operator-faces.js").SessionMemoryStatusFace | undefined;
29
29
  sessionPolicyStore: import("@sema-agent/core").SessionPolicyStore | undefined;
30
- sideQueryAccounting: (principal: string | undefined, r: {
31
- model: string;
32
- usage?: {
33
- input?: number;
34
- output?: number;
35
- cacheRead?: number;
36
- cacheWrite?: number;
37
- cost?: {
38
- total?: number;
39
- };
40
- };
41
- }) => void;
30
+ sideQueryAccounting: import("../budget.js").SideQueryAccounting;
42
31
  toolResultStore: import("../plugins/store-backend.js").ToolResultStoreFull | undefined;
43
32
  tracer: import("@sema-agent/core").TracerHook;
44
33
  backend: import("../index.js").StoreBackend | undefined;