@sema-agent/server 7.5.0 → 7.6.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 (94) 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 +37 -1
  5. package/dist/approval-reconciler.d.ts +1 -1
  6. package/dist/approval-reconciler.js +6 -5
  7. package/dist/boot/config-center.js +15 -2
  8. package/dist/boot/execution-env.js +1 -1
  9. package/dist/boot/parked-revive-gate.d.ts +78 -0
  10. package/dist/boot/parked-revive-gate.js +114 -0
  11. package/dist/boot/resolve-spec.d.ts +3 -20
  12. package/dist/boot/resolve-spec.js +25 -119
  13. package/dist/budget.d.ts +1 -1
  14. package/dist/budget.js +1 -1
  15. package/dist/capabilities/repo-tools.d.ts +1 -1
  16. package/dist/capabilities/repo-tools.js +8 -2
  17. package/dist/config-provider.d.ts +1 -0
  18. package/dist/config-provider.js +23 -3
  19. package/dist/config-types.d.ts +5 -3
  20. package/dist/config.js +5 -4
  21. package/dist/deployment-governance.d.ts +168 -0
  22. package/dist/deployment-governance.js +206 -0
  23. package/dist/fleet/fleet-bus.d.ts +11 -1
  24. package/dist/fleet/fleet-bus.js +43 -0
  25. package/dist/fleet/fleet-terminal-window.d.ts +98 -0
  26. package/dist/fleet/fleet-terminal-window.js +316 -0
  27. package/dist/http/routes/approvals-assistant.js +6 -7
  28. package/dist/http/routes/fleet.js +160 -14
  29. package/dist/http/routes/runs.js +5 -1
  30. package/dist/http/routes/trace-usage.js +3 -4
  31. package/dist/http/send.d.ts +23 -0
  32. package/dist/http/send.js +23 -0
  33. package/dist/http/server.d.ts +4 -0
  34. package/dist/http/server.js +5 -2
  35. package/dist/http/sse-log.js +3 -4
  36. package/dist/leader/diffout.d.ts +10 -0
  37. package/dist/leader/diffout.js +14 -2
  38. package/dist/leader/diffup.js +3 -2
  39. package/dist/leader/planner.js +7 -0
  40. package/dist/main.js +22 -26
  41. package/dist/observability/fail-open.d.ts +4 -0
  42. package/dist/observability/fail-open.js +4 -0
  43. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  44. package/dist/orchestration/workflow-notify-journal.js +14 -34
  45. package/dist/plugins/approval-ask-store-sql.d.ts +33 -0
  46. package/dist/plugins/approval-ask-store-sql.js +66 -33
  47. package/dist/plugins/background-agent-store-sql.js +16 -16
  48. package/dist/plugins/breaker-state-sql.js +2 -2
  49. package/dist/plugins/checkpoint-store-sql.d.ts +5 -2
  50. package/dist/plugins/checkpoint-store-sql.js +5 -2
  51. package/dist/plugins/image-bake-store-sql.d.ts +1 -1
  52. package/dist/plugins/image-bake-store-sql.js +27 -27
  53. package/dist/plugins/image-index-sql.js +15 -15
  54. package/dist/plugins/mailbox-store-sql.js +3 -3
  55. package/dist/plugins/memory-engine-pg.js +9 -9
  56. package/dist/plugins/memory-engine-tidb.js +7 -7
  57. package/dist/plugins/memory-sync-store-pg.js +13 -13
  58. package/dist/plugins/memory-sync-store-tidb.js +5 -5
  59. package/dist/plugins/outcome-ledger-sql.js +7 -7
  60. package/dist/plugins/pg-cost-quota.js +3 -3
  61. package/dist/plugins/pg-pool.js +84 -75
  62. package/dist/plugins/pg-rate-limiter.js +3 -3
  63. package/dist/plugins/pg-session-storage.d.ts +1 -1
  64. package/dist/plugins/pg-session-storage.js +12 -13
  65. package/dist/plugins/remote-env-host.js +3 -1
  66. package/dist/plugins/remote-env-local-docker.js +6 -3
  67. package/dist/plugins/remote-env-ssh.d.ts +13 -1
  68. package/dist/plugins/roster-store-sql.js +8 -8
  69. package/dist/plugins/store-contracts.d.ts +19 -0
  70. package/dist/plugins/store-contracts.js +42 -0
  71. package/dist/plugins/task-attachment-store.js +5 -5
  72. package/dist/plugins/task-list-store-sql.js +1 -1
  73. package/dist/plugins/tidb-cost-quota.js +1 -1
  74. package/dist/plugins/tidb-pool.js +83 -60
  75. package/dist/plugins/tidb-rate-limiter.js +1 -1
  76. package/dist/plugins/tidb-session-store.js +2 -5
  77. package/dist/plugins/tool-result-store-sql.js +2 -2
  78. package/dist/plugins/usage-window-store-sql.js +13 -13
  79. package/dist/plugins/write-behind-counter.d.ts +10 -2
  80. package/dist/plugins/write-behind-counter.js +13 -3
  81. package/dist/resource-suspend.d.ts +3 -1
  82. package/dist/resource-suspend.js +3 -1
  83. package/dist/run-local.d.ts +73 -1
  84. package/dist/run-local.js +146 -5
  85. package/dist/runs.d.ts +3 -1
  86. package/dist/runs.js +3 -1
  87. package/dist/security.d.ts +12 -0
  88. package/dist/security.js +12 -0
  89. package/dist/session-sync-kernel.d.ts +13 -0
  90. package/dist/session-sync-kernel.js +13 -0
  91. package/dist/trace/core-keyset-guard.d.ts +1 -1
  92. package/dist/trace/project.d.ts +10 -1
  93. package/dist/trace/project.js +31 -0
  94. package/package.json +3 -3
@@ -18,15 +18,15 @@
18
18
  * 描述的代码下移进阶段②。
19
19
  */
20
20
  import { realpathSync } from "node:fs";
21
- import { join, posix } from "node:path";
22
- import { FileError, NodeExecutionEnv, QUESTION_AWAITS_RESUME, StubExecutionEnv, combinePolicies, createAllowDenyPolicy, createDurableQuestionPolicy, createSensitivePathPolicy, err, expandTiers, ok, tightenTaskSpec } from "@sema-agent/core";
23
- import { createDurableAskPolicy } from "../approval.js";
21
+ import { join } from "node:path";
22
+ import { NodeExecutionEnv, QUESTION_AWAITS_RESUME, expandTiers, tightenTaskSpec } from "@sema-agent/core";
24
23
  import { cappedCeiling } from "../budget.js";
25
24
  import { centerPromptProvider, centerIdentityAssembled } from "../capabilities/center-prompts.js";
26
25
  import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilities/select-environment-tool.js";
27
26
  import { sendUserFileTool } from "../capabilities/send-user-file-tool.js";
28
27
  import { gateScenarioRequest, mergeUserSkills, selectScenario } from "../capabilities/scenarios.js";
29
28
  import { applyLongtailDefer } from "../capabilities/tool-defer.js";
29
+ import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs } from "../deployment-governance.js";
30
30
  import { acceptShellScratchpadDir, buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, resumeFactsForLane } from "../env-facts.js";
31
31
  import { FleetEventBus } from "../fleet/fleet-bus.js";
32
32
  import { composeHooks, createTaskHooks } from "../hooks/hook-runner.js";
@@ -49,105 +49,25 @@ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSet
49
49
  import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
50
50
  import { redactSecrets } from "../trace/redact.js";
51
51
  import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
52
- /** #152([2703] 案二):durable 部署上的 AskUserQuestion 门。活体面(QuestionCoordinator)缺席 ⇒ 原形
53
- * `createDurableQuestionPolicy()`(恒 ask ⇒ 恒 durable park)。在场 ⇒ **判决时**按活流上下文分腿:
54
- * 活流腿(bg/SSE,coordinator.runWithContext 包裹且投递面此刻可达,ALS 判)allow——工具执行落到
55
- * RunnerDeps.onQuestion 的 coordinator,问正在 tail 流的活人;无活流腿(sync /v1/tasks、verify/cascade、
56
- * durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留(#166 后无活流腿放行执行
57
- * 也不会产出空答:coordinator 无 ALS ctx ⇒ 冻结 `{kind:"unavailable"}`(src/question.ts),永不悬挂;
58
- * park 仍是把问题送到人面前的唯一那条腿,正当性不变、只是反事实前提换了)。
59
- * 工具名字面量与 server.ts 的 pre-CAS 守卫同源("AskUserQuestion",core 未根导出常量)。
60
- * ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
61
- * 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
62
- * 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
63
- * 自折 core 原形——该腿无活体问答面可装且 leader 不 import boot 层(分层),core 原形+sentinel 即其完整
64
- * 语义;豁免注在彼处互指,接活体面之日必须并回本工厂。 */
65
- export function createDurableQuestionGate(live) {
66
- if (live === undefined)
67
- return createDurableQuestionPolicy();
68
- return {
69
- check(req) {
70
- if (req.toolName !== "AskUserQuestion")
71
- return { action: "allow" };
72
- return live.hasLiveContext()
73
- ? { action: "allow" }
74
- : { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
75
- },
76
- };
77
- }
78
- /**
79
- * 沙箱 lane 上**相对形**写目标的守卫补层用 env(codex 对抗复审 round1 finding 1,红先复现)。
80
- *
81
- * 缺口:沙箱 lane 的守卫策略拿不到 `rootPath`(沙箱 cwd 不是 server 能猜的,#165 裁定 1),而
82
- * `DeferredSandboxPathEnv.absolutePath` 对相对形一律报错。core 的 `canonicalizeTarget` 在
83
- * **absolutePath 失败**这一支不置 `unresolvedSymlink`,于是 `createSensitivePathPolicy` 走的是
84
- * 「判不了就弃权」的 `allow`(dist 亲读)。写门在场时这条腿被门的 `ask` 兜住;而
85
- * `bypassPermissions` / settings 缺席这几形**根本没有门**,于是 `Write(file_path: ".env")` 一路放行——
86
- * 而 core 的结构化写工具会把相对形按 engine 跟踪的 cwd 解析后真写下去(fs-write.js `resolveKey`)。
87
- *
88
- * 补法:**同一只** `createSensitivePathPolicy` 再铸一个实例,只把「路径→canonical key」这一步换成
89
- * 纯词法基准(本 env)。判定与提取(哪个参数是写目标、NotebookEdit 的 notebook_path 优先、段匹配)
90
- * 全部仍是 core 的,server 侧零复刻——复刻 core 的裁决逻辑正是「同源谎」那一类错误。
91
- *
92
- * 三条不可动的边界:
93
- * · **绝对形一律弃权**(absolutePath 报错 ⇒ canon 失败且非 unresolvedSymlink ⇒ core 判 allow):
94
- * 绝对形归真身裁决那一层,#165「真身胜过名字」的裁定(域内良性软链名叫 `.ssh` 只 ask)不受影响。
95
- * · **只会 deny,不会放行**:相对形自身拼写里出现的段,解析成绝对路径后仍在,所以词法命中即真命中;
96
- * 反过来一条名叫 `.env` 而真身良性的相对软链会被误 deny —— 方向是 fail-closed,与守卫集语义同向。
97
- * · 🔴 **覆盖面到此为止:cwd 里的守卫段看不见**(codex 对抗复审 round2,验真)。本层把相对形挂在 `/`
98
- * 上,而工具是挂在 engine **活** cwd 上的:`cd .git` 后 `Write("config")` 真写 `<root>/.git/config`
99
- * (命中缺省模式 `.git/config`),本层只看得到 `/config` ⇒ 不命中 ⇒ 弃权。同形的还有 cwd 落在
100
- * `.ssh`/`.aws` 里再写叶名。**这一格今天无人裁决**(沙箱 lane ∧ 守卫集开 ∧ 写门不在场的
101
- * bypass/settings 缺席两形)。收口只有两条路,都超出本件自裁范围,已上交:①(正解)一条「按当前
102
- * 工具调用的活 cwd/env 裁决」的引擎缝——与子代继承那条残余面同一条缝;②(代价形)守卫集开启时把
103
- * 沙箱 lane 的相对写目标一律 deny——那是拿「相对写从 ask 变 deny」换覆盖,有真受损方且无对应旋钮,
104
- * 须部署方拍板。特征化钉见 test/task-settings.test.ts(「cwd 里的守卫段」那条),别把它读成已保护。
105
- */
106
- class RelativeTargetLexicalEnv extends StubExecutionEnv {
107
- /** 相对形 → `/<词法归一>`;绝对形 / 空串 / 含 NUL 一律报错(= 弃权,见类注)。 */
108
- absolutePath(path) {
109
- if (path.length === 0 || path.startsWith("/") || path.includes("\u0000")) {
110
- return Promise.resolve(err(new FileError("not_supported", "this guard layer only adjudicates RELATIVE write targets (absolute forms are adjudicated against the real sandbox filesystem)", path)));
111
- }
112
- return Promise.resolve(ok(posix.normalize(`/${path}`)));
113
- }
114
- /** 恒「不存在」⇒ core 的 `canonicalizeNewPath` 逐级回退,最终把词法归一形当 canonical key 交给段匹配。
115
- * 这里绝不能报错:报错会被 core 读成 `unresolvedSymlink` 而对**每一个**相对目标 deny(含普通文件)。 */
116
- exists(_path, _abortSignal) {
117
- return Promise.resolve(ok(false));
118
- }
119
- }
120
52
  export function createResolveSpec(ctx) {
121
53
  const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, } = ctx;
122
54
  // ── #177 的两件 boot 期收口(把守卫集搬到 governance 拍之后才成立的两条)────────────────────────
123
55
  //
124
56
  // ① 守卫集的**编译**从此每个请求都发生(旧家只在 default/auto/acceptEdits 三个模式臂里发生)。
125
- // core `compilePatterns` 对「一个路径段都没有」的模式(`"/"`、`"//"`)THROW,那条 throw 在旧家
126
- // 还会被 `applyTaskSettings` 的 catch 误标成「settings 不能放松部署策略」的 422;搬家后它会变成
127
- // **每一个任务一条 500**,且运维从错误里看不出是自己的 env 写错了。这里在 boot 期先编译一次:
128
- // 非法旋钮值当场炸在启动上(与 config.ts 的 env fail-loud 同族),指名键与 core 的原因。
129
- // env 只是编译期的占位(compilePatterns 不碰它),真裁决用的是每请求按 lane 铸的那一个。
130
- if (config.sensitiveWritePatterns.length > 0) {
131
- try {
132
- createSensitivePathPolicy({ env: new StubExecutionEnv(), patterns: config.sensitiveWritePatterns });
133
- }
134
- catch (e) {
135
- throw new Error(`SENSITIVE_WRITE_PATTERNS is not a usable guard set: ${e instanceof Error ? e.message : String(e)}`);
136
- }
137
- }
57
+ // 非法旋钮值必须当场炸在启动上而不是每任务一条 500 —— 判据与文案随 design/181 件一搬进构造口
58
+ // (`assertGuardPatternsUsable`),赎回腿/run-local 装配时调同一只,三腿同得。
59
+ assertGuardPatternsUsable(config);
138
60
  // ② governance 拍的 DENY 基线让 `spec.toolPolicy` 在**任何**客户端表态下都在场,于是 core 的
139
61
  // 「write-capable hand tools are present but UNGATED」那条 onError 不再触发(core 判据是
140
62
  // `policyLayers.length > 0`,prepare-task dist 亲读)。它此前是「部署一个门都没接」这个 misconfig
141
63
  // 的唯一信号,而守卫集只挡那二十来个路径段、其余写目标照旧无裁决 —— 信号不能因为本件静默消失。
142
64
  // 条件 = 两条产 toolPolicy 的腿都不在场(durable 门关 ∧ 单用户 auto-accept 基线不适用),即修前
143
65
  // `spec.toolPolicy` 恒缺席的那一形;三个量都是 boot 常量,所以这里说一次而不是每任务说一次。
144
- if (config.sensitiveWritePatterns.length > 0 && !durableEnabled && !singleUserAutoAcceptBaseline) {
145
- logger.warn("tool_policy_only_sensitive_baseline", {
146
- detail: "the sensitive-path DENY baseline (SENSITIVE_WRITE_PATTERNS) is this deployment's ONLY tool policy — writes outside the guarded segments are unadjudicated, and core's own UNGATED warning no longer fires because a policy is now always present",
147
- remedy: "wire an approval face (DURABLE_APPROVAL / APPROVAL_REQUIRE with a store) or accept the single-user auto-accept baseline",
148
- guardedPatterns: config.sensitiveWritePatterns.length,
149
- });
150
- }
66
+ // 判据与文案随 design/181 件三搬进构造口(`buildOnlySensitiveBaselineWarning`),run-local 腿调同
67
+ // 一只 —— 本处零行为变化(条件与字段逐字同前)。
68
+ const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, { durableEnabled, singleUserAutoAcceptBaseline });
69
+ if (onlySensitiveBaseline)
70
+ logger.warn(onlySensitiveBaseline.event, onlySensitiveBaseline.fields);
151
71
  // Deployment-owned mapping. Tools/prompt/skills come from the selected scenario (assembled once,
152
72
  // bound per request); identity/session/policy stay server-side. Never taken from the request body.
153
73
  // design/158 A8:从 createHttpServer 实参里提出来(原地占该字面量 1,093 行中的 967 行)——
@@ -823,9 +743,11 @@ export function createResolveSpec(ctx) {
823
743
  // the QuestionAnswer (server.ts `body.answer` → onQuestion closure). #152([2703] 案二): the question
824
744
  // policy is LIVE-AWARE when the QuestionCoordinator is wired — a live-stream leg adjudicates `allow`
825
745
  // (the live human answers over the stream) instead of unconditionally parking; see durableQuestionPolicy.
746
+ // design/181 件一:基线的**构造**归 `createApprovalBaselinePolicy`(三腿单一属主);本处只做部署形
747
+ // 判断——哪一形该有 durable 轴、哪一形铺 allow-all、哪一形留 `undefined`(见下两条注)。
826
748
  toolPolicy: durableEnabled
827
- ? combinePolicies(createDurableQuestionGate(liveQuestionFace), createDurableAskPolicy({
828
- requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
749
+ ? createApprovalBaselinePolicy(config, {
750
+ question: liveQuestionFace,
829
751
  // The probe key is the CONTINUED session (auth.sessionId — the same id that keys the
830
752
  // durable checkpoint /decide route). A fresh session (no body.sessionId) has no exemptions by
831
753
  // construction; fail-closed inside the policy on a store error.
@@ -835,20 +757,21 @@ export function createResolveSpec(ctx) {
835
757
  onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: auth.sessionId, toolName, rawToolName }),
836
758
  }
837
759
  : {}),
838
- }))
760
+ })
839
761
  : singleUserAutoAcceptBaseline
840
762
  // 🔴 (live finding: single-user host-lane Bash ran UNGATED, no adjudication): a
841
763
  // single-user turnkey worker with NO expressed gate intent previously left `toolPolicy` UNDEFINED →
842
764
  // core's write-capable hand tools mount UNADJUDICATED (core error-logs it every task; host lane has NO
843
765
  // sandbox to fall back on). CC's trust model is auto-accept, but the GATE MECHANISM must be PRESENT
844
766
  // (core 原则: "机制留、默认可更宽 auto-accept"). So wire an adjudicated auto-accept BASELINE
845
- // (`createAllowDenyPolicy({})` = a present, effect-aware policy that allows all): satisfies core's
846
- // `hasEffectAwareGate`, restores observability + a hook/tighten point, and the operator STILL tightens
847
- // irreversible ops via `AUTONOMY` / `commandPolicy` (layered TIGHTEN-ONLY by applyRuntimeGovernance).
767
+ // (the durable-less arm of `createApprovalBaselinePolicy` = a present, effect-aware policy that allows
768
+ // all): satisfies core's `hasEffectAwareGate`, restores observability + a hook/tighten point, and the
769
+ // operator STILL tightens irreversible ops via `AUTONOMY` / `commandPolicy` (layered TIGHTEN-ONLY by
770
+ // applyRuntimeGovernance).
848
771
  // Interactive approval routed to the shell HITL = follow-on. 🔴 `singleUserAutoAcceptBaseline` requires
849
772
  // ZERO gate intent — a single-user op who SET approval flags but wired no store falls to `undefined`
850
773
  // (core warns = real misconfig, not masked); multi-tenant likewise stays `undefined` (approval required).
851
- ? createAllowDenyPolicy({})
774
+ ? createApprovalBaselinePolicy(config)
852
775
  : undefined,
853
776
  // Durable suspend needs both the store (here, per-task) and the opt-in scope key (multi-tenant =
854
777
  // principal; "_" when auth is off). core suspends on a policy `ask` only when these are present.
@@ -925,27 +848,10 @@ export function createResolveSpec(ctx) {
925
848
  logger: { warn: (event, fields) => logger.warn(event, { ...(fields ?? {}), lane: config.remoteExec?.provider }) },
926
849
  }),
927
850
  };
928
- const sensitivePathPolicy = config.sensitiveWritePatterns.length > 0
929
- ? (() => {
930
- const realTarget = createSensitivePathPolicy({
931
- env: pathAdjudication.env,
932
- patterns: config.sensitiveWritePatterns,
933
- ...(pathAdjudication.cwd !== undefined ? { rootPath: pathAdjudication.cwd } : {}),
934
- });
935
- // cwd 在场(host 形)⇒ 相对形已被 core 按 rootPath 解析进真身裁决,一层就够。
936
- // cwd 缺席(沙箱形)⇒ 相对形在真身那一层是「判不了 ⇒ 弃权 allow」,而写门恰恰在
937
- // bypass/settings 缺席这几形不在场 ⇒ 补一层纯词法的相对形守卫(见 RelativeTargetLexicalEnv)。
938
- if (pathAdjudication.cwd !== undefined)
939
- return realTarget;
940
- return combinePolicies(realTarget, createSensitivePathPolicy({ env: new RelativeTargetLexicalEnv(), patterns: config.sensitiveWritePatterns }));
941
- })()
942
- : undefined;
943
- const governedBase = applyRuntimeGovernance(spec, {
944
- autonomy: config.autonomy,
945
- commandPolicy: config.commandPolicy,
946
- ...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
947
- ...(sensitivePathPolicy ? { sensitivePathPolicy } : {}),
948
- });
851
+ // design/181 件一:governance 实参(含守卫集预铸)归 `createDeploymentGovernanceInputs`(三腿单一
852
+ // 属主),**折叠属主不变**——仍是这一句 `applyRuntimeGovernance` → core `tightenTaskSpec`。每请求现调,
853
+ // 读的是活 config(autonomy/commandPolicy 热改;resume 腿按当前值重折,与上方审批基线同姿势)。
854
+ const governedBase = applyRuntimeGovernance(spec, createDeploymentGovernanceInputs(config, pathAdjudication));
949
855
  // Fold the client's per-request settings stamp onto the governed spec, TIGHTEN-ONLY (deny-wins) — AFTER
950
856
  // the approval baseline + operator governance, so the order is deployment ⊇ operator ⊇ client (each can only
951
857
  // narrow). tightenTaskSpec THROWS if the client settings would LOOSEN a stricter baseline → a 422 client error
package/dist/budget.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Cost / budget / trace wiring for the Runner (core 1.37) — pure, testable helpers kept out of the
2
+ * Cost / budget / trace wiring for the Runner (core 5.x) — pure, testable helpers kept out of the
3
3
  * auto-running main.ts:
4
4
  * ③ buildPricing — Model.cost catalog → injected ModelPricing (authoritative costMicroUsd).
5
5
  * ④ createTracer — core's content-free TraceEvents → /metrics (spend, first-token, tool calls).
package/dist/budget.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Cost / budget / trace wiring for the Runner (core 1.37) — pure, testable helpers kept out of the
2
+ * Cost / budget / trace wiring for the Runner (core 5.x) — pure, testable helpers kept out of the
3
3
  * auto-running main.ts:
4
4
  * ③ buildPricing — Model.cost catalog → injected ModelPricing (authoritative costMicroUsd).
5
5
  * ④ createTracer — core's content-free TraceEvents → /metrics (spend, first-token, tool calls).
@@ -11,7 +11,7 @@ export interface RepoCoords {
11
11
  owner: string;
12
12
  repo: string;
13
13
  }
14
- /** Parse `owner/name` or a repo URL (e.g. http://host:9999/AI-Only/sema-server.git). */
14
+ /** Parse `owner/name` or a repo URL (e.g. https://git.example.com/some-org/some-repo.git). */
15
15
  export declare function parseRepo(input: string): RepoCoords;
16
16
  /** Thin read-only Gitea API client. Built once at startup; bound to a repo per task. */
17
17
  export declare class GiteaClient {
@@ -1,5 +1,5 @@
1
1
  import { Type } from "typebox";
2
- /** Parse `owner/name` or a repo URL (e.g. http://host:9999/AI-Only/sema-server.git). */
2
+ /** Parse `owner/name` or a repo URL (e.g. https://git.example.com/some-org/some-repo.git). */
3
3
  export function parseRepo(input) {
4
4
  const s = input.trim().replace(/\.git$/, "");
5
5
  const m = /^[a-z]+:\/\/[^/]+\/(.+)$/i.exec(s);
@@ -42,7 +42,13 @@ export class GiteaClient {
42
42
  async tree(coords, ref) {
43
43
  const { owner, repo } = coords;
44
44
  const body = (await (await this.api(`/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=true&per_page=${MAX_TREE_ENTRIES}`)).json());
45
- return (body.tree ?? []).filter((e) => e.type === "blob").map((e) => e.path);
45
+ const paths = (body.tree ?? []).filter((e) => e.type === "blob").map((e) => e.path);
46
+ // The upstream API caps the listing at `per_page` and reports the overflow via `truncated` — it does
47
+ // not report how many entries were left out. Surface that (same disclosure-marker family as
48
+ // `readFile`/`pullDiff` below) so a review lens never mistakes a capped tree for the whole repo.
49
+ return body.truncated
50
+ ? [...paths, `…[truncated, tree has more than ${MAX_TREE_ENTRIES} entries]`]
51
+ : paths;
46
52
  }
47
53
  async readFile(coords, path, ref) {
48
54
  const { owner, repo } = coords;
@@ -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