/** * Entitlements resolver — the SERVICE-side wiring of core 1.157's `RunnerDeps.runtimeCapsResolver` ([ref] §K). * ([ref] N11: this module's own exports were renamed `*Caps*` → `*Entitlements*` to stop sharing a word * root with the UNRELATED `capabilities` face (deployment route discovery, `src/capabilities/`) and * `ScenarioBundle` assembly — "caps" meant three different things across those three faces. `RuntimeCaps` / * `EntitlementRuntimeCaps` themselves stay as-is below: they are core's / registry-core's own contract type * names, not ours to rename.) * It is the ENFORCE last-link of the three-stage workflows gate * (engine-can ∧ center-may ∧ shell-show): core calls it with `spec.principal`. * * ⚠️ **调用次数不是「每任务一次」**([ref]① 随 codex r1-[medium] 亲验后纠:旧句写「ONCE per task at * prepare time」):prepare 期解析一次(prepare-task.js),**并且** capture-opt-out 的 flip 动词每被打一次 * 就再解析一次(prepare-memory.js 的 `flip` → 同文件 `resolveCaptureEntitlementFresh` → 本 resolver)。 * 所以本 resolver 必须是**可重入、可多次调用**的,缓存的 TTL 语义对 flip 腿同样生效;把它当「每任务一次的 * 快照」来实现(例如在里面攒 per-task 状态)会在 flip 腿上出错。 * * ⚠️ [ref]① 纠失实:核心那句曾写作「引擎只执法它能执法的**两**个 cap」—— 早已不属实。core 7.2.0 * 装树 dist **亲读**的消费点是**六**个(本仓 node_modules 实测;下列坐标除注明外均在 `dist/core/runner/` 下, * `agents/subagent.js` 那一条已就地标出 —— 说「均在 runner 下」是 r1 抓到的第二处过头话): * · `allowWorkflows` — prepare-task.js `runtimeCaps?.allowWorkflows !== false`(deny 自编排;同处另发一条 onError) * · `allowFork` — prepare-task.js `forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork)`(拒由同处铸出,拒词在 agents/subagent.js) * · `allowObservers` — prepare-task.js `=== true`(**极性与 allow* 相反**:显式 opt-in,默认 OFF) * · `forceDurableGate`— prepare-task.js 的 durable/content mandate、活 content-ask 抑制、 * durable question face(该处合取 `resolveCheckpointStore(spec, deps) !== undefined` * ⇒ **无 checkpoint 店的部署上这一面本就不成立**,该键在那里 inert) * · `autoMode` — prepare-task.js `=== true` **∧** `deps.autoMode !== undefined`(部署需先具备分类器; * 显式 opt-in 极性,且 [ref] 起本侧传的是三态 org deny 位,见 toCoreRuntimeCaps 头注) * · `allowMemoryOptOut` — prepare-memory.js `resolveCaptureEntitlementFresh`(+prepare-task.js * 的 value/faulted 投影);**极性反转,但只在 `open` 姿态下**——判决全表在 * `dist/core/runner/memory-capture-optout.js` 的 `adjudicateCaptureOptOut`,按 * `RunnerDeps.memoryCapturePolicy` 三姿态分叉(codex r1-[medium] 亲验后纠:旧句无条件 * 写「fault 读作 ALLOW」,只对其中一姿态成立): * · `"open"`(缺省):1 只有显式 `false` 拒;**fault ⇒ ALLOW** + `faultDisclosure`(:32-37) * · `"governed"` :14-23 要**显式 `true`** 才准;`false` / 缺席 / fault **一律拒** * (拒由分「answered false」与「verdict absent / resolver fault」两句) * · `"capture-required"`:4-12 **完全不看本 verdict**(逐字「resolver-independent」),恒拒 * 只有 `allowUltracode` 是被 DROP 的那一个(下文)。 * * It pulls the PER-PRINCIPAL entitlement from center's `GET /api/config/effective?principal=` (via * {@link fetchPrincipalCaps}). center has ALREADY pre-resolved the tighten-only intersection (fleet defaults ∩ * tier ⊕ roles + time-bounded overrides); the service passes the resolved value through VERBATIM (per center * ruling: "service 只透传 resolved 值、无需再 tighten"). The resolver adds only the data-plane concerns center can't: * a per-principal TTL+ETag CACHE (so per-task prepare-time lookups don't hammer center) and FAIL-CLOSED degrade. * * 🔐 FAIL-CLOSED (core security mandate, prepare-task.ts): an entitlement-resolver outage must NEVER silently * GRANT a capability center may have denied. On a hard fetch error we mint `{ allowWorkflows: false, allowFork: false, * autoMode: false }` — the three OPTIONAL **amplification** caps — and do NOT force the durable gate (that could * strand an autonomous run on an absent operator). We never serve a STALE cached GRANT past its TTL on a * fresh-fetch failure (a revoked cap must not linger because center blipped). * ⚠️ [ref]① 纠失实:这段曾写作「降级为 `{ allowWorkflows: false }`」,与真码不符 —— 真铸点在本文件 * `load()` 的 catch 臂(搜 `Caps face FAIL CLOSED`),逐字是上行那三键:`allowFork` 自提货批就在, * `autoMode` 由 [ref] / core [ref] 同车加入(org deny 位不能因一次抖动在 denyTtl 窗内抬起)。 * 🔒 **三键以外的那三键刻意不铸**:`forceDurableGate` 同上(强挂人在环会搞死无人值守的 run); * `allowObservers` 是 `=== true` opt-in,缺席已等于关;`allowMemoryOptOut` 是隐私轴的 FAULT-TRANSIT 契约 * (core types.d.ts 逐字禁止在 fault 上自造一个显式值)—— 铸 false 就是把故障臂默默换成判决臂。 * * The resolver maps center's `EntitlementRuntimeCaps` → the SUBSET core enforces ({@link RuntimeCaps}): center * also carries `allowUltracode` (a shell/UX reasoning-tier cap the engine has no primitive for), which we DROP — * this seam feeds the ENGINE, and `allowUltracode` is enforced at the service settings-resolution layer, not here. */ import type { RuntimeCaps } from "@sema-agent/core"; import type { EntitlementRuntimeCaps } from "@sema-agent/settings-schema"; import { fetchPrincipalCaps, type ExecutionRuling } from "./config-center/facade.js"; import type { ScenarioRuling } from "./capabilities/scenarios.js"; import type { ServiceConfig } from "./config-types.js"; import { type Logger } from "./observability/logger.js"; /** test-only: reset the once-per-process trace so each test observes a fresh "first hit". */ export declare function resetExecutionLaneDefaultTraceForTest(): void; export interface EntitlementsResolverOpts { baseUrl: string; token: string; /** This worker's name (`SEMA_REGISTRY_WORKER`), passed as `?worker=` so a worker-scoped `wpt_` token can * resolve its OWN worker's principal caps (caps-only); harmless on a full token (the caps value * is worker-independent). Omitted ⇒ no `?worker=` (a full-token, non-fleet deployment). */ worker?: string; /** Per-principal cache TTL (ms). A resolved entitlement is reused for this long before an ETag-conditional * re-pull, collapsing per-task prepare-time lookups to ~1 center call/principal/TTL. Default 60s. */ ttlMs?: number; /** Short window (ms) a FAIL-CLOSED deny is cached to avoid hammering center (8s timeout each) on a sustained * outage, without pinning a deny long past a transient blip. Default min(ttlMs, 10s). */ denyTtlMs?: number; /** Injected for tests. */ fetchImpl?: typeof fetch; fetchCaps?: typeof fetchPrincipalCaps; now?: () => number; /** Observability hook for a resolve failure (defaults to a `runtime_caps_resolve_failed` warn). */ onError?: (err: Error, principal: string) => void; } /** Map center's `EntitlementRuntimeCaps` → the subset core's engine enforces. `allowUltracode` (shell/UX) is * dropped on purpose (not a core primitive); `allowWorkflows` / `forceDurableGate` / `allowFork` are **three of the * six** the engine reads — the other three (`allowObservers` / `autoMode` / `allowMemoryOptOut`) are documented * below and forwarded by the same body; the full consumption-point table with core 7.2.0 dist coordinates is in this * module's header ([ref]①: this line used to say 「the three」, i.e. claimed the set was closed at three). * Returns undefined when no enforceable cap is set (= no restriction). FAIL-CLOSED defense-in-depth: * do NOT trust the wire to be boolean. center serializes these as zod-validated booleans, but a schema drift / proxy * mangling that sent a TRUTHY non-boolean (e.g. the STRING "false") would slip past core's strict `=== false` deny * check and silently GRANT. So a cap that is PRESENT but not a real boolean is a contract breach → THROW (the * resolver's catch then fail-closes to deny). `allowFork` (registry-core 0.1.47): the per-principal Fork * ceiling — core mounts Fork only when `runtimeCaps.allowFork !== false` (prepare-task), tighten-only like allowWorkflows. * * `allowObservers`(observer 开闸线,core 1.270.0/1.273.0):**极性与 allow* 相反** — * core 是 `=== true` 显式 opt-in(默认 OFF,undefined ships dark)。registry-core 0.10.3 起该键已收编进 * `EntitlementRuntimeCaps` 类型(此前 0.10.2 走 unknown 宽读透传,现为类型直读,行为零变)。 * **缺键=不写键**(绝不把缺键补成显式 false —— core 是唯一默认源,补 false 语义上等价但会自造「center * 说过话」的显式判决,污染 env-baseline 合成序,见 applyObserverEnvOptIn)。非 boolean 虽然骗不过 core * 的 `=== true`(不会误 GRANT),但 PRESENT-非-boolean 与 allow* 同属同一 body 的契约破损信号 → 同款 THROW。 * * `autoMode`(core 1.276.0 `RuntimeCaps.autoMode`;**[ref] / core [ref] 起三态**,黑板 [ref]/[ref]): * `undefined`(center 缺席/未答)= 不阻;`true` = 不阻;`false` = **org deny**(CC `permissions.disableAutoMode` * 镜像)。core 武装式 = `spec.autoModeRequested === true ∧ deps.autoMode !== undefined ∧ runtimeCaps?.autoMode !== false` * ——「用户开 + org 拒」极性(此前是 `=== true` 的「org 授予」极性,无 center 的本地部署零授予路径 ⇒ auto 永不 * 武装,真实用户诊断的病根)。 * 🔴 **上面那条武装式是 [ref] 的目标形,不是「本仓现在装的那版 core 的形」——引用它之前先看装的是哪版** * ([ref]① 随 codex r2-[high] 补:我方在 WIRE 附录 F 照抄这一句,写成了「7.2.0 的行为」,而 npm **7.2.0 * 不含 [ref]** —— 它的 `dist/core/runner/prepare-task.js` 逐字仍是 `runtimeCaps?.autoMode === true && * deps.autoMode !== undefined`,即旧「org 授予」极性、**缺键 ⇒ 不武装**)。判「现在是哪套极性」的唯一判据 = * 安装包探针 `CORE_ARMS_AUTO_ON_INTENT`(`src/auto-mode-face.ts`;实测本树 7.2.0 ⇒ **false** = 旧式臂), * 代码侧已按它分臂,失实的只是散落各处的散文。⚠️ 同形措辞在本文件 :209 / :362-364 / :548 与 * `src/boot/runtime-caps.ts:182` 还各有一处(均 [ref] 既有文字,本批未逐条改 —— 登记在 handoff 未闭环项)。 * 所以**缺键=不写键**在这条轴上是行为承重的(在新式 core 上):补一个 false 就是替 org 说了一句 * 它没说的 deny;在旧式 core 上两者同为「不武装」,差别不显形。[ref]B-6a:registry-core 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps——类型直读。 */ export declare function toCoreRuntimeCaps(ent: EntitlementRuntimeCaps | null | undefined): RuntimeCaps | undefined; /** * #A5([ref] 件2)—— 「这台 worker 上**存在** per-principal entitlement 源吗」的**单一谓词**。 * * 判据逐字 = `boot/runtime-caps.ts` 建 caps 客户端的那一条(**配了 center ∧ 非 dry-run**):dry-run 的部署 * 拉取并记录 center 配置但**不应用、不执法**(observe-only),所以它解不出任何 per-principal caps —— 把它 * 算成源会让能力面对着一台永不武装的机器说「有源」。 * * 为什么要抽成函数:这条判据现在有**两个**读者 —— boot 的装配点,与 `/v1/capabilities` 的 * `permissionModeAuto.entitlementSource` 披露位。两处各写一份条件,就是本仓在册的 * 「诊断页说亮、能力位说暗」病族的入口(装配与披露对同一件事各算各的)。 * * ⚠️ **辖域**:说的是「源在不在」,不是「某个 principal 被授权/被拒了」。org deny 位是 per-principal 的 * (`RuntimeCaps.autoMode` 三态逐 principal 解,[ref] 起「缺席=不阻」),任何部署级布尔都对它说不了话—— * 所以本谓词为 false **不再**意味着 auto 不武装;「会不会武装」由 `auto-mode-face.ts` 的三项合取答。 */ export declare function centerEntitlementSourceWired(config: Pick): boolean; /** A MISCONFIG diagnostic (review MEDIUM): a worker-scoped `wpt_` pull token REQUIRES `?worker=` to * resolve per-principal caps — so a `wpt_` token with NO `SEMA_REGISTRY_WORKER` set will 403 * on every caps fetch → fail-closed DENY ALL workflows for ALL principals (correct degrade, but otherwise silent * bar per-task warns). True ⇒ the caller should log a one-shot BOOT warning so the misconfig is diagnosable. * (The orchestrator normally injects SEMA_REGISTRY_WORKER alongside a wpt_ token, so this is a hand-misconfig.) */ export declare function scopedTokenNeedsWorker(token: string, worker: string | undefined): boolean; /** The two per-principal faces served off ONE caps fetch + cache. */ export interface PrincipalEntitlementsClient { /** core's `RunnerDeps.runtimeCapsResolver` seam — FAIL-CLOSED on error (deny workflows/fork/autoMode; * [ref]① 随批纠:此处是同一句降级声明的第二个写者,漏了 [ref] 加的 `autoMode`。真铸点同上)。 */ resolveRuntimeCaps: (principal: string | undefined) => Promise; /** * 🔴 [ref] —— **只读**腿:与 {@link PrincipalEntitlementsClient.resolveRuntimeCaps} **同一只**缓存/同一条抓取/同一份 * fail-closed 判决,唯一的差别是 center 硬失败时它铸的降级项**只对自己算数**,提交腿不认。 * * 病灶(合并重扫 finding):`/v1/capabilities` 的 auto 自查读面用的是提交腿那只 resolver,于是**一次 GET** * 在 center 抖动的瞬间就会替这个 principal 铸下一条 `{allowWorkflows:false, allowFork:false, autoMode:false}` * 的短窗 deny —— 随后 `denyTtlMs` 窗内该 principal 的**真提交**被这条读面留下的项拒掉。读面是纯披露,不该有 * 写副作用,更不该是别人任务被拒的原因。 * * 姿态选择(为什么不是「读面改答 resolver_fault」):判决**逐字不变** —— 读面回答的是「此刻提交会不会武装」, * 而 center 持续故障时提交腿确实看到 fail-closed 的 deny,所以答 `org_denied` 是当下真值;本修只摘掉副作用, * 不改词、不改极性(`onError` 审计行照打,故障照样有可观察面)。 * * 🔴 **界画在「这句话是谁说的」**(codex r2 [high] 逐条核后的定界,不是「读面完全不碰缓存」): * · **成功项 = center 亲口答的那份** —— 与提交腿自己抓到的**逐字同一份**、同一条 `ttlMs`、同一个**已验证** * 身份(读面的 principal 走 `gatedPrincipal` → `verifiedPrincipal`,与提交腿同源,读不到别人的格)。读面 * 只是把这个 principal 自己那一格**预热**了一下;org 撤销后的陈旧窗由部署级 `ttlMs` 定义,与哪一腿预热 * 的无关(提交腿自己预热同样如此),本批**零改动**。 * · **降级项 = server 自铸的判决** —— center 从没说过这句 deny,是本层在故障时替它说的。让一次**读**铸的 * 这句话去拒掉别人的**提交**,才是本修要摘的那件事。两者的差别不是「值一不一样」,是「谁是作者」。 * * ⚠️ 两条缓存纪律(缺一就换出另一个病): * · **成功抓取照旧落缓存**,且命中提交腿刚拉的那份即用 —— 读面不是缓存旁路; * · **硬失败的降级项照旧落缓存,但标出处**(`readMintedDeny`):只读腿自己认(否则 center 持续故障时每次 * GET 一个 8s RTT,读面成了对着故障 center 的放大面),提交腿忽略它、照常重抓。 * in-flight 去重与提交腿共用(同 principal 的并发只一次往返);提交腿**亲历**了这次抓取(自起 or 搭在途) * 而它硬失败 ⇒ 该降级项当场升格为执法项(codex r1 [medium]:否则一条总抢在提交前面的轮询会让每次提交 * 都重抓,denyTtl 的界对提交腿失效)。让它升格的是一次真提交自己吃到的硬失败,不是那次读。 */ peekRuntimeCaps: (principal: string | undefined) => Promise; /** The resolved per-principal scenario ruling for resolveSpec's gateScenarioRequest — * FAIL-OPEN on error / older center without scenario keys / no principal (undefined = no governance, legacy passthrough). * Scenario switching is not a privilege-amplifying face; a center blip must not pin users to `default`. */ scenarioRuling: (principal: string | undefined) => Promise; /** (stage7 P5) The resolved per-principal execution-lane ruling for resolveSpec's * gateExecutionLane. FAIL-OPEN + audit on error (this phase is "honest-but-bypassable"; * tightening to fail-closed is a future, separate decision) / older center without the execution key / no principal. */ executionRuling: (principal: string | undefined) => Promise; } /** * Task-admission enforcement of the per-principal execution policy(stage7 P5 的「真强制」 * service 半场——壳侧锁定是易用性强制可绕,这里是引擎闸)。The lane is a WORKER-PROCESS identity * (REMOTE_EXEC is boot wiring; there is no per-task lane switch), so the gate is "this worker's lane is not * permitted for this principal ⇒ reject the task": typed 403 `execution_lane_not_allowed` carrying the * allowlist + this worker's lane (machine code on the wire, prose stays with the shell/web). * No ruling / required=false / caps blip ⇒ pass (fail-open + audit). `allowedLanes` is an * OPEN domain — unknown names simply never match this worker's lane. * * `logger` (optional, defaults to this module's `defaultLogger`) is an injection seam purely for the * once-per-process default trace below (test observability) — it does not change enforcement. */ export declare function gateExecutionLane(ruling: ExecutionRuling | undefined, ownLane: string, logger?: Logger): void; /** * Build the per-principal caps client: `resolveRuntimeCaps` matches core's seam signature * `(principal) => Promise`; `scenarioRuling` rides the SAME fetch/cache/in-flight * (zero extra center RTT — center attaches the ruling to the caps body). Wire ONLY when a center is * configured (and not in dry-run) — absent ⇒ core sees `undefined` ⇒ NO per-principal restriction, and the * scenario gate sees `undefined` ⇒ legacy body-or-default chain. */ export declare function createPrincipalEntitlementsClient(opts: EntitlementsResolverOpts): PrincipalEntitlementsClient; /** Back-compat face for core's seam-only consumers: the caps half of {@link createPrincipalEntitlementsClient}. */ export declare function createEntitlementsResolver(opts: EntitlementsResolverOpts): (principal: string | undefined) => Promise; /** core's `RunnerDeps.runtimeCapsResolver` seam signature (types.d.ts): sync or async, undefined = no caps. */ export type EntitlementsResolverFn = (principal: string | undefined) => RuntimeCaps | undefined | Promise; /** * [ref] §3.1([ref] S-1 / [ref] §1.2)—— `RuntimeCaps.allowMemoryOptOut` 的 **verdict 源**(本地腿)。 * 唯一实现 = `plugins/memory-optout-grant-store-sql.ts`(三层折叠:per-principal 行 → 部署缺省哨兵行 → * 代码缺省 allow,所以健康的源**恒答显式 boolean**;`principal` 缺席=匿名/系统 run,只看缺省行)。 * 契约:答非 boolean 或 throw = **面故障**(见 {@link applyMemoryOptOutGrant} 的处置)。 */ export interface MemoryOptOutVerdictSource { resolveAllow(principal: string | undefined): Promise; } /** * [ref] §3.1 **F2 例外臂**(server 阻断级,采;core 7.0.0 `RuntimeCaps.allowMemoryOptOut` JSDoc 的 * FAULT-TRANSIT CONTRACT 逐字):把本地 grant 腿的 verdict 合成进 caps resolver —— **与 allowWorkflows 家族 * 反向的例外臂,逐字注释**: * * · 本文件 `load()` 的 catch 臂对 center 硬失败铸 `{allowWorkflows:false, allowFork:false}`(fail-closed * 降级:放大面上「故障 = 拒」是安全方向)。**本键禁止照抄**:隐私轴上「拒」= 把用户明说不要记的会话 * 记进去(不可逆),而「错误放行」只是少记一次会话(可恢复)。core 因此把 `open` 姿态的故障臂定成 * **放行 + 具名披露**,把 `governed` 姿态的故障臂定成**拒跑**;两臂都以「故障」为输入。把故障编成 * 显式 `false` = 把「故障」伪装成「显式 denied verdict」,遮蔽这两条分臂 —— 在 `open` 下恰是 * 极性反转(故障走了「显式拒」臂)。 * · 故障过境形写死:本键故障 = **键缺席**(对象上不写键;core 读作 `value:undefined`)。不选 throw * 透传是因为 throw 会把**同一次**解析里 center 已答好的 allowWorkflows/allowFork 一并作废(core 的 * resolver-throw 降级臂),那是把一条腿的故障扩成整车的故障。缺席的代价如实记:`open` 下 core 读 * 「clean undefined」不铸 `faultDisclosure`(那句披露只给 throw/非 boolean),所以**本层**用 `onFault` * 打一行 warn 补上披露;`governed` 下缺席 = verdict absent = 拒跑,方向正确。 * · 源答非 boolean 同判故障(契约:never truthiness-read)。 * · 无源 ⇒ 原 resolver **同一引用**透传(零行为差;多数部署路径)。 * · `principal` 缺席也问源:部署缺省行那一层对匿名 run 同样成立(governed 部署的匿名 run 也该被 * 一个显式 verdict 罩住,而不是因为「没 principal」结构性拿不到 verdict 而被拒跑)。 * * 双向 G 钉在 `test/memory-capture-optout-lane.test.ts` ②:故障 ⇒ 键缺席;显式 false ⇒ 键=false。 */ export declare function applyMemoryOptOutGrant(base: EntitlementsResolverFn | undefined, source: MemoryOptOutVerdictSource | undefined, opts: { onFault: (err: Error, principal: string | undefined) => void; }): EntitlementsResolverFn | undefined; /** * [ref] —— `governed` 姿态 + **center-only** verdict 源那一形的**运行期**披露(纯观测,零判决改写)。 * * 为什么单独存在:`allowMemoryOptOut` 在 settings-schema 的 `EntitlementRuntimeCaps` 里是**可选键** * (`z.ZodOptional`),而 {@link toCoreRuntimeCaps} 对缺席**不铸键**(FAULT-TRANSIT:缺席 * 原样过境,绝不铸 false)。于是「配了一个非 dry-run 的 center」只证**源在场**,不证 center 会**为这个 * principal 发这个键** —— 一台 center 从不发该键的 governed worker 会正常起动,然后把每一个 * `memory.capture:"off"` 都以 `memory.capture_optout_denied`(verdict absent)拒掉,而运维只看见「opt-out 全拒」。 * 启动期那一行 warn 说的是「这一形有风险」;这一行说的是「它**真的**发生了」。 * * 姿态:**每因由一次**(防每任务刷屏)、**不改判**(caps 逐字返回,极性仍由 core 按姿态判)、 * 只在 `enabled`(= governed ∧ 无本地 grant 店 ∧ center 源在场)时挂;否则返回 **同一引用**(零行为差)。 * 缺席判据取最终合成结果:`typeof caps?.allowMemoryOptOut !== "boolean"` —— center 没发键、center 拉取 * 降级(fail-closed entry 不含本键)都算,两者对 core 是同一件事(verdict absent ⇒ 拒跑)。 * * 🔴 **因由是闭集两员,各占各的格**(codex r1-[medium] 验真后修):`createPrincipalEntitlementsClient` 的 * `resolveRuntimeCaps` 对 `principal === undefined` **直接返回 undefined、根本不问 center**(匿名/系统 run * 没有 per-principal 身份),而 core 对**每一个**任务都调这只 resolver(不只是带 opt-out 声明的那些)。 * 单格去重会让**第一个匿名任务**烧掉那一格,日志却说「center 省了键」——归因错、且此后真正的具名缺键 * 永不留证。两员分格,且两句话说的是两件不同的事:匿名腿在 center-only 源上**结构性**永远拿不到 verdict * (本地 grant 表的部署缺省行才答匿名),具名腿则是「center 这次没发这个可选键」。 */ export type GovernedVerdictAbsenceReason = "anonymous_lane" | "center_verdict_absent"; export declare function applyGovernedVerdictAbsenceWarning(base: EntitlementsResolverFn | undefined, opts: { enabled: boolean; warn: (reason: GovernedVerdictAbsenceReason, principal: string | undefined) => void; }): EntitlementsResolverFn | undefined; /** * observer 开闸线的 env 半场:`EXPERIMENTAL_OBSERVER_AGENTS=true` 时把 * `RuntimeCaps.allowObservers: true` 作为**部署基线**合成进 caps resolver(core 1.270.0/1.273.0 的 * observer 面是 `=== true` 显式 opt-in、默认 OFF —— 命名先例=CC 的 `CLAUDE_CODE_EXPERIMENTAL_OBSERVER_AGENTS` * env 对儿)。租户姿势(与 FORK_ENABLED/SELF_ORCHESTRATION 同款判别): * - **单用户部署**(`requirePrincipal !== true`,operator 即用户):env=true 直开 —— 无 center 时静态 * `{allowObservers:true}`;有 center 时作 BASELINE,**center caps 带键则 center 赢**(含显式 false; * 缺键=center 没说话 → env 基线生效。这依赖 toCoreRuntimeCaps 缺键不补 false 的纪律 —— core 是唯一 * 默认源,center 只有真说了话才覆盖)。fail-closed 错误 entry(deny)不带 allowObservers 键 → 单用户 * env 基线在 center blip 期间维持(operator 在自己盒子上显式 opt-in 过;observers 非 workflows/fork * 级的放大面,且 deny entry 的两把闸不受影响)。 * - **多租户**(`requirePrincipal === true`):env **不认**,只认 center caps(半配置 fail-loud:置了 * env 却多租户 → 一次 boot warn,行为零变——与 MEMORY_ENGINE_BACKEND 半配置同款姿势)。 * 返回合成后的 resolver;两边都无源(无 center、env 无效)⇒ undefined(RunnerDeps 不接线,零行为差)。 * ⚠️ 调用方注意:`Boolean(centerResolver)` 才是「entitlement resolver wired」语义(enableForkFromBody / * selfOrchestrationFromBody 的多租户 fail-close 判别),不要改用本函数的返回值判 —— env-only lane 不是 * center 背书的 entitlement 源。 */ export declare function applyObserverEnvOptIn(centerResolver: EntitlementsResolverFn | undefined, opts: { experimentalObserverAgents: boolean; requirePrincipal: boolean; warn?: (msg: string, fields?: Record) => void; }): EntitlementsResolverFn | undefined; /** * [ref] ③(黑板 [ref]/[ref],core [ref] 合修)—— `PERMISSIONS_DISABLE_AUTO_MODE` 的合成层:CC * `permissions.disableAutoMode` 的本地镜像(ANCHOR-250 §6.4 的只可更严棘轮),**tighten-only**: * · deny=true ⇒ 每个 principal 的 `caps.autoMode` 折成 `false`(org deny 位)。center 授予 `true` **翻不回**—— * 本层写在最外(boot/runtime-caps.ts 合成序:center → observer env 基线 → memory grant → 本层),后写者赢, * 且只往严处写;center 对该 principal 无答(undefined)同样折 false(deny 是部署级表态,不看 principal)。 * · deny 未设 ⇒ 原 resolver **同一引用**透传(零行为差;无 center 的部署仍是 `undefined` ⇒ caps 缺席 ⇒ * 三态之「缺席=不阻」,这正是 auto 在本地部署能武装起来的路径)。 * · 极性与 applyObserverEnvOptIn 相反:那条是「env 开」的放宽基线(center 带键则 center 赢),本条是 * 「env 拒」的收紧棘轮(center 带键也不能赢)。两条不共用一只函数正是因为方向相反。 * 🔴 没有 fail-open 臂:本层是纯合成,不做 I/O;base 抛错原样上抛(base 各层自带 fail-closed/缺席臂)。 */ export declare function applyLocalAutoModeDeny(base: EntitlementsResolverFn | undefined, deny: boolean): EntitlementsResolverFn | undefined; //# sourceMappingURL=runtime-caps-resolver.d.ts.map