import type { ImageCapabilities } from "./plugins/store-contracts.js"; export interface SandboxEnvFacts { profile?: string; capabilities?: string[]; pkgSource?: string; egress?: "none" | "allowlist" | "full"; /** [ref]③ scratchpad first wave: core `TaskSpec.envFacts.scratchpadDir` (field + prompt chain — * buildScratchpadSection — shipped in core 1.257.3; only this server half was missing). Core renders the * "# Scratchpad Directory" block steering temp files away from /tmp AND the user's project. Core's prompt * wording promises: session-specific, isolated from the user's project, "can generally be used without * permission prompts" — that last clause is exactly the exemption domain createFsWriteGatePolicy.exemptDirs * will honor ([ref]③/[ref]①), so the path is produced by the single-source {@link scratchpadDirFor} and the * 1.290 wiring must reference the SAME function for exemptDirs. */ scratchpadDir?: string; /** [ref]②(core 1.360 B 半场)durable resume 铸句真值——server 按**车道能力面**填(部署静态事实, * 非车道名判——[ref]③ 诚实裁:缺席字段=core 零句,绝不写死车道行为): * - e2b:VM snapshot suspend/resume ⇒ processes+scratch 双 preserved。 * - k8s(s3Snapshot):resume=fresh pod+workspace untar ⇒ processes+scratch 双 lost(note 讲清 * workspace 文件已从快照恢复——「scratch lost」≠「工作丢了」)。 * - ssh/adb:park-only,远端 host/device 持续存在 ⇒ scratch preserved;processes 不声明(前台随 * 连接断,daemon 存活——不知道的不说)。 * - device(v2 §4.2):同 ssh/adb 那一档 —— 工作区是员工机磁盘,park-only 赎回后文件还在 * (core 的 `suspendable:false` 恰恰断言「工作区在目标上外部持久」);processes 不声明: * executor 是员工机上的独立进程,断连/合盖后前台进程存亡我们不知道,不知道的不说。 * - host:同机 ⇒ scratch preserved;processes 不声明(worker 进程可能换代)。 * - local-docker:容器跨 leg 存续不保证(reconnect 或有或无)⇒ 整域不声明。 */ resumeFacts?: { processes?: "preserved" | "lost"; scratch?: "preserved" | "lost"; note?: string; }; } /** Compose envFacts from what the deployment knows at spec-prep. Returns undefined when nothing is known * (no fact beats a vacuous one — an empty block would still spend prompt tokens). */ export declare function buildEnvFacts(opts: { profile?: string | undefined; capabilities?: ImageCapabilities | undefined; pkgSource?: string | undefined; egress?: "none" | "allowlist" | "full" | undefined; scratchpadDir?: string | undefined; resumeFacts?: SandboxEnvFacts["resumeFacts"] | undefined; }): SandboxEnvFacts | undefined; /** resumeFacts 按车道能力面的单真源(部署静态;undefined=整域不声明)。取值依据见 * {@link SandboxEnvFacts.resumeFacts} 的逐车道注。 * codex 复审 medium:k8s 必须按**有效快照能力**分叉,不能只看车道名——s3Snapshot 未配=park-only, * resume 是全新 pod 且**工作区不恢复**,「文件已从快照恢复」那句会让模型信赖不存在的文件(比缺席 * 更糟的谎)。 */ export declare function resumeFactsForLane(provider: "e2b" | "k8s" | "ssh" | "adb" | "local-docker" | "host" | "device" | undefined, caps?: { k8sSnapshot?: boolean; }): SandboxEnvFacts["resumeFacts"] | undefined; /** Path-safe session segment: the session id can be CALLER-SUPPLIED (body.sessionId), so it must not be able to * walk the tree ("../", separators, control bytes). Allowlist [A-Za-z0-9_-] (dots excluded so ".." cannot * survive), everything else becomes "_". Empty after sanitize → "_". * * INJECTIVITY (codex F3): allowlist sanitization alone is NOT injective — "team/a" and "team?a" both map to * "team_a", so two DIFFERENT sessions (possibly different principals) would collide onto ONE scratchpad dir. * The segment is therefore `-`: * the hash is over the raw pre-sanitize id, so distinct ids always produce distinct segments (the prefix stays * purely for human readability). Max length 24+1+16=41 (uuidv7 = 36 raw). */ export declare function scratchpadSessionSegment(sessionId: string): string; /** THE scratchpad path convention: `/scratchpad/`. Session-scoped (core's * prompt says "session-specific" — every task on the session shares one dir, which is the useful semantics: * intermediate artifacts survive across turns/resume legs). Exported as the single source both the envFacts * composer (below) and the future createFsWriteGatePolicy `exemptDirs` wiring ([ref]①, 1.290) must use. */ export declare function scratchpadDirFor(localDataRoot: string, sessionId: string): string; /** Resolve + CREATE the session scratchpad dir (mkdir -p, idempotent). Returns the path, or undefined when * creation fails (honest-facts posture: never advertise a directory that does not exist — same class as * egress/pkgSource "don't declare what you don't know"). Lifecycle: the reaper primitives live below — * {@link purgeScratchpadDir}(session delete)+ {@link sweepStaleScratchpads}(mtime 周期扫),keyed by the * same scratchpadDirFor convention;接线(E21 DELETE purge + 周期 sweep)在 main.ts 侧(D 组)。 */ export declare function ensureScratchpadDir(localDataRoot: string, sessionId: string): Promise; /** G3([ref] 壳侧承诺③/[ref]③,[ref]§四接单):**壳供** scratchpad 路径的验收门。 * * 壳(cli)vendored getScratchpadDir 算出 per-session scratchpad 路径随 `TaskRequest.scratchpadDir` 发来; * 接受后它**优先于** server 自算(ensureScratchpadDir),喂 main.ts 的同一单点(envFacts 事实 + * fs-write gate exemptDirs + additionalDirectories 根围栏)。「没到货引擎忽略 = 无害 additive」。 * * 信任边界:这是 exemptDirs **放宽**输入,验收 fail-closed(任何拒收形回落自算,不 fault 任务): * - 多租户(requirePrincipal)一律拒——共享 worker 盘上租户自选豁免目录=超范围面; * - 仅 host-semantics lane——远程沙箱 lane 的 worker 本机路径对 hands 是谎言(honest-facts 同轴); * - 绝对路径 + ≤{@link MAX_PATH}(core 渲染上限同源,超限=core 只能给模型假路径)+ canonical * 深度 ≥3(裸 /、/tmp、/home 一整块系统目录不能当豁免域;穿越形按 resolve 归一后判); * - mkdir -p 确保存在(gate 的 canonicalize 与 core 根围栏都要真目录)。 * 敏感路径的最后一道**不在**写门折叠里([ref] 起搬家):守卫集 `SENSITIVE_WRITE_PATTERNS` 由 * governance 拍(boot/resolve-spec.ts → applyRuntimeGovernance)无条件铸成 DENY 基线,经 tightenTaskSpec * 与写门 deny-wins 折叠 —— 结论不变(deny 恒赢,本函数放出的 exemptDirs 越不过它),但施加点已换。 * * 🔴 **接缝登记(7.74.0 / core 7.17.0 [ref];今日无可达分歧)**:下面那道「绝对路径」闸回答的是「引擎眼里 * 这条豁免目录算不算绝对」——引擎自本版起按**树的家族**判(`isAbsoluteForFamily`,家族从树的 root/cwd 拼法 * 读,不看宿主 OS),本函数用的是 Node 宿主口径。**posix 家族两者逐字同判**(2026-09-12 实测六形全同), * 而本闸又只在 `hostSemanticsLane` 上场(路径就是这台 worker 自己的盘)⇒ 今天零分歧,不改。**分歧只在 win * 家族的 `/`-开头无盘符那一类**(Node 说绝对、引擎说未完全限定)。判据与坐标属主见 `task-cwd.ts` 的 * {@link import("./task-cwd.js").isValidCwd} 顶注(同一条接缝,那里是唯一成文点,这里不复述第二份)。 */ export declare function acceptShellScratchpadDir(raw: unknown, opts: { requirePrincipal: boolean; hostSemanticsLane: boolean; warn: (msg: string, meta?: object) => void; }): Promise; /** * {@link acceptShellScratchpadDir} 的**词法半场**(S-351 拆出):零 I/O、可在请求的任何一点问。 * * 为什么拆:S-351 的前判要在**任何副作用之前**回答「这条请求会把哪个目录当写门豁免域」,而验收门的 * 后半场是 `mkdir`(副作用 + I/O)。把判据抄一份到前判处 = 两份验收规则,而两份规则漂移的那天,前判 * 会替一条**根本不会被接受**的壳供路径拒掉请求(或反过来放过一条会被接受的)。所以规则只有这一份, * 两个调用点各取所需:验收门 = 本函数 + mkdir;前判 = 本函数。 * * 返回的是**词法归一后**(`resolve`)的拼法 —— 与验收门交给写门的那一个逐字相同。 */ export declare function screenShellScratchpadDir(raw: unknown, opts: { requirePrincipal: boolean; hostSemanticsLane: boolean; warn: (msg: string, meta?: object) => void; }): string | undefined; /** * S-351(core 7.20.0 / [ref])—— **这条请求会不会把任务根放进写门的豁免域里**。 * * ## 病形(引擎侧的硬拒,不是判决期的宽严) * 写门的豁免域就是 scratchpad(`exemptDirs: [scratchpadDir]`,`task-settings.ts` 的 * `createFsWriteGatePolicy` 那一句),而门的根是这条 task 的 cwd。core 7.20.0 起,**豁免域等于根或是 * 根的祖先 ⇒ 构造期抛**(亲读安装树 `dist/core/fs-write-gate-policy.js`, * `code = "config.write_exempt_dir_covers_root"`)——理由逐字:一条盖住根的豁免把根下**每一次写**都自动 * 批准,那就是整个工作区,门宁可拒绝被建出来,也不肯带着一扇静默关掉的写治理去跑。 * * 本仓真正够得着这一形的只有**一条边**:调用方自设 `body.cwd` 进 scratchpad 树(server 自己**从不** * 把 cwd 缺省到 scratchpad —— scratchpad 与 task root 是两棵子树)。它今天的下场是 spec 折叠期抛出一条 * 没人分诊过的错,而调用方看到的是一条与病因无关的 422。所以前判把它提前成一条**读得懂的请求拒绝**。 * * ## 判据只有词法(不承诺 canonical) * 引擎那一侧的构造期**没有 I/O**(同上坐标:`lexicalViewOf` 纯字符串),所以这里也只做词法 —— 承诺 * canonical 就是承诺一件此刻做不到的事(软链形的真身要到裁决时刻才问 fs,那是写门自己的第二道)。 * * ## 射程(每一条都是「不这样写就会误拒」) * · **只在 cwd 真会被尊重时判**:`cwdHonored` 为假的部署(多租户 / 非 host 车道)把 cwd 直接忽略, * 它永远不会变成门的根 —— 在那里拒等于凭空多一条 400; * · **豁免域取这条会话可能挂上的每一只**(不是「这一次挂哪只」):server 自算那条恒是候选(有 session * 就算得出),壳供 `body.scratchpadDir` **过了词法验收**才追加为候选(过不了的那条永远挂不上,拿它拒 * 就是凭空多一条 400)。只判其一会留一条**跨请求**的洞:一条请求供一只合法壳 scratchpad 把判据引开、 * 同时把 cwd 指进自算那只,放行后 cwd 坐进会话表,下一条不带路径字段的请求回落自算形、死在引擎构造期, * 而它根本没送过 cwd(codex r2 [medium] 实证); * · **`in-process` 车道(provider 未设)不在射程**:那条腿的门根是**进程自己的 cwd**,与请求字段无关 —— * 对着一个请求字段拒一件不是它造成的事,是把诊断指向错的地方(如实登记,不假装覆盖)。 */ export declare function scratchpadCoveringTaskRoot(args: { cwd: unknown; shellScratchpadDir: unknown; localDataRoot: string; sessionId: string | undefined; requirePrincipal: boolean; cwdHonored: boolean; }): { cwd: { asSpelled: string; lexical: string; }; scratchpadDir: { asSpelled: string; lexical: string; }; } | undefined; /** 删除一个 session 的 scratchpad 目录(rm -rf,幂等:目录不存在=no-op)。失败吞 warn 不抛—— * 清理绝不 fault 调用它的 session-delete 路径;下一轮 {@link sweepStaleScratchpads} 仍会兜到它。 */ export declare function purgeScratchpadDir(localDataRoot: string, sessionId: string): Promise; /** 周期 sweep:扫 `/scratchpad/` 下 mtime 早于 `olderThanMs` 且不属于 `activeSessionIds` * (先经 {@link scratchpadSessionSegment} 映射到目录名域)的目录并删除,返回删除数。逐目录失败跳过 * (下一轮再兜);scratchpad 根不存在=0。mtime 取目录自身(子文件写入会碰父目录 mtime 的语义因平台而异 * ——诚实边界:一个只被读的活跃 session 目录可能被判过期,activeSessionIds 排除面才是活会话的真保险)。 */ export declare function sweepStaleScratchpads(localDataRoot: string, opts: { olderThanMs: number; activeSessionIds?: Set; }): Promise; /** Map the exec-lane posture to the egress fact (the model must know when downloads can't work). */ export declare function egressForRemoteExec(remoteExec: { provider?: string; allowInternetAccess?: boolean; } | undefined): "none" | "full" | undefined; //# sourceMappingURL=env-facts.d.ts.map