import { FileMemoryEngineBackend, type TaskSpec } from "@sema-agent/core"; import type { ServiceConfig } from "./config-types.js"; /** 前缀词表本体(供门/诊断读;判定一律走 {@link isV2ScopeKey},别在别处重写 startsWith 链)。 */ export declare const V2_SCOPE_PREFIXES: readonly string[]; /** * 一个 scope 键是否带 v2 typed 前缀。 * * ⚠️ 判据刻意停在**前缀**层,不升级成 core 的 `parseScopeKey`:后者对「带可识别前缀但结构坏」的键 * 是 fail-loud 抛错,而本谓词的两个调用点都只是在决定「要不要挂 v2 标 / 要不要再包一层」——把抛点 * 挪到这里会把 core 的校验时机整体前移(行为面改动,不在本条案的射程内)。坏键仍由 core 在它自己的 * 校验点 fail-loud。 */ export declare function isV2ScopeKey(key: string): boolean; /** Resolve the memory scope for a request ([ref] S1 — the file-based memory engine is the only plane). * * SINGLE-USER (REQUIRE_PRINCIPAL !== "true"): ONE shared scope — `config.memoryScope` (default "local"), * regardless of any principal label the caller self-reports (a single-user worker's `anon:*` / ad-hoc * principals are surface labels, not isolation identities — keying memory by them would split the one * user's memory across surfaces). Explicit `MEMORY_SCOPE` wins (it IS config.memoryScope). * * MULTI-TENANT (REQUIRE_PRINCIPAL=true): always `undefined` ⇒ no `spec.memory` ⇒ memory is DARK, * fail-closed. The engine's file basement carries NO tenant isolation (one shared directory tree), so * handing tenants per-principal scopes over it would be a cross-tenant bleed surface; until a * tenant-isolated backend exists, multi-tenant memory is off by construction. */ /** R5(批γ):memory scope 列宽(两方言 VARCHAR(190) 同宽)。principal 上限 190 **字符**在 * security.ts 把身份轴守住了,但 `formatUserScope`/`formatProjScope` 的段编码(百分号转义)会 * **膨胀**——非 ASCII principal 编码后可超列宽,此前落裸 SQL 错。产出处响亮拒,错误可分类。 * * [ref] 件1:定义**搬去** `plugins/memory-key-guards.ts`(列宽与 store 写口同源单一属主),此处 * 只再导出——旧 import 路径(本文件)逐字不变,`memoryScopeFor` 的产出处消费点也不变。 */ export { MEMORY_SCOPE_COLUMN_CHARS, assertMemoryScopeWidth } from "./plugins/memory-key-guards.js"; export declare function memoryScopeFor(config: ServiceConfig, principal?: string, projectId?: string): string | undefined; /** * N0 (通宵测试 2026-07-09,三重坐实 e2b+kata+代码): the file memory engine (core [ref]) materializes and * harvests via `node:fs` on the WORKER's local fs, while a remote exec lane routes the model's file tools to * the SANDBOX fs — the planes never meet: materialized memory is invisible inside the sandbox (the injected * instructions burn failing Write turns) and sandbox writes are never harvested (add=0). Until core defines * the boundary (engine routed through the ExecutionEnv seam, or host-only by contract — relayed on the team * channel), a remote lane forces memory DARK unless the operator explicitly vouches both lanes share one fs * (`MEMORY_ENGINE_REMOTE_LANE=allow`, e.g. ssh to a host on the same NFS mount). * * Pure posture probe (memoryEngineBackendFor consumes it; main.ts logs it): `undefined` = no plane split * (no remote lane, or the `host` lane), `dark` = split + fail-closed, `forced` = split + operator override. */ export declare function memoryEngineRemoteLanePosture(config: ServiceConfig): { lane: string; posture: "dark" | "forced"; } | undefined; /** * N0 启动告警的**文案**(纯,可单测;boot/stores.ts 的装配点只负责喂「引擎这一腿真的接上了吗」并打日志)。 * * 为什么要按引擎真身分腿(2026-08-12 复扫,已核真):{@link memoryEngineRemoteLanePosture} 的 `dark` 只对 * **file** 引擎腿有裁决权 —— 车道门的唯一消费者是 {@link memoryEngineBackendFor}。`MEMORY_ENGINE_BACKEND=pg|tidb` * 的两条腿在 boot/stores.ts 里**先于**任何车道判断就被点亮(库是持久真身,与手的文件平面无关),于是同一次 * 启动会先打 `memory_engine_enabled {enabled:true, backend:"pg"}`,几行之后再打一句「memory dark * (fail-closed) … Set MEMORY_ENGINE_REMOTE_LANE=allow」—— 两句直接对撞,而且把运维指向一个在该腿上**不接线** * 的旋钮(`memoryEngineRemoteLaneAllowed` 的读者只有本文件与那条日志)。日志是运维唯一能看见的部署事实, * 一句谎比没有这句更贵。 * * 返回 `undefined` = 本部署没什么可说的(无平面分裂车道 / 记忆整体关 / 多租户 file 腿的 dark 成因是租户隔离, * 已由 `memory_engine_enabled` 的 reason 位报因,不在这里重复)。 */ export declare function buildMemoryRemoteLaneWarn(config: ServiceConfig, /** 记忆引擎这一腿**真的**接上了吗(`memoryEngine !== undefined`)——装配结果,不在本函数里重算。 */ engineWired: boolean): { lane: string; effect: string; } | undefined; /** * [ref] S1 wiring gate (pure — unit-testable without booting main): build the file-based memory-engine * backend for this deployment, or `undefined` when memory must stay dark. * * - SINGLE-USER TURNKEY ONLY (`requirePrincipal !== true`): the engine's file basement * (`FileMemoryEngineBackend` over one directory tree) has NO tenant isolation, so a multi-tenant * deployment gets `undefined` — memory dark, fail-closed (never a shared cross-tenant store). * - `MEMORY_ENGINE=off` (`config.memoryEngineEnabled === false`) is the explicit operator kill-switch. * - REMOTE EXEC LANE (N0): a plane-splitting lane (any non-`host` remoteExec) forces `undefined` — memory * dark, fail-closed — unless `MEMORY_ENGINE_REMOTE_LANE=allow` (see memoryEngineRemoteLanePosture above). * - Directory: `/memory` where root = `resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot)` * (explicit `MEMORY_ENGINE_DIR` → caller's data root → `AGENT_DATA_DIR` env → `~/.ai-agent`). The * FileBackend PINS this directory (core §2.7: `directoryRoot` wins over per-repo derivation), so the * single user has ONE memory home across repos/tasks — the CC `~/.claude` posture. * * Returns the backend + the resolved config root (the root also rides on `RunnerDeps.memoryEngineDir` so * core derives the B3 control plane beside it). */ /** * S-133(7.59.0 合并重扫 wf_5ac18676 #1)—— **引擎数据根的唯一推导点**。模型是用 fs 工具往这里写记忆的 * ([ref] 记忆边界不变式,boot/memory-boundary.ts),所以三件事必须读**同一只**值:①boot 起哪只记忆后端的根 * (boot/stores.ts 三腿)、②`RunnerDeps.memoryEngineDir`、③守卫集 `createSensitivePathPolicy` 的 `dataRoot` * (deployment-governance.ts:落在这个根**里面**的目标按相对根判,根自己的祖先段不算目标的账)。 * 此前 ③ 读的是 `config.localDataRoot`(本地**店**根:`LOCAL_DATA_ROOT → CONFIG_LOCAL_DIR → AGENT_DATA_DIR`), * 而 ① 的 file 腿读的是 `MEMORY_ENGINE_DIR ?? AGENT_DATA_DIR`——两条链在「店根另指他处 + 记忆根坐在守卫段下 * (壳布局 `~/.sema/engine-data`)」时分家 ⇒ 记忆库每一次 Write 被硬拒(7.58.0 放行 ⇒ 回归);反向 * (`CONFIG_LOCAL_DIR=~/.sema` 当 dataRoot)则让配置根自身免检。两根本就是两件事:店根归誊本门,引擎根归本函数。 * * 形与 boot/stores.ts 逐字同律:DB 记忆腿(pg/tidb)的根 = `/memory-work` * (per-worker 物化区);file 腿 = `resolveMemoryEngineRoot(memoryEngineDir ?? fallbackRoot)`。**不判引擎是否 * 启用**——纯路径推导,禁用态下守卫仍以「若开引擎会写到哪」为根,与 core 缺省(`$AGENT_DATA_DIR`)同向。 */ export declare function memoryEngineRootFor(config: Pick, fallbackRoot?: string): string; /** file 后端的根链(`MEMORY_ENGINE_DIR ?? fallback ?? $AGENT_DATA_DIR ?? ~/.ai-agent`)—— 与后端选择器**无关**: * `memoryEngineBackendFor` 恒造 file 后端(run-local 从 env 继承 `MEMORY_ENGINE_BACKEND=pg|tidb` 时仍是 file), * 所以它的根必须走这条链而不是 `memoryEngineRootFor` 的分派(codex 修复验证轮 [medium]:分派会把 run-local 的 * 记忆搬到 `/memory-work`,存量记忆与控制面升级后隐形)。 */ export declare function fileMemoryEngineRoot(config: Pick, fallbackRoot?: string): string; /** 本部署 file 记忆引擎的**平面三坐标**(根 / 记忆目录 / 控制面目录)—— {@link fileMemoryPlaneFor} 一次算齐。 */ export interface FileMemoryPlane { readonly root: string; readonly memoryDir: string; readonly controlDir: string; } /** * S-215(B-085)—— **file 记忆引擎平面的单铸点**,兼这条腿的**接线门**。 * * 为什么是一只函数而不是两处各自推导:平面坐标有**两个**消费者 —— ① {@link memoryEngineBackendFor} * 钉给 `FileMemoryEngineBackend` 的 `memoryDir` + `controlDir`;② local 车道的 capture opt-out **记录店** * (`StoreBackend.sessionCaptureRecords()` 喂 core 的 `fileSessionCaptureRecordStore(controlDir)`)。 * 两者必须是**同一只目录**,否则声明写进 A、引擎读 B ⇒ 一条被「受理」的隐私声明在引擎面根本不存在, * 那比一句诚实的 409 更坏。两处各写一份推导链 = 哪天根链改了只改一处的必然漂移,所以推导(根 → 记忆目录 → * 控制面目录,三步都在这里、只写一遍)与门合并在此(合并复审 R37:此前 `join(root, "memory")` 在本函数与 * `memoryEngineBackendFor` 各写一份 —— 记忆目录规则有了第二个写者,现已收回一处)。 * * 返回 `undefined` ⇔ 本部署上 file 记忆引擎**不接线**(多租户 / `MEMORY_ENGINE=off` / 平面分裂车道未 allow)。 * 门与 `memoryEngineBackendFor` 逐字同源(它就读本函数),所以「有引擎 ⇔ 有控制面 ⇔ 供记录店」三者 * 在构造上不可能分家。 * * 🔴 **core 7.16.0 起每个 scope 的家深一层**(`/