import type { ServiceConfigFlat } from "./config-types.js"; /** 一位能力的三态。`single-user-only` = 车道支持,但只在单用户部署(`requirePrincipal !== true`)开 —— * 多租户下这一位必须关(host 家族的 cwd/scheduler/backgroundShell/LSP 都是这一档,理由同源: * 一个租户的输入不许把 agent 指向 worker 宿主上另一个租户/运维的文件与进程)。 */ export type LaneCapability = "supported" | "unsupported" | "single-user-only"; /** `REMOTE_EXEC` 判别联合里的 provider 词全集 —— **派生自真配置型**,不是手抄一份。 * 手抄的那份挡不住本模块要挡的病:给 `ServiceConfigFlat["remoteExec"]` 加一条臂(`remote-docker` 就是 * config-types.ts 里写着的下一条)可以完全不碰本文件而编译通过,穷举 switch 于是只对**自己的**副本穷举, * 能力表与普查数组一起静默漏行(codex R1-F2)。派生之后:配置加臂 ⇒ 本文件的 switch/词表/夹具同时编译红。 */ export type RemoteExecProvider = NonNullable["provider"]; /** 闭集:执行车道全集 = provider 词 + `in-process`(`REMOTE_EXEC` 未设的进程内 stub 形,配置面无 provider * 可派生 —— 它是「没有 remoteExec」这个状态的名字)。加词 ⇒ {@link executionLaneCaps} 的穷举 switch 与 * {@link EXECUTION_LANE_WORDS} 两处都编译红,这正是要的(「加词必须逐位表态」)。 */ export type ExecutionLane = "in-process" | RemoteExecProvider; /** * per-lane 能力位。每一位的注里点名**今天真正做判决的那处代码**——对账测试按这些坐标跑真函数, * 所以坐标漂了(重命名/搬家/改语义)门会红,注释不会烂在这里。 */ export interface ExecutionLaneCaps { /** 调用方送来的 `cwd` 是否被尊重。判别式:`task-cwd.ts` 的 `cwdHonored`。 */ readonly callerCwd: LaneCapability; /** core 的后台 shell 面(`run_in_background`/`BashOutput`/`KillShell`)。判别式:各 adapter 的 * `backgroundCapabilities.supported`(`plugins/remote-env-{host,e2b,k8s}.ts`);host 腿的闸在 * `boot/execution-env.ts` 的 `hostCfg.backgroundShell`。 */ readonly backgroundShell: LaneCapability; /** 自唤醒 scheduler(`CronCreate`/…)。判别式:`boot/execution-env.ts` host 臂的 `hostScheduler` * 与 `http/routes/capabilities.ts` 的 `scheduler` 位(两处同判据)。 */ readonly scheduler: LaneCapability; /** LSP。判别式:`boot/execution-env.ts` 的 `lspManager` 两臂(沙箱桥 e2b/k8s;host 本地 NodeLspManager)。 */ readonly lsp: LaneCapability; /** SendUserFile 的**沙箱直传腿**(host/in-process 的本机 fs 源是另一条腿,不是本位)。 * 判别式:`capabilities/sandbox-file-send.ts` 的 `sandboxSendLaneEnabled`。 */ readonly sandboxSendUserFile: LaneCapability; /** OS 级隔离边界。判别式:`boot/execution-env.ts` 的 `isolated`(经 `remote_exec_enabled` 日志可观测)。 */ readonly isolation: LaneCapability; /** 可挂起(k8s 以配了 `s3Snapshot` 为前提 —— 「结构上能不能」的口径)。判别式:同上的 `suspendable`。 */ readonly suspendable: LaneCapability; /** `/tmp/scratchpad/` 远端约定。判别式:`plugins/remote-scratchpad.ts` 的 `isRemoteScratchpadLane`。 */ readonly remoteScratchpad: LaneCapability; /** 写门走 [ref] 的 `DeferredSandboxPathEnv` 代理裁决(而不是直接拿 worker 本机 fs 裁)。 * 判别式:`boot/deferred-sandbox-path-env.ts` 的 `isSandboxPathAdjudicationLane`。 */ readonly sandboxPathAdjudication: LaneCapability; /** git-worktree 隔离。判别式:`boot/execution-env.ts` 的 host 臂 vs `worktree_isolation_unsupported_lane` warn。 */ readonly worktreeIsolation: LaneCapability; /** 记忆持久面的**自动**表态(部署可用 `MEMORY_PERSISTENCE_CAPABLE` 显式覆盖,那是旋钮不是车道位)。 * 判别式:`boot/memory-boundary.ts` 的 `effectiveMemoryPersistenceCapable`。 */ readonly memoryPersistenceCapable: LaneCapability; /** 文件平面是否就在 worker 本机(= `hostSemanticsLane`)。判别式:`boot/resolve-spec.ts` 的 * `!isSandboxPathAdjudicationLane(...)` 与 `boot/stores.ts` 的 `lane === undefined || lane === "host"` * ——**两处各写了一份**,对账测试同时咬住两份(它们分叉过就是记忆边界判错盘)。 */ readonly hostFilePlane: LaneCapability; /** core 的 `isRemoteExecutionEnv` 对本车道的判决(鸭子类型;判别位 = `config.remoteExec` 在不在场, * **含 host**)。5.26.0 起它决定 `# Memory` 写指令撤不撤。 */ readonly coreRemoteExecutionEnv: LaneCapability; /** * S-354 / core 7.20.0([ref]):这条车道的适配器**声明**了 `capabilities.canonicalPathAuthoritative` * 没有 —— 即「我的 `canonicalPath` 是路径落点的权威,整链答不出来是解析器出了事,不是一个我看不见的 * 命名空间」。声明 ⇒ 读边界的执行期复核在解析器沉默时**拒**;缺席 ⇒ 今天每个适配器的读法(词法判决 * 成立、命令照跑)—— **缺席不是「声明为 false」**,所以这一位的 `unsupported` 读作「本车道没表态」。 * * 判别式:各 adapter 的 `capabilities` 字面量(`plugins/remote-env-{host,ssh,adb,k8s,device,e2b,local-docker}.ts`), * 由 core 的严格读器 `canonicalPathIsAuthoritative(env)` 读;对账测试逐 lane 拿**真 adapter 实例**问它。 * 本位存在的理由 = 让「新车道/新适配器不表态」变成**编译红**(穷举 switch 少一行即 tsc 红),而不是 * 静默继承一个没人表过态的缺席。 */ readonly canonicalPathAuthority: LaneCapability; } /** * per-lane 能力表。**穷举 switch,禁 default 臂**(hands-lane.ts:39-46 先例)。 * * device 行的表态来自 v2 §4.1/§4.3: * · `callerCwd: supported` —— 员工在自己设备的项目目录里干活是这条车道的**存在理由**;host 闸防的 * 「跨租户宿主路径穿越」在此结构性不存在(路径落在发起者自己的设备上,归属已由 deviceId↔principal * 绑定验证),故不要求单用户。今天 `cwdHonored` 还答 false ⇒ 登记在 {@link PLANNED_LANE_DIVERGENCES}。 * · `sandboxPathAdjudication: supported` —— 自动判真且**这是深思后的正确默认**:写门经 [ref] 代理把四读 * 原语转发到设备取真文件系统事实,云盘永不参与设备写目标的裁决。 * · `remoteScratchpad: supported` —— executor 平台闭集 = darwin|linux,`/tmp/scratchpad/` * 的惰性 mkdir 约定原样适用;不加词则 scratchpad envFact 对模型整块缺席。 * · scheduler / backgroundShell / lsp / sandboxSendUserFile / worktreeIsolation = v1 **诚实关闭** * (不是遗漏:设备侧长驻进程、LSP sidecar、直传腿都是 follow-on,声明了就是谎)。 * · `isolation`/`suspendable` = false/false —— 真设备,不隔离不可快照(与 ssh/adb 同宣告); * `suspendable:false` 在 core 契约里同时断言「工作区在目标上外部持久」,员工机磁盘满足,park-only * 赎回腿因此合法。 * · `memoryPersistenceCapable: unsupported` —— 设备的手够不着云 SQL 记忆店,默认撤 `# Memory` 写指令 * 恰是诚实形(§4.3.4)。 */ export declare function executionLaneCaps(lane: ExecutionLane): ExecutionLaneCaps; /** 一个开放字符串是不是闭集里的词。判据 = 词表键在不在,故与联合天然同源。 */ export declare function isExecutionLane(word: string): word is ExecutionLane; /** 车道普查全集(顺序 = 词表声明序;对账测试按它逐 lane 跑真判别式)。 */ export declare const ALL_EXECUTION_LANES: readonly ExecutionLane[]; /** * `REMOTE_EXEC` **真能写下去的**那几个词(= {@link ALL_EXECUTION_LANES} 去掉 `in-process`:后者是 * 「没有 remoteExec」这个状态的名字,env 里写它没有意义)。 * * 🔴 S-426 以点改面:此前 `config-catalog.ts` 的 `REMOTE_EXEC` 行把这张表**手抄**成一个 * `enumValues: ["e2b","k8s",…]` 字面量(字段静态型是 `readonly string[]`,与 {@link RemoteExecProvider} * 零类型联系)。病形与 S-382 逮到的 `WEB_SEARCH_PROVIDER` 逐字同族:给 * `ServiceConfigFlat["remoteExec"]` 加一条 provider 臂(config-types.ts 里写着的下一条就是 * `remote-docker`),本文件的穷举 switch 与词表**会**编译红,而目录行不会 —— 于是运维在 * `GET /v1/config/catalog` 上读到的可写词集静默少一个,新车道成了「配了但目录说不认识」。 * 收成一只派生数组之后,目录行**就用它本人**(身份相等有钉),全仓只剩这一张表。 */ export declare const REMOTE_EXEC_PROVIDER_WORDS: readonly RemoteExecProvider[]; /** * `config.remoteExec?.provider` → 车道词。 * * · `undefined`(REMOTE_EXEC 未设)⇒ `"in-process"`,那是真车道不是缺席; * · 认不出的词 ⇒ `undefined`,**调用方必须 fail-closed**(禁静默按某个默认车道处理)。生产链上这一臂 * 不可达:`loadConfig` 的 [ref] 拒启门先咬未知词;它存在是为了消费点自卫时有个响亮出口。 */ export declare function executionLaneOf(provider: string | undefined): ExecutionLane | undefined; /** 把一位能力在**具体部署**上落成布尔:租户面是唯一入参(旋钮由消费点自己再合取)。 * 穷举 switch,无 default 臂 —— {@link LaneCapability} 加词而此处不加臂 = 编译红。 */ export declare function laneCapabilityHolds(cap: LaneCapability, deployment: { requirePrincipal?: boolean; }): boolean; /** 一条**已登记**的分叉:表写的是目标态,今天的判别式还答别的。 */ export interface PlannedLaneDivergence { readonly lane: ExecutionLane; readonly capability: keyof ExecutionLaneCaps; /** 今天那处判别式给出的答案(对账测试拿它对拍 —— 分叉被补齐了这一行就红,逼着销行)。 */ readonly today: LaneCapability; /** 谁来补(设计稿里的车号)。 */ readonly owner: string; readonly why: string; } /** * 分叉登记簿 —— **闭集**。对账测试双向咬:表外分叉 = 漂移(红),表内分叉被补齐而不销行 = 陈账(红)。 * * 🟢 **空表 = 今天的真实状态**(车A-4,2026-08-28 销行)。唯一那条(device 的 `callerCwd`)已按登记的 * 条件补齐:`deviceCwdHonored`(`task-cwd.ts`)+ 写侧闸(`boot/resolve-spec.ts` 的同一处单写者)+ 读侧 * 消费(`plugins/remote-env-device.ts` 的 `effectiveDeviceCwd`)**同批**落地,于是能力表写的 `supported` * 与判别式给的答案一致,分叉自然消失 —— 对账测试的双向咬(表外分叉 = 红 / 表内陈账 = 红)因此对本表 * 只剩「保持空」这一条要求。 * * 加行的门槛不变:一条分叉必须写清 today / owner / why 三件,并在补齐的**同一批**里销掉。 */ export declare const PLANNED_LANE_DIVERGENCES: readonly PlannedLaneDivergence[]; /** * E2B 沙箱里 agent 跑成的那个用户的 home。 * * 这是 **E2B 平台的事实**,不是本仓的猜:E2B 的基础模板建 `user` 这个用户,每个 sandbox 的进程都以它 * 运行,home 恒 `/home/user`;本仓 adapter 自己的工作区根默认值(`plugins/remote-env-e2b.ts` 的 * `DEFAULT_MOUNT_PATH`)用的就是同一个目录。**若某个部署的自定义模板换了用户**,运维用 * `REMOTE_EXEC_HOME_DIR` 显式声明覆盖 —— 那条旋钮恒赢,见 {@link executionLaneHomeDir}。 * ⚠️ 本值**未经真机实测**(本批无 e2b 真机),按平台约定 + 仓内既有默认常量登记。 */ export declare const E2B_SANDBOX_HOME = "/home/user"; /** {@link executionLaneHomeDir} 读的那两位部署配置(`ServiceConfig` 结构上满足)。 */ export interface LaneHomeDeployment { readonly remoteExec?: { readonly provider: RemoteExecProvider; } | undefined; /** `REMOTE_EXEC_HOME_DIR` —— 运维显式声明的执行环境 home(绝对路径;坏值在 config 层拒启)。 */ readonly remoteExecHomeDir?: string | undefined; } /** * 「**这条部署的执行环境**以哪个 home 运行」—— `~/…` 形权限规则(`Edit(~/.ssh/**)`)唯一的基。 * * ## 病(接入审计 M13 / DEBTS S-213④) * core 的 `ExecutionEnv.homeDir` 是**由 adapter 声明**的座(`engine/harness/types.d.ts`,可选): * 缺席 ⇒ core 的 `resolvePathPattern` 回 `{missingBase:"home"}` ⇒ 该规则对这条腿判 `unreadable` * (fail-closed 的 ask)。四只远端 adapter(e2b/k8s/host/ssh)一个都没声明,而 server 侧的 422 前置门 * (`task-settings.ts` 的 `rejectUnmatchableSettingsNames`)在 home 未知时整表拒 `~/` 形 —— * **于是远端部署根本写不出 `~/` 规则**,恒 422。 * * ## 本函数 = 那个值的**单一属主** * 两个消费面读同一份,不许各算各的(两处不同 = 一条规则在 422 门上被拒、却在引擎里编得出来,或反之): * ① 每只 adapter 的 `homeDir` 座(`boot/execution-env.ts` 把本函数的答案递进 adapter 配置); * ② 规则基 `PathRuleBases.home`(`boot/resolve-spec.ts` / `boot/parked-revive-gate.ts` 的 * `pathAdjudication.taskHome`)。 * * ## 一条规则,不是七条特判 * **运维显式声明恒赢**(`REMOTE_EXEC_HOME_DIR`,部署级旋钮,不挂任何客户端表态);没有声明时, * 只有「home 是**车道本身**的属性」的那几条车道才有答案: * · `host` / `in-process` —— 命令就跑在本进程这台机器、这个用户下,`os.homedir()` 读的就是它 * (core 自己的 `NodeExecutionEnv` 同判); * · `e2b` —— 平台固定用户,见 {@link E2B_SANDBOX_HOME}; * · `k8s` / `local-docker` —— home 是**镜像**的属性,boot 期无从得知; * · `ssh` —— home 是**目标机上那个登录用户**的属性(root 是 `/root`、macOS 是 `/Users/`), * 连上之前无从得知,而 422 门要在下单那一刻回答; * · `adb` —— Android 没有 POSIX 意义上的登录 home; * · `device` —— 员工自己的机器,运行期才连进来。 * 这五条一律**缺席**(诚实:不猜一个目录去守)。缺席的后果是 `~/` 规则在下单时被 422 响亮拒, * 与 core 的 `missingBase:"home"` 同向 —— 这正是负控要的那一格。 * * 认不出的车道 ⇒ `undefined`(调用方 fail-closed)。生产链上不可达:`loadConfig` 的 [ref] 拒启门先咬。 */ export declare function executionLaneHomeDir(deployment: LaneHomeDeployment): string | undefined; //# sourceMappingURL=execution-lane-caps.d.ts.map