Skip to content

Build the Vfs foundation and pivot the executor to VfsRef - #34

Closed
vinniefalco wants to merge 3 commits into
cppalliance:masterfrom
vinniefalco:master
Closed

Build the Vfs foundation and pivot the executor to VfsRef#34
vinniefalco wants to merge 3 commits into
cppalliance:masterfrom
vinniefalco:master

Conversation

@vinniefalco

Copy link
Copy Markdown
Member

Summary

One filesystem abstraction replaces the run-scoped Store trait and serves every consumer: Lua, the model's tools, and the Bashkit engine. The Store survives as a public concrete facade with an unchanged Lua surface, mounted at /_promptforge/store inside the namespace it used to stand apart from, and executor::run() takes VfsRef in place of StoreRef.

What changes

  • shared-vfs (new, std-only, zero dependencies): the sync Vfs/VfsAccess trait pair behind a poison-safe cloneable VfsRef; a mount router with longest-prefix dispatch, builder-fixed mount tables, and nestable overlays; universal POSIX-shaped path canonicalization with interning; a claims model attributing every operation to an ExecId through an RAII Access capability, where concurrent write conflicts are fatal; a policy layer with reason-carrying Deny/Ask verdicts (AllowAll in v1); a generic memory backend; and a stage-1 host backend (HostBackend::identity() / HostBackend::rooted(dir)) with containment enforcement and failure-atomic writes.
  • promptforge-vfs (new): the promptforge policy layer - the /_promptforge mount layout, the empty() stock constructor with the store mount preinstalled, and ModePolicy (Ask / Plan / Agent) behind a UI-flippable shared mode.
  • promptforge-store: the Store trait, MemStore, and FileStore leave the public API; a public concrete Store facade wraps a prefix-scoped Access and is exposed as vfs.store(&access). The StoreError vocabulary, anchor-edit rules, numbered reads, idempotent delete, and glob grammar are preserved exactly. The WriteScope registry is deleted in favor of the claims model.
  • promptforge-core: execute::run() takes &VfsRef; RunContext builds the Store facade internally; Lua store operations become leaf yields in the coroutine protocol, answered via spawn_blocking uniformly for all backends; a claims violation maps to a fatal determinism RunErrorKind that terminates the run and is not catchable from Lua.

Verification

  • The existing promptforge-store test suite passes against the rewritten facade unmodified in intent (parity gate).
  • Router longest-prefix, nesting, shadowing, read-only enforcement, and mount-escape rejection matrices; cross-platform path canonicalization matrix.
  • Claims lifecycle: borrow-vs-spawn, transfer, drop release, alias collision on one interned key; compile-time Send/Sync assertions for VfsRef and Access.
  • Executor end-to-end on VfsRef, including a fanout whose cross-arm append terminates the run with a determinism violation naming both arms.
  • Bashkit adapter spike: FsBackend over VfsRef compile-checked and smoke-tested with an ls/cat/grep script across mounted backends.

Out of scope

The do_shell dispatcher, git builtins, approval policy, the SQLite run-record backend, terminal mirrors, Bashkit integration proper, and stage-2 host-backend hardening.

Plan: vibe/2026-09-11-3-vfs-foundation.md

Plan: vibe/2026-09-07-2-gateway-tts-phase-1.md
Introduces a new zero-dependency crate that sits permanently at the bottom of the dependency stack, holding the generic virtual filesystem machinery: one shared error type, the value types backends exchange, and canonical interned virtual paths. Paths are normalized and interned the moment the API receives them, so claim lookups stay pointer-cheap and aliases cannot slip past the claims tables. The only way to form a path is through a crate-private canonicalizer, so canonicalization at receipt is enforced by visibility rather than convention. A self-policing test reads the crate manifest and fails if any dependency table gains an entry.

- `crates/shared-vfs/Cargo.toml` declares an empty `[dependencies]` table carrying the zero-dependency rule in comments; `the_manifest_declares_no_dependencies` reads the manifest through `CARGO_MANIFEST_DIR` and fails if any dependency table carries an entry.
- `VfsPath` wraps an interned id so equality is an integer compare; `canonicalize` is crate-private, making it the only way to form one.
- `Interner` is hand-rolled on std behind a poison-safe `OnceLock` mutex; each string is leaked once, so resolution is a vector index and a panicking writer cannot leave the tables half-updated.
- `FileType` names all seven POSIX kinds with no catch-all, so an exotic backend node cannot be hidden.
- `Stat` uses optional fields for mode, modified, and created, so a backend that does not track a field says `None` rather than fabricating a value.
- `canonicalize` treats backslashes as separators, collapses duplicates, removes dot segments, pops exactly one segment per dotdot, and rejects traversal past the root plus relative and empty paths; case is preserved and significant. Ten tests cover the matrix and interning identity.
- `canonicalize` and `Interner::intern` carry `#[allow(dead_code)]`: only tests call them today, with the production caller still to arrive.

Design: new facade @ crates/shared-vfs/src/lib.rs boundary: pub
Design: new global-state @ crates/shared-vfs/src/path.rs::interner
Design: new newtype @ crates/shared-vfs/src/path.rs::VfsPath boundary: pub
Design: new value-object @ crates/shared-vfs/src/path.rs::VfsPath boundary: pub
Design: new newtype @ crates/shared-vfs/src/path.rs::VfsPathBuf boundary: pub
Design: new value-object @ crates/shared-vfs/src/path.rs::VfsPathBuf boundary: pub
Design: new value-object @ crates/shared-vfs/src/types.rs::FileType boundary: pub
Design: new bag-of-state @ crates/shared-vfs/src/types.rs::GrepQuery boundary: pub
Deferred: canonicalize and Interner::intern carry #[allow(dead_code)] with no production caller until Access arrives
Plan: vibe/2026-09-11-3-vfs-foundation.md
@vinniefalco
vinniefalco marked this pull request as draft September 12, 2026 01:22
This change defines the backend trait pair behind the virtual namespace: one trait for a backend that vends and releases identity-bound sessions, and one carrying all sixteen filesystem operations with defaults for ranged reads, string replacement, and content search. It adds the policy hook consulted before every operation, the operation and verdict types it matches on, and an allow-everything policy as the initial implementation. Execution identities are opaque and vended from a process-wide monotonic counter, so every operation can be attributed to one serial thread of execution. The default search matches literal text only and refuses regular expressions, since the crate is std-only and a regex engine must come from an overriding backend.

- `Vfs` - acquire and release are the only entry points; every operation lives on the acquired access object, so storage is unreachable without an identity. Send is required and Sync is not: the handle serializes access.
- `VfsAccess` - sixteen methods, with defaults for read_range, str_replace, grep, and the POSIX extras keeping the required set at eleven.
- `Verdict` - Deny and Ask carry reason strings: Deny's flows back to the model as its recovery path, Ask's is the approval dialog text.
- `ExecId` - an opaque single-field wrapper with no public constructor; vend draws from a process-global counter and has no caller until the handle arrives.
- `read_range` - the default reads the whole file and slices, clipping at the end and returning empty when the offset is past it.
- `str_replace` - the default rewrites the unique occurrence; zero or multiple matches are errors and leave the file unchanged.
- `grep` - the default globs, reads, and line-scans with literal substring matching; regex queries return Unsupported, non-UTF-8 files and directories are skipped, and results cap at max_results with a truncation flag.
- `AllowAll` - permits every operation.
- `write_owned` - noted in the write documentation but not implemented in v1; the zero-copy move for the memory overlay waits for profiling.
- `crates/shared-vfs/src/path.rs` - the dead-code allows on intern and canonicalize are removed now that the grep default canonicalizes.

Design: new newtype @ crates/shared-vfs/src/traits.rs::ExecId boundary: pub
Design: new global-state @ crates/shared-vfs/src/traits.rs::ExecId::vend
Design: new speculative-abstraction @ crates/shared-vfs/src/traits.rs::VfsAccess boundary: pub
Design: new speculative-abstraction @ crates/shared-vfs/src/traits.rs::Policy boundary: pub
Design: new value-object @ crates/shared-vfs/src/traits.rs::Op boundary: pub
Deferred: ExecId::vend carries #[allow(dead_code)] with no caller until the handle arrives in a later step
Deferred: the defaulted write_owned method is noted but not implemented in v1, to be added when profiling calls for it
Plan: vibe/2026-09-11-3-vfs-foundation.md
@vinniefalco
vinniefalco marked this pull request as ready for review September 12, 2026 01:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant