import { type BackgroundAgentRecord, type RunnerDeps, type ToolExecuteContext } from "@sema-agent/core"; import type { ServiceConfig } from "../config.js"; import type { RunnerDepsOnAsk } from "./runner-deps.js"; import { type ApprovalBaselineConfigView, type DeploymentGovernanceConfigView, type LiveQuestionFace } from "../deployment-governance.js"; import { type SandboxPathEnvSlots } from "./deferred-sandbox-path-env.js"; /** 本腿读的配置切面(`ServiceConfig` 结构满足)。两个构造口各自的切面 + lane 判别式读的 `remoteExec`。 */ export interface ParkedReviveGateConfigView extends DeploymentGovernanceConfigView, ApprovalBaselineConfigView { readonly remoteExec?: ServiceConfig["remoteExec"]; /** S-213④:`executionLaneHomeDir` 读的运维声明(切面必须点名本腿消费的每一个字段,否则窄视图字面量会静默丢掉旋钮)。 */ readonly remoteExecHomeDir?: ServiceConfig["remoteExecHomeDir"]; /** host 腿裁决 env 的 cwd 占位根(见下方 `hostGateCwd`)。 */ readonly localDataRoot?: ServiceConfig["localDataRoot"]; } /** 会话豁免探针店的所需切面(canonical toolName 键空间;`StoreBackend.approvalExemption()` 结构满足)。 */ export interface ApprovalExemptionProbe { has(sessionId: string, toolName: string): Promise; } export interface ParkedReviveGateDeps { /** ⚠️ **活引用**:热改字段(autonomy/commandPolicy/守卫集/审批四旋钮)每次赎回现读,见下方「禁 memoize」。 */ readonly config: ParkedReviveGateConfigView; /** [ref]:活体 AskUserQuestion 面。赎回腿今天无 ALS ctx ⇒ 判 ask,与原形同判。 * ⚠️ 它同时是**决议链元数据** `contentMandate` 的唯一判据(见 {@link mandatePostureOf})。 */ readonly question: LiveQuestionFace | undefined; /** * 部署级活体审批席 —— `RunnerDeps.onAsk` 的**同源铸法**(main.ts 递 `createRunnerDepsOnAsk(toolApproval)`, * 单一属主同 boot/runner-deps.ts)。 * * ⚠️ **本仓确有 per-task `spec.onAsk` 装配点**(codex 交叉复审 R1-中1 验真纠正 —— 别照抄 * `createRunnerDepsOnAsk` 头注那句「bg/resume 两条腿没有 per-task 装配点」,那句话的射程只是那两条腿): * `http/routes/tasks.ts` 的宿主 sync/stream 腿在装配点写 `spec.onAsk = toolApproval.boundAsk({owner, * taskId, sessionId, legKey…})`(windowZero 形则写一个恒返 `"unavailable"` 的闭包)。core 冻的是 * `spec.onAsk ?? deps.onAsk`,所以那条腿冻下去的是**带身份的** bound 席,不是这一格。 * * 这对本腿的两个用途分别意味着什么: * · **`durableMandate` 判据(承重,见 {@link mandatePostureOf})—— 不受影响**:两个装配点是**同一个 * 条件**的两种表达(`spec.onAsk` 的两支都在 `deps.toolApproval` 在场时才写,windowZero 支写的也是 * 函数),所以「席位在不在场」这个**布尔**在 spec 位与 deps 位上恒等。core 的 * `isLiveApproverSeat` 只判 `typeof === "function"` ⇒ 本腿算出的位与 park 时冻的位逐字相同,摘要对得上。 * · **重建条目的 `onAsk` 席位本身 —— 是一个诚实的降级,不是等价替换**:bound 席携的身份 * (owner/taskId/sessionId/legKey/腿时限)是**宿主那条腿**的,park 时未持久化 ⇒ 跨副本重建不出来。 * 交这一格的效果:赎回腿没有 ALS 上下文,`ToolApprovalCoordinator.ask` 第一行 `if (!ctx) return * "unavailable"`(tool-approval.ts)⇒ 继承 ask 恒走 core 的 `approverUnavailable` 回路 = **再 park * 一次给人**。它**到不了**宿主原来那张卡(那是残余,与「客户端折叠层重建不出」同族);它也**不会** * 错投到别处(无 ctx 就直接返回,`askBroadcast` 根本不执行 —— codex 提出的「按赎回腿归因/投递」 * 这条机制经亲读证伪)。 * 为什么仍然要交:席位缺席 + `durableMandate` 为 false(装了活体席的部署就是这一形)⇒ 继承 ask 只剩 * headless auto-deny,比「再问人一次」严且哑。交了之后最坏也是 park,方向 fail-safe。 */ readonly approverSeat: RunnerDepsOnAsk | undefined; readonly approvalExemptionStore: ApprovalExemptionProbe | undefined; /** * [ref] —— core `RunnerDeps.runtimeCapsResolver` 的**同一只**(main.ts 的 `runtimeCapsResolver`, * 单一属主同 `boot/runtime-caps.ts`)。重建条目的第三位决议链元数据 `autoModeArmed` 的唯一判据是 * per-principal 的 `RuntimeCaps.autoMode`,而它只有 center 解得出 —— 见 {@link autoModePostureOf}。 * 缺席(无 center 的部署 / dry-run)⇒ core 侧 `runtimeCaps` 也恒 undefined ⇒ 该位在 park 时也从不置位, * 两侧同为「不供」,逐字零行为差。 * * 类型从 **core 的载体位**推导(同 `boot/runner-deps.ts:94` 的先例,理由同 {@link RebuiltInheritedGate}: * 手抄一个等价签名会让 core 改这条 seam 的那天变成静默漂移而不是编译期事件)。 */ readonly resolveRuntimeCaps?: RunnerDeps["runtimeCapsResolver"]; /** `RunnerDeps.autoMode` 分类器面(信任门半场)在不在场 —— core 7.2.0 的武装条件是 * `runtimeCaps.autoMode === true` **∧** 这一面在场(prepare-task 原式)。⚠️ [ref] / core [ref] 起武装式换成 * `spec.autoModeRequested === true ∧ 席位 ∧ runtimeCaps?.autoMode !== false`——本文件的镜像 * {@link autoModePostureOf} **未随改**(赎回行无父 run 的 auto 意图可读),见该函数头注的未闭环登记。 */ readonly autoModeSeatMounted?: boolean; /** [ref]:父 run 的 auto 意图(`TaskSpec.autoModeRequested` / 链位)——意图武装式下重建 decider 的第三项。⚠️ 今日 * **无持久源**(core [ref] 链位 live-only;RunRecord 无 body),生产装配不递 ⇒ 恒缺席;座留给 core 持久化到货。 */ readonly parentAutoModeIntent?: ((row: BackgroundAgentRecord) => boolean) | undefined; readonly logger: { info(event: string, fields?: Record): void; warn(event: string, fields?: Record): void; }; /** host 腿 cwd 占位的落地根(main.ts 的 `localRoot`;`config.localDataRoot` 优先)。 */ readonly localRoot: string; /** S-133:守卫集 `dataRoot` = boot 已铸的引擎记忆根(`memoryEngine.root`,boot/stores.ts 单一写者),不在本腿重推导 * (重推导 = 第二个写者,7.59.0 重扫 codex 轮抓到 HTTP 腿 fallback 与 stores 不同源正是这一形)。`undefined` = 引擎未接。 */ readonly memoryEngineRoot: string | undefined; /** 沙箱 lane 的真 env 登记簿。缺省 = 进程内唯一那只(工厂装饰器写的同一个);测试注入自建实例。 */ readonly slots?: SandboxPathEnvSlots; } /** * core 1.396 的父约束席位形(`parkedResume.inheritedGate`)—— 从**载体自己的签名位推导**,绝不手写。 * * 🔴 手写这个形状会**藏住轴**(codex 交叉复审 R1-高2 的真回归):第一版在这里手抄了一个只有 * `parentConstraints` 的接口,于是 core 载体上那个 `shellGate` 槽在类型层面根本看不见,「shellGate 轴 * 也要交回」这件事就无声地漏了一整刀。推导形让「core 又加了一条能收紧的轴」变成本仓的编译期/钉子面 * 事件(载体键集钉见 test/parked-revive-gate-wiring.test.ts)。 */ export type RebuiltInheritedGate = NonNullable["parkedResume"]>["inheritedGate"]>; /** 重建链的**单层条目**形 —— 同样从载体推导(理由同 {@link RebuiltInheritedGate}:core 又加一个 * 能收紧的槽时,本仓的编译面要看得见)。 */ export type RebuiltParentConstraint = NonNullable[number]; /** * 赎回腿的父约束链重建([ref] 件二)。 * * 语义:**同/跨副本一致地重建「部署 ⊇ 操作员」两层完整链**——审批基线(durable question 门 + F4 高危写 * 审批门 + 会话豁免探针)作 base 的 `toolPolicy` 座,经**折叠属主** `applyRuntimeGovernance` * (→ core `tightenTaskSpec`)叠上部署治理段(autonomy / commandPolicy / MANUAL_MODE_SHELL_GATE / * 守卫集),取其 `toolPolicy` 装进单层 `parentConstraints`。 * * 🔴 **禁 memoize**:构造整体在 per-row lambda **内**。`autonomy`/`commandPolicy`/守卫集/审批四旋钮都是 * 热改字段(registry 热应用换 config 引用),boot 期铸一次 = 把治理冻在启动那一刻的值上,而 resume 腿的 * 书面语义是「按**当前** config 重折」(与 resolve-spec 的审批基线读活 config 同一姿势)。 * * 🔴 **单层链是正确的响亮拒**([ref] §2.5 特征化格):本腿恒建 count=1。嵌套子代(孙代 park, * count≥2)的重建本腿做不到——跨副本连「祖先各层分别是什么」都没有持久化——于是 core 的 pre-CAS * `resume.parent_constraint_mismatch` 会响亮拒绝,checkpoint 留 pending。**不许**顺手把它补齐成静默单层: * 那等于让孙代在一条比它挂起时更松的祖先链下复活。 * * 交回**两条轴**(载体上本仓供得出的全部,见 {@link RebuiltInheritedGate} 的推导注): * · `parentConstraints` —— 上面那条重建链; * · `shellGate` —— 治理段折出来的 shell 门档位。**必须交**:core 按 rank 取 `max(live, seed)`,不交 * 等于 `live` 缺席、max 恒等于 park 时的旧种子,于是「挂起期间运维收紧 AUTONOMY/MANUAL_MODE_SHELL_GATE」 * 在赎回腿上整条失效(codex R1-高2 的真回归,红先复现)。交了之后收紧被兑现、放松不被兑现(fail-safe)。 * * 载体上**故意不供**的三个槽(都不是本腿产得出的):`ancestorRules`(会话权限规则,属会话面)、 * `admittedOrgScopes` / `orgAdmissionGoverned`(组织记忆准入,属 memory 面)—— 交一个瞎猜的值比不交更坏 * (core 对这三个都是 seed 兜底)。 * * 时点语义(成文进 docs/ASSISTANT-WIRE-CONTRACT.md,免运维当 bug 报): * · toolPolicy 轴 = **当前** config(与 resume 重折一致,收紧与放松都被兑现——放松是一次显式运维动作); * · shellGate 轴 = core 取 max(live, park 时 seed)—— 收紧兑现、放松**不**兑现(fail-safe)。 * · `autoModeArmed` 轴([ref])= 按**当前** per-principal entitlement 现解(见 {@link autoModePostureOf}); * 与 toolPolicy 轴同族:挂起期间权益被撤/被授 ⇒ 摘要对不上 ⇒ core pre-CAS 响亮拒,不静默换姿势跑。 * · `onAsk` 轴 = 部署的活体审批席(`deps.approverSeat`),**在场即供**:park 时那一层冻的就是它。 * 席位缺席的部署上不供,而**这不等于 auto-deny**(codex R3-中1 验真纠正):此形恒带 * `durableMandate`(见 {@link mandatePostureOf}),继承层判 `ask` 时 core 只要还看得见 checkpoint 店 * + durable 审批配置就把它**重新浮回 durable 门**(再 park 一次,人再批一次);deny 只是「durable * 设施不在场」的兜底臂。⛔ 但**不是无条件**(codex R4-中1):core 对挂起链计 `suspendCount`,到 * `maxSuspends`(默认 5)即终止任务而不再 park。 * · 客户端每请求 settings 折出来的层(`settings.permissions` 的 allow/deny/ask、模式派生的 fs-write * ask 门、scratchpad 豁免……整类)park 时未持久化 ⇒ 重建不出。§2.3 已披露的残余,机制面有特征化钉 * (test/parked-revive-e2e.test.ts);真 settings→park→赎回的端到端钉未建(候件,随收口件一起)。 */ /** * 🔴 [ref] 第二半(codex 交叉复审 medium,验真后采纳)—— 席位多收一位**权威 principal**。 * * 为什么不能只看 `row.scope`:`principal ?? "default"` 这套约定在那一列上是**有损**的(匿名与一个真名叫 * `default` 的租户同形),而匿名别名匹配(见 `parked-decide.ts` 的 `backgroundScopesForCheckpointScope`) * 让匿名行第一次真的走到本函数。解错 principal 的后果不是拒绝,是**解到另一份 caps** ⇒ 与 park 时记的 * 摘要对不上 ⇒ 刚刚变得可达的那批卡又赎不回(方向仍 fail-closed,但那是白丢一次赎回)。 * 权威值来自 **checkpoint 的 scope**(`decodeCheckpointScope(cp.scope)`)—— 那一列由 * `encodeCheckpointScope(auth?.principal)` 铸,与 core 喂给 `runtimeCapsResolver` 的是同一个值,无损。 * 参数**必填**(不给可选默认值):唯一的生产调用方是 `decideParkedAgent`,漏传要是编译错而不是静默回退。 */ export interface ParkedReviveIdentity { /** `decodeCheckpointScope(cp.scope)`:匿名 ⇒ `undefined`,有 principal ⇒ 逐字那一个。 */ readonly principal: string | undefined; } export declare function createParkedReviveInheritedGate(deps: ParkedReviveGateDeps): (row: BackgroundAgentRecord, identity: ParkedReviveIdentity) => Promise; //# sourceMappingURL=parked-revive-gate.d.ts.map