Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/runvil-internal/engine/src/error/tests-chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ fn a_parse_failure_run_raises_names_the_prompt_and_its_file() {
let code = ErrorCode::StructureInvalid;
let message = "not a runvil prompt: no runvil version";
let stamped = crate::prompt::detail::with_prompt_context(
crate::prompt::detail::parse_error(code, message),
runvil_types::error::failure(code, message),
"greeter",
"agents/greeter.md",
);
Expand Down
4 changes: 2 additions & 2 deletions crates/runvil-internal/engine/src/execute/scheduler/drive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! every issued effect has its answer, so every effect the caller was
//! handed has exactly one answer.

use runvil_types::error::{ErrorCode, place};
use runvil_types::error::{ErrorCode, failure, place};
use runvil_types::event::lifecycle;
use runvil_types::ids::AbandonReason;
use runvil_vfs::Origin;
Expand Down Expand Up @@ -173,7 +173,7 @@ impl Scheduler {
prompt.file(),
crate::prompt::detail::key_line(prompt, "runvil"),
)),
None => Some(crate::prompt::detail::parse_error(
None => Some(failure(
ErrorCode::StructureInvalid,
"not a runvil prompt: no runvil version",
)),
Expand Down
4 changes: 2 additions & 2 deletions crates/runvil-internal/lua/src/coro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use runvil_model_client::model::ModelBinding;

use crate::engine_globals::route_store_to_shims;
use crate::error::{self, Result};
use crate::error_value::{install_error_value, install_normalize_failure};
use crate::error_value::{install_error_value, normalized};
use crate::hardening::CancelHook;
use crate::models::{LuaModelHandle, selection_poisoned};
use crate::program::LuaProgram;
Expand Down Expand Up @@ -171,7 +171,7 @@ pub(crate) fn install_shims(
let compactors: Table = globals.raw_get("compactors").map_err(error::lua)?;
let error_value = install_error_value(lua).map_err(error::lua)?;
let stash_failure = stash_failure_capture(lua)?;
let normalize_failure = install_normalize_failure(lua).map_err(error::lua)?;
let normalize_failure = lua.create_function(normalized).map_err(error::lua)?;
let (enter_adhoc_handler, leave_adhoc_handler) =
adhoc_handler_captures(lua, adhoc_handler_depth)?;
let hook = cancel_hook.clone();
Expand Down
14 changes: 1 addition & 13 deletions crates/runvil-internal/lua/src/error-value-classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::num::NonZeroU32;

use mlua::{Function, Lua, Value};
use mlua::{Lua, Value};
use runvil_types::error::Error;

use super::error_table;
Expand Down Expand Up @@ -64,18 +64,6 @@ fn traceback_line(error: &mlua::Error) -> Option<NonZeroU32> {
}
}

/// Builds the `normalize_failure(value)` chunk capture for the shim's
/// `pcall` and `xpcall` replacements: a Rust callback's failure (mlua's
/// wrapped error) becomes the error table its classification names; any
/// other caught value - a string, an author's own table, an error table
/// already built - passes through unchanged.
///
/// # Errors
/// Returns an `mlua` error if the function cannot be created.
pub(crate) fn install_normalize_failure(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(normalized)
}

/// Returns what `normalize_failure` returns for `value`: a Rust callback's
/// failure as the error table its classification names, anything else
/// unchanged, for the capture and the loop's compactor rule alike.
Expand Down
4 changes: 2 additions & 2 deletions crates/runvil-internal/lua/src/error-value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//!
//! [`ErrorKind`] names the closed vocabulary of kinds; [`error_kind`] and
//! [`error_fields`] render a universal error into the shape;
//! [`install_normalize_failure`] is the capture the shim's `pcall` and
//! [`normalized`] is the capture the shim's `pcall` and
//! `xpcall` replacements run a caught value through (so a Rust callback's
//! failure, which mlua raises as an opaque userdata, takes the same shape);
//! and [`raised_from`] reads a table back into a universal error when a
Expand All @@ -42,7 +42,7 @@ use runvil_types::error::{Error, ErrorCode, FactKey, coordinates};

use crate::error::accepted_tags;

pub(crate) use classify::{install_normalize_failure, normalized};
pub(crate) use classify::normalized;
pub(crate) use raised::raised_from;
pub use store::store_error;
#[cfg(any(test, feature = "test-support"))]
Expand Down
11 changes: 1 addition & 10 deletions crates/runvil-internal/prompt/src/detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,10 @@

use std::num::NonZeroU32;

use runvil_types::error::{Error, ErrorCode};
use runvil_types::error::Error;

use crate::{Block, Prompt, Section};

/// Builds a parse failure with no location, for a failure the Engine
/// raises about a parsed prompt.
///
/// Its prose is the code's default until [`with_prompt_context`] writes it.
#[must_use]
pub fn parse_error(code: ErrorCode, message: impl Into<String>) -> Error {
crate::error::failure(code, message)
}

/// Gives a parse failure the Engine raises about a parsed prompt the
/// prompt's frontmatter `name` as its `Name` fact, its place in the
/// prompt's `file`, and the prose a failure the parse returns has.
Expand Down
2 changes: 1 addition & 1 deletion crates/runvil-internal/vfs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
pub use error::{ParsePathReasonError, PathReason, VfsError};
pub use handle::{Access, VfsRef};
pub use memory::MemoryBackend;
pub use observe::{OpEvent, OpSink, Origin};
pub use observe::{OpEvent, Origin};
pub use path::{VfsPath, VfsPathBuf};
pub use real::{RealBackend, Writability};
pub use router::VfsRefBuilder;
Expand Down
2 changes: 1 addition & 1 deletion crates/runvil-internal/vfs/src/observe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl<'a> OpEvent<'a> {
///
/// The sink must be cheap. Store operations call it from the blocking
/// pool, inline with the operation.
pub type OpSink = Arc<dyn Fn(OpEvent<'_>) + Send + Sync>;
pub(crate) type OpSink = Arc<dyn Fn(OpEvent<'_>) + Send + Sync>;

#[cfg(test)]
mod tests {
Expand Down
157 changes: 157 additions & 0 deletions vibe/2026-11-06-1-engine-surface-trims.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
name: Engine surface trims
overview: Remove three pieces of Engine surface that carry no weight - a public type alias nothing outside its crate names, a one-line forwarder in the prompt crate's `detail` module, and a one-line wrapper with one caller in the Lua crate - with no change in behavior.
todos:
- id: op-sink
content: "Stop exporting the `OpSink` alias from `runvil-vfs` and narrow it to `pub(crate)`, in its own commit"
status: completed
- id: parse-error
content: "Delete `runvil_prompt::detail::parse_error` and point its two callers at `runvil_types::error::failure`, in its own commit"
status: completed
- id: normalize-failure
content: "Inline `install_normalize_failure` into `install_shims` in `runvil-lua`, in its own commit, and finish with the full gate set"
status: completed
isProject: false
---

# Engine surface trims

<product-contract>

## Product Requirements

Three items in the Engine crates exist only to be named once. Removing them shrinks what a reviewer reads in `runvil-vfs`, `runvil-prompt`, and `runvil-lua` and changes nothing a run does. No replacement facility is needed for any of them.

- Problem and users:
- `runvil-vfs` exports `OpSink`, a type alias for `Arc<dyn Fn(OpEvent<'_>) + Send + Sync>`. No crate outside `runvil-vfs` names it, in production or in tests, and the facade does not re-export it. The public builder takes `impl Fn(OpEvent<'_>)`.
- `runvil_prompt::detail::parse_error` is one call to `runvil_types::error::failure` with its two arguments passed on unchanged. It has one production caller, `Scheduler::check_start`, and one test.
- `install_normalize_failure` in `runvil-lua` is `lua.create_function(normalized)`. It has one caller, `install_shims` in `coro.rs`.
- The user is the reviewer reading each crate's surface (`AGENTS.md`, Principles).
- Goals:
- `OpSink` is nameable only inside `runvil-vfs`.
- `prompt::detail` holds one item fewer and the Engine calls `runvil_types::error::failure` itself.
- `install_shims` creates the `normalize_failure` capture directly from `normalized`.
- Non-goals:
- Gating test-only exports of `runvil-lua` (`ErrorField`, `error_fields`, `RESERVED_NAMES`) behind `test-support`.
- Any change to `runvil-types`.
- Inlining `block_guard` (see the Decision Record).
- Renaming anything, or any change to behavior, a persisted format, or the facade.
- Success criteria:
- `crates/runvil/public-api.txt` does not change.
- Every gate in `AGENTS.md` "Verification" passes with no new warning.
- Constraints:
- Each change is its own commit and leaves the workspace green.
- Open questions: None

## Functional Specification

No behavior changes. Every failure, event, and Lua value is identical before and after.

- Inputs and outputs: none change.
- States and validation: none change.
- Errors and recovery: `check_start` still returns a `StructureInvalid` failure with the same message and no location, stamped by `with_prompt_context` as before.
- Security and privacy behavior: none change.

</product-contract>
<implementation-contract>

## Technical Design

Each change deletes a name and points its users at what the name already forwarded to.

- Architecture:
- `OpSink` stays as a `pub(crate)` alias in `observe.rs`, because `Volume`, `VfsRef::from_router`, and `VfsRefBuilder` name it. Only the root re-export goes.
- `parse_error` goes. `check_start` and its test call `runvil_types::error::failure`, which `runvil-engine` already imports in `engine/src/error.rs`.
- `install_normalize_failure` goes. `install_shims` calls `lua.create_function(normalized)`, and the `error_value` module doc names `normalized` where it named the installer.
- Data model: none.
- Interfaces and APIs: `runvil_vfs::OpSink` and `runvil_prompt::detail::parse_error` leave the crates' surfaces. Neither is in `crates/runvil/public-api.txt`.
- Failure handling: unchanged.
- Observability: unchanged.
- Performance and scale: unchanged.

</implementation-contract>
<verification-contract>

## Testing Plan

No new test, because no behavior changes. The existing tests cover each call site: the vfs observer tests for `OpSink`, `a_parse_failure_run_raises_names_the_prompt_and_its_file` for `parse_error`, and the Lua prelude and `pcall` tests for the `normalize_failure` capture.

- Unit: unchanged.
- Exit criteria:
- `cargo clippy --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-targets --all-features` and the workshop clippy run, both with `CARGO_BUILD_WARNINGS=deny`
- `cargo fmt --all --check`
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features --exclude workshop --exclude workshop-server --exclude workshop-server-api`
- `RUSTDOCFLAGS="-D warnings" cargo doc -p runvil --no-deps`
- `cargo nextest run --locked --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-features`
- `cargo nextest run --locked -p workshop -p workshop-server -p workshop-server-api`
- `cargo +nightly-2026-09-05 xtask api --check`
- `cargo test -p build-xtask`
- `node --test crates/workshop/ui/test/docs-claims.mjs`

</verification-contract>
<decision-record>

## Decision Record

- Decisions:
- Narrow `OpSink` to `pub(crate)` instead of deleting it. Rationale: three in-crate items name it, and a named alias is shorter than the closure type at each of them.
- Do not inline `block_guard`. Rationale: `GUARD_REGISTRY` is private to `coro.rs`, which both stores the guard (`install_shims`) and reads it back (`block_guard`). Inlining at the one caller in `vm/run.rs` would make the key `pub(crate)` and split the pair across two modules, to save a three-line function. That is not a clear win.
- Reuse considered: all three removals reuse the facility the removed name pointed to (`runvil_types::error::failure`, `normalized`, the `impl Fn` builder). No new facility.

### Deferred and Out of Scope

- Deferred: moving `ErrorField`, `error_fields`, and `RESERVED_NAMES` behind `test-support`. The definitions need the same `cfg` as the export, because dead code fails the deny-warnings gate, so it is a larger change than a trim.
- Deferred: `runvil-types`, where another engineer is proposing work.
- Deferred: `block_guard`, for the reason above.

</decision-record>
<execution-plan>

## Execution Instructions

<step-1>

### Step 1: Stop exporting OpSink [completed]

- Component: `op-sink`
- Tests:
- `cargo clippy -p runvil-vfs --all-targets --all-features`
- `RUSTDOCFLAGS="-D warnings" cargo doc -p runvil --no-deps`

Build: in `crates/runvil-internal/vfs/src/observe.rs` change `pub type OpSink` to `pub(crate) type OpSink`, and in `lib.rs` drop `OpSink` from `pub use observe::{OpEvent, OpSink, Origin};`.

Commit: one commit. Suggested subject: ``Stop exporting `OpSink` from `runvil-vfs` ``.

</step-1>

<step-2>

### Step 2: Delete prompt::detail::parse_error [completed]

- Component: `parse-error`
- Tests:
- `cargo nextest run --locked -p runvil-engine -p runvil-prompt --all-features`
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features --exclude workshop --exclude workshop-server --exclude workshop-server-api`

Build: delete `parse_error` from `crates/runvil-internal/prompt/src/detail.rs`, dropping `ErrorCode` from that file's imports if nothing else uses it. In `engine/src/execute/scheduler/drive.rs` (`check_start`) and `engine/src/error/tests-chain.rs`, call `runvil_types::error::failure` in its place.

Commit: one commit. Suggested subject: ``Delete `prompt::detail::parse_error` ``.

</step-2>

<step-3>

### Step 3: Inline install_normalize_failure [completed]

- Component: `normalize-failure`
- Tests:
- `cargo nextest run --locked -p runvil-lua --all-features`
- The full gate set in the Testing Plan

Build: delete `install_normalize_failure` from `crates/runvil-internal/lua/src/error-value-classify.rs` and its `Function` import, and re-export only `normalized` from `error-value.rs`. In `coro.rs` build the capture with `lua.create_function(normalized).map_err(error::lua)?`. In the `error_value` module doc name `normalized` where it named the installer.

Commit: one commit. Suggested subject: ``Inline `install_normalize_failure` into `install_shims` ``.

</step-3>

</execution-plan>
Loading