Skip to content

One fence-label error, no removed-feature traces, and a slimmer facade suite - #148

Merged
vinniefalco merged 21 commits into
cppalliance:masterfrom
vinniefalco:master
Oct 15, 2026
Merged

vinniefalco merged 21 commits into
cppalliance:masterfrom
vinniefalco:master

Conversation

@vinniefalco

Copy link
Copy Markdown
Member

Summary

  • The parser rejects any lua-prefixed fence label that isn't exactly lua with one general Fence error, located at the fence's opening line: unrecognized fence label `lua extra`; the only Lua fence label is `lua` . This replaces the named errors for the two removed fence forms. Other-language fences, nested fences, and malformed openers labeled exactly lua stay prose.
  • Removes the retired-symbol scan from build-xtask and the retired-phrase list from docs-claims.mjs, and sweeps tests and comments that described or asserted removed features.
  • Removes live compatibility code for removed features in the gateway and Workshop:
    • A gateway config with a [workshop] section now fails to load as an unknown section, and removed config layouts fail through the generic unknown-key rejection.
    • The model cache migration is gone: an old unpinned cached model is still used, but is no longer listed.
    • Also gone: the old log-segment marker handling, the old temp-file sweep, the tolerated open_browser key, five --space-* CSS aliases, and the boolean renderWhitespace reader.
  • The greeter example answers a timer with EffectAnswer::Timer at once instead of dropping it.
  • Slims the facade suite (crates/promptforge/tests/suite/):
    • Moves it onto a small test harness (Scenario, Driver, Trace).
    • Deletes 13 tests that duplicate internal-crate tests or the auto lines in public-api.txt, including all of auto_traits.rs, tools.rs, and metrics.rs.
    • Moves the shipped-prompt parse test into the Engine suite, and shares model, descriptor, and note helpers.
    • The suite goes from 20,858 to 12,397 tokens (o200k_base), and from 2,349 to 1,539 lines.
  • tempfile replaces the three hand-written TempDir test helpers. The vfs crate's zero-dependency rule now allows dev-dependencies; its library stays std only.
  • Bumps rustls to 0.23.46 for RUSTSEC-2026-0346, RUSTSEC-2026-0347, and RUSTSEC-2026-0349.

Test plan

  • Local Windows: fmt, both clippy partitions with warnings denied, featureless gateway check, workspace nextest (5017 passed), Workshop nextest (310 passed), both doc gates, xtask api --check against an unchanged public-api.txt, docs-claims, cargo deny
  • CI green on this PR

Plans: vibe/2026-10-14-1-no-trace-fence.md, vibe/2026-10-14-2-facade-suite-dedup.md

Any fence whose label starts with lua in any letter case, but is not exactly lua, now fails the parse with one general fence error at its opening line. This replaces the two checks that rejected specific removed fence forms by name, so those forms are caught as ordinary near-misses and no error message names a removed feature. Fences with other labels, fences nested inside longer fences, and malformed openers labeled exactly lua still stay prose. The tests now use neutral labels throughout, and the public crate's invalid-prompt fixture moves to the new error.

- `fence_openings` yields the opening offset and label of every top-level fence from the pulldown-cmark event stream, and both `exact_lua_openings` and `first_unrecognized_lua_label` filter it. It takes the place of `exact_fence_openings`, which compared each opening line against one fixed marker per call.
- `first_unrecognized_lua_label` replaces `exact_shared_openings` as the one body-wide check in `Prompt::parse_body`, and `split_h1` no longer inspects its leading line for a `lua prompt` opener.
- `Prompt::parse` reports only the first offending fence, as a `Fence` error spanning its opening line with the message ``unrecognized fence label `{label}`; the only Lua fence label is `lua` ``. The scan covers notes before the H1 and runs before any Lua compiles, so the parse events are only `PARSE_STARTED` then `PARSE_FAILED` and the error omits the fence body.
- `h1_label_near_misses_fail_whatever_the_opener_shape` pins that four-backtick and one-space-indented openers with a label like `Lua` or `luau` also fail, because pulldown-cmark still reports them as fences. The same shapes labeled exactly `lua` stay prose, as do labels such as `text lua` and `moonlua`.
- `removed-lua-prompt.md` is gone from the invalid fixtures, and `unrecognized-lua-label.md` covers the fence error in the public crate's parsing suite.

Design: replaces pure-function @ crates/promptforge-internal/parser/src/fence.rs::fence_openings deps: str was: crates/promptforge-internal/parser/src/fence.rs::exact_fence_openings
Design: extends pure-function @ crates/promptforge-internal/parser/src/fence.rs::exact_lua_openings deps: str
Design: replaces pure-function @ crates/promptforge-internal/parser/src/fence.rs::first_unrecognized_lua_label deps: str was: crates/promptforge-internal/parser/src/fence.rs::exact_shared_openings
Design: extends surface-growth @ crates/promptforge-internal/parser/src/parse.rs::Prompt::parse boundary: pub
Plan: vibe/2026-10-14-1-no-trace-fence.md
The build tooling no longer scans live Engine source for a list of retired identifiers, and the documentation claims test no longer rejects a list of retired phrases in the rulebooks. Both lists spelled out the removed names they guarded against, so the lints themselves kept those names in the repository. The manifest guard and the test support leak guard remain the Engine guards and still run from the build tooling tests and the tidy command. The rulebook check for a lowercase host still rejects the lowercase forms of the dropped phrases, so only their capitalized forms become allowed.

- `crates/build-xtask/src/retired_symbols.rs` is gone along with `crates/build-xtask/src/retired_symbols-tests.rs` and the `mod retired_symbols;` line in `main.rs`. The comment and literal masking lexer, the `#[cfg(test)]` item remover, and `Hit` go with it.
- `engine_guard_violations` now joins only `engine_manifest_violations` and `test_support_leak_violations`. `RETIRED_SEEDS` and `retired_symbol_violations` leave `engine_guards.rs`, and neither its module doc nor the `tidy.rs` module doc lists a retired-symbol scan.
- `RETIRED` and its loop leave the rulebook test in `docs-claims.mjs`. The lowercase host check still rejects the lowercase forms of those six phrases, but capitalized forms such as "the Engine's Host" now pass.
- `every_seed_is_caught_and_a_seed_confined_to_test_code_passes` leaves `engine_guards-tests.rs` along with `engine_sources_name_no_retired_symbols` and `a_retired_symbol_reintroduced_in_an_engine_crate_fails_the_guard`. No test left in that file names a retired identifier.

Design: removes value-object @ crates/build-xtask/src/retired_symbols.rs::Hit
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::is_test_support deps: str
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::normalize deps: Path
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::identifiers deps: str
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::line_end deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::block_comment_end deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::string_end deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::char_literal_end deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::raw_string_start deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::raw_string_end deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::skip_whitespace deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::balanced_end deps: [char],char,usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::path_attribute deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::skip_visibility deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::external_module deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::item_end deps: [char],usize
Design: removes pure-function @ crates/build-xtask/src/retired_symbols.rs::module_files deps: Option<&str>,Path,str
Plan: vibe/2026-10-14-1-no-trace-fence.md
Tests, comments, and docs across the gateway, the Workshop, the engine, the Lua layer, the parser, and the build tooling stop describing or asserting features that were taken out. Tests and assertions that only proved an old function, route, command, config section, callback, or dependency was absent go away, and wording that contrasted current behavior with a former one now states only what the code does. Config tests that used old key and section names to prove unknown-input rejection now use neutral names, so that rejection stays covered without naming anything removed. The change adds no compatibility handling.

- `rejects_an_unknown_root_section`, `rejects_an_unknown_endpoint_key`, and `rejects_an_unknown_local_model_key` keep one gateway config test per level, each with a key or section named `unknown` and each expecting `ConfigError::Parse`. They take the place of the tests built on old queue, device, endpoint, and local model layouts, and `an_unknown_section_is_rejected` does the same for the Workshop config in place of its `[voice]` test.
- `reply-nil-section-one.md` leaves the engine's execution fixtures along with `reply_nil_in_section_one` and its `REPLY_NIL_SECTION_ONE` and `REPLY_NIL_EXECUTION` constants.
- `the_slot_era_functions_are_not_installed` still asserts that `tools.add`, `tools.always`, `tools.offered`, and `tools.add_local` are nil and that the six current `tools` functions exist. Only the `tools.always_offer` clause and its tuple slot go.
- `a_workshop_section_still_loads_and_earns_the_deprecation_warning` still expects a deprecation warning that names `[workshop]`. Only its check that the warning omits `[workshop.stt]` goes.
- `LAYOUT_SCHEMA_VERSION` stays 5. Its comment now says a snapshot of any other version falls back to the default layout, instead of listing the tab components a v4 snapshot named.
- `crates/workshop/ui/test/workspace-files.mjs`, `agent-keybindings.mjs`, `apply-revert.test.mjs`, and the Lua `tool_name` test change only their failure messages, and `crates/shared-ui/AGENTS.md`, `foundation-css.mjs`, `boot.mjs`, and several module docs change only wording. The conditions those tests check stay the same.
- `models_bind_is_gone`, `models_bind_is_gone_from_the_lua_surface`, `tools_bind_is_gone_from_every_section`, `store_inject_is_absent`, and `store_read_lines_is_absent` leave the engine and Lua suites. The unified pipeline test's Lua also drops its two `assert(reply == nil, ...)` lines.
- `the_serve_verb_is_rejected`, `harness_web_is_no_longer_a_public_harness_crate`, and `a_leftover_tools_key_is_refused_at_parse` go, and `the_plugins_key_takes_plain_names` drops its `capabilities` refusal.
- `workshop_exposes_only_the_realtime_speech_route` goes, and `assert_final_speech_route_surface` drops its loop, so the Workshop relay and gateway speech-route tests stop checking that `/stt` and `/stt/capability` answer 404.
- `rejects_legacy_stt_section` and `rejects_canonical_and_legacy_stt_sections_together` go, `serialized_shape_uses_the_toml_key_names` drops its check that serialization emits no `workshop.stt`, and the production logging test drops its `forced_final_overlap_unaligned` check.
- `crates/workshop/ui/test/stt-stream.mjs` drops its check that the service has no `onCommitted`, `onSnapshot`, `onCompleted`, or `onFailed` member. The config UI tests drop their `stt.interim_model`, `stt.final_source`, `workshop?.stt`, and `.global-progress` checks, and the router test stops visiting `#/downloads` and `#/models`.
- `crates/workshop/ui/test/no-lucide.mjs` goes, taking with it the checks that the Workshop UI sources and its `package.json` name no lucide.

Plan: vibe/2026-10-14-1-no-trace-fence.md
Removed configuration layouts, cache entries, log rotation markers, model sidecar formats, temp file names, stored editor settings, and spacing aliases no longer have live code that exists only to accept, migrate, clean up after, warn about, or name them. Leftover inputs of those kinds now fail or are ignored through the generic paths that already exist, so an old gateway config section or layout key fails as an ordinary unknown-key parse error with no named diagnostic. The public configuration API loses the inert workshop section type, its accessor, and the dedicated error kind for removed layouts. Tests that exercised the removed paths are gone, and new tests show that an unknown key still fails at the root, section, and nested table levels.

- `RawConfig` drops its `workshop` field and keeps `deny_unknown_fields`, so a `[workshop]` table, a top-level `include` or `models` key, or a `[workshop.voice]` table now fails as an unknown key with kind `Parse`.
- `WorkshopConfig` and the `Config::workshop` accessor leave the public API, and `RESTART_SECTIONS` and the apply docs no longer list the section as boot-read.
- `ConfigErrorKind` loses `HardBreak` along with its private error variant, and loading or saving a config no longer probes for a sibling `profiles/` directory.
- `find_rotation_prepared` now returns `Option` rather than `io::Result`, because it only probes the 64 masked marker names. `recover_rotation` drops its mask range check to match.
- `extract_fenced_block` drops its heading parameter and returns the first fence tagged with the given language, which is all the current sidecar format needs.
- `crates/shared-ui/tokens.css` and `crates/workshop/look/tokens.css` lose the five legacy spacing aliases. Every rule that used one now names the numbered token it resolved to.
- `parse_sidecar` reads only the current sidecar version. A version 1 sidecar now keeps just its frontmatter `source` and `fetched`, with no template or card.
- `blob_meta_matches` is gone, so an unpinned cache hit without listing metadata is reused as is, without hashing the blob or writing that metadata.
- `sweep_orphaned_temps` now removes only names ending in the temp suffix and leaves `workshop-state.json.tmp` alone.
- `readRenderWhitespace` no longer maps a stored `true` to `all`. Any stored boolean reads as the default `selection`.
- `check_removed_workshop_stt_claims` is gone, so staging the user guide no longer rejects a chapter that names `[workshop.stt]`.
- `error_chain` lets the new schema tests read the deserializer detail behind the opaque error. They pin a missing `config-version` as a `Parse` error naming the key and version 1 as a `Validation` error naming required and actual.
- `the_server_client_url_swaps_an_unspecified_bind_for_loopback` and `the_server_client_url_keeps_a_reachable_bind` keep the client URL coverage that lived in the deleted workshop module.
- `loopback_state` replaces `workshop_state` in the relay and speech route tests, and the STT test fixtures drop their empty `[workshop]` tables, since that table no longer parses.
- `a_retired_open_browser_key_still_parses_and_is_ignored` is removed and the desktop config fixture drops `open_browser`. The diff touches only tests for that key.
- `gateway.local.example.toml` drops the deprecated `[workshop]` block, and its header comment now names schema version 0.

Design: removes pure-function @ crates/gateway/app/src/runner.rs::workshop_section_deprecation deps: Config
Design: removes surface-growth @ crates/gateway/config/src/api_error.rs::ConfigErrorKind::HardBreak
  boundary: pub
Design: new schema-change @ crates/gateway/config/src/config.rs::RawConfig
  boundary: persisted
  instead-of: shim: a fallback that keeps the removed section parsing
Design: removes surface-growth @ crates/gateway/config/src/config/accessors.rs::Config::workshop
  boundary: pub
Design: removes pure-function @ crates/gateway/config/src/config/imp.rs::reject_removed_layout deps: Option<&Path>,str
Design: removes pure-function @ crates/gateway/config/src/config/imp.rs::hard_break deps: PathBuf,str,usize
Design: removes pure-function @ crates/gateway/config/src/config/imp.rs::line_for_span deps: Range<usize>,str
Design: removes pure-function @ crates/gateway/config/src/config/imp.rs::find_voice_header_line deps: str
Design: new pure-function @ crates/gateway/config/src/config/tests/schema.rs::error_chain deps: ConfigError
Design: removes pure-function @ crates/gateway/config/src/config/workshop.rs::default_workshop_bind
Design: removes shim @ crates/gateway/config/src/config/workshop.rs::WorkshopConfig
  boundary: pub
Design: removes shim @ crates/gateway/local/src/cache.rs::blob_meta_matches deps: Path,str
  boundary: persisted
Design: removes shim @ crates/gateway/local/src/sidecar.rs::extract_section_text deps: str
  boundary: persisted
Design: removes shim @ crates/gateway/logging/src/worker/rotation.rs::legacy_rotation_prepared_path deps: Path
  boundary: persisted
Design: removes shim @ crates/gateway/logging/src/worker/rotation.rs::read_legacy_rotation_mask deps: Path
  boundary: persisted
Design: removes shim @ crates/shared-ui/tokens.css
Design: removes shim @ crates/workshop/look/tokens.css
Design: removes shim @ crates/workshop/support/src/atomic.rs::LEGACY_TEMP_NAME
  boundary: persisted
Plan: vibe/2026-10-14-1-no-trace-fence.md
The greeter example now answers a timer as if it fired right away, instead of dropping it as though the caller had given up. Under the old answer, a prompt that waited on a timer in this loop would have resumed with a cancelled error. The answering function's doc now says that a caller with a real clock waits the requested seconds before answering, and a new test pins the immediate answer. The greeter prompt itself sets no timer, so the example's printed result is unchanged.

- `answer` returns `EffectAnswer::Timer` for every `Effect::Timer`, whatever its `seconds`, and never sleeps. Its doc drops the line that the greeter never waits on a timer and says a caller with a real clock waits `seconds` before answering.
- `EffectAnswer::Dropped` no longer comes from `answer`. The loop in `greet` still sends it for each pending effect once the run is decided.
- `the_example_answers_a_timer_as_fired_at_once` pins that `answer` gives a one-hour timer `EffectAnswer::Timer`, which the old `EffectAnswer::Dropped` arm fails. The suite's module doc adds timers firing at once to its list of tours.

Deferred: Effect::Timer keeps its f64 seconds duration.
Repairs: every timer effect gets EffectAnswer::Timer @ crates/promptforge/examples/greeter.rs::answer - answer dropped every timer effect
Plan: vibe/2026-10-14-1-no-trace-fence.md
The check that every positive example prompt parses now runs in the Engine's own test suite, beside the fixtures it reads, instead of reaching into the Engine's directory from the facade. The facade's suite keeps only the check over the shipped prompts. The tool catalog documentation and its matching test comment now say that the catalog lists every tool a run can use, that Lua calls a tool by id, and that a section offers a tool to its model, replacing the claim that a run offers every catalog tool.

- `every_positive_fixture_parses_offline` resolves `tests/prompts` against the Engine crate's `CARGO_MANIFEST_DIR` instead of the facade-relative `../promptforge-internal/engine/tests/prompts`. It still walks `valid` and `execution` and leaves `invalid` out.
- `collect_markdown` and `every_prompt_under_parses` match the copies that stay in `crates/promptforge/tests/suite/shipped.rs` line for line, except that the Engine's copy parses under `POSITIVE_PARSE` ("fixture-positive-prompts"). A fix to one walker does not reach the other.
- `every_prompt_under_parses` fails when a directory holds no `.md` file and panics with the file's path on the first prompt that does not parse.
- `RunContext` documents its `tools` setter with the new catalog wording, names `tools.offer` as the way a section offers a tool, and still says the caller performs every call. The catalog comment in `crates/promptforge/tests/suite/prepare.rs` carries the same catalog wording.
- `crates/promptforge/tests/suite/shipped.rs` no longer checks Engine fixtures, and its module doc drops the clause about them.

Design: new clone-block @ crates/promptforge-internal/engine/src/execute/tests/suite/parsing.rs::collect_markdown deps: Path,Vec<PathBuf>
Design: new clone-block @ crates/promptforge-internal/engine/src/execute/tests/suite/parsing.rs::every_prompt_under_parses deps: Path
Plan: vibe/2026-10-14-1-no-trace-fence.md
The facade test suite gets a small harness that holds a test's setup as data and steps the run for it. A prompt builder, a canned model, scripted chat replies, and a shouting tool replace the setup each test used to repeat. The greeter tests use it and keep their names and expected results, so each test shows only what it checks. The harness fails a test instead of hanging when a script runs dry or a run can only wait.

- `Scenario` holds a test's setup as data and offers `prepare`, `start`, and `run`. The `replies` and `tool` constructors and the `hold` builder keep a typical setup on one or two formatted lines.
- `Driver` steps a started run by hand with `pump`, `pump_until_held`, and `finish`. Its `run` field is public so a test can reach the cancel handle while effects are held.
- `Tool` pairs an id string with a plain function that answers each call from its arguments. The driver matches a call by comparing the called tool's printed id to that string.
- `crates/promptforge/tests/suite/support/scenario.rs` holds `Scenario`, `Driver`, and `Trace`. `support.rs` declares it with `mod scenario;` and re-exports `Scenario` next to the builders and answers.
- `prepare` sets the canned model on every scenario and numbers run events on from the parse events. It fails the test when the engine's prepare reports a gap.
- `pump` answers store effects with `perform_vfs_op`, timers as fired at once, chat effects from the script, and tool calls with the registered handler. It drops a held effect only when the run is decided or the effect is withdrawn.
- `advance` fails the test when an undecided run issues nothing new while only held effects remain. `answer` fails it when the chat script runs out or the run calls an unregistered tool, and `pump` fails it when replies are left over or effects are still held at `Done`.
- `prompt` trims every line and fixes the frontmatter and the `lua` fence, so tests pass indented raw strings.
- `Trace` holds only the run's result, and `Scenario` offers no way to change the run name, arguments, clock, or model.

Design: new facade @ crates/promptforge/tests/suite/support/scenario.rs::Scenario
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::prompt deps: slice,str
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::greeter deps: str
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::text deps: str
Design: new stringly-typed @ crates/promptforge/tests/suite/support.rs::Tool
Deferred: calls and fail replies and Tool::untrusted wait for the first test that uses them
Deferred: name, args, started_at, model, reverse, and context settings on Scenario and the event log and effect records on Trace wait for the first test that reads them
Plan: vibe/2026-10-14-1-no-trace-fence.md
The effect, event, ids, and cancel test suites now build their runs and drive them through the shared test harness instead of each repeating its own setup and stepping loop. To support them, the harness takes the run's argument text, can answer each batch of effects in reverse order, and lets a test edit the run's context before it is prepared. A finished run now also returns its events, parse events first, and a log of every effect with its provenance and answer. Tests keep their names and what they check, with one added assertion that reversing a batch changes the order its effects are answered in.

- `Scenario` gains `args`, `reverse`, and `context` settings, and `prepare` returns the parse events beside the prompt and context so `start` can seed the driver's event log.
- `Issued` carries a held effect's id, provenance, and effect record, so an effect dropped later is logged like any other answer.
- `Trace` now holds `events` and `effects`, where each `Logged` pairs a provenance with its effect record and answer record.
- `respond` logs an effect and its answer at the moment the driver answers it, so held effects appear in the log when they drop and `reverse` makes the log follow the reversed batch order.
- `context` runs its edit after the scenario's own settings and before prepare, and the quiet run in the event round-trip test uses it to set provenance numbering to 0.
- `answering_a_batch_in_reverse_gives_the_same_result_as_issue_order` gains an `assert_ne!` that reversing a batch changes the order its effects are answered in. It used to compare only the results.
- `Scenario` still sets no run name, start time, or model of its own, so the two runs in `one_parent_cancel_ends_every_run_holding_one_of_its_children` now share the name `greeter` where they were `greeter-1` and `greeter-2`.

Design: extends facade @ crates/promptforge/tests/suite/support/scenario.rs::Scenario
Design: removes flag-parameter @ crates/promptforge/tests/suite/effect.rs::drive deps: Run,bool
Design: new pure-function @ crates/promptforge/tests/suite/effect.rs::greeter
Design: new pure-function @ crates/promptforge/tests/suite/effect.rs::scenario
Design: new pure-function @ crates/promptforge/tests/suite/event.rs::shouting
Deferred: name, started_at, and model settings on Scenario wait for the first test that reads them
Plan: vibe/2026-10-14-1-no-trace-fence.md
The remaining facade test suites now build their runs and drive them through the shared test harness. Each suite used to repeat its own prompt text and its own loop that steps the run and answers its effects. The harness now starts a run at a chosen time, uses a chosen model, scripts a failed model round, scripts a model call to a tool, and answers tool calls with fixed untrusted text. Preparing a run no longer fails when preparation reports a gap, so a test can read what preparation reports. Test names stay the same.

- `Reply` is either a completion or a failed round, and the chat script now holds `Reply` values. `text`, `calls`, and `fail` build them, and the driver answers a `Failure` as a failed chat round.
- `Tool` holds its `handler` as a boxed closure instead of a plain function, so a handler can capture values. `Tool::untrusted` builds a tool that answers every call with fixed untrusted text.
- `Prepared` holds the parsed prompt, the prepared context, the requirements, and the parse events. It replaces the three-part tuple that `prepare` returned.
- `prepare` no longer fails on a requirement gap. It returns the requirements in `Prepared`, and `start` fails on a refusal.
- `started_at` defaults to the Unix epoch, because `Timestamp` derives `Default` as zero milliseconds. `model` replaces `canned` as the run's current model when a scenario sets it.
- `context` is removed from `support.rs`. The prepare tests that used it now run under seed 7 where it passed seed 1.
- `two_runs_writing_the_same_real_file_through_the_shared_base_conflict` now prepares both runs under the name `greeter`, where they were `run-a` and `run-b`. The test still acquires access under `Origin::new("run-a")` and `Origin::new("run-b")`.
- `the_frontmatter_lists_every_declaration_with_arguments_sorted_by_name` now builds its source with `prompt`. The description it asserts is the builder's fixed text, and the section body is `return 'done'`.
- `the_shipped_research_prompt_offers_its_web_tools_by_id_and_runs_end_to_end` now reads the advertised tool lists and the tool calls from `trace.effects` after the run, where it used to collect them while stepping. It registers `web/search` with `Tool::untrusted` and sets the two-tool catalog through `context`.
- `Scenario` still has no setting for the run name. `prepare` builds every run as `greeter` with seed 7.

Design: extends facade @ crates/promptforge/tests/suite/support/scenario.rs::Scenario
Design: removes clone-block @ crates/promptforge/tests/suite/replay.rs::run_to_end deps: Run
Design: removes clone-block @ crates/promptforge/tests/suite/timestamp.rs::the_prompt_reads_the_contexts_start_time_as_sys_when
Design: removes clone-block @ crates/promptforge/tests/suite/vfs.rs::run_note deps: VfsRef
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::calls deps: Value,str
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::fail deps: CompletionErrorKind
Design: new pure-function @ crates/promptforge/tests/suite/prepare.rs::declaring deps: str
Design: new pure-function @ crates/promptforge/tests/suite/prepare.rs::report deps: ThinkingMode,str,u32
Design: new pure-function @ crates/promptforge/tests/suite/prepare.rs::only_unmet deps: Requirements
Design: new pure-function @ crates/promptforge/tests/suite/prepare.rs::prepared_over deps: ToolDescriptor
Design: new pure-function @ crates/promptforge/tests/suite/model.rs::asking
Plan: vibe/2026-10-14-1-no-trace-fence.md
Plan: vibe/2026-10-14-1-no-trace-fence.md
Remove thirteen tests from the facade's integration suite to shrink its source, along with the constants, fixtures, and imports that went with them. Three whole test files go: the compile-time auto-trait checks, the tool output trust check, and the call metrics check. The other six come out of the task id, prompt argument, model, plugin, and replay flag tests. Each touched module doc stops describing the removed cases. No library code changes.

- `crates/promptforge/tests/suite/auto_traits.rs` is deleted, taking the build-time checks that `Run` is `Send`, that `RunContext`, `RunLimits`, `RunResult`, `RunError`, `RunErrorKind`, `CancelHandle`, and `ToolError` are `Send + Sync + 'static`, that `Event`, `ToolCatalog`, `VfsRef`, and `Access` are `Send + Sync`, and that `Cancelled` is `Unpin`. The suite doc in `main.rs` stops promising them.
- `crates/promptforge/tests/suite/tools.rs` is deleted with its `shout` and `fetch` fixtures, dropping the check that `ToolOutput::trusted` and `ToolOutput::untrusted` set `OutputTrust`, and that a `ToolErrorKind::Transport` error built by `ToolError::with_source` is retryable and displays only its message.
- `crates/promptforge/tests/suite/metrics.rs` is deleted, dropping the check that `Completion::from_result` carries no metrics until `with_metrics` attaches a `CallMetrics`.
- `a_model_started_task_is_abandoned_when_its_owning_section_returns` leaves `ids.rs` with its `LOOPER` constant, dropping the check that a model-started task ends as `TaskAbandoned` with `AbandonReason::OwnerReturned` when its section returns without waiting for it.
- `declared_arguments_take_json_and_an_undeclared_prompt_takes_plain_prose` leaves `prompt.rs`, dropping the check that declared arguments reach Lua's `argv` as parsed JSON while an undeclared prompt receives plain prose.
- `a_text_completion_served_under_the_bound_models_name_becomes_the_result` leaves `model.rs`, dropping the check that a text completion becomes the run's result.
- `crates/promptforge/tests/suite/plugins.rs` loses the checks that `PluginId::parse` refuses a two-part name with `PluginIdErrorKind::SegmentCount` and that a Plugin id contains only its own tools, and that a `Prelude` on the context defines a function the section calls.
- `flags_from_a_newer_record_keep_the_bits_this_version_does_not_name` leaves `replay.rs`, dropping the check that `Flags::from_bits` keeps bits this version does not name.

Design: removes pure-function @ crates/promptforge/tests/suite/tools.rs::shout deps: Value
Design: removes pure-function @ crates/promptforge/tests/suite/tools.rs::fetch deps: Value
Plan: vibe/2026-10-14-2-facade-suite-dedup.md
Move the check that every shipped prompt parses offline out of the facade's integration suite and into the engine's parser suite, which already has a walker that parses every prompt under a directory. The facade suite drops its own copy of that walker along with the check and keeps the end-to-end run of the shipped research prompt. The module docs on both sides follow the move. No library code changes.

- `every_shipped_prompt_parses_offline` now lives in the engine suite's parser tests and calls the `every_prompt_under_parses` already there. It walks `../../../prompts` from the engine manifest, the same workspace `prompts/` tree the facade test reached through `../../prompts`.
- `crates/promptforge/tests/suite/shipped.rs` loses `SHIPPED_PARSE`, `collect_markdown`, `every_prompt_under_parses`, and the moved test, plus the `PathBuf` and `Prompt` imports that only they used. Its module doc now covers only the research prompt's run, and the suite doc in `main.rs` names that run where it named shipped-prompt policy.
- `every_prompt_under_parses` in the engine suite parses each shipped prompt with the engine's own `Prompt` under the name `POSITIVE_PARSE`, where the facade's copy used `promptforge::Prompt` under `SHIPPED_PARSE`.

Design: removes clone-block @ crates/promptforge/tests/suite/shipped.rs::collect_markdown deps: Path,Vec
Design: removes clone-block @ crates/promptforge/tests/suite/shipped.rs::every_prompt_under_parses deps: Path
Plan: vibe/2026-10-14-2-facade-suite-dedup.md
Replace the hand-rolled self-deleting temporary directories in the facade suite, the engine suite, and the virtual filesystem's real-backend tests with a temporary-directory crate, added as a test-only dependency of all three crates. To allow it, the virtual filesystem's zero-dependency rule now governs only the library: normal and build dependencies stay forbidden, test-only dependencies are allowed, and the manifest tests pin both sides. The crate's docs restate the rule in those terms. No library code changes.

- `crates/promptforge-internal/vfs/Cargo.toml` gains a `[dev-dependencies]` table holding `tempfile`, and its rule comment now names only normal and build dependencies and no longer says "never weaken it". The facade and engine manifests add `tempfile` beside their other dev-dependencies, and `Cargo.lock` gains only `tempfile` entries.
- `is_dependency_table` now matches only `dependencies` and `build-dependencies`, directly, as a sub-table, or under a `target.<cfg>` qualifier. The manifest test is renamed `the_manifest_declares_no_normal_or_build_dependencies` to match.
- `temp_dir` in the vfs real-backend tests wraps `tempfile::tempdir()` and maps its I/O error through `map_io`, so the tests in `atomicity.rs`, `links.rs`, and `semantics.rs` keep returning `VfsError`.
- `a_manifest_with_a_target_qualified_dependency_table_is_refused` now asserts that a `target.'cfg(windows)'.dev-dependencies` entry passes, and the sub-table test moves `dev-dependencies` and `dev-dependencies.foo` to its passing list.
- `two_runs_writing_the_same_real_file_through_the_shared_base_conflict` and `fanout_interleaving_is_invariant_across_memory_and_real_backends` root their real backends at `temp.path()`, the first propagating a setup failure with `?` and the second panicking through `expect`.
- `crates/promptforge/tests/suite/main.rs` drops its crate-wide `clippy::expect_used` expectation, whose stated reason was test helpers that panic on setup failure. The `expect_err` left in `prepare.rs` sits inside a `#[test]` body, not a helper.

Design: removes clone-block @ crates/promptforge/tests/suite/prepare.rs::TempDir
Design: removes clone-block @ crates/promptforge-internal/engine/src/execute/tests/suite/vfs.rs::TempDir
Design: removes global-state @ crates/promptforge-internal/vfs/src/real/tests.rs::TempDir
Plan: vibe/2026-10-14-2-facade-suite-dedup.md
The facade suite built model and tool descriptors by hand in several files, repeated the prompt that writes a note and reads it back, and unpacked each successful run with a pattern match. Those now come from shared helpers in the test harness, so each test states only the values it checks. A run that does not succeed fails its test with an error that names the actual result. The invalid-prompt fixtures become a short table that the test reads from disk at run time. Every test keeps its name and expected values.

- `model` builds a `gateway/<name>` descriptor from a name, a context window, and a thinking mode. `canned` calls it, and the model, prepare, and prompt suites use it in place of `fast`, `current_model`, and an inline descriptor.
- `descriptor` builds a tool descriptor from an id and a schema, and a caller that needs structured output chains `.structured(true)`. The scenario's own tool, the two prepare catalog tests, and the shipped research test use it in place of `web_tool` and inline descriptors.
- `note` wraps a Lua expression in the one-section greeter that writes it to `note.md` and returns what it reads back. It replaces the shared `NOTE` constant, the event suite's own `NOTE`, and the timestamp suite's inline `sys.when` block.
- `impl Trace` gains `text`, which returns the text of `RunResult::Ok` and otherwise fails with `expected RunResult::Ok, got {result:?}`. Tests compare `trace.text()?` with `assert_eq!` instead of matching on `trace.result`.
- `INVALID_FIXTURES` becomes four `(&str, ParseErrorKind, &str)` tuples of file stem, error kind, and message fragment, replacing the `InvalidFixture` struct, so each fixture name appears once.
- `invalid_prompt_files_report_public_error_contracts` reads each fixture from `tests/prompts/invalid` under `CARGO_MANIFEST_DIR` at run time instead of through `include_str!`. A missing fixture now fails the test rather than the build, and a fixture that parses returns an error instead of panicking.
- `"A model the facade suite describes."` is now the description of every model `model` builds, and `"A tool the facade suite describes."` of every tool `descriptor` builds. Per-test descriptions such as `"Quick replies for the greeter"` are gone, and the touched tests assert on neither.
- `ModelId::new(ModelId::GATEWAY, "deep")` moves into the catalog lookup while `model` builds `deep` through `ModelId::gateway`, so the catalog test still shows the two constructors yield the same id.

Design: removes pure-function @ crates/promptforge/tests/suite/model.rs::fast
Design: removes pure-function @ crates/promptforge/tests/suite/prepare.rs::current_model deps: ThinkingMode,u32
Design: removes pure-function @ crates/promptforge/tests/suite/shipped.rs::web_tool deps: str
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::note deps: str
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::model deps: ThinkingMode,str,u32
Design: new pure-function @ crates/promptforge/tests/suite/support.rs::descriptor deps: Value,str
Plan: vibe/2026-10-14-2-facade-suite-dedup.md
Trim the facade integration suite's comments down to the ones that state something the code cannot show. Docs that repeated an item's name, restated the line below them, or described the Lua beside them are gone. Each test file's module doc comes down to a sentence saying what the file tours. The surviving docs keep why every scenario defaults to one model, how a tool's registered id differs from the name a model calls it by, and which prepare cases live in the engine suite. No code or test names change.

- `Scenario` loses all eight field docs, and each builder method keeps the doc of its pair. `started_at` takes the one fact only its field doc stated: the prompt reads the start time as `sys.when`.
- `Tool` and `calls` now tell a tool's full id from its wire name: a scenario registers a tool under its full id, and a scripted reply calls it by its wire name.
- `gateway/canned` no longer appears in the `canned` doc, and the `gateway/<name>` doc on `model` is gone, so neither helper's doc names the model id it builds.
- `crates/promptforge/tests/suite/prepare.rs` keeps its pointer to the prepare cases in `promptforge-engine`'s own suite. Its module doc and catalog comment block no longer say that Plugin install, catalog assembly, and the installed-Plugin report belong to the Harness.
- `crates/promptforge/tests/suite/main.rs` no longer lists what the suite covers or says which fixture cases run in `promptforge-engine`'s suite, and `crates/promptforge/tests/suite/parsing.rs` no longer names `tests/prompts/invalid/` in its doc.
- `crates/promptforge/tests/suite` hunks change only `//!`, `///`, and `//` comments. No code, test name, or assertion changes.

Plan: vibe/2026-10-14-2-facade-suite-dedup.md
Plan: vibe/2026-10-14-2-facade-suite-dedup.md
`cargo deny` refuses `rustls` 0.23.45 for RUSTSEC-2026-0346, RUSTSEC-2026-0347, and RUSTSEC-2026-0349. The locked version moves to 0.23.46, which fixes all three. Only `Cargo.lock` changes.
Both writers into the shared model cache now write a blob's metadata sidecar from the staged download before renaming the download into place, so a published blob always has its sidecar. Before, a failed sidecar write left the new blob published without one. The cache listing ignored such a blob, and unpinned model provisioning reused it on later calls without ever writing the missing sidecar. All sidecar writes now go through one shared helper, and a regression test for each writer plants a sidecar fault, checks that nothing is published, and checks that the retry downloads again.

- `write_blob_meta` is now the only code that builds and writes a `BlobMeta` sidecar. It measures the `measured` file apart from the `blob` whose sidecar path it derives, and returns the size it recorded.
- `download_to_cache` drops its inline sidecar copy and publishes in a fixed order: `remove_cache_entry`, then `write_blob_meta` on the staged file, then `rename_confined`. Removing first means a crash never leaves an old blob beside a sidecar that describes new content.
- `ArtifactStore` writes the sidecar from the staged file before `rename_confined` on a fresh download, so the pinned `.verified` marker now follows the sidecar instead of preceding it. The verified cache hit still passes `destination` as both the blob and the measured file.
- `rename_confined` now runs only after the sidecar is on disk in both writers. A failed sidecar write returns its error with nothing at the destination, and the next call downloads again.
- `list` skips a sidecar whose blob is not a file, and the cache-hit check requires the blob, so a crash between the sidecar write and the rename leaves no visible entry.
- `validate_cache_path` now confinement-checks the sidecar path that `download_to_cache` writes, which its inline copy skipped.
- `BlobMeta` is unchanged, so the sidecar JSON format stays the same.
- `download_to_cache_publishes_no_blob_whose_sidecar_write_failed` checks that the retried sidecar parses but asserts none of its fields.

Design: removes clone-block @ crates/gateway/local/src/cache.rs::BlobCache::download_to_cache boundary: persisted
Design: extends temporal-coupling @ crates/gateway/local/src/cache.rs::BlobCache::download_to_cache boundary: persisted
Design: extends temporal-coupling @ crates/gateway/local/src/artifacts/blob.rs::ArtifactStore::ensure_blob_with_progress boundary: persisted
Design: new clone-block @ crates/gateway/local/src/cache/tests.rs::download_to_cache_publishes_no_blob_whose_sidecar_write_failed
Deferred: nothing re-hashes or repairs blobs already on disk
Repairs: a published blob always has its sidecar @ crates/gateway/local/src/cache.rs::BlobCache::download_to_cache - a failed sidecar write left the new blob published without a sidecar
Repairs: a published blob always has its sidecar @ crates/gateway/local/src/artifacts/blob.rs::ArtifactStore::ensure_blob_with_progress - a failed sidecar write left an unpinned blob published, unlisted, and never downloaded again
Plan: vibe/2026-10-14-3-sidecar-strict-fences.md
A Lua fence must now open with a line that is exactly three backticks followed by the word lua and close with a line that is exactly three backticks. Openers with trailing whitespace, indent, a space before the label, tildes, or four backticks, fences inside block quotes and list items, closing lines that are not exactly three backticks, and Lua-looking lines in indented code or raw HTML blocks used to pass as prose or swallow the text after them. Each now fails the parse at the offending line, before any Lua compiles. Lines inside the body of another fenced code block are never inspected, so a document can still show a Lua fence as an example.

- `first_fence_violation` replaces `first_unrecognized_lua_label` as the single whole-body check. It checks every fence the Markdown parser reports against the Label, Opener, and Closer rules in that order, scans every line outside those fences for Lookalike openers, and returns the earliest violation in source order.
- `closing_line` takes a fence's closing line from the block's own extent in the Markdown event stream, so the check and the parser agree on where the fence ends. A fence the parser leaves unclosed reports nothing here and still gets the per-section "is not closed" error.
- `FenceViolation` names the broken rule as `Label(String)`, `Opener`, or `Closer`, and the parse maps each variant to its own message. A Lookalike line reports as `Opener`.
- `split_section_blocks` now answers a fence opening inside the previous fence's source with the internal `Structure` mismatch error instead of the author-facing "is not closed exactly" message. Its doc comment states that it relies on the whole-body check having already run.
- `ParseErrorKind::Fence` now also covers the opener shapes above, closers with trailing whitespace, indent, or four backticks, and Lookalike lines. The new messages are "`lua` fence opening line must be exactly ```lua" and "`lua` fence closing line must be exactly ```", each located at the offending line. Every opener and Lookalike shape parsed as prose before.
- `non_exact_section_closing_lines_fail_the_parse` covers a bad closing line followed by prose and a `python` fence, which used to compile the prose as Lua and fail with kind `Lua`.
- `is_lua_lookalike` also flags a prose line that opens with an inline code span of three backticks around text starting with `lua`.
- `lines_inside_another_fence_body_are_never_inspected` pins the exception: fence-looking lines inside a longer fence's body stay prose, including non-exact openers.
- `INVALID_FIXTURES` gains `lua-fence-opener` and `lua-fence-closer`, which pin both message fragments through the public facade.

Design: extends pure-function @ crates/promptforge-internal/parser/src/fence.rs::fence_openings deps: str
Design: replaces pure-function @ crates/promptforge-internal/parser/src/fence.rs::first_fence_violation deps: str
  was: crates/promptforge-internal/parser/src/fence.rs::first_unrecognized_lua_label
Design: new pure-function @ crates/promptforge-internal/parser/src/fence.rs::fence_violation deps: Range<usize>,str
Design: new pure-function @ crates/promptforge-internal/parser/src/fence.rs::line_at deps: str,usize
Design: new pure-function @ crates/promptforge-internal/parser/src/fence.rs::closing_line deps: Range<usize>,str
Design: new pure-function @ crates/promptforge-internal/parser/src/fence.rs::closes_three_backtick_fence deps: str
Design: new pure-function @ crates/promptforge-internal/parser/src/fence.rs::is_lua_lookalike deps: str
Design: new pure-function @ crates/promptforge-internal/parser/src/fence.rs::is_lua_like deps: str
Design: new temporal-coupling @ crates/promptforge-internal/parser/src/fence.rs::split_section_blocks deps: str
Design: extends surface-growth @ crates/promptforge-internal/parser/src/parse.rs::Prompt::parse
  boundary: pub
Deferred: a Lua-looking line in an indented code block inside a block quote is not caught
Repairs: a Lua fence without an exact closing line fails with kind Fence @ crates/promptforge-internal/parser/src/fence.rs::first_fence_violation - prose after a non-exact closing line was compiled as Lua and failed with kind Lua
Plan: vibe/2026-10-14-3-sidecar-strict-fences.md
The tools namespace test no longer checks that the four removed slot-era functions are absent, and keeps only its check that the six current functions are installed. Several doc comments described the engine as offering every catalog tool. They now say the catalog lists every tool a run can use, the prompt's Lua calls one by id, and a section offers one to its model. No runtime code changes.

- `the_tools_namespace_installs_its_six_functions` replaces `the_slot_era_functions_are_not_installed` and drops its four `== nil` probes. It still asserts that `offer`, `offer_local`, `required`, `extras`, `get`, and `allow_tasks` are all functions, so it pins the installed surface rather than the removed one.
- `ToolCatalog`: its doc, the `ToolDescriptor` doc, and the engine `tools` module doc now say the catalog lists every tool the run can use, Lua calls one by id, and a section offers one to its model with `tools.offer`. The `bound_with_tools` comment now says the fixture Plugin's tools are in the run's catalog.
- `tools.add`, `tools.always`, `tools.offered`, and `tools.add_local` are no longer named in `crates/promptforge-internal/lua/src/tools/tests-offering.rs`, and its module doc no longer lists the removed slot-era functions.

Plan: vibe/2026-10-14-3-sidecar-strict-fences.md
Plan: vibe/2026-10-14-3-sidecar-strict-fences.md
@vinniefalco
vinniefalco merged commit 3b1ea0d into cppalliance:master Oct 15, 2026
20 checks passed
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