@sema-agent/server 7.5.0 → 7.7.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 (104) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +18 -3
  3. package/README.zh-CN.md +14 -3
  4. package/USAGE.md +100 -2
  5. package/dist/approval-reconciler.d.ts +1 -1
  6. package/dist/approval-reconciler.js +6 -5
  7. package/dist/boot/budget-tracing.js +1 -1
  8. package/dist/boot/config-center.js +17 -3
  9. package/dist/boot/execution-env.js +1 -1
  10. package/dist/boot/parked-revive-gate.d.ts +78 -0
  11. package/dist/boot/parked-revive-gate.js +114 -0
  12. package/dist/boot/resolve-spec.d.ts +8 -20
  13. package/dist/boot/resolve-spec.js +40 -125
  14. package/dist/budget.d.ts +1 -1
  15. package/dist/budget.js +1 -1
  16. package/dist/capabilities/hands-lane.d.ts +91 -0
  17. package/dist/capabilities/hands-lane.js +104 -0
  18. package/dist/capabilities/repo-tools.d.ts +1 -1
  19. package/dist/capabilities/repo-tools.js +8 -2
  20. package/dist/capabilities/scenarios.d.ts +49 -2
  21. package/dist/capabilities/scenarios.js +84 -16
  22. package/dist/config-provider.d.ts +1 -0
  23. package/dist/config-provider.js +23 -3
  24. package/dist/config-types.d.ts +5 -3
  25. package/dist/config.js +5 -4
  26. package/dist/deployment-governance.d.ts +168 -0
  27. package/dist/deployment-governance.js +206 -0
  28. package/dist/fleet/fleet-bus.d.ts +11 -1
  29. package/dist/fleet/fleet-bus.js +43 -0
  30. package/dist/fleet/fleet-terminal-window.d.ts +98 -0
  31. package/dist/fleet/fleet-terminal-window.js +319 -0
  32. package/dist/hooks/hook-runner.js +11 -0
  33. package/dist/http/route-ctx.d.ts +7 -1
  34. package/dist/http/routes/approvals-assistant.js +6 -7
  35. package/dist/http/routes/fleet.js +164 -14
  36. package/dist/http/routes/runs.js +7 -2
  37. package/dist/http/routes/tasks.js +5 -4
  38. package/dist/http/routes/trace-usage.js +3 -4
  39. package/dist/http/send.d.ts +23 -0
  40. package/dist/http/send.js +23 -0
  41. package/dist/http/server.d.ts +10 -0
  42. package/dist/http/server.js +28 -12
  43. package/dist/http/sse-log.js +3 -4
  44. package/dist/leader/diffout.d.ts +10 -0
  45. package/dist/leader/diffout.js +14 -2
  46. package/dist/leader/diffup.js +3 -2
  47. package/dist/leader/planner.js +7 -0
  48. package/dist/main.js +79 -32
  49. package/dist/observability/fail-open.d.ts +4 -0
  50. package/dist/observability/fail-open.js +4 -0
  51. package/dist/observability/tool-trace.d.ts +13 -0
  52. package/dist/observability/tool-trace.js +14 -0
  53. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  54. package/dist/orchestration/workflow-notify-journal.js +14 -34
  55. package/dist/plugins/approval-ask-store-sql.d.ts +33 -0
  56. package/dist/plugins/approval-ask-store-sql.js +66 -33
  57. package/dist/plugins/background-agent-store-sql.js +16 -16
  58. package/dist/plugins/breaker-state-sql.js +2 -2
  59. package/dist/plugins/checkpoint-store-sql.d.ts +5 -2
  60. package/dist/plugins/checkpoint-store-sql.js +5 -2
  61. package/dist/plugins/image-bake-store-sql.d.ts +1 -1
  62. package/dist/plugins/image-bake-store-sql.js +27 -27
  63. package/dist/plugins/image-index-sql.js +15 -15
  64. package/dist/plugins/mailbox-store-sql.js +3 -3
  65. package/dist/plugins/memory-engine-pg.js +9 -9
  66. package/dist/plugins/memory-engine-tidb.js +7 -7
  67. package/dist/plugins/memory-sync-store-pg.js +13 -13
  68. package/dist/plugins/memory-sync-store-tidb.js +5 -5
  69. package/dist/plugins/outcome-ledger-sql.js +7 -7
  70. package/dist/plugins/pg-cost-quota.js +3 -3
  71. package/dist/plugins/pg-pool.js +84 -75
  72. package/dist/plugins/pg-rate-limiter.js +3 -3
  73. package/dist/plugins/pg-session-storage.d.ts +1 -1
  74. package/dist/plugins/pg-session-storage.js +12 -13
  75. package/dist/plugins/remote-env-host.js +3 -1
  76. package/dist/plugins/remote-env-local-docker.js +6 -3
  77. package/dist/plugins/remote-env-ssh.d.ts +13 -1
  78. package/dist/plugins/roster-store-sql.js +8 -8
  79. package/dist/plugins/store-contracts.d.ts +19 -0
  80. package/dist/plugins/store-contracts.js +42 -0
  81. package/dist/plugins/task-attachment-store.js +5 -5
  82. package/dist/plugins/task-list-store-sql.js +1 -1
  83. package/dist/plugins/tidb-cost-quota.js +1 -1
  84. package/dist/plugins/tidb-pool.js +83 -60
  85. package/dist/plugins/tidb-rate-limiter.js +1 -1
  86. package/dist/plugins/tidb-session-store.js +2 -5
  87. package/dist/plugins/tool-result-store-sql.js +2 -2
  88. package/dist/plugins/usage-window-store-sql.js +13 -13
  89. package/dist/plugins/write-behind-counter.d.ts +10 -2
  90. package/dist/plugins/write-behind-counter.js +13 -3
  91. package/dist/resource-suspend.d.ts +3 -1
  92. package/dist/resource-suspend.js +3 -1
  93. package/dist/run-local.d.ts +73 -1
  94. package/dist/run-local.js +160 -9
  95. package/dist/runs.d.ts +3 -1
  96. package/dist/runs.js +3 -1
  97. package/dist/security.d.ts +12 -0
  98. package/dist/security.js +12 -0
  99. package/dist/session-sync-kernel.d.ts +13 -0
  100. package/dist/session-sync-kernel.js +13 -0
  101. package/dist/trace/core-keyset-guard.d.ts +1 -1
  102. package/dist/trace/project.d.ts +10 -1
  103. package/dist/trace/project.js +31 -0
  104. package/package.json +3 -3
@@ -7,6 +7,7 @@ import { stablePrompt } from "./prompt.js";
7
7
  import { createTeamTool, teamCoordinatorPrompt, getTeam, teamNames } from "./team.js";
8
8
  import { createWebSearchBackend, webSearchConfigFromSettings } from "../plugins/web-search.js";
9
9
  import { skillsForScenario } from "./skills.js";
10
+ import { pickHandsRunner } from "./hands-lane.js";
10
11
  /**
11
12
  * Sema product identity, prepended to the core default base for the `default` scenario when
12
13
  * {@link ScenarioDeps.brandIdentity} is on. Honest about the substrate (the engine runs whichever model
@@ -88,6 +89,11 @@ export function codePromptProvider(brand) {
88
89
  // [2400] CAPS-OPS-11:autonomousPromptProvider 别名导出已随场景退役删除(真身=codePromptProvider)。
89
90
  // 其 `@deprecated` JSDoc 曾残留于此,被 tsc 误挂到下方 buildScenarios(三处调用点全告 deprecated
90
91
  // ——TODO-3 立案的「迁移」实为孤儿注释,随删除清账;buildScenarios 本身非退役面)。
92
+ /** #196 唯一取用口:场景按自己的判别位取子任务 Runner。场景体内一律不直接摸 `deps.subRunner` /
93
+ * `deps.handslessSubRunner` —— 两处各挑一次就是漂移的成因,判别位必须是唯一开关。 */
94
+ function subRunnerFor(deps, hands) {
95
+ return pickHandsRunner(hands, { full: deps.subRunner, handsless: deps.handslessSubRunner });
96
+ }
91
97
  export function buildScenarios(deps) {
92
98
  // The default scenario filters skills by "default" — NOT by req.scenario (which may be an unknown name
93
99
  // that fell back here): the bundle must be a coherent "default" set, matching how main.ts resolves an
@@ -137,7 +143,9 @@ export function buildScenarios(deps) {
137
143
  // core 1.382 checkpointStore/ensureChildSessionDurable = design/153 parked 状态机的「同车必接」
138
144
  // 一对([1561] 提货单①②,ScenarioDeps 字段注释详述);两者任一缺席 = 净是 pre-153 行为。
139
145
  subagent: {
140
- runner: deps.subRunner,
146
+ // #196:default/code 的委派子代**要**手(CC parity;main.ts subRunner 注里那条 "subagent
147
+ // couldn't run pwd" 缺陷就是这条腿缺手),故按本场景判别位取 full 支。
148
+ runner: subRunnerFor(deps, "full"),
141
149
  background: {
142
150
  registry: defaultTaskRegistry,
143
151
  owner: "unattributed",
@@ -158,6 +166,9 @@ export function buildScenarios(deps) {
158
166
  ...(webSearch ? { webSearch } : {}),
159
167
  }).filter((t) => t.name !== "WebFetch" || deps.requirePrincipal !== true),
160
168
  ],
169
+ // default/code 是手带并集的**故意消费者**(上方 rank-1 BLOCKER 注:CC agent loop MUST have the
170
+ // full-body roster)——#196 判别位在这里表态 full,收窄只落在声明了 none 的场景上。
171
+ hands: "full",
161
172
  skills: skillsForScenario(deps.skills, "default"),
162
173
  // Local/TOC deployment brands the neutral default scenario as Sema (so the local engine answers "who
163
174
  // are you" as Sema, not the model's hallucinated identity). Cloud leaves it neutral unless opted in.
@@ -190,13 +201,15 @@ export function buildScenarios(deps) {
190
201
  // would let runtime-distributed content rewrite the deployment-pinned system prompt and break the
191
202
  // stablePrompt STABLE→VARIABLE prefix-cache discipline (design/12/17).
192
203
  scan: (req) => {
193
- if (!deps.repoClient)
194
- throw new HttpError(501, "scan scenario needs GIT_API_BASEURL configured");
204
+ const repoClient = requireRepoClient(deps, "scan");
195
205
  if (typeof req.repo !== "string" || !req.repo.trim()) {
196
206
  throw new HttpError(400, "scan scenario requires a 'repo' field (owner/name or repo URL)");
197
207
  }
198
208
  return {
199
- tools: [...repoToolsFor(deps.repoClient, parseRepo(req.repo)), nowTool()],
209
+ tools: [...repoToolsFor(repoClient, parseRepo(req.repo)), nowTool()],
210
+ // #196:头注的「工具集即边界」自此在**最终 roster** 上成立(修前 core 把手带 band 并集进来,
211
+ // clone-free 承诺只存在于这份声明里)。
212
+ hands: "none",
200
213
  skills: skillsForScenario(deps.skills, "scan"),
201
214
  };
202
215
  },
@@ -212,8 +225,11 @@ export function buildScenarios(deps) {
212
225
  }
213
226
  const objective = typeof req.objective === "string" ? req.objective : "Discuss the task.";
214
227
  const businessContext = typeof req.businessContext === "string" ? req.businessContext : undefined;
228
+ // #196:成员/synthesizer 是纯讨论人格(模板声明零工具),跑在无手 subRunner 上。
229
+ const hands = "none";
215
230
  return {
216
- tools: [createTeamTool(deps.subRunner, template, { objective, businessContext })],
231
+ tools: [createTeamTool(subRunnerFor(deps, hands), template, { objective, businessContext })],
232
+ hands,
217
233
  skills: skillsForScenario(deps.skills, "team"),
218
234
  promptProvider: teamCoordinatorPrompt,
219
235
  };
@@ -224,6 +240,11 @@ export function buildScenarios(deps) {
224
240
  export function selectScenario(scenarios, name) {
225
241
  return (name && scenarios[name]) || scenarios.default;
226
242
  }
243
+ /** 把属主的判别式摊平成 wire 上的两个键(可用臂整键省略原因,契约=「缺席即没有理由」)。 */
244
+ function availabilityFields(deps, toolset) {
245
+ const verdict = scenarioAvailability(deps, toolset);
246
+ return verdict.available ? { available: true } : { available: false, unavailableReason: verdict.reason };
247
+ }
227
248
  /** 内建五场景详情。default/code/team 工厂对良性请求无副作用可真调(拿真实工具名单);code-review/scan 是
228
249
  * fail-loud 语义(缺 GIT_API 配置/principal 即 throw)→ 探针失败落静态表兜底(表↔工厂一致性由测试锁:
229
250
  * 测试喂 fake deps 真调工厂对账工具名)。enabled 对内建恒 true(约定②)。 */
@@ -237,7 +258,12 @@ export function builtinScenarioDetails(scenarios, deps) {
237
258
  }
238
259
  };
239
260
  const REPO_TOOLS = ["repo_tree", "repo_read_file", "repo_pull_diff", "Now"];
240
- const mk = (name, summary, toolset, tools, promptSummary) => ({ name, source: "builtin", builtin: true, summary, toolset, tools, promptSummary, enabled: true });
261
+ const mk = (name, summary, toolset, tools, promptSummary) => ({
262
+ name, source: "builtin", builtin: true, summary, toolset, tools, promptSummary, enabled: true,
263
+ // [C132]:可用性与上面的静态回退表分家 —— 回退表答的是「工具面长什么样」(缺配置时也照画),
264
+ // 这两个键答的是「现在点下去会不会 501」。同一份属主判据,内建与 center 两条产线共用。
265
+ ...availabilityFields(deps, toolset),
266
+ });
241
267
  return {
242
268
  code: mk("code", "编码主场景([891] 出厂缺省;CC 编码 persona 蒸馏版,default 全量工具面;终验旋钮独立)", "full-body", probe("code", {}, undefined, ["Now"]), deps.brandIdentity
243
269
  ? "Sema 产品身份 + core 编码完全体提示词(CODE_AGENT persona+自治自检;CC 血统对表)"
@@ -250,7 +276,7 @@ export function builtinScenarioDetails(scenarios, deps) {
250
276
  }
251
277
  /** center 条目详情(有效性判定与 centerScenarios 完全同款:无效条目既不进 overlay 也不进详情——
252
278
  * 保证约定①「详情显示的来源=运行实际用的定义」永不错位)。 */
253
- export function centerScenarioDetails(specs, builtinNames) {
279
+ export function centerScenarioDetails(specs, builtinNames, deps) {
254
280
  const out = {};
255
281
  for (const spec of specs ?? []) {
256
282
  if (spec.enabled === false)
@@ -270,24 +296,62 @@ export function centerScenarioDetails(specs, builtinNames) {
270
296
  // 恒空串;字段本身保留(builtin 条目仍有真值,wire 形不变)。
271
297
  promptSummary: "",
272
298
  enabled: true,
299
+ // [C132]:center 声明的 repo 型场景与内建的 scan/code-review 受**同一份**判据管辖
300
+ // ——overlay 不是判据的第二属主。
301
+ ...availabilityFields(deps, spec.toolset),
273
302
  };
274
303
  }
275
304
  return out;
276
305
  }
277
306
  const TOOLSETS = {
278
- none: { build: () => [nowTool()] },
307
+ none: { build: () => [nowTool()], hands: "none" },
279
308
  "repo-readonly": {
280
309
  requiresRepo: true,
310
+ hands: "none",
281
311
  build: (deps, req) => {
282
- if (!deps.repoClient)
283
- throw new HttpError(501, "this scenario needs GIT_API_BASEURL configured");
312
+ const repoClient = requireRepoClient(deps, "this");
284
313
  if (typeof req.repo !== "string" || !req.repo.trim()) {
285
314
  throw new HttpError(400, "this scenario requires a 'repo' field (owner/name or repo URL)");
286
315
  }
287
- return [...repoToolsFor(deps.repoClient, parseRepo(req.repo)), nowTool()];
316
+ return [...repoToolsFor(repoClient, parseRepo(req.repo)), nowTool()];
288
317
  },
289
318
  },
290
319
  };
320
+ const SCENARIO_AVAILABLE = { available: true };
321
+ /**
322
+ * 🔴 场景可用性的**唯一属主**。列举面(`ScenarioDetail.available` / `unavailableReason`)与请求面
323
+ * ({@link requireRepoClient} 的 501 拒绝臂,三处调用点)都只从这里取值——两处各写一份就是本仓反复
324
+ * 吃过的「同一语义两个属主」病:判据一漂,列举面开始说谎而没人先红。一致性由 capabilities.test 的
325
+ * **对表格**逐名钉住(`available:false` ⟺ 良性请求真吃 501),而不是靠这段注释。
326
+ *
327
+ * 判据键 = **toolset**:内建详情与 center 条目都带这个字段,故两条产线天然共用同一份判据。
328
+ * `requiresRepo` 的 toolset 需要部署配好 git 后端(`GIT_API_BASEURL` ⇒ `deps.repoClient`)。
329
+ * 词表外的 toolset(内建的 `full-body`/`team`)不依赖后端 ⇒ 恒可用。
330
+ */
331
+ export function scenarioAvailability(deps, toolset) {
332
+ if (TOOLSETS[toolset]?.requiresRepo === true && deps.repoClient === undefined) {
333
+ return { available: false, reason: "git_client_unconfigured" };
334
+ }
335
+ return SCENARIO_AVAILABLE;
336
+ }
337
+ /** 拒绝文案的唯一属主:闭集穷举 switch(新增原因词不在这里表态即编译红)。 */
338
+ export function scenarioUnavailableMessage(reason, scenarioLabel) {
339
+ switch (reason) {
340
+ case "git_client_unconfigured":
341
+ return `${scenarioLabel} scenario needs GIT_API_BASEURL configured`;
342
+ }
343
+ }
344
+ /** repo 型场景取 git 客户端的唯一口(三处场景工厂同源)。不可用 ⇒ 按属主给的原因与文案 501 响亮拒。 */
345
+ function requireRepoClient(deps, scenarioLabel) {
346
+ const client = deps.repoClient;
347
+ if (client !== undefined)
348
+ return client;
349
+ const verdict = scenarioAvailability(deps, "repo-readonly");
350
+ // 判成 available 却没有客户端 = 属主与消费面脱钩(对表格会先红)。此处仍 fail-loud,不静默放行。
351
+ throw new HttpError(501, verdict.available
352
+ ? `${scenarioLabel} scenario has no repo client although the availability owner reports it available (wiring defect)`
353
+ : scenarioUnavailableMessage(verdict.reason, scenarioLabel));
354
+ }
291
355
  export const SCENARIO_NAME_RE = /^[a-z][a-z0-9-]{1,31}$/;
292
356
  // ([1053] registry-core 0.10.15:SCENARIO_PROMPT_MAX 随 ScenarioEntry.prompt 一并退役)
293
357
  /**
@@ -316,6 +380,7 @@ export function centerScenarios(deps, specs, builtinNames, logger) {
316
380
  const name = spec.name;
317
381
  overlay[name] = (req) => ({
318
382
  tools: toolset.build(deps, req),
383
+ hands: toolset.hands, // #196:表态随 toolset 走(center 声明面不可自选 hands —— 能力仍是代码定死的)
319
384
  skills: skillsForScenario(deps.skills, name),
320
385
  });
321
386
  if (builtinNames.includes(name))
@@ -324,13 +389,12 @@ export function centerScenarios(deps, specs, builtinNames, logger) {
324
389
  return { overlay, shadows };
325
390
  }
326
391
  function codeReview(deps, req) {
327
- if (!deps.repoClient)
328
- throw new HttpError(501, "code-review scenario needs GIT_API_BASEURL configured");
392
+ const repoClient = requireRepoClient(deps, "code-review");
329
393
  if (typeof req.repo !== "string" || !req.repo.trim()) {
330
394
  throw new HttpError(400, "code-review scenario requires a 'repo' field (owner/name or repo URL)");
331
395
  }
332
396
  const coords = parseRepo(req.repo);
333
- const repoTools = repoToolsFor(deps.repoClient, coords);
397
+ const repoTools = repoToolsFor(repoClient, coords);
334
398
  const skills = skillsForScenario(deps.skills, "code-review");
335
399
  const objective = typeof req.objective === "string" ? req.objective : "Review this repository.";
336
400
  // Tiering: `council: true` runs the multi-lens council (L1 parallel lenses + L3 arbiter) — for
@@ -340,11 +404,14 @@ function codeReview(deps, req) {
340
404
  // (deterministic + cheaper + cacheable; see design/35). For OPEN-ENDED tasks where the decomposition
341
405
  // isn't known, core's `createSubagentTool` (model-driven self-delegation, with maxDepth / isolated
342
406
  // context / compressed-report guardrails) is the right tool — wire it in a dedicated scenario then.
407
+ // #196:两条腿(直评 / council)都声明 none —— 直评腿的 lead 只读仓库工具,council 腿的 lead 只调
408
+ // run_council;lens/arbiter 子任务同样只读仓库,故 council 工具拿无手 subRunner。
409
+ const hands = "none";
343
410
  if (req.council === true) {
344
411
  const rounds = clampRounds(req.rounds); // finite-guarded (NaN/±Inf → undefined → council default 1)
345
412
  return {
346
413
  tools: [
347
- createCouncilTool(deps.subRunner, repoTools, objective, {
414
+ createCouncilTool(subRunnerFor(deps, hands), repoTools, objective, {
348
415
  metrics: deps.metrics,
349
416
  logger: deps.logger,
350
417
  debate: req.debate === true, // L1+L2+L3 (peer debate) vs L1+L3
@@ -355,11 +422,12 @@ function codeReview(deps, req) {
355
422
  ...(rounds !== undefined ? { rounds } : {}),
356
423
  }),
357
424
  ],
425
+ hands,
358
426
  skills,
359
427
  promptProvider: coordinatorPrompt,
360
428
  };
361
429
  }
362
- return { tools: repoTools, skills, promptProvider: directReviewerPrompt };
430
+ return { tools: repoTools, hands, skills, promptProvider: directReviewerPrompt };
363
431
  }
364
432
  const reviewBody = "Produce ONE prioritized review: a short summary, then findings grouped by severity " +
365
433
  "(blocker / major / minor), each with `path:line`, the problem, and a concrete fix. Cite real code — never invent files or symbols.";
@@ -98,6 +98,7 @@ export declare class RemoteConfigProvider implements ConfigProvider {
98
98
  * version as the etag (so the caller's "skip re-apply if etag unchanged" works locally too).
99
99
  */
100
100
  export declare class LocalConfigProvider implements ConfigProvider {
101
+ #private;
101
102
  readonly kind: "local";
102
103
  private readonly store;
103
104
  /** [875]b 可观测性:config_loaded 日志要能回答「到底读的哪个目录」——复验实录:只设 LOCAL_DATA_ROOT 时
@@ -131,8 +131,12 @@ export class LocalConfigProvider {
131
131
  /** [875]b 可观测性:config_loaded 日志要能回答「到底读的哪个目录」——复验实录:只设 LOCAL_DATA_ROOT 时
132
132
  * config.d 静默读不到且 0 模型无告警(CONFIG_LOCAL_DIR 才是本 lane 的选目录旋钮)。 */
133
133
  root;
134
+ /** 注入 store(测试缝)时保持其自带行为;自建时 fetchEffective 每次按调用铸新 store 挂 warning 座
135
+ * (见 fetchEffective 头注)。 */
136
+ #injectedStore;
134
137
  constructor(root, store) {
135
138
  this.root = root;
139
+ this.#injectedStore = store;
136
140
  this.store = store ?? new FileConfigStore(root);
137
141
  }
138
142
  async fetchEffective(etag) {
@@ -146,7 +150,20 @@ export class LocalConfigProvider {
146
150
  // [898] tolerant read(0.10.12):catalog 域坏文件 ⇒ 该域 default+domainErrors 单列(不再连坐整包回落
147
151
  // env);gate 域坏文件仍 throw(走 caller 的整包 catch)。坏文件文本 hash 参与 version(坏≠absent、
148
152
  // 坏→修好均 bump),304-skip 纪律不漏拍——所以坏域场景下 etag 对比照旧安全。
149
- const { effective: eff, domainErrors } = await this.store.getEffective({ tolerant: true });
153
+ // [3073]#204④:判形失败(schema 坏形/重名条目)走的是 buildEffective `onWarning`(parse 层落
154
+ // default),不走 READ 层 throw——不接这个座,domainErrors 恒空,域被静默清空而排障 hint 只能谎报
155
+ // 「文件空」。warning 座是 store 构造级而 fetch 可能重叠(boot 超窗后台腿 vs 首拍 refresh),
156
+ // 共享缓冲会把别次快照的警告错记到本次结果(codex F2)——故自建形每次 fetch 铸新 store 配本地
157
+ // 收集器(FileConfigStore 无跨调用状态,按次构造零成本);注入 store(测试缝)保持其自带行为。
158
+ const parseWarnings = [];
159
+ const store = this.#injectedStore ??
160
+ new FileConfigStore(this.root, {
161
+ onWarning: (w) => {
162
+ if (w.kind === "domain-defaulted")
163
+ parseWarnings.push({ domain: w.domain, error: w.error instanceof Error ? w.error.message : String(w.error) });
164
+ },
165
+ });
166
+ const { effective: eff, domainErrors } = await store.getEffective({ tolerant: true });
150
167
  const tag = String(eff.version);
151
168
  if (etag !== undefined && etag === tag)
152
169
  return null; // unchanged → mirror remote 304
@@ -157,14 +174,17 @@ export class LocalConfigProvider {
157
174
  // The resolver gets THIS snapshot's domain values as `pending` (builder output is default-filled for
158
175
  // every domain), so the check never re-reads config.d — the one-atomic-snapshot invariant above holds.
159
176
  const refErrors = [];
160
- const resolve = siblingResolver(this.store, eff);
177
+ const resolve = siblingResolver(store, eff);
161
178
  for (const d of ["rosters", "workers", "models", "collab", "systems", "entitlement"]) {
162
179
  const issues = await refIntegrityIssues(d, eff[d], resolve);
163
180
  // path CLEAR (locates the entry) + message operand-fingerprinted (see redactConfigError).
164
181
  if (issues.length > 0)
165
182
  refErrors.push({ domain: d, error: issues.map((i) => `${i.path.join(".")}: ${redactConfigError(i.message)}`).join("; ") });
166
183
  }
167
- const errors = [...domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })), ...refErrors];
184
+ // parse 层判形失败与 READ 层坏文件同渠道:两者都是「坏域落 default」,候选门(§9.5-5)与排障 hint
185
+ // 需要同样看见——此前判形失败静默(domainErrors 空+域被清空),比坏文件更隐蔽。
186
+ const parseErrors = parseWarnings.map((w) => ({ domain: w.domain, error: redactConfigError(w.error) }));
187
+ const errors = [...domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })), ...parseErrors, ...refErrors];
168
188
  return { effective: mapToServiceEffective(eff, eff.version), etag: tag, ...(errors.length > 0 ? { domainErrors: errors } : {}) };
169
189
  }
170
190
  async fetchSkillContent(contentHash) {
@@ -70,7 +70,8 @@ export interface ImageBakeConfig {
70
70
  * `repo = ${registry}/${profile}` matches the P1 seed convention (seed-image-index.ts) and the index id
71
71
  * `sha256(repo@digest)` is stable across seed and bake. `BAKE_IMAGE_REGISTRY` env; **code default = null**
72
72
  * ⇒ the host-less `sema-images/<profile>` (node-local, documented non-pullable). Fleet posture (clay 拍
73
- * 2026-07-13, SWR 弃用): `docker.io/claybobby`(海外主源;CN 兜底=内网 Gitea registry)。 */
73
+ * 2026-07-13, SWR 弃用; 2026-08 内网 Gitea registry 容器已下线全删): `docker.io/claybobby`(唯一在跑
74
+ * 的公共源;无内建 CN 兜底,国内网络不通的部署需自行配置 dockerd `registry-mirrors` 或自建 registry)。 */
74
75
  registry: string | null;
75
76
  /** The build-host promoted fetch-once cache base injected as `--cache-base` (NEVER caller-settable — a foreign
76
77
  * origin would defeat the domestic-only iron rule). `BAKE_CACHE_BASE` (e.g. `http://<build-host>:<port>`). */
@@ -769,8 +770,9 @@ export interface ServiceConfigFlat {
769
770
  * ⚠️ 非法模式(不含任何路径段,如 `"/"`)在 boot 期 fail-loud(createResolveSpec 先编译一次)——
770
771
  * **对存量部署是行为变更**:搬家前这种坏值只在 default/auto/acceptEdits 三臂上每请求炸,只跑
771
772
  * headless 的部署带着坏值也能起服务;现在起不来(方向=运维当场看见,而不是每任务一条 500)。
772
- * ⚠️ 沙箱 lane 的相对形写目标有一格今天无人裁决(cwd 里的守卫段看不见)——边界与两条收口路
773
- * 成文在 boot/resolve-spec.ts `RelativeTargetLexicalEnv` 类注。 */
773
+ * 旧注写的「沙箱 lane 相对形有一格无人裁决(cwd 里的守卫段看不见)」自 core 5.19.0(#108)起
774
+ * 已收口:守卫按 `ToolCallRequest.cwd` 解析写目标,两条 lane 同得。剩余射程边界(引擎未盖戳的直接
775
+ * 调用形)成文在 `deployment-governance.ts` 的 `RelativeTargetLexicalEnv` 类注。 */
774
776
  sensitiveWritePatterns: string[];
775
777
  /** [1557]§四 opt-in (cli[1555]② finding, core[1556] suggested mechanism): the CC manual-family permission
776
778
  * modes (default/auto/acceptEdits) gate every fs WRITE hand tool (Write/Edit/NotebookEdit) but never touched
package/dist/config.js CHANGED
@@ -1215,7 +1215,7 @@ function parseOrchestrationDomain(ctx) {
1215
1215
  hostBackgroundShellEnabled();
1216
1216
  hostExecSpoolEnabled();
1217
1217
  // ── design/158 B4 ③ — LSP: one knob per lane. `LSP_ENABLED` used to drive BOTH the sandbox lane (opt-in,
1218
- // default OFF — it needs a baked `sema-code-lsp` template) and the host lane (opt-out, default ON — it needs
1218
+ // default OFF — it needs a baked `ai-agent-code-lsp` template) and the host lane (opt-out, default ON — it needs
1219
1219
  // nothing and degrades to grep/read), i.e. one env name with two OPPOSITE defaults and no way to silence the
1220
1220
  // host lane without also naming the sandbox knob. `LSP_HOST_ENABLED` now owns the host lane; an explicit
1221
1221
  // `LSP_ENABLED=false` keeps working as a host opt-out (it was the only one operators ever had) with a notice
@@ -1347,8 +1347,9 @@ function parseOrchestrationDomain(ctx) {
1347
1347
  }
1348
1348
  : // DUAL-MODE §5: `local-docker` = a per-task container on THIS machine's docker daemon (isolation:true,
1349
1349
  // suspendable:false). DOCKER_IMAGE is REQUIRED — no docker.io default (domestic-images iron rule); a
1350
- // deployment points it at the docker.io/claybobby public pool or an in-network mirror (SWR
1351
- // deprecated). Secrets are env-NAMEs the worker resolves from its
1350
+ // deployment points it at the docker.io/claybobby public pool or its own private registry (SWR
1351
+ // deprecated; the in-network Gitea registry container was retired 2026-08, no CN fallback baked
1352
+ // in). Secrets are env-NAMEs the worker resolves from its
1352
1353
  // own process.env (DOCKER_SANDBOX_ENV, a CSV of NAMEs) — never the values in the spec/center.
1353
1354
  process.env.REMOTE_EXEC === "local-docker" && process.env.DOCKER_IMAGE
1354
1355
  ? {
@@ -1427,7 +1428,7 @@ function parseOrchestrationDomain(ctx) {
1427
1428
  selectEnvironmentTool: boolEnv("SELECT_ENVIRONMENT_TOOL", true), // RFC A2: default ON (mount additionally gated on k8s+catalog)
1428
1429
  envFactsEnabled: boolEnv("SANDBOX_ENV_FACTS", true), // RFC A1: default ON (core 1.240.0 TaskSpec.envFacts; no-facts = no field)
1429
1430
  toolDeferLongtail: boolEnv("TOOL_DEFER_LONGTAIL", false), // [803]④ defer face: EXPERIMENTAL, default OFF
1430
- lspEnabled: boolEnv("LSP_ENABLED", false), // sandbox lane: opt-in (needs a baked sema-code-lsp template)
1431
+ lspEnabled: boolEnv("LSP_ENABLED", false), // sandbox lane: opt-in (needs a baked ai-agent-code-lsp template)
1431
1432
  lspHostEnabled, // host lane: LSP_HOST_ENABLED, DEFAULT ON (CC-parity, degrades gracefully) — see the derivation above
1432
1433
  workflowAgentsReadOnly: boolEnv("WORKFLOW_AGENTS_READONLY", false), // [824]① TOB 保守旋钮:默认 off = workflow 子 agent 同权(CC parity)
1433
1434
  imageBakes: {
@@ -0,0 +1,168 @@
1
+ import { FileError, StubExecutionEnv, type ExecutionEnv, type Result, type ToolPolicy } from "@sema-agent/core";
2
+ import { type DurableAskOptions } from "./approval.js";
3
+ import type { ServiceConfig } from "./config.js";
4
+ import type { applyRuntimeGovernance } from "./runtime-governance.js";
5
+ /** `applyRuntimeGovernance` 的 governance 实参 —— 从折叠属主的签名位**推导**,不复制形状
6
+ * (改一处签名,本口跟着变;两份手写形状正是漂移的成因)。 */
7
+ export type DeploymentGovernanceInputs = Parameters<typeof applyRuntimeGovernance>[1];
8
+ /** 本口读的部署配置切面(`ServiceConfig` 结构满足;窄接口让消费腿与测试不必铸整只 config)。 */
9
+ export interface DeploymentGovernanceConfigView {
10
+ readonly autonomy?: ServiceConfig["autonomy"];
11
+ readonly commandPolicy?: ServiceConfig["commandPolicy"];
12
+ readonly manualModeShellGate?: ServiceConfig["manualModeShellGate"];
13
+ readonly sensitiveWritePatterns: ServiceConfig["sensitiveWritePatterns"];
14
+ }
15
+ /** 审批基线读的配置切面(同上,窄接口)。 */
16
+ export interface ApprovalBaselineConfigView {
17
+ readonly approvalRequire: ServiceConfig["approvalRequire"];
18
+ readonly approvalDeny: ServiceConfig["approvalDeny"];
19
+ readonly approvalAutoBudget: ServiceConfig["approvalAutoBudget"];
20
+ readonly approvalNeverAuto: ServiceConfig["approvalNeverAuto"];
21
+ }
22
+ /** 守卫集裁决的 env/cwd 基准。**lane 分形归调用腿**(host 真 fs / 沙箱 deferred 代理 / run-local
23
+ * workspace)—— 那是消费腿的属主知识,本口不重复它,只按 cwd 的在场性决定要不要补词法臂。 */
24
+ export interface PathAdjudication {
25
+ /** 守卫策略 canonicalize 每一个写目标用的 env。 */
26
+ readonly env: ExecutionEnv;
27
+ /** 相对路径基准(core 的 `rootPath`)。缺席 ⇒ 相对形在真身那层判不了,补词法臂(见下)。 */
28
+ readonly cwd?: string;
29
+ }
30
+ /** #152([2703] 案二):durable 部署上的 AskUserQuestion 门的活体面探针(QuestionCoordinator 的切面)。 */
31
+ export interface LiveQuestionFace {
32
+ hasLiveContext(): boolean;
33
+ }
34
+ /** durable 轴的审批基线入参。缺席 ⇒ adjudicated allow-all 基线(见 {@link createApprovalBaselinePolicy})。 */
35
+ export interface DurableApprovalSeat {
36
+ /** 活体问答面;`undefined` ⇒ 恒 durable park 的原形门。 */
37
+ readonly question: LiveQuestionFace | undefined;
38
+ /** 每会话「本会话不再询问」探针(canonical toolName 键空间)。 */
39
+ readonly exempt?: DurableAskOptions["exempt"];
40
+ /** 豁免短路时的审计钩子。 */
41
+ readonly onExempted?: DurableAskOptions["onExempted"];
42
+ }
43
+ /**
44
+ * #152([2703] 案二):durable 部署上的 AskUserQuestion 门。活体面(QuestionCoordinator)缺席 ⇒ 原形
45
+ * `createDurableQuestionPolicy()`(恒 ask ⇒ 恒 durable park)。在场 ⇒ **判决时**按活流上下文分腿:
46
+ * 活流腿(bg/SSE,coordinator.runWithContext 包裹且投递面此刻可达,ALS 判)allow——工具执行落到
47
+ * RunnerDeps.onQuestion 的 coordinator,问正在 tail 流的活人;无活流腿(sync /v1/tasks、verify/cascade、
48
+ * durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留(#166 后无活流腿放行执行
49
+ * 也不会产出空答:coordinator 无 ALS ctx ⇒ 冻结 `{kind:"unavailable"}`(src/question.ts),永不悬挂;
50
+ * park 仍是把问题送到人面前的唯一那条腿,正当性不变、只是反事实前提换了)。
51
+ * 工具名字面量与 server.ts 的 pre-CAS 守卫同源("AskUserQuestion",core 未根导出常量)。
52
+ * ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
53
+ * 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
54
+ * 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
55
+ * 自折 core 原形——该腿无活体问答面可装且 leader 不 import boot 层(分层),core 原形+sentinel 即其完整
56
+ * 语义;豁免注在彼处互指,接活体面之日必须并回本工厂。
57
+ */
58
+ export declare function createDurableQuestionGate(live: LiveQuestionFace | undefined): ToolPolicy;
59
+ /**
60
+ * 沙箱 lane 上**相对形**写目标的守卫补层用 env(codex 对抗复审 round1 finding 1,红先复现)。
61
+ *
62
+ * 缺口:沙箱 lane 的守卫策略拿不到 `rootPath`(沙箱 cwd 不是 server 能猜的,#165 裁定 1),而
63
+ * `DeferredSandboxPathEnv.absolutePath` 对相对形一律报错。core 的 `canonicalizeTarget` 在
64
+ * **absolutePath 失败**这一支不置 `unresolvedSymlink`,于是守卫策略走的是
65
+ * 「判不了就弃权」的 `allow`(dist 亲读)。写门在场时这条腿被门的 `ask` 兜住;而
66
+ * `bypassPermissions` / settings 缺席这几形**根本没有门**,于是 `Write(file_path: ".env")` 一路放行——
67
+ * 而 core 的结构化写工具会把相对形按 engine 跟踪的 cwd 解析后真写下去(fs-write.js `resolveKey`)。
68
+ *
69
+ * 补法:**同一只**守卫策略工厂再铸一个实例,只把「路径→canonical key」这一步换成
70
+ * 纯词法基准(本 env)。判定与提取(哪个参数是写目标、NotebookEdit 的 notebook_path 优先、段匹配)
71
+ * 全部仍是 core 的,server 侧零复刻——复刻 core 的裁决逻辑正是「同源谎」那一类错误。
72
+ *
73
+ * 三条不可动的边界:
74
+ * · **绝对形一律弃权**(absolutePath 报错 ⇒ canon 失败且非 unresolvedSymlink ⇒ core 判 allow):
75
+ * 绝对形归真身裁决那一层,#165「真身胜过名字」的裁定(域内良性软链名叫 `.ssh` 只 ask)不受影响。
76
+ * · **只会 deny,不会放行**:相对形自身拼写里出现的段,解析成绝对路径后仍在,所以词法命中即真命中;
77
+ * 反过来一条名叫 `.env` 而真身良性的相对软链会被误 deny —— 方向是 fail-closed,与守卫集语义同向。
78
+ * · **覆盖面(2026-08-08 按 core 5.19.0 #108 校正;旧文见下方「历史」段)**:本层只在
79
+ * `ToolCallRequest.cwd` **缺席**那一形上说话。core 5.19.0 起每条路径解析型守卫按 `req.cwd ?? rootPath`
80
+ * 解析写目标(dist `core/sensitive-path-policy.js`),而 `canonicalizeTarget` 拿到 baseCwd 后会先把
81
+ * 相对形**拼成绝对形**再交给 env(dist `tools/fs/safety.js` 的 `baseCwd && !isAbsolutePathForm(...)`
82
+ * 分支)—— 本层的 `absolutePath` 对绝对形一律报错弃权 ⇒ **有戳时本层自动让位**,由真身那一层
83
+ * (沙箱 `DeferredSandboxPathEnv` / host `NodeExecutionEnv`)按活 cwd 裁决,cwd 里的守卫段现在真看得见。
84
+ * ⇒ 本层今天的射程 = 「引擎没盖戳」的调用:相对形**自身拼写**里带守卫段的那一类(`Write(".env")`、
85
+ * `Write("cfg/.ssh/id_rsa")`),仍由本层 fail-closed 兜住。**不删臂**:让位与冗余不是一回事——
86
+ * 删掉它等于把「缺戳即无守卫」写死,而缺戳形在契约上是 core 明确保留的回落语义(直接调用形)。
87
+ * 两处特征化钉现在各带两臂(带戳 deny / 缺戳 allow):test/task-settings.test.ts 与
88
+ * test/run-local.test.ts(后者是真引擎端到端,已翻成 🔴 正控)。
89
+ *
90
+ * 📜 **历史(留档,别当现状读)**:2026-08-08 之前本层的覆盖面到「cwd 里的守卫段看不见」为止——
91
+ * 本层把相对形挂在 `/` 上,而工具挂在 engine 活 cwd 上,`cd .git` 后 `Write("config")` 真写
92
+ * `<root>/.git/config` 而本层只看得到 `/config` ⇒ 弃权。design/181 刀3 的口径更正查明这条残余面
93
+ * **不是沙箱 lane 局部的**:host 腿虽供了 `rootPath`,那也是装配期的静态值,一样追不上被 Bash `cd`
94
+ * 就地改写的 `cwdRef.current`(run-local 端到端真复现:`cd .git/hooks` 后 `Write("pre-commit")` 真落盘)。
95
+ * 当年判定属主是引擎那条缝(不是任何一条消费腿——在消费腿里自己拿静态 cwd 追 `cd`,是拿会漂的复制品
96
+ * 追引擎的真值,本文件反复点名的病),并把两条钉写成「引擎缝落地后一起翻面」。**该缝即 core backlog
97
+ * #108,已在 5.19.0 到货**,两条钉按上述翻面完毕。另一条备选收口(守卫集开启即把沙箱 lane 相对写
98
+ * 一律 deny,有真受损方且无对应旋钮)因此作废,无需部署方拍板。
99
+ */
100
+ export declare class RelativeTargetLexicalEnv extends StubExecutionEnv {
101
+ /** 相对形 → `/<词法归一>`;绝对形 / 空串 / 含 NUL 一律报错(= 弃权,见类注)。 */
102
+ absolutePath(path: string): Promise<Result<string, FileError>>;
103
+ /** 恒「不存在」⇒ core 的 `canonicalizeNewPath` 逐级回退,最终把词法归一形当 canonical key 交给段匹配。
104
+ * 这里绝不能报错:报错会被 core 读成 `unresolvedSymlink` 而对**每一个**相对目标 deny(含普通文件)。 */
105
+ exists(_path: string, _abortSignal?: AbortSignal): Promise<Result<boolean, FileError>>;
106
+ }
107
+ /**
108
+ * boot 期的守卫集**可编译性**门(#177 收口①,随 design/181 件一搬进本口 —— 三条消费腿同得)。
109
+ *
110
+ * 守卫集的编译发生在**每个请求**上。core 的 `compilePatterns` 对「一个路径段都没有」的模式(`"/"`、
111
+ * `"//"`)THROW,那条 throw 会变成**每一个任务一条 500**,且运维从错误里看不出是自己的 env 写错了。
112
+ * 消费腿在装配期先编译一次:非法旋钮值当场炸在启动上(与 config.ts 的 env fail-loud 同族),指名键与
113
+ * core 的原因。env 只是编译期的占位(compilePatterns 不碰它),真裁决用的是每请求按 lane 铸的那一个。
114
+ */
115
+ export declare function assertGuardPatternsUsable(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">): void;
116
+ /** 一条 boot 期运维告警的**载荷**(纯数据 ⇒ `build*`;打给谁、用哪只 logger 归消费腿)。 */
117
+ export interface OperatorWarning {
118
+ readonly event: string;
119
+ readonly fields: Record<string, unknown>;
120
+ }
121
+ /** {@link buildOnlySensitiveBaselineWarning} 读的座位量 —— 「这个部署到底有没有一道**真**门」。 */
122
+ export interface GateSeatView {
123
+ /** durable 审批门(checkpoint suspend/resume)是否在这条腿上真装。 */
124
+ readonly durableEnabled: boolean;
125
+ /** 单用户 turnkey 的 auto-accept 基线是否适用(= 零门意图的既定姿势,不是 misconfig)。 */
126
+ readonly singleUserAutoAcceptBaseline: boolean;
127
+ }
128
+ /**
129
+ * UNGATED 信号的**补偿**(design/181 件一收编 / 件三三腿同得)。
130
+ *
131
+ * 审批基线铺开之后 core 的 `hasEffectAwareGate` 恒真,于是它那条 "write-capable hand tools are present
132
+ * but UNGATED" 的 onError 不再触发(判据是 `policyLayers.length > 0`,prepare-task dist 亲读)。那条信号
133
+ * 此前是「这个部署一个门都没接」这个 misconfig 的**唯一**提示,而守卫集只挡那二十来个路径段、其余写
134
+ * 目标照旧无裁决 —— 信号不能因为我们铺了基线就静默消失,所以由我们自己按同一判据说一次。
135
+ *
136
+ * 判据(与信号消失的条件逐字互补):守卫集在场 ∧ 两条产**真**门的腿都不在场(durable 门关 ∧ 单用户
137
+ * auto-accept 基线不适用)。三个量都是部署常量 ⇒ 消费腿在 boot 期说一次,不是每任务一次。
138
+ *
139
+ * ⚠️ 单一属主(design/181 件三):HTTP 腿与 run-local 腿共用本判据与文案。两处各写一份 = 一处改了另一处
140
+ * 没改,而两份都长得像对的 —— 那正是本文件存在的理由。
141
+ */
142
+ export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">, seat: GateSeatView): OperatorWarning | undefined;
143
+ /**
144
+ * `applyRuntimeGovernance` 的 governance 实参预铸(design/181 件一)。
145
+ *
146
+ * 键存在性 profile 逐字保持消费腿原样:`autonomy`/`commandPolicy` 恒在场(值可为 `undefined`),
147
+ * `manualModeShellGate`/`sensitivePathPolicy` 按在场性条件展开 —— `applyRuntimeGovernance` 的
148
+ * `!== undefined` 判据对两者等价,但 profile 是折叠面的可观测字节,搬家不许顺手改。
149
+ */
150
+ export declare function createDeploymentGovernanceInputs(config: DeploymentGovernanceConfigView, pathAdjudication: PathAdjudication): DeploymentGovernanceInputs;
151
+ /**
152
+ * 审批基线 —— `applyRuntimeGovernance` 那个 base 的 `toolPolicy` 座(design/181 件一)。**恒非
153
+ * `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
154
+ *
155
+ * 两形:
156
+ * · `durable` 在场 ⇒ durable 轴 = AskUserQuestion 判决门 + F4 高危写审批门(deny/neverAuto/预算/
157
+ * 会话豁免全在 `createDurableAskPolicy` 里)。gated `ask` 由 core 变成 durable checkpoint suspend。
158
+ * · `durable` 缺席 ⇒ **adjudicated allow-all**(`createAllowDenyPolicy({})`):一条**在场的**、
159
+ * effect-aware 的策略。CC 的信任模型是 auto-accept,但门**机制**必须在场(core 原则「机制留、默认
160
+ * 可更宽」)—— 满足 core 的 `hasEffectAwareGate`,恢复可观测性与 hook/tighten 点,而运维照旧用
161
+ * `AUTONOMY`/`commandPolicy` 收紧不可逆操作(由 `applyRuntimeGovernance` tighten-only 叠上)。
162
+ *
163
+ * ⚠️ 「零门意图才铺 allow-all」这条准入判据**不在本口**:它是消费腿的部署形判断(单用户/多租户、
164
+ * 有无 checkpoint 店),属主是 `hasOperatorGateIntent`/`assertGateIntentServiceable`(src/approval.ts)。
165
+ * 本口只按调用方给的形铸策略,绝不替它判「这个部署该不该有门」。
166
+ */
167
+ export declare function createApprovalBaselinePolicy(config: ApprovalBaselineConfigView, durable?: DurableApprovalSeat): ToolPolicy;
168
+ //# sourceMappingURL=deployment-governance.d.ts.map