@sema-agent/server 7.56.0 → 7.58.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 (121) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +2 -2
  3. package/USAGE.md +101 -9
  4. package/dist/approval-ask-audit-store.d.ts +100 -1
  5. package/dist/approval-ask-audit-store.js +103 -2
  6. package/dist/approval-card.d.ts +148 -13
  7. package/dist/approval-card.js +82 -13
  8. package/dist/approval.d.ts +31 -0
  9. package/dist/approval.js +18 -0
  10. package/dist/auto-mode-face.d.ts +111 -0
  11. package/dist/auto-mode-face.js +99 -0
  12. package/dist/bench/s1/arms.js +5 -5
  13. package/dist/bench/s1/live-deps.js +11 -11
  14. package/dist/bench/s1/run-firm.js +3 -0
  15. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  16. package/dist/bench/s1/runner-ctx.js +7 -0
  17. package/dist/boot/config-center.js +23 -1
  18. package/dist/boot/coordinators.js +13 -1
  19. package/dist/boot/parked-revive-gate.d.ts +7 -2
  20. package/dist/boot/parked-revive-gate.js +12 -1
  21. package/dist/boot/resolve-spec.d.ts +3 -0
  22. package/dist/boot/resolve-spec.js +3 -1
  23. package/dist/boot/runner-deps.d.ts +12 -0
  24. package/dist/boot/runner-deps.js +28 -3
  25. package/dist/boot/runtime-caps.d.ts +19 -9
  26. package/dist/boot/runtime-caps.js +49 -21
  27. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  28. package/dist/boot/session-shell-gate-registry.js +21 -0
  29. package/dist/boot/stores.js +12 -5
  30. package/dist/config-catalog.js +14 -5
  31. package/dist/config-center/http-client.js +7 -11
  32. package/dist/config-center/read-warnings.d.ts +20 -0
  33. package/dist/config-center/read-warnings.js +36 -0
  34. package/dist/config-provider.d.ts +3 -0
  35. package/dist/config-provider.js +4 -8
  36. package/dist/config-types.d.ts +41 -10
  37. package/dist/config.d.ts +21 -0
  38. package/dist/config.js +40 -5
  39. package/dist/hooks/hook-runner.js +8 -8
  40. package/dist/http/route-ctx.d.ts +75 -0
  41. package/dist/http/routes/a2a-serve.js +5 -3
  42. package/dist/http/routes/approvals-assistant.js +19 -6
  43. package/dist/http/routes/capabilities.js +21 -4
  44. package/dist/http/routes/memory-origin.d.ts +2 -2
  45. package/dist/http/routes/rules.js +3 -2
  46. package/dist/http/routes/runs.d.ts +0 -14
  47. package/dist/http/routes/runs.js +20 -8
  48. package/dist/http/routes/tasks.js +34 -8
  49. package/dist/http/routes/workflows.js +19 -4
  50. package/dist/http/server.d.ts +11 -1
  51. package/dist/http/server.js +215 -43
  52. package/dist/http/wire-types.d.ts +9 -4
  53. package/dist/leader/diffout.js +2 -1
  54. package/dist/leader/endpoint.js +45 -12
  55. package/dist/leader/fanout.js +6 -5
  56. package/dist/leader/leader.js +17 -14
  57. package/dist/leader/merge.js +17 -12
  58. package/dist/leader/planner.js +3 -2
  59. package/dist/leader/repair-oracle.js +6 -4
  60. package/dist/leader/repair-wire.js +5 -3
  61. package/dist/leader/wire.d.ts +30 -1
  62. package/dist/leader/wire.js +36 -19
  63. package/dist/main.js +43 -7
  64. package/dist/observability/err-text.d.ts +6 -0
  65. package/dist/observability/err-text.js +10 -0
  66. package/dist/observability/fail-open.d.ts +40 -0
  67. package/dist/observability/fail-open.js +19 -0
  68. package/dist/observability/metrics.js +2 -2
  69. package/dist/observability/run-terminal-log.d.ts +120 -0
  70. package/dist/observability/run-terminal-log.js +360 -0
  71. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  72. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  73. package/dist/permission-rule-vocab.d.ts +40 -0
  74. package/dist/permission-rule-vocab.js +17 -0
  75. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  76. package/dist/plugins/checkpoint-store-sql.js +19 -3
  77. package/dist/plugins/file-run-store.d.ts +3 -34
  78. package/dist/plugins/file-run-store.js +19 -0
  79. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  80. package/dist/plugins/local-checkpoint-store.js +3 -0
  81. package/dist/plugins/mailbox-store-sql.d.ts +28 -9
  82. package/dist/plugins/mailbox-store-sql.js +72 -10
  83. package/dist/plugins/memory-run-store.d.ts +3 -15
  84. package/dist/plugins/memory-run-store.js +19 -0
  85. package/dist/plugins/permission-rule-store-file.js +8 -3
  86. package/dist/plugins/permission-rule-store-sql.js +16 -10
  87. package/dist/plugins/remote-scratchpad.js +3 -2
  88. package/dist/plugins/run-store-sql.d.ts +3 -42
  89. package/dist/plugins/run-store-sql.js +30 -1
  90. package/dist/plugins/store-backend.d.ts +1 -1
  91. package/dist/plugins/store-contracts.d.ts +66 -1
  92. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  93. package/dist/plugins/workflow-run-store-sql.js +10 -2
  94. package/dist/rules-consent.js +20 -13
  95. package/dist/run-cancel-context.d.ts +13 -0
  96. package/dist/run-cancel-context.js +15 -0
  97. package/dist/run-local.js +2 -2
  98. package/dist/runs.d.ts +4 -1
  99. package/dist/runs.js +41 -10
  100. package/dist/runtime-caps-resolver.d.ts +133 -17
  101. package/dist/runtime-caps-resolver.js +41 -3
  102. package/dist/task-settings.d.ts +57 -3
  103. package/dist/task-settings.js +78 -5
  104. package/dist/task-workflow.d.ts +32 -2
  105. package/dist/task-workflow.js +8 -3
  106. package/dist/tool-approval.d.ts +26 -3
  107. package/dist/tool-approval.js +96 -13
  108. package/dist/trace/core-keyset-guard.d.ts +7 -8
  109. package/dist/trace/engine-notice-wire.d.ts +1 -1
  110. package/dist/trace/engine-notice-wire.js +2 -0
  111. package/dist/trace/ledger-sink.d.ts +13 -4
  112. package/dist/trace/ledger-sink.js +14 -6
  113. package/dist/trace/project.d.ts +58 -3
  114. package/dist/trace/project.js +33 -1
  115. package/dist/trace/redact.d.ts +22 -11
  116. package/dist/trace/redact.js +660 -21
  117. package/dist/trace/sema-provenance.d.ts +35 -0
  118. package/dist/trace/sema-provenance.js +14 -0
  119. package/dist/turn-activity.d.ts +32 -2
  120. package/dist/turn-activity.js +29 -4
  121. package/package.json +3 -3
@@ -6,9 +6,35 @@
6
6
  * `EntitlementRuntimeCaps` themselves stay as-is below: they are core's / registry-core's own contract type
7
7
  * names, not ours to rename.)
8
8
  * It is the ENFORCE last-link of the three-stage workflows gate
9
- * (engine-can ∧ center-may ∧ shell-show): core calls it ONCE per task at prepare time with `spec.principal`,
10
- * and the engine enforces the two caps it CAN enforce — `allowWorkflows` (deny self-orchestration) and
11
- * `forceDurableGate` (force a policy `ask` onto the durable-approval wire).
9
+ * (engine-can ∧ center-may ∧ shell-show): core calls it with `spec.principal`.
10
+ *
11
+ * ⚠️ **调用次数不是「每任务一次」**([ref]① codex r1-[medium] 亲验后纠:旧句写「ONCE per task at
12
+ * prepare time」):prepare 期解析一次(prepare-task.js:1826),**并且** capture-opt-out 的 flip 动词每被打一次
13
+ * 就再解析一次(prepare-memory.js:572 的 `flip` → :538 `resolveCaptureEntitlementFresh` → :542 本 resolver)。
14
+ * 所以本 resolver 必须是**可重入、可多次调用**的,缓存的 TTL 语义对 flip 腿同样生效;把它当「每任务一次的
15
+ * 快照」来实现(例如在里面攒 per-task 状态)会在 flip 腿上出错。
16
+ *
17
+ * ⚠️ [ref]① 纠失实:核心那句曾写作「引擎只执法它能执法的**两**个 cap」—— 早已不属实。core 7.2.0
18
+ * 装树 dist **亲读**的消费点是**六**个(本仓 node_modules 实测;下列坐标除注明外均在 `dist/core/runner/` 下,
19
+ * `agents/subagent.js` 那一条已就地标出 —— 说「均在 runner 下」是 r1 抓到的第二处过头话):
20
+ * · `allowWorkflows` — prepare-task.js:1917 `runtimeCaps?.allowWorkflows !== false`(deny 自编排;:1918-1919 另发 onError)
21
+ * · `allowFork` — prepare-task.js:1867 `forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork)`(+:2418 拒由,agents/subagent.js:1575 拒词)
22
+ * · `allowObservers` — prepare-task.js:1868 `=== true`(**极性与 allow* 相反**:显式 opt-in,默认 OFF)
23
+ * · `forceDurableGate`— prepare-task.js:1644/1646 的 durable/content mandate、:2337 活 content-ask 抑制、
24
+ * :2365 durable question face(该行合取 `resolveCheckpointStore(spec, deps) !== undefined`
25
+ * ⇒ **无 checkpoint 店的部署上这一面本就不成立**,该键在那里 inert)
26
+ * · `autoMode` — prepare-task.js:1869 `=== true` **∧** `deps.autoMode !== undefined`(部署需先具备分类器;
27
+ * 显式 opt-in 极性,且 [ref] 起本侧传的是三态 org deny 位,见 toCoreRuntimeCaps 头注)
28
+ * · `allowMemoryOptOut` — prepare-memory.js:542-543 `resolveCaptureEntitlementFresh`(+prepare-task.js:2474-2475
29
+ * 的 value/faulted 投影);**极性反转,但只在 `open` 姿态下**——判决全表在
30
+ * `dist/core/runner/memory-capture-optout.js` 的 `adjudicateCaptureOptOut`,按
31
+ * `RunnerDeps.memoryCapturePolicy` 三姿态分叉(codex r1-[medium] 亲验后纠:旧句无条件
32
+ * 写「fault 读作 ALLOW」,只对其中一姿态成立):
33
+ * · `"open"`(缺省):1 只有显式 `false` 拒;**fault ⇒ ALLOW** + `faultDisclosure`(:32-37)
34
+ * · `"governed"` :14-23 要**显式 `true`** 才准;`false` / 缺席 / fault **一律拒**
35
+ * (拒由分「answered false」与「verdict absent / resolver fault」两句)
36
+ * · `"capture-required"`:4-12 **完全不看本 verdict**(逐字「resolver-independent」),恒拒
37
+ * 只有 `allowUltracode` 是被 DROP 的那一个(下文)。
12
38
  *
13
39
  * It pulls the PER-PRINCIPAL entitlement from center's `GET /api/config/effective?principal=` (via
14
40
  * {@link fetchPrincipalCaps}). center has ALREADY pre-resolved the tighten-only intersection (fleet defaults ∩
@@ -17,10 +43,16 @@
17
43
  * a per-principal TTL+ETag CACHE (so per-task prepare-time lookups don't hammer center) and FAIL-CLOSED degrade.
18
44
  *
19
45
  * 🔐 FAIL-CLOSED (core security mandate, prepare-task.ts): an entitlement-resolver outage must NEVER silently
20
- * GRANT a capability center may have denied. On a hard fetch error we return `{ allowWorkflows: false }` (deny the
21
- * OPTIONAL workflows privilege) and do NOT force the durable gate (that could strand an autonomous run on an
22
- * absent operator the same minimal degrade core applies when the resolver throws). We never serve a STALE
23
- * cached GRANT past its TTL on a fresh-fetch failure (a revoked cap must not linger because center blipped).
46
+ * GRANT a capability center may have denied. On a hard fetch error we mint `{ allowWorkflows: false, allowFork: false,
47
+ * autoMode: false }` — the three OPTIONAL **amplification** caps and do NOT force the durable gate (that could
48
+ * strand an autonomous run on an absent operator). We never serve a STALE cached GRANT past its TTL on a
49
+ * fresh-fetch failure (a revoked cap must not linger because center blipped).
50
+ * ⚠️ [ref]① 纠失实:这段曾写作「降级为 `{ allowWorkflows: false }`」,与真码不符 —— 真铸点在本文件
51
+ * `load()` 的 catch 臂(搜 `Caps face FAIL CLOSED`),逐字是上行那三键:`allowFork` 自提货批就在,
52
+ * `autoMode` 由 [ref] / core [ref] 同车加入(org deny 位不能因一次抖动在 denyTtl 窗内抬起)。
53
+ * 🔒 **三键以外的那三键刻意不铸**:`forceDurableGate` 同上(强挂人在环会搞死无人值守的 run);
54
+ * `allowObservers` 是 `=== true` opt-in,缺席已等于关;`allowMemoryOptOut` 是隐私轴的 FAULT-TRANSIT 契约
55
+ * (core types.d.ts 逐字禁止在 fault 上自造一个显式值)—— 铸 false 就是把故障臂默默换成判决臂。
24
56
  *
25
57
  * The resolver maps center's `EntitlementRuntimeCaps` → the SUBSET core enforces ({@link RuntimeCaps}): center
26
58
  * also carries `allowUltracode` (a shell/UX reasoning-tier cap the engine has no primitive for), which we DROP —
@@ -55,8 +87,11 @@ export interface EntitlementsResolverOpts {
55
87
  onError?: (err: Error, principal: string) => void;
56
88
  }
57
89
  /** Map center's `EntitlementRuntimeCaps` → the subset core's engine enforces. `allowUltracode` (shell/UX) is
58
- * dropped on purpose (not a core primitive); `allowWorkflows` / `forceDurableGate` / `allowFork` are the three the
59
- * engine enforces. Returns undefined when no enforceable cap is set (= no restriction). FAIL-CLOSED defense-in-depth:
90
+ * dropped on purpose (not a core primitive); `allowWorkflows` / `forceDurableGate` / `allowFork` are **three of the
91
+ * six** the engine reads the other three (`allowObservers` / `autoMode` / `allowMemoryOptOut`) are documented
92
+ * below and forwarded by the same body; the full consumption-point table with core 7.2.0 dist coordinates is in this
93
+ * module's header ([ref]①: this line used to say 「the three」, i.e. claimed the set was closed at three).
94
+ * Returns undefined when no enforceable cap is set (= no restriction). FAIL-CLOSED defense-in-depth:
60
95
  * do NOT trust the wire to be boolean. center serializes these as zod-validated booleans, but a schema drift / proxy
61
96
  * mangling that sent a TRUTHY non-boolean (e.g. the STRING "false") would slip past core's strict `=== false` deny
62
97
  * check and silently GRANT. So a cap that is PRESENT but not a real boolean is a contract breach → THROW (the
@@ -70,11 +105,20 @@ export interface EntitlementsResolverOpts {
70
105
  * 说过话」的显式判决,污染 env-baseline 合成序,见 applyObserverEnvOptIn)。非 boolean 虽然骗不过 core
71
106
  * 的 `=== true`(不会误 GRANT),但 PRESENT-非-boolean 与 allow* 同属同一 body 的契约破损信号 → 同款 THROW。
72
107
  *
73
- * `autoMode`(core 1.276.0 `RuntimeCaps.autoMode`):auto-mode 分类器 per-principal
74
- * 开闸线,极性同 allowObservers(`=== true` 显式 opt-in,默认 OFF;真执行还要 deployment 装配
75
- * `RunnerDeps.autoMode` 分类器面=信任门,center 键只是 entitlement 半场)。[ref]B-6a:registry-core
76
- * 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps(types.d.ts:1786)——宽读逃逸期结束,现为类型直读
77
- * (allowObservers 同款收编先例)。 */
108
+ * `autoMode`(core 1.276.0 `RuntimeCaps.autoMode`;**[ref] / core [ref] 起三态**,黑板 [ref]/[ref]):
109
+ * `undefined`(center 缺席/未答)= 不阻;`true` = 不阻;`false` = **org deny**(CC `permissions.disableAutoMode`
110
+ * 镜像)。core 武装式 = `spec.autoModeRequested === true ∧ deps.autoMode !== undefined runtimeCaps?.autoMode !== false`
111
+ * ——「用户开 + org 拒」极性(此前是 `=== true` 的「org 授予」极性,无 center 的本地部署零授予路径 ⇒ auto 永不
112
+ * 武装,真实用户诊断的病根)。
113
+ * 🔴 **上面那条武装式是 [ref] 的目标形,不是「本仓现在装的那版 core 的形」——引用它之前先看装的是哪版**
114
+ * ([ref]① 随 codex r2-[high] 补:我方在 WIRE 附录 F 照抄这一句,写成了「7.2.0 的行为」,而 npm **7.2.0
115
+ * 不含 [ref]** —— 它的 `dist/core/runner/prepare-task.js:1869` 逐字仍是 `runtimeCaps?.autoMode === true &&
116
+ * deps.autoMode !== undefined`,即旧「org 授予」极性、**缺键 ⇒ 不武装**)。判「现在是哪套极性」的唯一判据 =
117
+ * 安装包探针 `CORE_ARMS_AUTO_ON_INTENT`(`src/auto-mode-face.ts`;实测本树 7.2.0 ⇒ **false** = 旧式臂),
118
+ * 代码侧已按它分臂,失实的只是散落各处的散文。⚠️ 同形措辞在本文件 :209 / :362-364 / :548 与
119
+ * `src/boot/runtime-caps.ts:182` 还各有一处(均 [ref] 既有文字,本批未逐条改 —— 登记在 handoff 未闭环项)。
120
+ * 所以**缺键=不写键**在这条轴上是行为承重的(在新式 core 上):补一个 false 就是替 org 说了一句
121
+ * 它没说的 deny;在旧式 core 上两者同为「不武装」,差别不显形。[ref]B-6a:registry-core 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps——类型直读。 */
78
122
  export declare function toCoreRuntimeCaps(ent: EntitlementRuntimeCaps | null | undefined): RuntimeCaps | undefined;
79
123
  /**
80
124
  * #A5([ref] 件2)—— 「这台 worker 上**存在** per-principal entitlement 源吗」的**单一谓词**。
@@ -87,8 +131,9 @@ export declare function toCoreRuntimeCaps(ent: EntitlementRuntimeCaps | null | u
87
131
  * `permissionModeAuto.entitlementSource` 披露位。两处各写一份条件,就是本仓在册的
88
132
  * 「诊断页说亮、能力位说暗」病族的入口(装配与披露对同一件事各算各的)。
89
133
  *
90
- * ⚠️ **辖域**:说的是「源在不在」,不是「某个 principal 被授权了」。授权是 per-principal 的
91
- * (`RuntimeCaps.autoMode === true` principal ),任何部署级布尔都对它说不了话。
134
+ * ⚠️ **辖域**:说的是「源在不在」,不是「某个 principal 被授权/被拒了」。org deny 位是 per-principal 的
135
+ * (`RuntimeCaps.autoMode` 三态逐 principal 解,[ref] 起「缺席=不阻」),任何部署级布尔都对它说不了话——
136
+ * 所以本谓词为 false **不再**意味着 auto 不武装;「会不会武装」由 `auto-mode-face.ts` 的三项合取答。
92
137
  */
93
138
  export declare function centerEntitlementSourceWired(config: Pick<ServiceConfig, "configCenter">): boolean;
94
139
  /** A MISCONFIG diagnostic (review MEDIUM): a worker-scoped `wpt_<self>` pull token REQUIRES `?worker=<self>` to
@@ -99,8 +144,39 @@ export declare function centerEntitlementSourceWired(config: Pick<ServiceConfig,
99
144
  export declare function scopedTokenNeedsWorker(token: string, worker: string | undefined): boolean;
100
145
  /** The two per-principal faces served off ONE caps fetch + cache. */
101
146
  export interface PrincipalEntitlementsClient {
102
- /** core's `RunnerDeps.runtimeCapsResolver` seam — FAIL-CLOSED on error (deny workflows/fork). */
147
+ /** core's `RunnerDeps.runtimeCapsResolver` seam — FAIL-CLOSED on error (deny workflows/fork/autoMode;
148
+ * [ref]① 随批纠:此处是同一句降级声明的第二个写者,漏了 [ref] 加的 `autoMode`。真铸点同上)。 */
103
149
  resolveRuntimeCaps: (principal: string | undefined) => Promise<RuntimeCaps | undefined>;
150
+ /**
151
+ * 🔴 [ref] —— **只读**腿:与 {@link PrincipalEntitlementsClient.resolveRuntimeCaps} **同一只**缓存/同一条抓取/同一份
152
+ * fail-closed 判决,唯一的差别是 center 硬失败时它铸的降级项**只对自己算数**,提交腿不认。
153
+ *
154
+ * 病灶(合并重扫 finding):`/v1/capabilities` 的 auto 自查读面用的是提交腿那只 resolver,于是**一次 GET**
155
+ * 在 center 抖动的瞬间就会替这个 principal 铸下一条 `{allowWorkflows:false, allowFork:false, autoMode:false}`
156
+ * 的短窗 deny —— 随后 `denyTtlMs` 窗内该 principal 的**真提交**被这条读面留下的项拒掉。读面是纯披露,不该有
157
+ * 写副作用,更不该是别人任务被拒的原因。
158
+ *
159
+ * 姿态选择(为什么不是「读面改答 resolver_fault」):判决**逐字不变** —— 读面回答的是「此刻提交会不会武装」,
160
+ * 而 center 持续故障时提交腿确实看到 fail-closed 的 deny,所以答 `org_denied` 是当下真值;本修只摘掉副作用,
161
+ * 不改词、不改极性(`onError` 审计行照打,故障照样有可观察面)。
162
+ *
163
+ * 🔴 **界画在「这句话是谁说的」**(codex r2 [high] 逐条核后的定界,不是「读面完全不碰缓存」):
164
+ * · **成功项 = center 亲口答的那份** —— 与提交腿自己抓到的**逐字同一份**、同一条 `ttlMs`、同一个**已验证**
165
+ * 身份(读面的 principal 走 `gatedPrincipal` → `verifiedPrincipal`,与提交腿同源,读不到别人的格)。读面
166
+ * 只是把这个 principal 自己那一格**预热**了一下;org 撤销后的陈旧窗由部署级 `ttlMs` 定义,与哪一腿预热
167
+ * 的无关(提交腿自己预热同样如此),本批**零改动**。
168
+ * · **降级项 = server 自铸的判决** —— center 从没说过这句 deny,是本层在故障时替它说的。让一次**读**铸的
169
+ * 这句话去拒掉别人的**提交**,才是本修要摘的那件事。两者的差别不是「值一不一样」,是「谁是作者」。
170
+ *
171
+ * ⚠️ 两条缓存纪律(缺一就换出另一个病):
172
+ * · **成功抓取照旧落缓存**,且命中提交腿刚拉的那份即用 —— 读面不是缓存旁路;
173
+ * · **硬失败的降级项照旧落缓存,但标出处**(`readMintedDeny`):只读腿自己认(否则 center 持续故障时每次
174
+ * GET 一个 8s RTT,读面成了对着故障 center 的放大面),提交腿忽略它、照常重抓。
175
+ * in-flight 去重与提交腿共用(同 principal 的并发只一次往返);提交腿**亲历**了这次抓取(自起 or 搭在途)
176
+ * 而它硬失败 ⇒ 该降级项当场升格为执法项(codex r1 [medium]:否则一条总抢在提交前面的轮询会让每次提交
177
+ * 都重抓,denyTtl 的界对提交腿失效)。让它升格的是一次真提交自己吃到的硬失败,不是那次读。
178
+ */
179
+ peekRuntimeCaps: (principal: string | undefined) => Promise<RuntimeCaps | undefined>;
104
180
  /** The resolved per-principal scenario ruling for resolveSpec's gateScenarioRequest —
105
181
  * FAIL-OPEN on error / older center without scenario keys / no principal (undefined = no governance, legacy passthrough).
106
182
  * Scenario switching is not a privilege-amplifying face; a center blip must not pin users to `default`. */
@@ -170,6 +246,33 @@ export interface MemoryOptOutVerdictSource {
170
246
  export declare function applyMemoryOptOutGrant(base: EntitlementsResolverFn | undefined, source: MemoryOptOutVerdictSource | undefined, opts: {
171
247
  onFault: (err: Error, principal: string | undefined) => void;
172
248
  }): EntitlementsResolverFn | undefined;
249
+ /**
250
+ * [ref] —— `governed` 姿态 + **center-only** verdict 源那一形的**运行期**披露(纯观测,零判决改写)。
251
+ *
252
+ * 为什么单独存在:`allowMemoryOptOut` 在 settings-schema 的 `EntitlementRuntimeCaps` 里是**可选键**
253
+ * (`z.ZodOptional<z.ZodBoolean>`),而 {@link toCoreRuntimeCaps} 对缺席**不铸键**(FAULT-TRANSIT:缺席
254
+ * 原样过境,绝不铸 false)。于是「配了一个非 dry-run 的 center」只证**源在场**,不证 center 会**为这个
255
+ * principal 发这个键** —— 一台 center 从不发该键的 governed worker 会正常起动,然后把每一个
256
+ * `memory.capture:"off"` 都以 `memory.capture_optout_denied`(verdict absent)拒掉,而运维只看见「opt-out 全拒」。
257
+ * 启动期那一行 warn 说的是「这一形有风险」;这一行说的是「它**真的**发生了」。
258
+ *
259
+ * 姿态:**每因由一次**(防每任务刷屏)、**不改判**(caps 逐字返回,极性仍由 core 按姿态判)、
260
+ * 只在 `enabled`(= governed ∧ 无本地 grant 店 ∧ center 源在场)时挂;否则返回 **同一引用**(零行为差)。
261
+ * 缺席判据取最终合成结果:`typeof caps?.allowMemoryOptOut !== "boolean"` —— center 没发键、center 拉取
262
+ * 降级(fail-closed entry 不含本键)都算,两者对 core 是同一件事(verdict absent ⇒ 拒跑)。
263
+ *
264
+ * 🔴 **因由是闭集两员,各占各的格**(codex r1-[medium] 验真后修):`createPrincipalEntitlementsClient` 的
265
+ * `resolveRuntimeCaps` 对 `principal === undefined` **直接返回 undefined、根本不问 center**(匿名/系统 run
266
+ * 没有 per-principal 身份),而 core 对**每一个**任务都调这只 resolver(不只是带 opt-out 声明的那些)。
267
+ * 单格去重会让**第一个匿名任务**烧掉那一格,日志却说「center 省了键」——归因错、且此后真正的具名缺键
268
+ * 永不留证。两员分格,且两句话说的是两件不同的事:匿名腿在 center-only 源上**结构性**永远拿不到 verdict
269
+ * (本地 grant 表的部署缺省行才答匿名),具名腿则是「center 这次没发这个可选键」。
270
+ */
271
+ export type GovernedVerdictAbsenceReason = "anonymous_lane" | "center_verdict_absent";
272
+ export declare function applyGovernedVerdictAbsenceWarning(base: EntitlementsResolverFn | undefined, opts: {
273
+ enabled: boolean;
274
+ warn: (reason: GovernedVerdictAbsenceReason, principal: string | undefined) => void;
275
+ }): EntitlementsResolverFn | undefined;
173
276
  /**
174
277
  * observer 开闸线的 env 半场:`EXPERIMENTAL_OBSERVER_AGENTS=true` 时把
175
278
  * `RuntimeCaps.allowObservers: true` 作为**部署基线**合成进 caps resolver(core 1.270.0/1.273.0 的
@@ -193,4 +296,17 @@ export declare function applyObserverEnvOptIn(centerResolver: EntitlementsResolv
193
296
  requirePrincipal: boolean;
194
297
  warn?: (msg: string, fields?: Record<string, unknown>) => void;
195
298
  }): EntitlementsResolverFn | undefined;
299
+ /**
300
+ * [ref] ③(黑板 [ref]/[ref],core [ref] 合修)—— `PERMISSIONS_DISABLE_AUTO_MODE` 的合成层:CC
301
+ * `permissions.disableAutoMode` 的本地镜像(ANCHOR-250 §6.4 的只可更严棘轮),**tighten-only**:
302
+ * · deny=true ⇒ 每个 principal 的 `caps.autoMode` 折成 `false`(org deny 位)。center 授予 `true` **翻不回**——
303
+ * 本层写在最外(boot/runtime-caps.ts 合成序:center → observer env 基线 → memory grant → 本层),后写者赢,
304
+ * 且只往严处写;center 对该 principal 无答(undefined)同样折 false(deny 是部署级表态,不看 principal)。
305
+ * · deny 未设 ⇒ 原 resolver **同一引用**透传(零行为差;无 center 的部署仍是 `undefined` ⇒ caps 缺席 ⇒
306
+ * 三态之「缺席=不阻」,这正是 auto 在本地部署能武装起来的路径)。
307
+ * · 极性与 applyObserverEnvOptIn 相反:那条是「env 开」的放宽基线(center 带键则 center 赢),本条是
308
+ * 「env 拒」的收紧棘轮(center 带键也不能赢)。两条不共用一只函数正是因为方向相反。
309
+ * 🔴 没有 fail-open 臂:本层是纯合成,不做 I/O;base 抛错原样上抛(base 各层自带 fail-closed/缺席臂)。
310
+ */
311
+ export declare function applyLocalAutoModeDeny(base: EntitlementsResolverFn | undefined, deny: boolean): EntitlementsResolverFn | undefined;
196
312
  //# sourceMappingURL=runtime-caps-resolver.d.ts.map
@@ -80,6 +80,7 @@ export function createPrincipalEntitlementsClient(opts) {
80
80
  const onError = opts.onError ?? ((err, principal) => defaultLogger.warn("runtime_caps_resolve_failed", { principal, err: String(err) }));
81
81
  const cache = new Map();
82
82
  const inflight = new Map();
83
+ const readMintedDeny = new WeakSet();
83
84
  const MAX_CACHE = 10_000;
84
85
  const putCache = (key, entry) => {
85
86
  if (cache.size >= MAX_CACHE) {
@@ -90,9 +91,15 @@ export function createPrincipalEntitlementsClient(opts) {
90
91
  }
91
92
  cache.set(key, entry);
92
93
  };
93
- async function load(principal) {
94
+ async function load(principal, recordDeny = true) {
95
+ const entry = await loadRaw(principal, recordDeny);
96
+ if (recordDeny)
97
+ readMintedDeny.delete(entry);
98
+ return entry;
99
+ }
100
+ async function loadRaw(principal, recordDeny) {
94
101
  const cached = cache.get(principal);
95
- if (cached && cached.expiresAt > now())
102
+ if (cached && cached.expiresAt > now() && !(recordDeny && readMintedDeny.has(cached)))
96
103
  return cached;
97
104
  const dup = inflight.get(principal);
98
105
  if (dup)
@@ -135,7 +142,9 @@ export function createPrincipalEntitlementsClient(opts) {
135
142
  catch {
136
143
  e = new Error("center fetch failed with an unstringifiable rejection reason");
137
144
  }
138
- const entry = { caps: { allowWorkflows: false, allowFork: false }, expiresAt: now() + denyTtlMs };
145
+ const entry = { caps: { allowWorkflows: false, allowFork: false, autoMode: false }, expiresAt: now() + denyTtlMs };
146
+ if (!recordDeny)
147
+ readMintedDeny.add(entry);
139
148
  putCache(principal, entry);
140
149
  try {
141
150
  onError(e, principal);
@@ -152,6 +161,7 @@ export function createPrincipalEntitlementsClient(opts) {
152
161
  }
153
162
  return {
154
163
  resolveRuntimeCaps: async (principal) => (principal ? (await load(principal)).caps : undefined),
164
+ peekRuntimeCaps: async (principal) => (principal ? (await load(principal, false)).caps : undefined),
155
165
  scenarioRuling: async (principal) => (principal ? (await load(principal)).scenario : undefined),
156
166
  executionRuling: async (principal) => (principal ? (await load(principal)).execution : undefined),
157
167
  };
@@ -183,6 +193,27 @@ export function applyMemoryOptOutGrant(base, source, opts) {
183
193
  return { ...(caps ?? {}), allowMemoryOptOut: allow };
184
194
  };
185
195
  }
196
+ export function applyGovernedVerdictAbsenceWarning(base, opts) {
197
+ if (!opts.enabled)
198
+ return base;
199
+ const warned = new Set();
200
+ return async (principal) => {
201
+ const caps = base !== undefined ? await base(principal) : undefined;
202
+ if (typeof caps?.allowMemoryOptOut !== "boolean") {
203
+ const reason = principal === undefined ? "anonymous_lane" : "center_verdict_absent";
204
+ if (!warned.has(reason)) {
205
+ warned.add(reason);
206
+ try {
207
+ opts.warn(reason, principal);
208
+ }
209
+ catch (hookErr) {
210
+ recordFailOpen("server.memory-optout-verdict-absence.warn-hook-threw", String(hookErr));
211
+ }
212
+ }
213
+ }
214
+ return caps;
215
+ };
216
+ }
186
217
  export function applyObserverEnvOptIn(centerResolver, opts) {
187
218
  if (!opts.experimentalObserverAgents)
188
219
  return centerResolver;
@@ -196,4 +227,11 @@ export function applyObserverEnvOptIn(centerResolver, opts) {
196
227
  return () => ({ allowObservers: true });
197
228
  return async (principal) => ({ allowObservers: true, ...(await centerResolver(principal)) });
198
229
  }
230
+ export function applyLocalAutoModeDeny(base, deny) {
231
+ if (!deny)
232
+ return base;
233
+ if (base === undefined)
234
+ return () => ({ autoMode: false });
235
+ return async (principal) => ({ ...(await base(principal)), autoMode: false });
236
+ }
199
237
  //# sourceMappingURL=runtime-caps-resolver.js.map
@@ -66,6 +66,9 @@ export type SettingsPermissionMode = "default" | "acceptEdits" | "plan" | "bypas
66
66
  * two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
67
67
  */
68
68
  export declare function coercePermissionMode(raw: unknown): SettingsPermissionMode | undefined;
69
+ /** 五模式闭集的**严格**读法([ref] ④:`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,
70
+ * 不走上面那条给 resume 重放留的 `default` 宽臂)。上面的宽读只是本函数外再包一层折叠,词表单点在此。 */
71
+ export declare function coercePermissionModeStrict(raw: unknown): SettingsPermissionMode | undefined;
69
72
  /** L2 ultracode ([ref]): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
70
73
  * preset. ultracode FLOORS at the "ultra" tier — core's single source `resolveReasoningProfile("ultra").thinking`
71
74
  * (= "max"), never a hardcoded literal — so an explicit higher tier still wins (none exists above max today;
@@ -129,6 +132,18 @@ export interface ParsedTaskSettings {
129
132
  /** Carried for completeness; v1 folds `ask` into the gate via the deny-wins base (see {@link applyTaskSettings}). */
130
133
  ask?: string[];
131
134
  defaultMode?: SettingsPermissionMode;
135
+ /** [ref] 件⑥([ref] §3.4,[ref];PM 裁 [ref]):CC settings 的 kill-switch `permissions.disableAutoMode`,cli 投影
136
+ * (settingsRulesWire.ts toWireSettings)而此前本白名单丢弃 ⇒ 用户/managed 层关 auto 到不了引擎。语义 = 只能关
137
+ * 不能开(tighten-only):把生效模式 auto 折 default(座不写 ⇒ 不武装)。折叠单点 = {@link effectivePermissionMode}。
138
+ * `disableBypassPermissionsMode` 本批不做落点([ref] 另立)。
139
+ *
140
+ * 🔴 **受理集 = `"disable" | true | false`**([ref] 放宽一档,原为「只认字面 `"disable"`」)。两套拼写**都是已发布
141
+ * 契约**:CC 250 的 `Ct` 严极词 `"disable"`,与 `@sema-agent/sdk` 8.1.0 `SettingsPermissions.disableAutoMode?: boolean`
142
+ * (`dist/settings.d.ts`)。按已发布 SDK 类型写 `true`/`false` 的客户端此前一律 400 —— [ref] 的「只认 disable」是
143
+ * 「拒非法**拼错**」,boolean 不是拼错。归一化**不做**:三形逐字过 parse(拒体/日志里看得见调用方真发的是哪一形),
144
+ * 语义归一只在折叠单点做(`"disable"` ≡ `true`)。受理集之外(别的词/数字/对象)= fresh 提交 HTTP 门 400,
145
+ * resume 重放宽臂丢弃(两层姿势同 defaultMode)。 */
146
+ disableAutoMode?: "disable" | boolean;
132
147
  };
133
148
  model?: string;
134
149
  outputStyle?: string;
@@ -164,12 +179,51 @@ export declare const MAX_SETTINGS_PERMISSION_RULES = 1000;
164
179
  export declare const MAX_SETTINGS_ENV_VARS = 256;
165
180
  export declare const MAX_SETTINGS_ENV_VALUE_CHARS = 32768;
166
181
  export declare const MAX_SETTINGS_ENV_KEY_CHARS = 256;
182
+ /** 本 service 真正**消费**的 `settings` 顶层键 —— 闭集。每一个都有消费点:
183
+ * `env`/`hooks`/`model`/`outputStyle`/`permissions`/`ultracode` 在 {@link parseTaskSettings};
184
+ * `webSearch` 在 `capabilities/scenarios.ts`(`webSearchConfigFromSettings`,单用户闸)。
185
+ * 加键必须同时加消费点,否则就是往闭集里塞一个新的静默丢。 */
186
+ export declare const TASK_SETTINGS_KEYS: readonly ["env", "hooks", "model", "outputStyle", "permissions", "ultracode", "webSearch"];
187
+ /** 本 service 消费的 `settings.permissions` 子键 —— 闭集(值形校验另有既有门,本门只判键名)。 */
188
+ export declare const TASK_SETTINGS_PERMISSION_KEYS: readonly ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
189
+ /** CC/SDK 合约里合法、本 service **不接线**的顶层键(出处 = `@sema-agent/sdk` `SemaSettings` 尾注逐字点名的
190
+ * 三个 deferred CC 键)。词表外的 CC 键落 `unknown` —— 两列都是 400,标签只影响文案,不影响判决。 */
191
+ export declare const TASK_SETTINGS_UNSUPPORTED_KEYS: readonly ["apiKeyHelper", "sandbox", "statusLine"];
192
+ /** 同上,`settings.permissions` 子层(出处 = SDK `SettingsPermissions` 的三个声明键)。
193
+ * `additionalDirectories` 有**同请求替代面**:顶层 `body.additionalDirectories`([ref],resolve-spec 真消费)。 */
194
+ export declare const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS: readonly ["additionalDirectories", "disableBypassPermissionsMode"];
195
+ /** 拒体里逐字回显的键名条数上限。键名原样回显(不猜语义),但**条数有界** —— 一个塞满键的体不许把 400 响应
196
+ * 放大成流量面。超出部分只报数。 */
197
+ export declare const MAX_SETTINGS_KEY_ECHO = 20;
198
+ /** {@link taskSettingsKeyIssue} 的拒体:一句 wire 文案 + 两张分类键名表(消费端可机读,不必解析文案)。 */
199
+ export interface TaskSettingsKeyIssue {
200
+ message: string;
201
+ /** 词表外的键(拼错/未来键),`settings.x` / `settings.permissions.x` 全路径形,已清洗+截断+排序+封顶。 */
202
+ unknownKeys: string[];
203
+ /** CC/SDK 合约合法但本 service 不接线的键,同形。 */
204
+ unsupportedKeys: string[];
205
+ }
206
+ /**
207
+ * FRESH 提交面的 `settings` 键闭集裁决:顶层或 `permissions` 子层出现受理集之外的键 ⇒ 返回拒体(调用方 400
208
+ * `request.body_shape`);全部受理 ⇒ `null`。
209
+ *
210
+ * 判据边界(有意窄):
211
+ * · 只判**键名**。值形校验(outputStyle 上限 / permissions 数组上限 / env 形 / hooks 契约 / defaultMode 闭集)
212
+ * 仍归各自既有门,本门零改动、零抢话。
213
+ * · `settings` 本身非对象、或 `permissions` 非对象(数组/标量/null)⇒ 本门**不判**,留给既有的
214
+ * 「settings must be an object」/「settings.permissions must be an object」两道门,免得同一个体被两句话抢答。
215
+ * · `permissions: null` 是既有钉住的「容器缺席」形,照旧不咬。
216
+ */
217
+ export declare function taskSettingsKeyIssue(raw: unknown): TaskSettingsKeyIssue | null;
167
218
  /**
168
219
  * Validate an untrusted `body.settings` (raw `unknown` off the wire) into the service-trusted {@link
169
- * ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked; unknown keys are ignored
170
- * (forward-compatible, matching the SDK's open `[k: string]: unknown`). `defaultMode` accepts the FIVE modes
171
- * (post-[ref]; unknown values coerce to `default` — see the header note). Returns the
220
+ * ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked. `defaultMode` accepts the FIVE
221
+ * modes (post-[ref]; unknown values coerce to `default` see the header note). Returns the
172
222
  * present-but-deferred field names so the caller can warn/advertise rather than silently dropping `env`/`hooks`.
223
+ *
224
+ * 🔴 未知键在**本层**照旧忽略,而那**不是** wire 的现行为([ref] 件①,PM 裁「拒」[ref]):本层是 RESUME
225
+ * 重放也走的宽容层,一个 4xx 在这里会把存量 parked 任务永久砖死;FRESH 提交面由 {@link taskSettingsKeyIssue}
226
+ * 在 `server.ts` prepareSpec 里 400 响亮拒 + 逐字列名。双层姿势与 outputStyle / permissionMode 两处逐字相同。
173
227
  */
174
228
  export declare function parseTaskSettings(raw: unknown): ParseTaskSettingsResult;
175
229
  /** Defensive acceptance of the `appendSystemPrompt` rider ([ref] R2 + codex F2): the HTTP 400 gate (prepareSpec,
@@ -3,12 +3,18 @@ import { findCcRuleFormNames, findUnmatchableToolNames, formatUnmatchableToolNam
3
3
  import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
4
4
  import { parseHooksConfig } from "./hooks/hook-runner.js";
5
5
  export function coercePermissionMode(raw) {
6
- if (raw === "plan" || raw === "acceptEdits" || raw === "default" || raw === "bypassPermissions" || raw === "auto")
7
- return raw;
6
+ const strict = coercePermissionModeStrict(raw);
7
+ if (strict !== undefined)
8
+ return strict;
8
9
  if (typeof raw === "string" && raw.length > 0)
9
10
  return "default";
10
11
  return undefined;
11
12
  }
13
+ export function coercePermissionModeStrict(raw) {
14
+ if (raw === "plan" || raw === "acceptEdits" || raw === "default" || raw === "bypassPermissions" || raw === "auto")
15
+ return raw;
16
+ return undefined;
17
+ }
12
18
  export function effectiveThinking(reasoningEffort, ultracode) {
13
19
  const explicit = isThinkingLevel(reasoningEffort) ? reasoningEffort : undefined;
14
20
  if (!ultracode)
@@ -21,7 +27,11 @@ export function withPermissionMode(settings, mode) {
21
27
  return { ...base, permissions: { ...base.permissions, defaultMode: mode } };
22
28
  }
23
29
  export function effectivePermissionMode(body, settings) {
24
- return coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
30
+ const requested = coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
31
+ const kill = settings?.permissions?.disableAutoMode;
32
+ if (requested === "auto" && (kill === "disable" || kill === true))
33
+ return "default";
34
+ return requested;
25
35
  }
26
36
  export function shellGateForMode(effMode) {
27
37
  switch (effMode) {
@@ -62,6 +72,61 @@ function cleanStringArray(v) {
62
72
  const out = v.filter((x) => typeof x === "string" && x.length > 0).slice(0, MAX_SETTINGS_PERMISSION_RULES);
63
73
  return out.length > 0 ? out : undefined;
64
74
  }
75
+ export const TASK_SETTINGS_KEYS = ["env", "hooks", "model", "outputStyle", "permissions", "ultracode", "webSearch"];
76
+ export const TASK_SETTINGS_PERMISSION_KEYS = ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
77
+ export const TASK_SETTINGS_UNSUPPORTED_KEYS = ["apiKeyHelper", "sandbox", "statusLine"];
78
+ export const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS = ["additionalDirectories", "disableBypassPermissionsMode"];
79
+ export const MAX_SETTINGS_KEY_ECHO = 20;
80
+ const MAX_SETTINGS_KEY_ECHO_CHARS = 64;
81
+ const ACCEPTED_TOP = new Set(TASK_SETTINGS_KEYS);
82
+ const ACCEPTED_PERM = new Set(TASK_SETTINGS_PERMISSION_KEYS);
83
+ const UNSUPPORTED_TOP = new Set(TASK_SETTINGS_UNSUPPORTED_KEYS);
84
+ const UNSUPPORTED_PERM = new Set(TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS);
85
+ function echoSettingsKey(prefix, key) {
86
+ const clean = key.replace(CONTROL_AND_BIDI_STRIP_RE, "");
87
+ return `${prefix}${clean.length > MAX_SETTINGS_KEY_ECHO_CHARS ? `${clean.slice(0, MAX_SETTINGS_KEY_ECHO_CHARS)}…` : clean}`;
88
+ }
89
+ export function taskSettingsKeyIssue(raw) {
90
+ if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
91
+ return null;
92
+ const unknown = [];
93
+ const unsupported = [];
94
+ const scan = (bag, prefix, accepted, tolerated) => {
95
+ for (const k of Object.keys(bag)) {
96
+ if (accepted.has(k))
97
+ continue;
98
+ (tolerated.has(k) ? unsupported : unknown).push(echoSettingsKey(prefix, k));
99
+ }
100
+ };
101
+ scan(raw, "settings.", ACCEPTED_TOP, UNSUPPORTED_TOP);
102
+ const perms = raw.permissions;
103
+ if (perms !== null && typeof perms === "object" && !Array.isArray(perms)) {
104
+ scan(perms, "settings.permissions.", ACCEPTED_PERM, UNSUPPORTED_PERM);
105
+ }
106
+ if (unknown.length === 0 && unsupported.length === 0)
107
+ return null;
108
+ unknown.sort();
109
+ unsupported.sort();
110
+ const clause = (names, label) => {
111
+ const shown = names.slice(0, MAX_SETTINGS_KEY_ECHO);
112
+ const more = names.length - shown.length;
113
+ return `${label}: ${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
114
+ };
115
+ const parts = [
116
+ ...(unknown.length > 0 ? [clause(unknown, "unknown (not a settings key here — check the spelling)")] : []),
117
+ ...(unsupported.length > 0 ? [clause(unsupported, "unsupported (a CC settings key this service does not wire)")] : []),
118
+ ];
119
+ const dirHint = unsupported.some((n) => n.endsWith(".additionalDirectories"))
120
+ ? " Extra directories ride the top-level body.additionalDirectories instead."
121
+ : "";
122
+ return {
123
+ message: `settings: unrecognized key(s) — refused rather than dropped, because a dropped key reads as "in effect" to the caller. ` +
124
+ `${parts.join("; ")}.${dirHint} ` +
125
+ `Accepted: settings.{${[...TASK_SETTINGS_KEYS].join(",")}} and settings.permissions.{${[...TASK_SETTINGS_PERMISSION_KEYS].join(",")}}`,
126
+ unknownKeys: unknown.slice(0, MAX_SETTINGS_KEY_ECHO),
127
+ unsupportedKeys: unsupported.slice(0, MAX_SETTINGS_KEY_ECHO),
128
+ };
129
+ }
65
130
  export function parseTaskSettings(raw) {
66
131
  if (raw === null || raw === undefined || typeof raw !== "object")
67
132
  return { deferred: [] };
@@ -90,8 +155,16 @@ export function parseTaskSettings(raw) {
90
155
  const ask = cleanStringArray(p.ask);
91
156
  const mode = p.defaultMode;
92
157
  const defaultMode = mode === "default" || mode === "acceptEdits" || mode === "plan" || mode === "bypassPermissions" || mode === "auto" ? mode : undefined;
93
- if (allow || deny || ask || defaultMode) {
94
- out.permissions = { ...(allow ? { allow } : {}), ...(deny ? { deny } : {}), ...(ask ? { ask } : {}), ...(defaultMode ? { defaultMode } : {}) };
158
+ const rawKill = p.disableAutoMode;
159
+ const disableAutoMode = rawKill === "disable" || rawKill === true || rawKill === false ? rawKill : undefined;
160
+ if (allow || deny || ask || defaultMode || disableAutoMode !== undefined) {
161
+ out.permissions = {
162
+ ...(allow ? { allow } : {}),
163
+ ...(deny ? { deny } : {}),
164
+ ...(ask ? { ask } : {}),
165
+ ...(defaultMode ? { defaultMode } : {}),
166
+ ...(disableAutoMode !== undefined ? { disableAutoMode } : {}),
167
+ };
95
168
  }
96
169
  }
97
170
  if (typeof s.model === "string" && s.model.length > 0)
@@ -15,13 +15,43 @@
15
15
  * source must NOT leak the LLM-authored workflow engine). WITH a resolver, core enforces `allowWorkflows`
16
16
  * per-principal (deny → fail-closed). Single-user (owns its own worker) honors directly. Three-gate: engine-can ∧
17
17
  * center-may(allowWorkflows) ∧ shell-show.
18
+ *
19
+ * 🔴 [ref](2026-09-03):fail-closed 的**方向**从来是对的,错的是它**没人说**。多租户 + 无 entitlement 源
20
+ * 的部署上,这条腿把 `body.selfOrchestration:true`(以及 `settings.ultracode` 展开出来的同一个意图)静默
21
+ * 折成 `undefined`,而 `/v1/capabilities` 照报 `workflows:true` ⇒ 「壳显示支持、引擎按普通 run 跑」。修法
22
+ * 是把部署级半场抽成下面的 {@link selfOrchestrationDenial}(能力位与准入**同一只**谓词),提交面在拒因
23
+ * 在场时**响亮**拒(`routes` 的 501 `capability.self_orchestration_required`),不再静默折。
18
24
  */
19
25
  export declare function selfOrchestrationFromBody(body: {
20
26
  selfOrchestration?: unknown;
21
- }, config: {
27
+ }, config: SelfOrchestrationGateConfig, hasEntitlementResolver: boolean): true | undefined;
28
+ /** 本模块读的部署面(`selfOrchestrationEnabled` 恒在真 config 上;遗留双重断言 stub 可能真缺,故一律 `!== true` 读)。 */
29
+ export interface SelfOrchestrationGateConfig {
22
30
  selfOrchestrationEnabled: boolean;
23
31
  requirePrincipal?: boolean;
24
- }, hasEntitlementResolver: boolean): true | undefined;
32
+ }
33
+ /**
34
+ * [ref](DEBTS,P1)—— 自编排准入的**部署级拒因**闭集。`null` = 本部署这一层不拒(是否真跑还看 engine-can
35
+ * 与 core 的 per-principal `allowWorkflows`)。
36
+ *
37
+ * 唯一成员 `"entitlement_resolver_absent"`:多租户(`requirePrincipal === true`)+ 运维**显式开了**
38
+ * `SELF_ORCHESTRATION_ENABLED` + 没有任何 per-principal entitlement 源(`hasEntitlementResolver` —— 判据由
39
+ * 调用方从 `centerEntitlementSourceWired(config)` / 装配好的 caps 客户端取)。这一形下上面的准入腿对
40
+ * **每一个** principal 都 fail-closed(理由见函数上方注:core 的 `allowWorkflows` 是 tighten-only,
41
+ * resolver 缺席 = 无限制,所以本层必须自己关死),于是这台机器上的自编排是**结构性死的**。
42
+ *
43
+ * 🔴 为什么这不是布尔而是**闭集拒因**:能力面要能对壳说清「为什么暗」——「这份二进制/这台机器没有工作流
44
+ * 引擎」与「引擎在、但本部署配不出 per-principal 授权」是两件事,壳对它们要做的事不同(前者别给这一格;
45
+ * 后者该提示运维接 center)。合成一个布尔就把这两件事打成同一个 false —— 与 `permissionModeAuto` 那一位
46
+ * 「三项不合成一个布尔」同判据。
47
+ *
48
+ * 🔴 为什么 `selfOrchestrationEnabled` 关着时返回 `null` 而不是另一个拒因:那是**运维刻意的姿态**
49
+ * (多租户缺省就是关),能力面的 engine-can 半场自己就已经诚实为假;而本拒因说的是**半配置**
50
+ * (阀门开着而配不出来),两者的处置不同——后者才需要点名。这也正是本件的补偿旋钮:把
51
+ * `SELF_ORCHESTRATION_ENABLED` 关掉(对这台机器而言是唯一诚实的姿态)即刻回到姿态形。
52
+ */
53
+ export type SelfOrchestrationDenial = "entitlement_resolver_absent";
54
+ export declare function selfOrchestrationDenial(config: SelfOrchestrationGateConfig, hasEntitlementResolver: boolean): SelfOrchestrationDenial | null;
25
55
  /**
26
56
  * §4 (Fork) — map the per-task `body.enableFork` intent onto `TaskSpec.enableFork`. core 1.257 ([ref] BREAKING)
27
57
  * RETIRED the standalone `Fork` tool; a fork is now `Agent(subagent_type:"fork")`, and `enableFork` is one of the two
@@ -1,9 +1,14 @@
1
1
  export function selfOrchestrationFromBody(body, config, hasEntitlementResolver) {
2
- if (body.selfOrchestration !== true || !config.selfOrchestrationEnabled)
2
+ if (body.selfOrchestration !== true || config.selfOrchestrationEnabled !== true)
3
3
  return undefined;
4
+ return selfOrchestrationDenial(config, hasEntitlementResolver) === null ? true : undefined;
5
+ }
6
+ export function selfOrchestrationDenial(config, hasEntitlementResolver) {
7
+ if (config.selfOrchestrationEnabled !== true)
8
+ return null;
4
9
  if (config.requirePrincipal !== true)
5
- return true;
6
- return hasEntitlementResolver ? true : undefined;
10
+ return null;
11
+ return hasEntitlementResolver ? null : "entitlement_resolver_absent";
7
12
  }
8
13
  export function enableForkFromBody(body, config, hasEntitlementResolver) {
9
14
  if (!config.forkEnabled)
@@ -541,6 +541,10 @@ export declare function resolveStreamApprovalGate(input: StreamApprovalGateInput
541
541
  * `prepare-task.js` 的 durableMandate/contentMandate(:1415/:1417)与 durableQuestionFace(:2136)等),
542
542
  * 故不在本函数内。
543
543
  *
544
+ * 🔴 **[ref](半接线旋钮族)**:`windowZero` 的第一源(`STREAM_ASK_WINDOW_MS=0`)自此**不再挂在
545
+ * `streamApprovalOn` 后面** —— 那是部署级旋钮,它的语义不该因为这台机器有没有 durable ask 账而改变
546
+ * (`ApprovalLegAssembly.windowZero` 顶注写了那条病)。第三源(贴 deadline)照旧只在协议上场时参与。
547
+ *
544
548
  * **`windowZero` 的两件事必须同时做**(调用方契约):不包 ALS **且**注入 immediate-unavailable 闭包。
545
549
  * 只做一件另一条路仍会呈卡。⚠️ [ref] 起**三腿都有** per-task `spec.onAsk` 装配点(sync=routes/tasks.ts,
546
550
  * bg=runs.ts,resume=server.ts driveResumeLeg),windowZero 的第二件在三腿都是显式闭包 —— 旧文「bg/
@@ -554,7 +558,14 @@ export interface ApprovalLegAssembly {
554
558
  * 同 (owner, session) 活流呈活卡(sync 腿 [ref] 起本就如此;bg/resume 的这条行为差 = [ref] CHANGELOG
555
559
  * 披露的行为面变更)。旧文「本腿逐字保持协议之前的行为」只对协议件成立,别再把它读成「协议关 = 零呈卡」。 */
556
560
  active: boolean;
557
- /** §8.3 窗=0 恒 park。`active` 为假时恒 false(协议都没上场,谈不上关窗) */
561
+ /** §8.3 窗=0 恒 park。
562
+ * 🔴 **[ref] 起它与 `active` 解耦**(旧文「`active` 为假时恒 false —— 协议都没上场,谈不上关窗」已
563
+ * 作废):`STREAM_ASK_WINDOW_MS` 是**部署级**旋钮,`0` 的语义(运维显式关窗 ⇒ 恒走
564
+ * {@link ToolApprovalCoordinator.unattendedAskOutcome},连卡都不发)在两条车道上必须是同一个意思。
565
+ * 旧形下 local 车道(`volatile_ask_ledger`)/ 无 park 设施 / 显式关协议的部署配了 `0`,拿到的是一张
566
+ * 等满 5min 的活卡 —— 与它自己的域注正好相反,且没有任何一行说出来。
567
+ * ⚠️ 「窗**缺席**」(`windowMs: undefined` = 这份装配根本没有 streamApproval 段,只出现在 stub-harness)
568
+ * **不是**关窗:那时逐字保持活卡腿。 */
558
569
  windowZero: boolean;
559
570
  /** §7.3 本 leg 的 walltime deadline(`performance.now()` 单调基)。只在 `active ∧ ¬windowZero ∧
560
571
  * 有 walltime 墙` 时在场 —— 关着时供值会把既有 5min 活卡窗按三元公式缩短(§7.2 真行为变更)。 */
@@ -583,8 +594,13 @@ export declare function createApprovalCardEmitter(sinks: {
583
594
  export declare function resolveApprovalLeg(input: {
584
595
  /** = `resolveStreamApprovalGate(...).active`(协议在本 worker 上场没有)。 */
585
596
  streamApprovalOn: boolean;
586
- /** `config.streamApproval.windowMs`(`STREAM_ASK_WINDOW_MS`)。 */
587
- windowMs: number;
597
+ /** `config.streamApproval.windowMs`(`STREAM_ASK_WINDOW_MS`)。
598
+ * 🔴 [ref]:`undefined` = **这份装配没有 `streamApproval` 段**(只出现在只填被测键的 stub-harness ——
599
+ * 真实 boot 无条件铸该段,见 `config.ts`)。此前三个调用点各写 `?? 0` 把这一形折成「0」,在旧语义下
600
+ * 是死代码(`windowZero` 那时挂在 `streamApprovalOn` 后面,协议关就恒 false);[ref] 让 `0` 在协议关的
601
+ * 腿上也真生效之后,那个折算会把一台**什么都没配**的 harness 判成「运维显式关窗」。故本字段收成
602
+ * `number | undefined`,由每个调用点如实交出「有没有这个值」,不许再就地编一个。 */
603
+ windowMs: number | undefined;
588
604
  /** `config.streamAskWindowMarginMs`(`STREAM_ASK_WINDOW_MARGIN_MS`)。 */
589
605
  windowMarginMs: number;
590
606
  /** `spec.limits.maxWalltimeMs`。缺席 = 没有 walltime 墙 ⇒ 第三源不参与(**缺席 ≠ 0**)。 */
@@ -802,6 +818,8 @@ export declare class ToolApprovalCoordinator {
802
818
  /** 两份候选是否**逐条逐键**相等(顺序即展示序 ⇒ 顺序敏感)。两侧都缺席 = 相等;一侧缺席 = 不等。
803
819
  * 用在幂等重入的「行 = 真源」对账上(见 `ensureAsk` 那段撤回臂)。 */
804
820
  private static ruleOffersEqual;
821
+ private static batchMemberEqual;
822
+ private static uncoveredDetailEqual;
805
823
  /**
806
824
  * [ref] / 5.58 复审 F-1 —— OFFER 的**文本座尺**(红先修)。
807
825
  *
@@ -824,6 +842,11 @@ export declare class ToolApprovalCoordinator {
824
842
  * 位置在**素材铸点**(与基数帽同一处):两族帧 + `card_json` + 回决口的等式左边从此拿的是**同一份**
825
843
  * 已截素材,而不是两处各截一遍。
826
844
  */
845
+ /** 兑付侧座(single 的 rule/command、command 成员的 rule/command、directoryRead 成员的 rule/directory)任一超尺?
846
+ * `segment` 与 `uncoveredDetail[].segment` 是纯渲染座,不在此列(截长是诚实省略)。 */
847
+ private static ruleOfferTextsOverSize;
848
+ /** codex r1 [high]([ref]):offer 的自称座与 `parseAllowRuleText(rule)` 派生的三元组逐字一致?(理由见调用点) */
849
+ private static ruleOfferDerivedFactsAgree;
827
850
  private static boundRuleOfferTexts;
828
851
  private buildRuleLaneMaterial;
829
852
  /** [ref]([ref]/[ref] 双属主裁定):把某 wire run 的**流内未决 ask** 立即转 durable park——断连支专用。