import { type BufferEncoding, type CommitResult, type CpOptions, type DirentEntry, type FileContent, type FsStat, type MkdirOptions, type ReadFileOptions, type RmOptions, VendoError, type WorkspaceFs, type WriteFileOptions } from "@vendoai/core"; import type { WorkspaceFileMeta, WorkspaceRows } from "./workspace-rows.js"; /** Build contract §3.1 — the frozen layout. `/user` is the subject's, rw; `/orgs/` is one mount per ASSERTED membership (§9.7), owned by the org; `/host` is host-authored, ro for everyone. No other top-level mount exists, and no path's meaning depends on who wrote it. */ export declare const USER_MOUNT = "/user"; export declare const HOST_MOUNT = "/host"; export declare const ORGS_MOUNT = "/orgs"; /** Resolve `.`/`..`, collapse slashes, drop the trailing one. Pure. */ export declare function normalizePath(path: string): string; /** * Build contract §3.2 — just-bash's `IFileSystem` over the store. * * Two tiers, one namespace: * - a **path index** built at turn start (`getAllPaths`/`resolvePath` are * synchronous in just-bash, so the paths must be known without I/O), kept * current on every write; * - **content read through the store**, never cached — except for paths this * turn has written, which stage in memory until `commit()`. * * Staging is what keeps the store write law at O(files changed): a `sed -i` * loop writing one file forty times is one row, one revision, one history * entry. * * The namespace is exactly the mounts the host asserted: `/user`, plus one * `/orgs/` per asserted membership. A write anywhere else is refused * (`EACCES`) rather than accepted into memory and dropped at commit — bash's * own scratch belongs in the `scratch` directory each mount reserves for it. * * `/host/**` is a read-only overlay the caller supplies per turn, not store * rows: skills and host knowledge are code-defined (`Skill.body`, * contract §5), so projecting them per turn is always current, while a copy in * the store could go stale against the deployed code. */ /** Build contract §9.7 — what one turn's façade may reach. `subject` owns `/user/**`; each asserted org owns `/orgs//**`. `canCommit` is the per-path live-rows check the commit runs (§9.3's path variant); absent, the façade is single-player and every `/user` write lands as it always did. */ export interface WorkspaceMounts { subject: string; /** Org ids the host ASSERTED this request. An org that is not here has no mount at all — not an empty one, not a forbidden one: absent. */ orgs: readonly string[]; /** Live-rows `can(editor)` for one path. Runs at commit, never at read: the box is a snapshot, so reads age gracefully and writes never sneak through. */ canCommit?: (path: string) => Promise; /** Live-rows `can(viewer)` for one path — consulted ONLY to choose the code a refused commit wears (§9.4): `forbidden` for a caller who provably sees the path, the masked `not-found` for everyone else. Absent ⇒ every refusal is `forbidden`, which is correct for a single-player façade where the only reachable paths are the caller's own. */ canView?: (path: string) => Promise; } /** Build contract §9.4 — what a caller who cannot even VIEW a path is told: exactly what a path that isn't there is told. Existence-masking is the default posture, and a `forbidden` handed to a non-viewer inverts it into an oracle ("this org has an app by that id"). Both doors that refuse a path — the façade and the staged-commit gate below — say it in these words, from here, because two copies of a refusal are two refusals that drift. */ export declare const pathNotFound: (path: string) => VendoError; /** §9.4's other half: a caller who provably SEES the path but may not change it gets `forbidden` — the code the consumer-voice fork offer renders from. Keeping "forbidden implies caller is >= viewer" true is what makes that offer safe to show. */ export declare const pathForbidden: (path: string) => VendoError; export declare class WorkspaceStoreFs implements WorkspaceFs { private readonly rows; private readonly mounts; private readonly host; private readonly staged; private readonly removed; private readonly directories; private readonly index; constructor(rows: WorkspaceRows, mounts: WorkspaceMounts, index: WorkspaceFileMeta[], host: Map); /** Build contract §9.7 — owner derivation is a PURE FUNCTION OF THE PATH: `/user/**` is the bound subject's, `/orgs//**` is the org's. An org the host did not assert has no owner here, so it has no mount. */ private ownerOf; /** The mount roots this caller has, in the order readdir reports them. */ private mountNames; private storeBacked; private readOnly; /** Every write goes through here: `/host` is read-only, and anything outside this caller's mounts is refused outright rather than accepted and dropped. */ private assertWritable; private persists; /** Every path the turn can see: the store's index, the host overlay, and this turn's writes. */ private livePaths; private isFile; /** Directories are implied by the paths under them (the store holds files, not directories), plus anything explicitly `mkdir`ed this turn. */ private isDirectory; private bytesOf; private stage; readFile(path: string, options?: ReadFileOptions | BufferEncoding): Promise; readFileBuffer(path: string): Promise; writeFile(path: string, content: FileContent, options?: WriteFileOptions | BufferEncoding): Promise; appendFile(path: string, content: FileContent, options?: WriteFileOptions | BufferEncoding): Promise; exists(path: string): Promise; stat(path: string): Promise; /** No symlinks over a document store, so lstat is stat. */ lstat(path: string): Promise; mkdir(path: string, options?: MkdirOptions): Promise; readdir(path: string): Promise; readdirWithFileTypes(path: string): Promise; rm(path: string, options?: RmOptions): Promise; private drop; cp(src: string, dest: string, options?: CpOptions): Promise; mv(src: string, dest: string): Promise; resolvePath(base: string, path: string): string; getAllPaths(): string[]; chmod(path: string, _mode: number): Promise; symlink(_target: string, linkPath: string): Promise; link(_existingPath: string, newPath: string): Promise; readlink(path: string): Promise; realpath(path: string): Promise; utimes(path: string, _atime: Date, mtime: Date): Promise; /** * Build contract §9.3, exposed — the same live-rows question `commit()` asks * itself below, asked one path at a time. * * The sandbox path (§3.5) needs it out loud: it holds a workspace and never a * store, and it has to know per FILE whether a checkout lands writable and * whether a changed file may go home. Answering from the mount shape instead * is what made every `/orgs/**` path invisible to `claudeCode()`. */ canCommit(path: string): Promise; /** * Build contract §3.2 — land the turn's writes. Commit policy is per mount: * `/user` is last-write-wins, `/orgs` is strict compare-and-swap against the * revision the turn opened with, and a lost swap returns `conflict`. * * **Preflighted.** Every staged file's content is placed first; only when the * whole set is placeable does any row change. A commit therefore either lands * all of it or lands none of it — an oversized upload can no longer swallow * the same turn's app edit just by being staged first. Deterministic failures * (over the store-backed cap, an adapter refusal) throw a `VendoError` naming * the file; `CommitResult` keeps its frozen `ok | conflict` vocabulary. * * Only paths whose bytes actually changed are written (§3.5), so `changed` is * the honest O(files changed) count. `/user/scratch/**` never lands. */ /** * Build contract §8/§9.3 — the box is born filtered, so `can()` runs at * exactly two moments; this is the second. Live rows, per changed path, * BEFORE anything is placed: a mid-session revoke must bite here even though * the reads it already served stand. */ private assertCommittable; commit(opts?: { message?: string; }): Promise; } //# sourceMappingURL=workspace-fs.d.ts.map