diff --git a/.config/hakari.toml b/.config/hakari.toml deleted file mode 100644 index 96ddad59b..000000000 --- a/.config/hakari.toml +++ /dev/null @@ -1,28 +0,0 @@ -# cargo-hakari configuration for the workspace-hack crate, which unifies -# third-party feature sets so -p and --workspace builds stop rebuilding -# shared dependencies. Regenerate contents with `cargo hakari generate` -# and apply dependency lines with `cargo hakari manage-deps`; CI verifies -# with `cargo hakari verify`. -hakari-package = "workspace-hack" -# The workspace sets resolver = "3"; its feature unification is resolver -# 2's, which is what hakari simulates. -resolver = "2" -dep-format-version = "4" -# Match the workspace convention: members inherit with `workspace = true`. -workspace-hack-line-style = "workspace-dotted" -# The platforms the workspace ships and builds on; naming them produces -# better feature unification than the all-platforms default. -platforms = [ - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "aarch64-apple-darwin", -] - -# promptforge-vfs keeps its zero-dependency rule (its manifest test enforces -# it); with no third-party dependencies it has nothing for hakari to unify. -# build-ceiling has no dependencies either, because six crates include its -# source from their build scripts and it must compile on std alone. -[final-excludes] -workspace-members = ["promptforge-vfs", "build-ceiling"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7f3204dd..fdede2a94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,36 +179,29 @@ jobs: CARGO_BUILD_WARNINGS: deny run: cargo clippy -p workshop -p workshop-server -p workshop-server-api --all-targets - - name: Test (workshop, concurrent via nextest) - run: cargo nextest run --locked -p workshop -p workshop-server -p workshop-server-api - - # The trio run above does not execute a dependency's own tests, so the - # workspace jail's Windows paths (verbatim, UNC, junctions, alternate - # data streams, symlinks) need their own invocation. It stays separate - # because the trio never takes --all-features. - - name: Test (workshop-workspace, Windows jail) - run: cargo nextest run --locked -p workshop-workspace --all-features - - # The headless feature swaps the webview asset layer for a no-op so - # server-only integration tests run without the UI bundle. It is not - # a default feature, so it needs its own invocation. - - name: Test (workshop-server headless) - run: cargo nextest run --locked -p workshop-server --features headless + # One build runs every Windows-only test: the Workshop crates' suites + # (the workspace jail's Windows paths included) and the gateway's + # process-ownership races, the only tests the filter selects from the + # gateway and gateway-api-discovery suites. + - name: Test (Windows) + shell: bash + run: >- + cargo nextest run --locked --no-default-features --features gateway/test-fixtures + -p workshop -p workshop-server -p workshop-server-api -p workshop-workspace + -p gateway-api-discovery -p gateway + -E 'package(workshop) | package(workshop-server) | package(workshop-server-api) + | package(workshop-workspace) + | (package(gateway-api-discovery) & test(a_process_lifetime_lease_recovers_after_its_owner_is_terminated)) + | (package(gateway) & binary(it) & (test(simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff) + | test(workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot) + | test(a_direct_launch_recovers_the_lease_from_a_terminated_owner) + | test(a_silent_process_lease_owner_causes_a_bounded_console_only_failure)))' - name: Docs (workshop-server, private items) env: RUSTDOCFLAGS: -D warnings run: cargo doc --locked --no-deps -p workshop-server --document-private-items - - name: Test Gateway process ownership races - shell: bash - run: | - cargo test --locked -p gateway-api-discovery a_process_lifetime_lease_recovers_after_its_owner_is_terminated - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it a_direct_launch_recovers_the_lease_from_a_terminated_owner - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it a_silent_process_lease_owner_causes_a_bounded_console_only_failure - - name: Remove Gateway sidecar if: always() run: node tools/stage-gateway-sidecar.mjs remove --target x86_64-pc-windows-msvc @@ -262,15 +255,6 @@ jobs: - name: Build (workshop, Linux) run: cargo build --locked -p workshop - - name: Test Gateway process ownership races - shell: bash - run: | - cargo test --locked -p gateway-api-discovery a_process_lifetime_lease_recovers_after_its_owner_is_terminated - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it a_direct_launch_recovers_the_lease_from_a_terminated_owner - cargo test --locked -p gateway --no-default-features --features test-fixtures --test it a_silent_process_lease_owner_causes_a_bounded_console_only_failure - - name: Remove Gateway sidecar if: always() run: node tools/stage-gateway-sidecar.mjs remove --target x86_64-unknown-linux-gnu @@ -339,7 +323,7 @@ jobs: - uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2 with: - tool: cargo-deny, cargo-audit, cargo-hakari + tool: cargo-deny, cargo-audit - name: cargo deny run: cargo deny check @@ -347,14 +331,11 @@ jobs: - name: cargo audit run: cargo audit - - name: cargo hakari verify - run: cargo hakari verify - # The gateway holds vendor credentials, so it links exactly one rustls # crypto backend, aws-lc-rs. This fails when `ring` re-enters its - # normal-edge closure (through a reqwest bump, a Tauri plugin default, - # or a hakari regeneration) and when `cargo tree` itself cannot run; - # only a successful, ring-free result passes. + # normal-edge closure (through a reqwest bump or a Tauri plugin default) + # and when `cargo tree` itself cannot run; only a successful, ring-free + # result passes. - name: One rustls crypto backend in the gateway shell: bash run: | diff --git a/Cargo.lock b/Cargo.lock index b90425af9..8e91b981c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,7 +627,6 @@ dependencies = [ "serde_json", "sha2 0.11.0", "tempfile", - "workspace-hack", "zip 8.6.0", ] @@ -639,7 +638,6 @@ dependencies = [ "build-ceiling", "serde_json", "tempfile", - "workspace-hack", ] [[package]] @@ -648,7 +646,6 @@ version = "0.3.0" dependencies = [ "build-ceiling", "tempfile", - "workspace-hack", ] [[package]] @@ -659,7 +656,6 @@ dependencies = [ "build-ceiling", "ctrlc", "tempfile", - "workspace-hack", ] [[package]] @@ -675,7 +671,6 @@ dependencies = [ "syn 3.0.3", "tempfile", "toml 1.1.4+spec-1.1.0", - "workspace-hack", ] [[package]] @@ -2036,7 +2031,6 @@ dependencies = [ "tray-icon", "url", "windows-sys 0.61.2", - "workspace-hack", "zbus", ] @@ -2052,7 +2046,6 @@ dependencies = [ "tempfile", "thiserror 2.0.19", "windows-sys 0.61.2", - "workspace-hack", ] [[package]] @@ -2063,7 +2056,6 @@ dependencies = [ "serde", "serde_json", "time", - "workspace-hack", ] [[package]] @@ -2083,7 +2075,6 @@ dependencies = [ "thiserror 2.0.19", "time", "tokio", - "workspace-hack", ] [[package]] @@ -2100,7 +2091,6 @@ dependencies = [ "thiserror 2.0.19", "toml 1.1.4+spec-1.1.0", "url", - "workspace-hack", ] [[package]] @@ -2115,7 +2105,6 @@ dependencies = [ "tempfile", "tokio", "tower", - "workspace-hack", ] [[package]] @@ -2144,7 +2133,6 @@ dependencies = [ "tokio-util", "tracing", "url", - "workspace-hack", "zip 8.6.0", ] @@ -2156,7 +2144,6 @@ dependencies = [ "gateway-api-discovery", "tracing", "tracing-subscriber", - "workspace-hack", ] [[package]] @@ -2166,7 +2153,6 @@ dependencies = [ "build-ceiling", "gateway-api-types", "tokio", - "workspace-hack", ] [[package]] @@ -2186,7 +2172,6 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "workspace-hack", ] [[package]] @@ -2198,7 +2183,6 @@ dependencies = [ "gateway-protocol", "thiserror 2.0.19", "tokio", - "workspace-hack", ] [[package]] @@ -2228,7 +2212,6 @@ dependencies = [ "tower", "tracing", "tracing-subscriber", - "workspace-hack", ] [[package]] @@ -2245,7 +2228,6 @@ dependencies = [ "tempfile", "tokio", "tracing", - "workspace-hack", ] [[package]] @@ -2256,7 +2238,6 @@ dependencies = [ "tempfile", "thiserror 2.0.19", "tokio", - "workspace-hack", ] [[package]] @@ -2272,7 +2253,6 @@ dependencies = [ "thiserror 2.0.19", "tokio", "url", - "workspace-hack", ] [[package]] @@ -2286,7 +2266,6 @@ dependencies = [ "sha2 0.11.0", "thiserror 2.0.19", "tracing", - "workspace-hack", ] [[package]] @@ -2655,7 +2634,6 @@ dependencies = [ "promptforge-plugin", "serde_json", "tokio", - "workspace-hack", ] [[package]] @@ -2674,7 +2652,6 @@ dependencies = [ "thiserror 2.0.19", "tokio", "url", - "workspace-hack", ] [[package]] @@ -2691,7 +2668,6 @@ dependencies = [ "thiserror 2.0.19", "tokio", "tracing", - "workspace-hack", ] [[package]] @@ -4588,7 +4564,6 @@ dependencies = [ "serde_json", "tokio", "tracing", - "workspace-hack", ] [[package]] @@ -4598,7 +4573,6 @@ dependencies = [ "async-trait", "promptforge-plugin", "serde_json", - "workspace-hack", ] [[package]] @@ -4622,7 +4596,6 @@ dependencies = [ "tokio", "tracing", "url", - "workspace-hack", ] [[package]] @@ -4808,7 +4781,6 @@ dependencies = [ "promptforge-types", "promptforge-vfs", "serde_json", - "workspace-hack", ] [[package]] @@ -4829,7 +4801,6 @@ dependencies = [ "thiserror 2.0.19", "tokio", "tracing", - "workspace-hack", ] [[package]] @@ -4845,7 +4816,6 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.19", - "workspace-hack", ] [[package]] @@ -4857,7 +4827,6 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.19", - "workspace-hack", ] [[package]] @@ -4872,7 +4841,6 @@ dependencies = [ "serde", "serde_yaml_ng", "thiserror 2.0.19", - "workspace-hack", ] [[package]] @@ -4883,7 +4851,6 @@ dependencies = [ "promptforge-types", "promptforge-vfs", "serde_json", - "workspace-hack", ] [[package]] @@ -4897,7 +4864,6 @@ dependencies = [ "sha2 0.11.0", "thiserror 2.0.19", "time", - "workspace-hack", ] [[package]] @@ -5988,7 +5954,6 @@ dependencies = [ "serde_json", "thiserror 2.0.19", "turso", - "workspace-hack", ] [[package]] @@ -5999,7 +5964,6 @@ dependencies = [ "build-ceiling", "tokio", "tower", - "workspace-hack", ] [[package]] @@ -8552,7 +8516,6 @@ dependencies = [ "webview2-com", "windows-core 0.61.2", "workshop-server-api", - "workspace-hack", ] [[package]] @@ -8571,7 +8534,6 @@ dependencies = [ "thiserror 2.0.19", "tokio", "tracing", - "workspace-hack", ] [[package]] @@ -8596,7 +8558,6 @@ dependencies = [ "workshop-protocol", "workshop-registry", "workshop-support", - "workspace-hack", ] [[package]] @@ -8613,7 +8574,6 @@ dependencies = [ "workshop-protocol", "workshop-registry", "workshop-support", - "workspace-hack", ] [[package]] @@ -8623,7 +8583,6 @@ dependencies = [ "build-ceiling", "serde", "serde_json", - "workspace-hack", ] [[package]] @@ -8635,7 +8594,6 @@ dependencies = [ "serde_json", "tokio", "workshop-protocol", - "workspace-hack", ] [[package]] @@ -8651,7 +8609,6 @@ dependencies = [ "thiserror 2.0.19", "tokio", "turso", - "workspace-hack", ] [[package]] @@ -8695,7 +8652,6 @@ dependencies = [ "workshop-support", "workshop-user-state", "workshop-workspace", - "workspace-hack", ] [[package]] @@ -8705,7 +8661,6 @@ dependencies = [ "build-ceiling", "workshop-server", "workshop-server-api", - "workspace-hack", ] [[package]] @@ -8717,7 +8672,6 @@ dependencies = [ "workshop-protocol", "workshop-registry", "workshop-support", - "workspace-hack", ] [[package]] @@ -8735,7 +8689,6 @@ dependencies = [ "toml 1.1.4+spec-1.1.0", "tower", "tracing", - "workspace-hack", ] [[package]] @@ -8754,7 +8707,6 @@ dependencies = [ "workshop-protocol", "workshop-registry", "workshop-support", - "workspace-hack", ] [[package]] @@ -8779,113 +8731,6 @@ dependencies = [ "workshop-registry", "workshop-support", "workshop-workspace", - "workspace-hack", -] - -[[package]] -name = "workspace-hack" -version = "0.1.0" -dependencies = [ - "bitflags 2.13.1", - "block2", - "brotli", - "cc", - "chrono", - "crossbeam-epoch", - "crossbeam-utils", - "crypto-common 0.1.7", - "deranged", - "digest 0.11.3", - "dpi", - "either", - "erased-serde", - "errno", - "fastrand", - "form_urlencoded", - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", - "getrandom 0.4.3", - "hyper", - "hyper-util", - "icu_locale_core", - "icu_normalizer", - "icu_properties", - "icu_provider", - "indexmap 2.14.0", - "itertools 0.10.5", - "libc", - "linux-raw-sys 0.12.1", - "litemap", - "lock_api", - "log", - "markup5ever 0.38.0", - "memchr", - "miniz_oxide", - "muda", - "num-traits", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "open", - "parking_lot", - "percent-encoding", - "potential_utf", - "proc-macro2", - "quote", - "regex", - "regex-automata", - "regex-syntax", - "reqwest", - "rustix 1.1.4", - "schemars 0.8.22", - "semver", - "serde", - "serde_core", - "serde_json", - "simd-adler32", - "slab", - "smallvec", - "stable_deref_trait", - "string_cache", - "subtle", - "syn 2.0.119", - "syn 3.0.3", - "sync_wrapper", - "tar", - "tauri-utils", - "time", - "tinystr", - "tokio", - "tokio-util", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "tower", - "tower-http", - "tracing", - "tracing-core", - "tracing-subscriber", - "typenum", - "url", - "uuid", - "web_atoms", - "windows-sys 0.59.0", - "windows-sys 0.61.2", - "winnow 1.0.4", - "writeable", - "yoke", - "zbus", - "zbus_macros", - "zerocopy", - "zerofrom", - "zerotrie", - "zerovec", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 35dec8025..e17947df1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,8 +72,6 @@ workshop-run-log = { path = "crates/workshop/run-log" } workshop-agents = { path = "crates/workshop/agents" } workshop-user-state = { path = "crates/workshop/user-state" } gateway-whisper-ffi = { path = "crates/gateway/stt/whisper-ffi" } -# cargo-hakari's feature-unification crate; every member inherits it. -workspace-hack = { path = "crates/workspace-hack" } pulldown-cmark = "0.12" serde = { version = "1", features = ["derive"] } serde_yaml_ng = "0.10" @@ -86,7 +84,8 @@ serde_json = { version = "1", features = ["float_roundtrip"] } # the gateway's normal closure. Root certificates come from the OS trust # store through the platform verifier (the `rustls` feature's default), not # from a bundled Mozilla list. `query` became opt-in in 0.13. -reqwest = { version = "0.13", default-features = false, features = ["json", "query", "rustls", "stream"] } +# `system-proxy` keeps the gateway honoring Windows registry and macOS proxies. +reqwest = { version = "0.13", default-features = false, features = ["json", "query", "rustls", "stream", "system-proxy"] } fastrand = "2" rand = "0.9" readabilityrs = "0.1" @@ -242,12 +241,12 @@ tauri-plugin-opener = "2.5.5" tauri-plugin-process = "2.3.1" # default-features off drops the Tauri plugin's `rustls-tls` default, which is # the one dependency in the tree that pins rustls to the `ring` backend (and -# installs it as the process-global CryptoProvider). Through workspace-hack -# that feature would put `ring` back into every member, the gateway included. -# The plugin's reqwest calls still get TLS: cargo unifies reqwest's features -# per binary and the workspace pin above enables `rustls` (aws-lc-rs), so the -# Workshop links one crypto backend. `system-proxy` and `zip` are the -# plugin's other defaults, kept as they were. +# installs it as the process-global CryptoProvider). The plugin's `rustls-tls` +# default would put `ring` into the Workshop binary. The plugin's reqwest +# calls still get TLS: cargo unifies reqwest's features per binary and the +# workspace pin above enables `rustls` (aws-lc-rs), so the Workshop links one +# crypto backend. `system-proxy` and `zip` are the plugin's other defaults, +# kept as they were. tauri-plugin-updater = { version = "2.11.0", default-features = false, features = ["system-proxy", "zip"] } tauri-plugin-single-instance = "2.4.4" # Test-only Jinja2 compatibility oracle for bundled chat templates. Members diff --git a/crates/build-ceiling/Cargo.toml b/crates/build-ceiling/Cargo.toml index 0c1bf941e..7edca9fa4 100644 --- a/crates/build-ceiling/Cargo.toml +++ b/crates/build-ceiling/Cargo.toml @@ -6,10 +6,9 @@ license.workspace = true repository.workspace = true publish = false -# No dependencies of any kind, not even workspace-hack: the six crates that -# may not name a build-* crate include src/lib.rs with #[path] from their -# build scripts, so it must compile on std alone. .config/hakari.toml -# excludes this crate from workspace-hack. +# No dependencies of any kind: the six crates that may not name a build-* +# crate include src/lib.rs with #[path] from their build scripts, so it must +# compile on std alone. [dependencies] [lints] diff --git a/crates/build-llama-cuda/Cargo.toml b/crates/build-llama-cuda/Cargo.toml index fd1618d5f..7ce9f4385 100644 --- a/crates/build-llama-cuda/Cargo.toml +++ b/crates/build-llama-cuda/Cargo.toml @@ -19,7 +19,6 @@ serde.workspace = true serde_json.workspace = true sha2.workspace = true zip.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/build-ui/Cargo.toml b/crates/build-ui/Cargo.toml index 324914084..0b1ef5c4a 100644 --- a/crates/build-ui/Cargo.toml +++ b/crates/build-ui/Cargo.toml @@ -9,7 +9,6 @@ publish = false [dependencies] anyhow.workspace = true serde_json.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/build-user-guide/Cargo.toml b/crates/build-user-guide/Cargo.toml index 74a74845a..77720a7b5 100644 --- a/crates/build-user-guide/Cargo.toml +++ b/crates/build-user-guide/Cargo.toml @@ -6,7 +6,6 @@ license.workspace = true publish = false [dependencies] -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/build-workshop/Cargo.toml b/crates/build-workshop/Cargo.toml index 062dbab27..06adcb9fb 100644 --- a/crates/build-workshop/Cargo.toml +++ b/crates/build-workshop/Cargo.toml @@ -13,7 +13,6 @@ path = "src/main.rs" [dependencies] anyhow.workspace = true ctrlc.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/build-xtask/Cargo.toml b/crates/build-xtask/Cargo.toml index e636f83d5..907190f34 100644 --- a/crates/build-xtask/Cargo.toml +++ b/crates/build-xtask/Cargo.toml @@ -14,7 +14,6 @@ rustdoc-types.workspace = true serde_json.workspace = true syn.workspace = true toml.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/build-xtask/src/engine_deps.rs b/crates/build-xtask/src/engine_deps.rs index 9b087cfb6..9cb84e0ca 100644 --- a/crates/build-xtask/src/engine_deps.rs +++ b/crates/build-xtask/src/engine_deps.rs @@ -19,8 +19,7 @@ //! `test_support_leak` guard fails the build when any non-dev table in the //! workspace enables it. //! -//! The check reads declared dependencies, not the resolved graph, so -//! `workspace-hack` unification is irrelevant to it. +//! The check reads declared dependencies, not the resolved graph. use std::collections::BTreeSet; use std::fmt; diff --git a/crates/build-xtask/src/harness_bans.rs b/crates/build-xtask/src/harness_bans.rs index fc9c723db..85dfce34c 100644 --- a/crates/build-xtask/src/harness_bans.rs +++ b/crates/build-xtask/src/harness_bans.rs @@ -8,9 +8,9 @@ //! forms. `[dev-dependencies]` are outside the ban: the suites and doc //! examples drive runs on tokio, as a real Host does. //! -//! The check reads declared dependencies, not the resolved graph, so -//! `workspace-hack` unification is irrelevant to it. It binds the facade -//! and both crates in the container, the set its workspace test pins. +//! The check reads declared dependencies, not the resolved graph. It binds +//! the facade and both crates in the container, the set its workspace test +//! pins. use std::fs; use std::path::{Path, PathBuf}; diff --git a/crates/build-xtask/src/product.rs b/crates/build-xtask/src/product.rs index 72092be92..9b045e0a9 100644 --- a/crates/build-xtask/src/product.rs +++ b/crates/build-xtask/src/product.rs @@ -14,13 +14,12 @@ //! `gateway-api-discovery`). //! - `harness`/`harness-*` crates must not depend on gateway, shared, or //! workshop crates; outside their own family they may name only -//! `promptforge`, `promptforge-plugin`, and `workspace-hack`, which -//! rules out `build-*` and other unaffiliated crates. +//! `promptforge` and `promptforge-plugin`, which rules out `build-*` +//! and other unaffiliated crates. //! - `plugin-*` crates may depend only on `promptforge-plugin`, `shared-*` -//! crates, `workspace-hack`, and outside libraries. `promptforge`, -//! gateway, and Harness crates must not depend on `plugin-*` crates, -//! except `harness-gateway-client`, which implements web's -//! `SearchProvider`. +//! crates, and outside libraries. `promptforge`, gateway, and Harness +//! crates must not depend on `plugin-*` crates, except +//! `harness-gateway-client`, which implements web's `SearchProvider`. //! - `shared-*` crates must not depend on any product crate. //! - Public API: a crate outside the promptforge family may depend on //! the family only through its two public crates, `promptforge` and @@ -156,9 +155,6 @@ const GATEWAY_CLIENT: &str = "harness-gateway-client"; const PUBLIC_HARNESS: [&str; 2] = [HARNESS_FACADE, GATEWAY_CLIENT]; /// The Plugin contract: the one promptforge crate Plugin crates may name. const PLUGIN_CONTRACT: &str = "promptforge-plugin"; -/// The hakari feature-unification crate: the one unaffiliated crate Harness -/// and Plugin crates may name. -const WORKSPACE_HACK: &str = "workspace-hack"; /// The reason a dependency from `package` to `dep` breaches the matrix, /// or `None` when the edge is legal. @@ -229,10 +225,10 @@ fn boundary_breach(package: &CrateInfo, dep: &CrateInfo) -> Option { Some("harness crates must not depend on shared crates") } (Family::Harness, Family::Build) => Some( - "harness crates must not depend on build-* crates; outside their family they may name only promptforge, promptforge-plugin, and workspace-hack", + "harness crates must not depend on build-* crates; outside their family they may name only promptforge and promptforge-plugin", ), - (Family::Harness, Family::Unaffiliated) if dep.package != WORKSPACE_HACK => Some( - "harness crates must not depend on unaffiliated crates other than workspace-hack; outside their family they may name only promptforge, promptforge-plugin, and workspace-hack", + (Family::Harness, Family::Unaffiliated) => Some( + "harness crates must not depend on unaffiliated crates; outside their family they may name only promptforge and promptforge-plugin", ), ( Family::Shared, @@ -273,14 +269,11 @@ fn boundary_breach(package: &CrateInfo, dep: &CrateInfo) -> Option { /// gateway client names a Plugin crate. fn plugin_breach(package: &CrateInfo, dep: &CrateInfo) -> Option<&'static str> { let (from, to) = (family(&package.package), family(&dep.package)); - // `workspace-hack` is unaffiliated, so the allow-list names it, as the - // Harness rule does. let allowed = to == Family::Shared - || [PLUGIN_CONTRACT, WORKSPACE_HACK, package.package.as_str()] - .contains(&dep.package.as_str()); + || [PLUGIN_CONTRACT, package.package.as_str()].contains(&dep.package.as_str()); match (from, to) { (Family::Plugin, _) if !allowed => Some( - "plugin crates may depend only on promptforge-plugin, shared-* crates, workspace-hack, and outside libraries", + "plugin crates may depend only on promptforge-plugin, shared-* crates, and outside libraries", ), (Family::Promptforge | Family::Gateway | Family::Harness, Family::Plugin) if package.package != GATEWAY_CLIENT => diff --git a/crates/build-xtask/src/product/harness_tests.rs b/crates/build-xtask/src/product/harness_tests.rs index 58ab45c96..930100949 100644 --- a/crates/build-xtask/src/product/harness_tests.rs +++ b/crates/build-xtask/src/product/harness_tests.rs @@ -1,7 +1,6 @@ //! Harness-family fixtures: outside their family, Harness crates depend on -//! `promptforge`, `promptforge-plugin`, and `workspace-hack` only, and -//! outside crates reach the family only through `harness` and -//! `harness-gateway-client`. +//! `promptforge` and `promptforge-plugin` only, and outside crates reach +//! the family only through `harness` and `harness-gateway-client`. use super::product_boundary_violations; use super::test_support::write_crate; @@ -49,23 +48,6 @@ fn a_harness_crate_depending_on_a_shared_crate_is_reported() { ); } -#[test] -fn a_harness_crate_depending_on_workspace_hack_passes() { - let root = tempfile::TempDir::new().expect("tempdir"); - write_crate( - root.path(), - "harness-internal/runner", - "harness-runner", - "[dependencies]\nworkspace-hack = { path = \"../../workspace-hack\" }\n", - ); - write_crate(root.path(), "workspace-hack", "workspace-hack", ""); - let violations = product_boundary_violations(root.path()); - assert!( - violations.is_empty(), - "workspace-hack belongs to no family, so harness crates may name it: {violations:?}" - ); -} - #[test] fn a_harness_crate_depending_on_the_plugin_contract_passes() { let root = tempfile::TempDir::new().expect("tempdir"); @@ -98,7 +80,7 @@ fn a_harness_crate_build_depending_on_a_build_crate_is_reported() { assert!( violations[0].starts_with("harness-runner depends on build-ui:") && violations[0].contains( - "harness crates must not depend on build-* crates; outside their family they may name only promptforge, promptforge-plugin, and workspace-hack" + "harness crates must not depend on build-* crates; outside their family they may name only promptforge and promptforge-plugin" ), "build-* crates are closed to harness crates in every dependency table: {violations:?}" ); @@ -119,9 +101,9 @@ fn a_harness_crate_depending_on_an_unaffiliated_crate_is_reported() { assert!( violations[0].starts_with("harness-plugins depends on some-tool:") && violations[0].contains( - "harness crates must not depend on unaffiliated crates other than workspace-hack; outside their family they may name only promptforge, promptforge-plugin, and workspace-hack" + "harness crates must not depend on unaffiliated crates; outside their family they may name only promptforge and promptforge-plugin" ), - "workspace-hack is the one unaffiliated crate harness crates may name: {violations:?}" + "unaffiliated crates are closed to harness crates: {violations:?}" ); } diff --git a/crates/build-xtask/src/product/plugin_tests.rs b/crates/build-xtask/src/product/plugin_tests.rs index ae449b7d3..17e154312 100644 --- a/crates/build-xtask/src/product/plugin_tests.rs +++ b/crates/build-xtask/src/product/plugin_tests.rs @@ -1,8 +1,8 @@ //! Plugin-family fixtures: a `plugin-*` crate depends only on -//! `promptforge-plugin`, `shared-*` crates, `workspace-hack`, and outside -//! libraries; Engine, Gateway, and Harness crates may not depend on a -//! Plugin crate, except `harness-gateway-client`; and the web Plugin is no -//! longer a public Harness crate. +//! `promptforge-plugin`, `shared-*` crates, and outside libraries; Engine, +//! Gateway, and Harness crates may not depend on a Plugin crate, except +//! `harness-gateway-client`; and the web Plugin is no longer a public +//! Harness crate. use super::product_boundary_violations; use super::test_support::write_crate; @@ -16,7 +16,6 @@ fn plugin_web_naming(deps: &str) -> Vec { ("promptforge-plugin", "promptforge-plugin"), ("promptforge", "promptforge"), ("shared-loopback", "shared-loopback"), - ("workspace-hack", "workspace-hack"), ("harness", "harness"), ("plugin-user-input", "plugin-user-input"), ("build-ui", "build-ui"), @@ -26,14 +25,14 @@ fn plugin_web_naming(deps: &str) -> Vec { product_boundary_violations(root.path()) } -const PLUGIN_RULE: &str = "plugin crates may depend only on promptforge-plugin, shared-* crates, workspace-hack, and outside libraries"; +const PLUGIN_RULE: &str = + "plugin crates may depend only on promptforge-plugin, shared-* crates, and outside libraries"; #[test] -fn a_plugin_crate_naming_the_contract_a_shared_crate_and_workspace_hack_passes() { +fn a_plugin_crate_naming_the_contract_and_a_shared_crate_passes() { let violations = plugin_web_naming( "[dependencies]\npromptforge-plugin = { path = \"../promptforge-plugin\" }\n\ shared-loopback = { path = \"../shared-loopback\" }\n\ - workspace-hack = { path = \"../workspace-hack\" }\n\ [dev-dependencies]\npromptforge-plugin = { path = \"../promptforge-plugin\", features = [\"test-support\"] }\n", ); assert!(violations.is_empty(), "{violations:?}"); diff --git a/crates/build-xtask/src/test_support_leak.rs b/crates/build-xtask/src/test_support_leak.rs index 5b673eebb..ed6935917 100644 --- a/crates/build-xtask/src/test_support_leak.rs +++ b/crates/build-xtask/src/test_support_leak.rs @@ -31,10 +31,10 @@ //! is gated by a feature this guard already confines to dev tables. //! Forwarding into the other family is reported. //! -//! The check reads declared dependencies, not the resolved graph, so -//! `workspace-hack` unification is irrelevant to it. Manifests that cannot -//! be read or parsed are skipped here; the product-boundary check and the -//! Engine manifest guard already report them. +//! The check reads declared dependencies, not the resolved graph. +//! Manifests that cannot be read or parsed are skipped here; the +//! product-boundary check and the Engine manifest guard already report +//! them. use std::fs; use std::path::{Path, PathBuf}; diff --git a/crates/build-xtask/src/tidy-wiring.rs b/crates/build-xtask/src/tidy-wiring.rs index 7abeaaf98..745621028 100644 --- a/crates/build-xtask/src/tidy-wiring.rs +++ b/crates/build-xtask/src/tidy-wiring.rs @@ -10,21 +10,16 @@ use std::path::{Component, Path, PathBuf}; use proc_macro2::{Delimiter, TokenStream, TokenTree}; use syn::{Expr, ExprLit, Lit, Meta}; -/// The hakari crate: generated, with no build script, so this check holds -/// its files to the limit instead. -const WORKSPACE_HACK: &str = "workspace-hack"; -/// The limit `build_ceiling::check()` enforces in every other crate. -const LINE_LIMIT: usize = 500; /// The ceiling's source, from the workspace root: the file the `#[path]` /// form includes. const CEILING_SOURCE: [&str; 4] = ["crates", "build-ceiling", "src", "lib.rs"]; /// Checks that every workspace crate's package-root `build.rs` calls /// `build_ceiling::check()` and reaches it through a `build-ceiling` -/// build-dependency or a `#[path]` include of the ceiling's source, and -/// that no `workspace-hack` file is over the limit. A crate whose manifest -/// could not be read is held to the `#[path]` form, the one its build -/// script alone can show, and a walk that found no crate fails. +/// build-dependency or a `#[path]` include of the ceiling's source. A +/// crate whose manifest could not be read is held to the `#[path]` form, +/// the one its build script alone can show, and a walk that found no +/// crate fails. pub(super) fn ceiling_wiring_violations(root: &Path) -> Vec { let walk = crate::product::workspace_crates(root); if walk.crates.is_empty() && walk.unread.is_empty() { @@ -37,14 +32,10 @@ pub(super) fn ceiling_wiring_violations(root: &Path) -> Vec { let mut violations = Vec::new(); for krate in &walk.crates { let dir = root.join(&krate.dir); - if krate.package == WORKSPACE_HACK { - violations.extend(workspace_hack_violations(&dir)); - } else { - let manifest = fs::read_to_string(dir.join("Cargo.toml")) - .ok() - .and_then(|text| toml::from_str::(&text).ok()); - violations.extend(crate_violation(root, &dir, manifest.as_ref())); - } + let manifest = fs::read_to_string(dir.join("Cargo.toml")) + .ok() + .and_then(|text| toml::from_str::(&text).ok()); + violations.extend(crate_violation(root, &dir, manifest.as_ref())); } for dir in &walk.unread { violations.extend(crate_violation(root, &root.join(dir), None)); @@ -190,28 +181,6 @@ fn lexical(path: &Path) -> PathBuf { resolved } -/// The `workspace-hack` files over the limit, and the ones that could not -/// be read. -fn workspace_hack_violations(dir: &Path) -> Vec { - super::rust_files(dir) - .into_iter() - .filter_map(|file| match fs::read_to_string(&file) { - Ok(text) => { - let lines = text.lines().count(); - (lines > LINE_LIMIT).then(|| { - format!( - "{} has {lines} lines, over the limit of {LINE_LIMIT}: workspace-hack \ - has no build script, so this check holds it to the limit \ - `build_ceiling::check()` enforces in every other crate", - file.display() - ) - }) - } - Err(error) => Some(format!("{}: unreadable source: {error}", file.display())), - }) - .collect() -} - #[cfg(test)] mod tests { use super::*; @@ -244,8 +213,7 @@ mod tests { dir } - /// A wired workspace: a crate in each form, the ceiling's own crate, - /// and a `workspace-hack` with no build script. + /// A wired workspace: a crate in each form and the ceiling's own crate. fn write_wired(root: &Path) { let local = Some(CALLS_CHECK); write_built( @@ -262,8 +230,6 @@ mod tests { write_built(root, dir, "harness-runner", "", Some(&runner)); let own = includes("src/lib.rs"); write_built(root, "build-ceiling", "build-ceiling", "", Some(&own)); - let hack = write_built(root, "workspace-hack", "workspace-hack", "", None); - std::fs::write(hack.join("src").join("lib.rs"), "// hakari\n").expect("lib.rs writes"); } /// The wiring violations of a wired workspace plus one crate. @@ -334,17 +300,6 @@ mod tests { assert!(violations[0].contains("broken")); } - #[test] - fn a_workspace_hack_file_over_the_limit_fails() { - let root = tempfile::TempDir::new().expect("tempdir"); - write_wired(root.path()); - let lib = root.path().join("crates/workspace-hack/src/lib.rs"); - std::fs::write(lib, "// line\n".repeat(501)).expect("lib.rs writes"); - let violations = ceiling_wiring_violations(root.path()); - assert_eq!(violations.len(), 1, "{violations:?}"); - assert!(violations[0].contains("workspace-hack") && violations[0].contains("501 lines")); - } - #[test] fn a_wiring_scan_that_finds_no_crate_fails() { let root = tempfile::TempDir::new().expect("tempdir"); diff --git a/crates/gateway-api-discovery/Cargo.toml b/crates/gateway-api-discovery/Cargo.toml index 7cb4e5b51..7168e4d2f 100644 --- a/crates/gateway-api-discovery/Cargo.toml +++ b/crates/gateway-api-discovery/Cargo.toml @@ -11,7 +11,6 @@ serde.workspace = true serde_json.workspace = true shared-error-source = { workspace = true, features = ["json"] } thiserror.workspace = true -workspace-hack.workspace = true # Process-image lookup for stale detection (src/sys/macos.rs): proc_pidpath. [target.'cfg(target_os = "macos")'.dependencies] diff --git a/crates/gateway-api-types/Cargo.toml b/crates/gateway-api-types/Cargo.toml index 0c1cbfc6d..6b54d9c2b 100644 --- a/crates/gateway-api-types/Cargo.toml +++ b/crates/gateway-api-types/Cargo.toml @@ -9,7 +9,6 @@ publish = false [dependencies] serde.workspace = true time = { workspace = true, features = ["parsing", "serde-human-readable"] } -workspace-hack.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/crates/gateway/app/Cargo.toml b/crates/gateway/app/Cargo.toml index 461de78c2..899be063b 100644 --- a/crates/gateway/app/Cargo.toml +++ b/crates/gateway/app/Cargo.toml @@ -86,7 +86,6 @@ tokio = { workspace = true, features = ["signal"] } tokio-util.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } -workspace-hack.workspace = true # First-run STT gating reads the installer's registry choice (src/boot.rs): # RegOpenKeyExW + RegQueryValueExW. The Windows tray backend (src/tray/) adds diff --git a/crates/gateway/cloud-providers/Cargo.toml b/crates/gateway/cloud-providers/Cargo.toml index 8430d70a2..34ede7b20 100644 --- a/crates/gateway/cloud-providers/Cargo.toml +++ b/crates/gateway/cloud-providers/Cargo.toml @@ -27,7 +27,6 @@ gateway-api-types.workspace = true thiserror.workspace = true time = { workspace = true, features = ["parsing"] } tokio.workspace = true -workspace-hack.workspace = true [build-dependencies] build-ceiling.workspace = true diff --git a/crates/gateway/config-ui/Cargo.toml b/crates/gateway/config-ui/Cargo.toml index 3ba73970c..d4297c8ce 100644 --- a/crates/gateway/config-ui/Cargo.toml +++ b/crates/gateway/config-ui/Cargo.toml @@ -12,7 +12,6 @@ axum.workspace = true # gateway builds get the same check without this crate's asset machinery. shared-loopback.workspace = true rust-embed.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/gateway/config/Cargo.toml b/crates/gateway/config/Cargo.toml index 6d4225f4c..9a184883c 100644 --- a/crates/gateway/config/Cargo.toml +++ b/crates/gateway/config/Cargo.toml @@ -14,7 +14,6 @@ gateway-api-types.workspace = true thiserror.workspace = true toml.workspace = true url.workspace = true -workspace-hack.workspace = true [dev-dependencies] reqwest = { workspace = true, features = ["blocking"] } diff --git a/crates/gateway/local/Cargo.toml b/crates/gateway/local/Cargo.toml index 6324c798e..95f7e569c 100644 --- a/crates/gateway/local/Cargo.toml +++ b/crates/gateway/local/Cargo.toml @@ -28,7 +28,6 @@ tokio-util.workspace = true tracing.workspace = true url.workspace = true zip.workspace = true -workspace-hack.workspace = true [features] test-fixtures = [] diff --git a/crates/gateway/logging/Cargo.toml b/crates/gateway/logging/Cargo.toml index b3c4bd056..a77137043 100644 --- a/crates/gateway/logging/Cargo.toml +++ b/crates/gateway/logging/Cargo.toml @@ -10,7 +10,6 @@ publish = false gateway-api-discovery.workspace = true tracing.workspace = true tracing-subscriber.workspace = true -workspace-hack.workspace = true [build-dependencies] build-ceiling.workspace = true diff --git a/crates/gateway/progress/Cargo.toml b/crates/gateway/progress/Cargo.toml index bb064c527..d66f5d90b 100644 --- a/crates/gateway/progress/Cargo.toml +++ b/crates/gateway/progress/Cargo.toml @@ -10,7 +10,6 @@ publish = false gateway-api-types.workspace = true # `sync` supplies the hub's watch channel; nothing else of tokio is used. tokio = { workspace = true, features = ["sync"] } -workspace-hack.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/crates/gateway/protocol/Cargo.toml b/crates/gateway/protocol/Cargo.toml index e077d5d10..0b3d0330b 100644 --- a/crates/gateway/protocol/Cargo.toml +++ b/crates/gateway/protocol/Cargo.toml @@ -18,7 +18,6 @@ gateway-api-types.workspace = true thiserror.workspace = true tokio.workspace = true tracing.workspace = true -workspace-hack.workspace = true [dev-dependencies] tracing-subscriber.workspace = true diff --git a/crates/gateway/routing/Cargo.toml b/crates/gateway/routing/Cargo.toml index 966381c53..f6f24b782 100644 --- a/crates/gateway/routing/Cargo.toml +++ b/crates/gateway/routing/Cargo.toml @@ -11,7 +11,6 @@ gateway-config.workspace = true gateway-protocol.workspace = true thiserror.workspace = true tokio.workspace = true -workspace-hack.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/crates/gateway/stt/api/Cargo.toml b/crates/gateway/stt/api/Cargo.toml index 82e764f6e..7ef8c1a84 100644 --- a/crates/gateway/stt/api/Cargo.toml +++ b/crates/gateway/stt/api/Cargo.toml @@ -25,7 +25,6 @@ thiserror.workspace = true tokio.workspace = true tokio-util.workspace = true tracing.workspace = true -workspace-hack.workspace = true [dev-dependencies] anyhow.workspace = true diff --git a/crates/gateway/stt/backend-whisper/Cargo.toml b/crates/gateway/stt/backend-whisper/Cargo.toml index 8760b3975..169c8bd9e 100644 --- a/crates/gateway/stt/backend-whisper/Cargo.toml +++ b/crates/gateway/stt/backend-whisper/Cargo.toml @@ -11,7 +11,6 @@ gateway-stt-engine.workspace = true gateway-whisper-ffi.workspace = true gateway-progress.workspace = true tracing.workspace = true -workspace-hack.workspace = true [dev-dependencies] gateway-stt-engine = { workspace = true, features = ["test-fixtures"] } diff --git a/crates/gateway/stt/engine/Cargo.toml b/crates/gateway/stt/engine/Cargo.toml index 303ed2f86..079ae0b59 100644 --- a/crates/gateway/stt/engine/Cargo.toml +++ b/crates/gateway/stt/engine/Cargo.toml @@ -9,7 +9,6 @@ publish = false [dependencies] thiserror.workspace = true tokio.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/gateway/stt/whisper-ffi/Cargo.toml b/crates/gateway/stt/whisper-ffi/Cargo.toml index bc7ca05b7..a1507925d 100644 --- a/crates/gateway/stt/whisper-ffi/Cargo.toml +++ b/crates/gateway/stt/whisper-ffi/Cargo.toml @@ -10,7 +10,6 @@ publish = false libloading.workspace = true thiserror.workspace = true tracing.workspace = true -workspace-hack.workspace = true [dev-dependencies] hound.workspace = true diff --git a/crates/gateway/web-search/Cargo.toml b/crates/gateway/web-search/Cargo.toml index e2092ab7e..943f27e47 100644 --- a/crates/gateway/web-search/Cargo.toml +++ b/crates/gateway/web-search/Cargo.toml @@ -14,7 +14,6 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true url.workspace = true -workspace-hack.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/harness-gateway-client/Cargo.toml b/crates/harness-gateway-client/Cargo.toml index 6c8e5b0e0..857f08920 100644 --- a/crates/harness-gateway-client/Cargo.toml +++ b/crates/harness-gateway-client/Cargo.toml @@ -35,7 +35,6 @@ thiserror.workspace = true # `time` for the per-receive timeout. Non-test code spawns no task. tokio = { workspace = true, features = ["time"] } url.workspace = true -workspace-hack.workspace = true [dev-dependencies] # The mock gateways the client tests send real rounds to. diff --git a/crates/harness-internal/runner/Cargo.toml b/crates/harness-internal/runner/Cargo.toml index 004d4fd9c..ce5842c26 100644 --- a/crates/harness-internal/runner/Cargo.toml +++ b/crates/harness-internal/runner/Cargo.toml @@ -27,7 +27,6 @@ serde_json.workspace = true sha2.workspace = true thiserror.workspace = true tracing.workspace = true -workspace-hack.workspace = true [dev-dependencies] # The scripted Plugins' tools and input brokers are `async_trait` @@ -35,7 +34,7 @@ workspace-hack.workspace = true async-trait.workspace = true # The suites drive runs on tokio's test runtime, pace performers with its # clock, and gate them with its semaphore and channels. -tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] } +tokio = { workspace = true, features = ["macros", "rt", "sync", "test-util", "time"] } [lints] workspace = true diff --git a/crates/harness/Cargo.toml b/crates/harness/Cargo.toml index 073005b56..e992f78cf 100644 --- a/crates/harness/Cargo.toml +++ b/crates/harness/Cargo.toml @@ -14,7 +14,6 @@ publish = false harness-runner.workspace = true promptforge.workspace = true promptforge-plugin.workspace = true -workspace-hack.workspace = true # The suite and the `run-prompt` example run a Host on tokio, as a real # Host does; the facade's own items name no tokio type. The suite's diff --git a/crates/plugin-mcp/Cargo.toml b/crates/plugin-mcp/Cargo.toml index 99702015b..b7c8bf1d9 100644 --- a/crates/plugin-mcp/Cargo.toml +++ b/crates/plugin-mcp/Cargo.toml @@ -20,7 +20,6 @@ serde_json.workspace = true tracing.workspace = true # The Host's runtime handle every connection and call is spawned onto. tokio.workspace = true -workspace-hack.workspace = true [dev-dependencies] axum.workspace = true diff --git a/crates/plugin-mcp/src/lib.rs b/crates/plugin-mcp/src/lib.rs index 25c041119..156019f00 100644 --- a/crates/plugin-mcp/src/lib.rs +++ b/crates/plugin-mcp/src/lib.rs @@ -54,9 +54,9 @@ //! //! ## Invariants //! -//! - May depend on: `promptforge-plugin`, `shared-*` crates, -//! `workspace-hack`, and outside libraries. `cargo test -p build-xtask` -//! enforces the Plugin family's allow-list. Only [`PACKAGE`] is public. +//! - May depend on: `promptforge-plugin`, `shared-*` crates, and outside +//! libraries. `cargo test -p build-xtask` enforces the Plugin family's +//! allow-list. Only [`PACKAGE`] is public. //! - Every output is untrusted. //! - No reason, log line, `Debug` output, or error names an `env` or //! `headers` value. They may name keys. diff --git a/crates/plugin-user-input/Cargo.toml b/crates/plugin-user-input/Cargo.toml index 210eda27b..768a19fed 100644 --- a/crates/plugin-user-input/Cargo.toml +++ b/crates/plugin-user-input/Cargo.toml @@ -14,7 +14,6 @@ async-trait.workspace = true # call is lent, and the tool and service vocabulary. promptforge-plugin.workspace = true serde_json.workspace = true -workspace-hack.workspace = true [dev-dependencies] # `testing::TestCall`, which lends the ask tool a context without a Harness. diff --git a/crates/plugin-user-input/src/lib.rs b/crates/plugin-user-input/src/lib.rs index 11094da5e..83979c6d4 100644 --- a/crates/plugin-user-input/src/lib.rs +++ b/crates/plugin-user-input/src/lib.rs @@ -65,9 +65,9 @@ //! //! ## Invariants //! -//! - May depend on: `promptforge-plugin`, `shared-*` crates, -//! `workspace-hack`, and outside libraries. `cargo test -p build-xtask` -//! enforces the Plugin family's allow-list. +//! - May depend on: `promptforge-plugin`, `shared-*` crates, and outside +//! libraries. `cargo test -p build-xtask` enforces the Plugin family's +//! allow-list. //! - The crate names no async runtime. The Harness polls the ask call //! inside the run's own future, so an [`InputBroker`] must not block //! while polled. diff --git a/crates/plugin-web/Cargo.toml b/crates/plugin-web/Cargo.toml index 9cd931cbb..28b6940d7 100644 --- a/crates/plugin-web/Cargo.toml +++ b/crates/plugin-web/Cargo.toml @@ -25,7 +25,6 @@ thiserror.workspace = true tracing.workspace = true # The Host's runtime handle every fetch is spawned onto. tokio = { workspace = true } -workspace-hack.workspace = true [dev-dependencies] axum.workspace = true diff --git a/crates/plugin-web/src/lib.rs b/crates/plugin-web/src/lib.rs index d30612c08..eb9cae151 100644 --- a/crates/plugin-web/src/lib.rs +++ b/crates/plugin-web/src/lib.rs @@ -45,9 +45,9 @@ //! //! ## Invariants //! -//! - May depend on: `promptforge-plugin`, `shared-*` crates, -//! `workspace-hack`, and outside libraries. `cargo test -p build-xtask` -//! enforces the Plugin family's allow-list. +//! - May depend on: `promptforge-plugin`, `shared-*` crates, and outside +//! libraries. `cargo test -p build-xtask` enforces the Plugin family's +//! allow-list. //! - Every URL selected by the model or a tool, and every resolved //! address, is validated again on each redirect hop. A non-global //! address is denied unless the fetch policy grants an exact diff --git a/crates/promptforge-internal/engine/Cargo.toml b/crates/promptforge-internal/engine/Cargo.toml index 59598e8d7..0bd7054eb 100644 --- a/crates/promptforge-internal/engine/Cargo.toml +++ b/crates/promptforge-internal/engine/Cargo.toml @@ -22,7 +22,6 @@ tracing.workspace = true # The one runtime the Engine names, and only for the tokio test driver # behind `test-support`; the Engine proper performs no I/O. tokio = { workspace = true, features = ["macros", "rt", "sync", "time"], optional = true } -workspace-hack.workspace = true [features] # The Engine's own test drivers, for `benches/models_loop.rs` alone, which diff --git a/crates/promptforge-internal/lua/Cargo.toml b/crates/promptforge-internal/lua/Cargo.toml index 6ea088b7b..b8f20f66e 100644 --- a/crates/promptforge-internal/lua/Cargo.toml +++ b/crates/promptforge-internal/lua/Cargo.toml @@ -14,7 +14,6 @@ promptforge-vfs.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true -workspace-hack.workspace = true [features] # Test-only installs and helpers for companion crates' suites and this diff --git a/crates/promptforge-internal/model-client/Cargo.toml b/crates/promptforge-internal/model-client/Cargo.toml index d6ba55c9f..02999a969 100644 --- a/crates/promptforge-internal/model-client/Cargo.toml +++ b/crates/promptforge-internal/model-client/Cargo.toml @@ -13,7 +13,6 @@ promptforge-types.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true -workspace-hack.workspace = true [build-dependencies] build-ceiling.workspace = true diff --git a/crates/promptforge-internal/parser/Cargo.toml b/crates/promptforge-internal/parser/Cargo.toml index 3f9845022..0222896ed 100644 --- a/crates/promptforge-internal/parser/Cargo.toml +++ b/crates/promptforge-internal/parser/Cargo.toml @@ -13,7 +13,6 @@ pulldown-cmark.workspace = true serde.workspace = true serde_yaml_ng.workspace = true thiserror.workspace = true -workspace-hack.workspace = true [dev-dependencies] mlua.workspace = true diff --git a/crates/promptforge-internal/types/Cargo.toml b/crates/promptforge-internal/types/Cargo.toml index 73c42ae6b..70a5ad924 100644 --- a/crates/promptforge-internal/types/Cargo.toml +++ b/crates/promptforge-internal/types/Cargo.toml @@ -12,7 +12,6 @@ serde_json.workspace = true # The guard nonce's one-way derivation from the run seed. sha2.workspace = true thiserror.workspace = true -workspace-hack.workspace = true [dev-dependencies] # The property tests' adversarial content and random run seeds; diff --git a/crates/promptforge-internal/types/src/lib.rs b/crates/promptforge-internal/types/src/lib.rs index a0b86c689..7d3ed7f67 100644 --- a/crates/promptforge-internal/types/src/lib.rs +++ b/crates/promptforge-internal/types/src/lib.rs @@ -28,9 +28,8 @@ //! //! ## Invariants //! -//! - May depend on: no workspace crate beyond `workspace-hack`. -//! `cargo test -p build-xtask` enforces the product and container -//! boundaries. +//! - May depend on: no workspace crate. `cargo test -p build-xtask` +//! enforces the product and container boundaries. //! - [`untrusted`] neutralizes chat-template control delimiters in //! untrusted tool and Lua text, and is never applied to assistant //! replay or tool-call wire payloads. diff --git a/crates/promptforge-internal/vfs/Cargo.toml b/crates/promptforge-internal/vfs/Cargo.toml index 73a520563..788526762 100644 --- a/crates/promptforge-internal/vfs/Cargo.toml +++ b/crates/promptforge-internal/vfs/Cargo.toml @@ -7,9 +7,7 @@ repository.workspace = true publish = false # Zero-dependency rule: std only. No dependencies, workspace or external. -# The manifest test enforces this; never weaken it. workspace-hack is -# excluded: this crate has no third-party dependencies, so feature -# unification has nothing to unify here. +# The manifest test enforces this; never weaken it. [dependencies] [lints] diff --git a/crates/promptforge-plugin/Cargo.toml b/crates/promptforge-plugin/Cargo.toml index d795b919d..f892aa6d7 100644 --- a/crates/promptforge-plugin/Cargo.toml +++ b/crates/promptforge-plugin/Cargo.toml @@ -13,7 +13,6 @@ promptforge-types.workspace = true promptforge-vfs.workspace = true # The JSON a Plugin's configuration and every tool call's arguments arrive as. serde_json.workspace = true -workspace-hack.workspace = true [features] # `testing::TestCall`, which lends a `ToolContext` to a Plugin crate's own diff --git a/crates/promptforge-plugin/src/lib.rs b/crates/promptforge-plugin/src/lib.rs index 851ee4447..fd13ba5fa 100644 --- a/crates/promptforge-plugin/src/lib.rs +++ b/crates/promptforge-plugin/src/lib.rs @@ -19,11 +19,11 @@ //! //! ## Invariants //! -//! - May depend on: `promptforge-types`, `promptforge-vfs`, and -//! `workspace-hack` among workspace crates. As an Engine crate it -//! declares no async runtime, no `async-trait`, and no HTTP client. -//! `cargo test -p build-xtask` enforces the Engine manifest guard and -//! the product and container boundaries. +//! - May depend on: `promptforge-types` and `promptforge-vfs` among +//! workspace crates. As an Engine crate it declares no async runtime, no +//! `async-trait`, and no HTTP client. `cargo test -p build-xtask` +//! enforces the Engine manifest guard and the product and container +//! boundaries. //! - Only `[dev-dependencies]` enable `test-support`, which the //! `test-support` leak guard enforces. //! - A Host builds each Plugin object once, through diff --git a/crates/promptforge/Cargo.toml b/crates/promptforge/Cargo.toml index 42e70f828..4495dd0f2 100644 --- a/crates/promptforge/Cargo.toml +++ b/crates/promptforge/Cargo.toml @@ -15,7 +15,6 @@ promptforge-model-client.workspace = true promptforge-parser.workspace = true promptforge-types.workspace = true promptforge-vfs.workspace = true -workspace-hack.workspace = true [dev-dependencies] # The suite's and the `greeter` example's tool descriptors carry JSON diff --git a/crates/shared-error-source/Cargo.toml b/crates/shared-error-source/Cargo.toml index 575c5eb89..39c64af22 100644 --- a/crates/shared-error-source/Cargo.toml +++ b/crates/shared-error-source/Cargo.toml @@ -11,7 +11,6 @@ reqwest = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } thiserror.workspace = true turso = { workspace = true, optional = true } -workspace-hack.workspace = true # One wrapper per feature, so a consumer takes only the third-party # dependency it already has: gateway-api-discovery does not acquire turso diff --git a/crates/shared-loopback/Cargo.toml b/crates/shared-loopback/Cargo.toml index 7d45ad111..b5086dc57 100644 --- a/crates/shared-loopback/Cargo.toml +++ b/crates/shared-loopback/Cargo.toml @@ -8,7 +8,6 @@ publish = false [dependencies] axum.workspace = true -workspace-hack.workspace = true [dev-dependencies] tokio.workspace = true diff --git a/crates/workshop/agents/Cargo.toml b/crates/workshop/agents/Cargo.toml index 3681b48e8..e2c87ca71 100644 --- a/crates/workshop/agents/Cargo.toml +++ b/crates/workshop/agents/Cargo.toml @@ -29,7 +29,6 @@ thiserror.workspace = true # registry's `oneshot`; `time` for the timer every run sleeps on. tokio = { workspace = true, features = ["sync", "time"] } tracing.workspace = true -workspace-hack.workspace = true [dev-dependencies] # The discovery suite seeds an agents directory on disk. diff --git a/crates/workshop/desktop/Cargo.toml b/crates/workshop/desktop/Cargo.toml index bd01f8fd2..f7970c9c3 100644 --- a/crates/workshop/desktop/Cargo.toml +++ b/crates/workshop/desktop/Cargo.toml @@ -36,7 +36,6 @@ tauri-plugin-process.workspace = true tauri-plugin-single-instance.workspace = true tauri-plugin-updater.workspace = true url.workspace = true -workspace-hack.workspace = true # Explorer path drops and the microphone permission grant (src/bridge.rs): # raw WebView2 COM. The versions mirror tauri-runtime-wry's, keeping one diff --git a/crates/workshop/gateway/Cargo.toml b/crates/workshop/gateway/Cargo.toml index df809975f..cbee31f52 100644 --- a/crates/workshop/gateway/Cargo.toml +++ b/crates/workshop/gateway/Cargo.toml @@ -28,7 +28,6 @@ workshop-protocol.workspace = true workshop-registry.workspace = true workshop-support.workspace = true tempfile = { workspace = true, optional = true } -workspace-hack.workspace = true [dev-dependencies] axum.workspace = true diff --git a/crates/workshop/menu/Cargo.toml b/crates/workshop/menu/Cargo.toml index faf1d8cd6..836bf4233 100644 --- a/crates/workshop/menu/Cargo.toml +++ b/crates/workshop/menu/Cargo.toml @@ -15,7 +15,6 @@ tracing.workspace = true workshop-protocol.workspace = true workshop-registry.workspace = true workshop-support.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/workshop/protocol/Cargo.toml b/crates/workshop/protocol/Cargo.toml index b940ba89e..ba06381bf 100644 --- a/crates/workshop/protocol/Cargo.toml +++ b/crates/workshop/protocol/Cargo.toml @@ -9,7 +9,6 @@ repository.workspace = true [dependencies] serde.workspace = true serde_json.workspace = true -workspace-hack.workspace = true [build-dependencies] build-ceiling.workspace = true diff --git a/crates/workshop/registry/Cargo.toml b/crates/workshop/registry/Cargo.toml index 97db97c69..8bc701c10 100644 --- a/crates/workshop/registry/Cargo.toml +++ b/crates/workshop/registry/Cargo.toml @@ -11,7 +11,6 @@ axum.workspace = true serde_json.workspace = true tokio.workspace = true workshop-protocol.workspace = true -workspace-hack.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/workshop/run-log/Cargo.toml b/crates/workshop/run-log/Cargo.toml index ba68ec125..8e1ff79b9 100644 --- a/crates/workshop/run-log/Cargo.toml +++ b/crates/workshop/run-log/Cargo.toml @@ -15,7 +15,6 @@ shared-error-source = { workspace = true, features = ["database", "json"] } thiserror.workspace = true tokio = { workspace = true, features = ["fs", "sync"] } turso.workspace = true -workspace-hack.workspace = true [dev-dependencies] # The fidelity suite round-trips the Engine's events through the log. diff --git a/crates/workshop/server-api/Cargo.toml b/crates/workshop/server-api/Cargo.toml index 44f562c4e..6f163341f 100644 --- a/crates/workshop/server-api/Cargo.toml +++ b/crates/workshop/server-api/Cargo.toml @@ -14,7 +14,6 @@ test-fixtures = ["workshop-server/test-fixtures"] [dependencies] workshop-server.workspace = true -workspace-hack.workspace = true [dev-dependencies] workshop-server-api = { path = ".", features = ["test-fixtures"] } diff --git a/crates/workshop/server/Cargo.toml b/crates/workshop/server/Cargo.toml index 9643a0c84..cccaafe8a 100644 --- a/crates/workshop/server/Cargo.toml +++ b/crates/workshop/server/Cargo.toml @@ -58,14 +58,9 @@ workshop-support.workspace = true workshop-user-state.workspace = true workshop-workspace.workspace = true tempfile = { workspace = true, optional = true } -workspace-hack.workspace = true [features] default = [] -# Drops the webview asset layer: the build script skips the esbuild UI -# build, and the asset routes serve through a no-op implementation, so -# server-only integration tests run without Node.js or the UI bundle. -headless = [] test-fixtures = [ "dep:tempfile", "workshop-gateway/test-fixtures", diff --git a/crates/workshop/server/build.rs b/crates/workshop/server/build.rs index 255cd0103..665278767 100644 --- a/crates/workshop/server/build.rs +++ b/crates/workshop/server/build.rs @@ -4,19 +4,13 @@ //! into the repository). The crate version is baked into the bundle as //! `__APP_VERSION__`. Requires Node.js 22 on `PATH` and one `npm ci` in //! the `crates/workshop/` npm workspace per checkout (`npm ci --prefix -//! crates/workshop` from the repository root). Under the -//! `headless` feature the UI build is skipped and the asset directory is -//! left empty: the asset routes serve through the no-op implementation, -//! so server-only integration tests need neither Node.js nor the bundle. +//! crates/workshop` from the repository root). fn main() -> std::process::ExitCode { if let Err(error) = build_ceiling::check() { eprintln!("{error}"); return std::process::ExitCode::FAILURE; } - if std::env::var_os("CARGO_FEATURE_HEADLESS").is_some() { - return empty_asset_dir(); - } match build_ui::build_sibling( "../ui", build_ui::UiBuild { @@ -32,20 +26,3 @@ fn main() -> std::process::ExitCode { } } } - -/// Creates the empty `$OUT_DIR/ui-dist/` the headless build embeds; the -/// folder must exist for the rust-embed derive. -fn empty_asset_dir() -> std::process::ExitCode { - let Some(out_dir) = std::env::var_os("OUT_DIR") else { - eprintln!("OUT_DIR is not set; run through cargo"); - return std::process::ExitCode::FAILURE; - }; - let dist_dir = std::path::Path::new(&out_dir).join("ui-dist"); - match std::fs::create_dir_all(&dist_dir) { - Ok(()) => std::process::ExitCode::SUCCESS, - Err(error) => { - eprintln!("create {}: {error}", dist_dir.display()); - std::process::ExitCode::FAILURE - } - } -} diff --git a/crates/workshop/server/src/assets.rs b/crates/workshop/server/src/assets.rs index dcc70a539..11b9f7683 100644 --- a/crates/workshop/server/src/assets.rs +++ b/crates/workshop/server/src/assets.rs @@ -1,6 +1,5 @@ -//! The embedded workshop UI assets, the narrow [`AssetServer`] interface -//! the server wires into the asset routes, and the file-serving helper; the -//! routes that expose them sit in [`crate::routes::assets`]. +//! The embedded workshop UI assets and the file-serving helper; the routes +//! that expose them sit in [`crate::routes::assets`]. use axum::http::header; use axum::response::{IntoResponse, Response}; @@ -10,9 +9,7 @@ use crate::error::AppError; /// The workshop UI assets under `$OUT_DIR/ui-dist/`, written by the crate's /// build script (the esbuild bundle plus copies of the static files). Debug /// builds read the files from disk at request time, so UI edits need no Rust -/// recompile; release builds embed them into the binary. Absent under the -/// `headless` feature, which drops the UI build entirely. -#[cfg(not(feature = "headless"))] +/// recompile; release builds embed them into the binary. #[derive(rust_embed::Embed)] #[folder = "$OUT_DIR/ui-dist/"] pub(crate) struct UiAssets; @@ -64,30 +61,17 @@ impl AssetManifest { } } -/// The narrow asset-serving interface of the server's webview asset -/// layer. The server wires one implementation into the asset routes: -/// [`EmbeddedAssets`] in a normal build, `NoopAssets` under the -/// `headless` feature, which drops the UI build so server-only -/// integration tests run without the webview bundle. -pub(crate) trait AssetServer { - /// Returns one asset's bytes, or None when the name is absent. - fn get(&self, path: &str) -> Option>; - - /// The build's asset manifest, or None when the build emitted none. - fn manifest(&self) -> Option; -} - -/// The real asset layer: the embedded UI bundle. -#[cfg(not(feature = "headless"))] +/// The server's webview asset layer: the embedded UI bundle. pub(crate) struct EmbeddedAssets; -#[cfg(not(feature = "headless"))] -impl AssetServer for EmbeddedAssets { - fn get(&self, path: &str) -> Option> { +impl EmbeddedAssets { + /// Returns one asset's bytes, or None when the name is absent. + pub(crate) fn get(path: &str) -> Option> { UiAssets::get(path).map(|asset| asset.data.into_owned()) } - fn manifest(&self) -> Option { + /// The build's asset manifest, or None when the build emitted none. + pub(crate) fn manifest() -> Option { let asset = UiAssets::get("manifest.json")?; // A parse failure is safe to ignore: the build writes the manifest // itself and embeds it beside the assets, so malformed JSON means a @@ -103,31 +87,10 @@ impl AssetServer for EmbeddedAssets { } } -/// The no-op asset layer of a `headless` build: every name misses, so the -/// asset routes answer 404 while the API surface keeps working. -#[cfg(any(feature = "headless", test))] -pub(crate) struct NoopAssets; - -#[cfg(any(feature = "headless", test))] -impl AssetServer for NoopAssets { - fn get(&self, _path: &str) -> Option> { - None - } - - fn manifest(&self) -> Option { - None - } -} - -/// Serves one UI asset through `server` with the given content type and -/// cache policy. -pub(crate) fn ui_asset( - server: &dyn AssetServer, - path: &str, - content_type: &'static str, - cache: CachePolicy, -) -> Response { - match server.get(path) { +/// Serves one embedded UI asset with the given content type and cache +/// policy. +pub(crate) fn ui_asset(path: &str, content_type: &'static str, cache: CachePolicy) -> Response { + match EmbeddedAssets::get(path) { Some(data) => ( [ (header::CONTENT_TYPE, content_type), @@ -140,7 +103,7 @@ pub(crate) fn ui_asset( } } -#[cfg(all(test, not(feature = "headless")))] +#[cfg(test)] mod tests { use axum::http::StatusCode; @@ -148,12 +111,7 @@ mod tests { /// Asserts an asset name answers 404 rather than file contents. fn assert_asset_misses(path: &str) { - let response = ui_asset( - &EmbeddedAssets, - path, - "text/plain; charset=utf-8", - CachePolicy::Revalidate, - ); + let response = ui_asset(path, "text/plain; charset=utf-8", CachePolicy::Revalidate); assert_eq!( response.status(), StatusCode::NOT_FOUND, @@ -186,20 +144,6 @@ mod tests { assert_asset_misses(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml")); } - #[test] - fn the_noop_asset_layer_serves_nothing() { - let server = NoopAssets; - assert!(server.get("index.html").is_none()); - assert!(server.manifest().is_none()); - let response = ui_asset( - &server, - "index.html", - "text/html; charset=utf-8", - CachePolicy::Revalidate, - ); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } - #[test] fn the_cache_policies_render_their_header_values() { assert_eq!(CachePolicy::Revalidate.header_value(), "no-cache"); diff --git a/crates/workshop/server/src/routes/assets-headless-tests.rs b/crates/workshop/server/src/routes/assets-headless-tests.rs deleted file mode 100644 index ab2ebfb7c..000000000 --- a/crates/workshop/server/src/routes/assets-headless-tests.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Asset-route behavior under the `headless` feature: the asset layer is -//! the no-op implementation, so every UI route answers 404 and the server -//! runs its API surface without the webview bundle. - -use axum::body::Body; -use axum::http::{Request, StatusCode}; -use tower::ServiceExt; - -use crate::app::router; -use crate::app::test_helpers::state_for; - -#[tokio::test] -async fn the_asset_routes_answer_not_found() { - for uri in [ - "/", - "/app.js", - "/app.css", - "/style.css", - "/pcm-worklet.js", - "/chunks/chunk-AAAAAAAA.js", - "/bundle/app-AAAAAAAA.js", - "/icons/promptforge-icon.png", - "/icons/promptforge-icon@2x.png", - ] { - let (state, _state_dir) = state_for("http://127.0.0.1:1"); - let request = Request::builder() - .uri(uri) - .body(Body::empty()) - .expect("static request parts are valid"); - let response = router(state).oneshot(request).await.expect("infallible"); - assert_eq!( - response.status(), - StatusCode::NOT_FOUND, - "{uri} is a no-op under the headless feature" - ); - } -} - -#[tokio::test] -async fn the_api_surface_still_answers() { - let (state, _state_dir) = state_for("http://127.0.0.1:1"); - let request = Request::builder() - .uri("/health") - .body(Body::empty()) - .expect("static request parts are valid"); - let response = router(state).oneshot(request).await.expect("infallible"); - assert_eq!( - response.status(), - StatusCode::OK, - "the headless server still serves its API" - ); -} diff --git a/crates/workshop/server/src/routes/assets.rs b/crates/workshop/server/src/routes/assets.rs index 850836a68..ef8bf6379 100644 --- a/crates/workshop/server/src/routes/assets.rs +++ b/crates/workshop/server/src/routes/assets.rs @@ -5,30 +5,15 @@ use axum::extract::Path; use axum::response::{IntoResponse, Response}; use axum::routing::get; -use crate::assets::{self, AssetServer, CachePolicy}; +use crate::assets::{self, CachePolicy}; use crate::error::AppError; -/// The asset layer the server wires into these routes: the embedded UI -/// bundle, or the no-op implementation under the `headless` feature, -/// which drops the UI build so server-only integration tests run without -/// the webview assets. -fn asset_server() -> &'static dyn AssetServer { - #[cfg(not(feature = "headless"))] - { - &assets::EmbeddedAssets - } - #[cfg(feature = "headless")] - { - &assets::NoopAssets - } -} - /// The UI asset routes: the index page, the bundled script and styles /// (stable logical routes resolving through the build's manifest, plus /// the content-hashed `bundle/` files the stamped index page loads /// directly), the code-split chunks the bundle lazy-loads, the PCM /// worklet, and the program icon at 1x and 2x. Stateless: every response -/// comes straight from the [`AssetServer`]. +/// comes straight from the [`EmbeddedAssets`](assets::EmbeddedAssets). pub(crate) fn routes() -> Router { Router::new() .route("/", get(ui_index)) @@ -45,7 +30,6 @@ pub(crate) fn routes() -> Router { /// Serves the chat UI's `index.html`. async fn ui_index() -> Response { assets::ui_asset( - asset_server(), "index.html", "text/html; charset=utf-8", CachePolicy::Revalidate, @@ -71,20 +55,18 @@ async fn ui_app_css() -> Response { /// Serves one logical bundle route by resolving the build manifest to /// the current hashed file. fn logical_asset(logical: &str, content_type: &'static str) -> Response { - let server = asset_server(); - let Some(manifest) = server.manifest() else { + let Some(manifest) = assets::EmbeddedAssets::manifest() else { return AppError::AssetMissing(logical.to_string()).into_response(); }; let Some(target) = manifest.resolve(logical) else { return AppError::AssetMissing(logical.to_string()).into_response(); }; - assets::ui_asset(server, target, content_type, CachePolicy::Revalidate) + assets::ui_asset(target, content_type, CachePolicy::Revalidate) } /// Serves the chat UI's own stylesheet. async fn ui_style_css() -> Response { assets::ui_asset( - asset_server(), "style.css", "text/css; charset=utf-8", CachePolicy::Revalidate, @@ -94,7 +76,6 @@ async fn ui_style_css() -> Response { /// Serves the AudioWorklet PCM capture processor. async fn ui_pcm_worklet() -> Response { assets::ui_asset( - asset_server(), "pcm-worklet.js", "text/javascript; charset=utf-8", CachePolicy::Revalidate, @@ -129,7 +110,6 @@ async fn ui_bundle(Path(name): Path) -> Response { return AppError::AssetMissing(format!("bundle/{name}")).into_response(); }; assets::ui_asset( - asset_server(), &format!("bundle/{name}"), content_type, CachePolicy::Immutable, @@ -148,7 +128,6 @@ async fn ui_chunk(Path(name): Path) -> Response { return AppError::AssetMissing(format!("chunks/{name}")).into_response(); }; assets::ui_asset( - asset_server(), &format!("chunks/{name}"), content_type, CachePolicy::Immutable, @@ -159,7 +138,6 @@ async fn ui_chunk(Path(name): Path) -> Response { /// the `src` of the title bar ``. async fn ui_program_icon() -> Response { assets::ui_asset( - asset_server(), "icons/promptforge-icon.png", "image/png", CachePolicy::Revalidate, @@ -170,17 +148,12 @@ async fn ui_program_icon() -> Response { /// entry for high-DPI displays. async fn ui_program_icon_2x() -> Response { assets::ui_asset( - asset_server(), "icons/promptforge-icon@2x.png", "image/png", CachePolicy::Revalidate, ) } -#[cfg(all(test, not(feature = "headless")))] +#[cfg(test)] #[path = "assets-tests.rs"] mod tests; - -#[cfg(all(test, feature = "headless"))] -#[path = "assets-headless-tests.rs"] -mod headless_tests; diff --git a/crates/workshop/server/src/serve-tests.rs b/crates/workshop/server/src/serve-tests.rs index abfccd28b..d1786f8b5 100644 --- a/crates/workshop/server/src/serve-tests.rs +++ b/crates/workshop/server/src/serve-tests.rs @@ -55,16 +55,10 @@ async fn the_server_boots_and_serves_the_ui_with_an_unreachable_gateway() { .await .expect("the health endpoint answers"); assert_eq!(health.status(), reqwest::StatusCode::OK); - // Under the headless feature the asset layer is a no-op by design, so - // the UI index answers 404; the boot-and-health behavior above is - // what this test proves in that configuration. - #[cfg(not(feature = "headless"))] - { - let index = reqwest::get(format!("{url}/")) - .await - .expect("the UI answers"); - assert_eq!(index.status(), reqwest::StatusCode::OK); - } + let index = reqwest::get(format!("{url}/")) + .await + .expect("the UI answers"); + assert_eq!(index.status(), reqwest::StatusCode::OK); server.shutdown().expect("graceful shutdown succeeds"); } diff --git a/crates/workshop/status/Cargo.toml b/crates/workshop/status/Cargo.toml index cab1ef932..870c081b9 100644 --- a/crates/workshop/status/Cargo.toml +++ b/crates/workshop/status/Cargo.toml @@ -11,7 +11,6 @@ tokio.workspace = true workshop-protocol.workspace = true workshop-registry.workspace = true workshop-support.workspace = true -workspace-hack.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread", "test-util"] } diff --git a/crates/workshop/support/Cargo.toml b/crates/workshop/support/Cargo.toml index 4dac12633..1b7d7d3ab 100644 --- a/crates/workshop/support/Cargo.toml +++ b/crates/workshop/support/Cargo.toml @@ -20,7 +20,6 @@ thiserror.workspace = true tokio.workspace = true toml.workspace = true tracing.workspace = true -workspace-hack.workspace = true [dev-dependencies] reqwest.workspace = true diff --git a/crates/workshop/user-state/Cargo.toml b/crates/workshop/user-state/Cargo.toml index 7ea67bb2e..edd23e216 100644 --- a/crates/workshop/user-state/Cargo.toml +++ b/crates/workshop/user-state/Cargo.toml @@ -19,7 +19,6 @@ tracing.workspace = true workshop-protocol.workspace = true workshop-registry.workspace = true workshop-support.workspace = true -workspace-hack.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/workshop/workspace/Cargo.toml b/crates/workshop/workspace/Cargo.toml index dbfefd9eb..9822bf3e0 100644 --- a/crates/workshop/workspace/Cargo.toml +++ b/crates/workshop/workspace/Cargo.toml @@ -28,7 +28,6 @@ turso.workspace = true workshop-protocol.workspace = true workshop-registry.workspace = true workshop-support.workspace = true -workspace-hack.workspace = true [dev-dependencies] # The save-timeout integration test drives `routes_with_deadline` and the diff --git a/crates/workspace-hack/Cargo.toml b/crates/workspace-hack/Cargo.toml deleted file mode 100644 index f9bf0b46f..000000000 --- a/crates/workspace-hack/Cargo.toml +++ /dev/null @@ -1,381 +0,0 @@ -[package] -name = "workspace-hack" -version = "0.1.0" -publish = false -edition.workspace = true -license.workspace = true -repository.workspace = true - -[lints] -workspace = true - -### BEGIN HAKARI SECTION -[dependencies] -bitflags = { version = "2", default-features = false, features = ["std"] } -crossbeam-epoch = { version = "0.9" } -crossbeam-utils = { version = "0.8" } -crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } -deranged = { version = "0.5", features = ["serde"] } -digest = { version = "0.11", features = ["alloc", "mac", "oid"] } -either = { version = "1", features = ["use_std"] } -erased-serde = { version = "0.4" } -fastrand = { version = "2" } -form_urlencoded = { version = "1" } -futures-channel = { version = "0.3", features = ["sink"] } -futures-core = { version = "0.3" } -futures-sink = { version = "0.3" } -futures-task = { version = "0.3", default-features = false, features = ["std"] } -futures-util = { version = "0.3", features = ["channel", "io", "sink"] } -icu_locale_core = { version = "2", default-features = false, features = ["alloc", "zerovec"] } -icu_normalizer = { version = "2", default-features = false, features = ["compiled_data", "utf16_iter", "utf8_iter"] } -icu_properties = { version = "2", default-features = false, features = ["compiled_data"] } -icu_provider = { version = "2", default-features = false, features = ["alloc", "baked"] } -indexmap = { version = "2", features = ["serde"] } -itertools = { version = "0.10" } -libc = { version = "0.2" } -litemap = { version = "0.8", default-features = false, features = ["alloc"] } -lock_api = { version = "0.4", features = ["arc_lock"] } -log = { version = "0.4", default-features = false, features = ["std"] } -markup5ever = { version = "0.38", default-features = false, features = ["serde"] } -memchr = { version = "2" } -miniz_oxide = { version = "0.8", features = ["simd"] } -num-traits = { version = "0.2", features = ["i128"] } -once_cell = { version = "1" } -open = { version = "5", default-features = false, features = ["shellexecute-on-windows"] } -parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] } -percent-encoding = { version = "2" } -potential_utf = { version = "0.1", default-features = false, features = ["alloc", "zerovec"] } -proc-macro2 = { version = "1", features = ["span-locations"] } -quote = { version = "1" } -regex = { version = "1" } -regex-automata = { version = "0.4", default-features = false, features = ["dfa-build", "dfa-onepass", "hybrid", "meta", "nfa-backtrack", "perf-inline", "perf-literal", "std", "unicode"] } -regex-syntax = { version = "0.8" } -reqwest = { version = "0.13", default-features = false, features = ["blocking", "brotli", "gzip", "json", "query", "rustls", "stream", "system-proxy"] } -schemars = { version = "0.8", features = ["preserve_order", "url", "uuid1"] } -semver = { version = "1", features = ["serde"] } -serde = { version = "1", features = ["alloc", "derive", "rc"] } -serde_core = { version = "1", features = ["alloc", "rc"] } -serde_json = { version = "1", features = ["alloc", "float_roundtrip", "raw_value", "unbounded_depth"] } -simd-adler32 = { version = "0.3" } -smallvec = { version = "1", default-features = false, features = ["const_new", "union"] } -stable_deref_trait = { version = "1" } -string_cache = { version = "0.9", default-features = false, features = ["serde_support"] } -subtle = { version = "2" } -syn-7b89eefb6aaa9bf3 = { package = "syn", version = "3", features = ["extra-traits", "full", "visit-mut"] } -sync_wrapper = { version = "1", default-features = false, features = ["futures"] } -tauri-utils = { version = "2", default-features = false, features = ["build-2", "compression", "resources"] } -time = { version = "0.3", features = ["macros", "serde-human-readable"] } -tinystr = { version = "0.8", default-features = false, features = ["alloc", "zerovec"] } -tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "test-util"] } -tokio-util = { version = "0.7" } -tracing = { version = "0.1", features = ["log"] } -tracing-core = { version = "0.1" } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -typenum = { version = "1", default-features = false, features = ["const-generics"] } -url = { version = "2", features = ["serde"] } -uuid = { version = "1", features = ["serde", "v4", "v5", "v7"] } -web_atoms = { version = "0.2", default-features = false, features = ["serde"] } -writeable = { version = "0.6", default-features = false, features = ["alloc"] } -yoke = { version = "0.8", default-features = false, features = ["alloc", "derive"] } -zerocopy = { version = "0.8", default-features = false, features = ["derive", "simd"] } -zerofrom = { version = "0.1", default-features = false, features = ["alloc", "derive"] } -zerotrie = { version = "0.2", default-features = false, features = ["alloc", "yoke", "zerofrom"] } -zerovec = { version = "0.11", default-features = false, features = ["alloc", "derive", "yoke"] } - -[build-dependencies] -bitflags = { version = "2", default-features = false, features = ["std"] } -crossbeam-epoch = { version = "0.9" } -crossbeam-utils = { version = "0.8" } -crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } -deranged = { version = "0.5", features = ["serde"] } -digest = { version = "0.11", features = ["alloc", "mac", "oid"] } -either = { version = "1", features = ["use_std"] } -erased-serde = { version = "0.4" } -fastrand = { version = "2" } -form_urlencoded = { version = "1" } -futures-channel = { version = "0.3", features = ["sink"] } -futures-core = { version = "0.3" } -futures-sink = { version = "0.3" } -futures-task = { version = "0.3", default-features = false, features = ["std"] } -futures-util = { version = "0.3", features = ["channel", "io", "sink"] } -icu_locale_core = { version = "2", default-features = false, features = ["alloc", "zerovec"] } -icu_normalizer = { version = "2", default-features = false, features = ["compiled_data", "utf16_iter", "utf8_iter"] } -icu_properties = { version = "2", default-features = false, features = ["compiled_data"] } -icu_provider = { version = "2", default-features = false, features = ["alloc", "baked"] } -indexmap = { version = "2", features = ["serde"] } -itertools = { version = "0.10" } -libc = { version = "0.2" } -litemap = { version = "0.8", default-features = false, features = ["alloc"] } -lock_api = { version = "0.4", features = ["arc_lock"] } -log = { version = "0.4", default-features = false, features = ["std"] } -markup5ever = { version = "0.38", default-features = false, features = ["serde"] } -memchr = { version = "2" } -miniz_oxide = { version = "0.8", features = ["simd"] } -num-traits = { version = "0.2", features = ["i128"] } -once_cell = { version = "1" } -open = { version = "5", default-features = false, features = ["shellexecute-on-windows"] } -parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] } -percent-encoding = { version = "2" } -potential_utf = { version = "0.1", default-features = false, features = ["alloc", "zerovec"] } -proc-macro2 = { version = "1", features = ["span-locations"] } -quote = { version = "1" } -regex = { version = "1" } -regex-automata = { version = "0.4", default-features = false, features = ["dfa-build", "dfa-onepass", "hybrid", "meta", "nfa-backtrack", "perf-inline", "perf-literal", "std", "unicode"] } -regex-syntax = { version = "0.8" } -reqwest = { version = "0.13", default-features = false, features = ["blocking", "brotli", "gzip", "json", "query", "rustls", "stream", "system-proxy"] } -schemars = { version = "0.8", features = ["preserve_order", "url", "uuid1"] } -semver = { version = "1", features = ["serde"] } -serde = { version = "1", features = ["alloc", "derive", "rc"] } -serde_core = { version = "1", features = ["alloc", "rc"] } -serde_json = { version = "1", features = ["alloc", "float_roundtrip", "raw_value", "unbounded_depth"] } -simd-adler32 = { version = "0.3" } -smallvec = { version = "1", default-features = false, features = ["const_new", "union"] } -stable_deref_trait = { version = "1" } -string_cache = { version = "0.9", default-features = false, features = ["serde_support"] } -subtle = { version = "2" } -syn-7b89eefb6aaa9bf3 = { package = "syn", version = "3", features = ["extra-traits", "full", "visit-mut"] } -syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } -sync_wrapper = { version = "1", default-features = false, features = ["futures"] } -tauri-utils = { version = "2", default-features = false, features = ["build-2", "compression", "resources"] } -time = { version = "0.3", features = ["macros", "serde-human-readable"] } -tinystr = { version = "0.8", default-features = false, features = ["alloc", "zerovec"] } -tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "signal", "test-util"] } -tokio-util = { version = "0.7" } -tracing = { version = "0.1", features = ["log"] } -tracing-core = { version = "0.1" } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -typenum = { version = "1", default-features = false, features = ["const-generics"] } -url = { version = "2", features = ["serde"] } -uuid = { version = "1", features = ["serde", "v4", "v5", "v7"] } -web_atoms = { version = "0.2", default-features = false, features = ["serde"] } -writeable = { version = "0.6", default-features = false, features = ["alloc"] } -yoke = { version = "0.8", default-features = false, features = ["alloc", "derive"] } -zerocopy = { version = "0.8", default-features = false, features = ["derive", "simd"] } -zerofrom = { version = "0.1", default-features = false, features = ["alloc", "derive"] } -zerotrie = { version = "0.2", default-features = false, features = ["alloc", "yoke", "zerofrom"] } -zerovec = { version = "0.11", default-features = false, features = ["alloc", "derive", "yoke"] } - -[target.x86_64-pc-windows-msvc.dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -brotli = { version = "8" } -chrono = { version = "0.4", features = ["serde"] } -dpi = { version = "0.1", features = ["serde"] } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -muda = { version = "0.19", default-features = false, features = ["common-controls-v6", "gtk", "libxdo", "serde"] } -slab = { version = "0.4", default-features = false, features = ["std"] } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } -windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Diagnostics_Debug", "Win32_System_LibraryLoader", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_WindowsAndMessaging"] } -windows-sys-d4189bed749088b6 = { package = "windows-sys", version = "0.61", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Globalization", "Win32_Graphics_Gdi", "Win32_Networking_WinSock", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Accessibility", "Win32_UI_Controls", "Win32_UI_HiDpi", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } - -[target.x86_64-pc-windows-msvc.build-dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -brotli = { version = "8" } -cc = { version = "1", default-features = false, features = ["parallel"] } -chrono = { version = "0.4", features = ["serde"] } -dpi = { version = "0.1", features = ["serde"] } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -muda = { version = "0.19", default-features = false, features = ["common-controls-v6", "gtk", "libxdo", "serde"] } -slab = { version = "0.4", default-features = false, features = ["std"] } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } -windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Diagnostics_Debug", "Win32_System_LibraryLoader", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_WindowsAndMessaging"] } -windows-sys-d4189bed749088b6 = { package = "windows-sys", version = "0.61", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Globalization", "Win32_Graphics_Gdi", "Win32_Networking_WinSock", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_Accessibility", "Win32_UI_Controls", "Win32_UI_HiDpi", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } - -[target.x86_64-unknown-linux-gnu.dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -brotli = { version = "8" } -chrono = { version = "0.4", features = ["serde"] } -futures-executor = { version = "0.3" } -futures-io = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -linux-raw-sys = { version = "0.12", default-features = false, features = ["auxvec", "elf", "errno", "general", "if_ether", "io_uring", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4" } -syn-7b89eefb6aaa9bf3 = { package = "syn", version = "3", default-features = false, features = ["fold"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -toml_datetime = { version = "1", features = ["serde"] } -toml_parser = { version = "1" } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } -winnow = { version = "1" } -zbus = { version = "5" } - -[target.x86_64-unknown-linux-gnu.build-dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -brotli = { version = "8" } -cc = { version = "1", default-features = false, features = ["parallel"] } -chrono = { version = "0.4", features = ["serde"] } -futures-executor = { version = "0.3" } -futures-io = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -linux-raw-sys = { version = "0.12", default-features = false, features = ["auxvec", "elf", "errno", "general", "if_ether", "io_uring", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4" } -syn-7b89eefb6aaa9bf3 = { package = "syn", version = "3", default-features = false, features = ["fold"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -toml_datetime = { version = "1", features = ["serde"] } -toml_parser = { version = "1" } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } -winnow = { version = "1" } -zbus = { version = "5" } -zbus_macros = { version = "5", features = ["blocking-api"] } - -[target.aarch64-unknown-linux-gnu.dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -brotli = { version = "8" } -chrono = { version = "0.4", features = ["serde"] } -futures-executor = { version = "0.3" } -futures-io = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -linux-raw-sys = { version = "0.12", default-features = false, features = ["auxvec", "elf", "errno", "general", "if_ether", "io_uring", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4" } -syn-7b89eefb6aaa9bf3 = { package = "syn", version = "3", default-features = false, features = ["fold"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -toml_datetime = { version = "1", features = ["serde"] } -toml_parser = { version = "1" } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } -winnow = { version = "1" } -zbus = { version = "5" } - -[target.aarch64-unknown-linux-gnu.build-dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -brotli = { version = "8" } -cc = { version = "1", default-features = false, features = ["parallel"] } -chrono = { version = "0.4", features = ["serde"] } -futures-executor = { version = "0.3" } -futures-io = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -linux-raw-sys = { version = "0.12", default-features = false, features = ["auxvec", "elf", "errno", "general", "if_ether", "io_uring", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4" } -syn-7b89eefb6aaa9bf3 = { package = "syn", version = "3", default-features = false, features = ["fold"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -toml_datetime = { version = "1", features = ["serde"] } -toml_parser = { version = "1" } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } -winnow = { version = "1" } -zbus = { version = "5" } -zbus_macros = { version = "5", features = ["blocking-api"] } - -[target.x86_64-apple-darwin.dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -block2 = { version = "0.6" } -brotli = { version = "8" } -chrono = { version = "0.4", features = ["serde"] } -dpi = { version = "0.1", features = ["serde"] } -errno = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -muda = { version = "0.19", default-features = false, features = ["common-controls-v6", "gtk", "libxdo", "serde"] } -objc2 = { version = "0.6", features = ["disable-encoding-assertions", "exception"] } -objc2-app-kit = { version = "0.3", default-features = false, features = ["NSAccessibilityProtocols", "NSAlert", "NSAnimation", "NSAppearance", "NSApplication", "NSAttributedString", "NSButton", "NSCell", "NSColor", "NSColorPanel", "NSControl", "NSController", "NSDragging", "NSEvent", "NSGraphics", "NSImage", "NSInputManager", "NSKeyValueBinding", "NSLayoutManager", "NSMenu", "NSMenuItem", "NSOpenGLView", "NSOpenPanel", "NSPanel", "NSPasteboard", "NSPopover", "NSPrintInfo", "NSPrintOperation", "NSResponder", "NSRunningApplication", "NSSavePanel", "NSScreen", "NSSpellProtocol", "NSStatusBar", "NSStatusBarButton", "NSStatusItem", "NSText", "NSTextContainer", "NSTextContent", "NSTextFinder", "NSTextInputClient", "NSTextView", "NSTrackingArea", "NSUserActivity", "NSUserInterfaceItemIdentification", "NSUserInterfaceValidation", "NSView", "NSVisualEffectView", "NSWindow", "NSWorkspace", "block2", "objc2-core-foundation", "std"] } -objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFAttributedString", "CFBase", "CFCGTypes", "CFCalendar", "CFCharacterSet", "CFData", "CFDate", "CFDictionary", "CFError", "CFFileSecurity", "CFLocale", "CFMachPort", "CFMessagePort", "CFNumber", "CFPlugInCOM", "CFRunLoop", "CFSet", "CFStream", "CFString", "CFURL", "CFUUID", "CFUserNotification", "objc2", "std"] } -objc2-foundation = { version = "0.3" } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4", default-features = false, features = ["std"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } - -[target.x86_64-apple-darwin.build-dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -block2 = { version = "0.6" } -brotli = { version = "8" } -cc = { version = "1", default-features = false, features = ["parallel"] } -chrono = { version = "0.4", features = ["serde"] } -dpi = { version = "0.1", features = ["serde"] } -errno = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -muda = { version = "0.19", default-features = false, features = ["common-controls-v6", "gtk", "libxdo", "serde"] } -objc2 = { version = "0.6", features = ["disable-encoding-assertions", "exception"] } -objc2-app-kit = { version = "0.3", default-features = false, features = ["NSAccessibilityProtocols", "NSAlert", "NSAnimation", "NSAppearance", "NSApplication", "NSAttributedString", "NSButton", "NSCell", "NSColor", "NSColorPanel", "NSControl", "NSController", "NSDragging", "NSEvent", "NSGraphics", "NSImage", "NSInputManager", "NSKeyValueBinding", "NSLayoutManager", "NSMenu", "NSMenuItem", "NSOpenGLView", "NSOpenPanel", "NSPanel", "NSPasteboard", "NSPopover", "NSPrintInfo", "NSPrintOperation", "NSResponder", "NSRunningApplication", "NSSavePanel", "NSScreen", "NSSpellProtocol", "NSStatusBar", "NSStatusBarButton", "NSStatusItem", "NSText", "NSTextContainer", "NSTextContent", "NSTextFinder", "NSTextInputClient", "NSTextView", "NSTrackingArea", "NSUserActivity", "NSUserInterfaceItemIdentification", "NSUserInterfaceValidation", "NSView", "NSVisualEffectView", "NSWindow", "NSWorkspace", "block2", "objc2-core-foundation", "std"] } -objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFAttributedString", "CFBase", "CFCGTypes", "CFCalendar", "CFCharacterSet", "CFData", "CFDate", "CFDictionary", "CFError", "CFFileSecurity", "CFLocale", "CFMachPort", "CFMessagePort", "CFNumber", "CFPlugInCOM", "CFRunLoop", "CFSet", "CFStream", "CFString", "CFURL", "CFUUID", "CFUserNotification", "objc2", "std"] } -objc2-foundation = { version = "0.3" } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4", default-features = false, features = ["std"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } - -[target.aarch64-apple-darwin.dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -block2 = { version = "0.6" } -brotli = { version = "8" } -chrono = { version = "0.4", features = ["serde"] } -dpi = { version = "0.1", features = ["serde"] } -errno = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -muda = { version = "0.19", default-features = false, features = ["common-controls-v6", "gtk", "libxdo", "serde"] } -objc2 = { version = "0.6", features = ["disable-encoding-assertions", "exception"] } -objc2-app-kit = { version = "0.3", default-features = false, features = ["NSAccessibilityProtocols", "NSAlert", "NSAnimation", "NSAppearance", "NSApplication", "NSAttributedString", "NSButton", "NSCell", "NSColor", "NSColorPanel", "NSControl", "NSController", "NSDragging", "NSEvent", "NSGraphics", "NSImage", "NSInputManager", "NSKeyValueBinding", "NSLayoutManager", "NSMenu", "NSMenuItem", "NSOpenGLView", "NSOpenPanel", "NSPanel", "NSPasteboard", "NSPopover", "NSPrintInfo", "NSPrintOperation", "NSResponder", "NSRunningApplication", "NSSavePanel", "NSScreen", "NSSpellProtocol", "NSStatusBar", "NSStatusBarButton", "NSStatusItem", "NSText", "NSTextContainer", "NSTextContent", "NSTextFinder", "NSTextInputClient", "NSTextView", "NSTrackingArea", "NSUserActivity", "NSUserInterfaceItemIdentification", "NSUserInterfaceValidation", "NSView", "NSVisualEffectView", "NSWindow", "NSWorkspace", "block2", "objc2-core-foundation", "std"] } -objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFAttributedString", "CFBase", "CFCGTypes", "CFCalendar", "CFCharacterSet", "CFData", "CFDate", "CFDictionary", "CFError", "CFFileSecurity", "CFLocale", "CFMachPort", "CFMessagePort", "CFNumber", "CFPlugInCOM", "CFRunLoop", "CFSet", "CFStream", "CFString", "CFURL", "CFUUID", "CFUserNotification", "objc2", "std"] } -objc2-foundation = { version = "0.3" } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4", default-features = false, features = ["std"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } - -[target.aarch64-apple-darwin.build-dependencies] -bitflags = { version = "2", default-features = false, features = ["serde"] } -block2 = { version = "0.6" } -brotli = { version = "8" } -cc = { version = "1", default-features = false, features = ["parallel"] } -chrono = { version = "0.4", features = ["serde"] } -dpi = { version = "0.1", features = ["serde"] } -errno = { version = "0.3" } -getrandom = { version = "0.4", default-features = false, features = ["std", "sys_rng"] } -hyper = { version = "1", features = ["client", "http1", "server"] } -hyper-util = { version = "0.1", features = ["client-legacy", "client-proxy", "client-proxy-system", "http1", "server", "service"] } -libc = { version = "0.2", default-features = false, features = ["extra_traits"] } -muda = { version = "0.19", default-features = false, features = ["common-controls-v6", "gtk", "libxdo", "serde"] } -objc2 = { version = "0.6", features = ["disable-encoding-assertions", "exception"] } -objc2-app-kit = { version = "0.3", default-features = false, features = ["NSAccessibilityProtocols", "NSAlert", "NSAnimation", "NSAppearance", "NSApplication", "NSAttributedString", "NSButton", "NSCell", "NSColor", "NSColorPanel", "NSControl", "NSController", "NSDragging", "NSEvent", "NSGraphics", "NSImage", "NSInputManager", "NSKeyValueBinding", "NSLayoutManager", "NSMenu", "NSMenuItem", "NSOpenGLView", "NSOpenPanel", "NSPanel", "NSPasteboard", "NSPopover", "NSPrintInfo", "NSPrintOperation", "NSResponder", "NSRunningApplication", "NSSavePanel", "NSScreen", "NSSpellProtocol", "NSStatusBar", "NSStatusBarButton", "NSStatusItem", "NSText", "NSTextContainer", "NSTextContent", "NSTextFinder", "NSTextInputClient", "NSTextView", "NSTrackingArea", "NSUserActivity", "NSUserInterfaceItemIdentification", "NSUserInterfaceValidation", "NSView", "NSVisualEffectView", "NSWindow", "NSWorkspace", "block2", "objc2-core-foundation", "std"] } -objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFAttributedString", "CFBase", "CFCGTypes", "CFCalendar", "CFCharacterSet", "CFData", "CFDate", "CFDictionary", "CFError", "CFFileSecurity", "CFLocale", "CFMachPort", "CFMessagePort", "CFNumber", "CFPlugInCOM", "CFRunLoop", "CFSet", "CFStream", "CFString", "CFURL", "CFUUID", "CFUserNotification", "objc2", "std"] } -objc2-foundation = { version = "0.3" } -rustix = { version = "1", features = ["io_uring", "pipe", "process", "time"] } -slab = { version = "0.4", default-features = false, features = ["std"] } -tar = { version = "0.4" } -tokio-util = { version = "0.7", default-features = false, features = ["io"] } -tower = { version = "0.5", default-features = false, features = ["log", "make", "retry", "timeout"] } -tower-http = { version = "0.6", default-features = false, features = ["decompression-br", "decompression-gzip", "follow-redirect"] } - -### END HAKARI SECTION diff --git a/crates/workspace-hack/src/lib.rs b/crates/workspace-hack/src/lib.rs deleted file mode 100644 index 7860bddb6..000000000 --- a/crates/workspace-hack/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -//! Workspace-hack crate managed by `cargo hakari`. See `.config/hakari.toml`. diff --git a/vibe/2026-10-13-3-remove-hakari.md b/vibe/2026-10-13-3-remove-hakari.md new file mode 100644 index 000000000..022d30c0b --- /dev/null +++ b/vibe/2026-10-13-3-remove-hakari.md @@ -0,0 +1,635 @@ +--- +name: Remove hakari +overview: Remove the workspace-hack crate and cargo-hakari from promptforge, delete workshop-server's `headless` feature, and fold the five Windows Workshop test runs into one nextest command, while keeping what the shipped gateway and Workshop do at runtime unchanged. +todos: + - id: baseline + content: Capture baseline cargo tree feature lists for gateway and workshop on the five targets (scratch) + status: completed + - id: headless + content: Delete workshop-server's headless feature, NoopAssets, the one-impl AssetServer trait, and the headless tests + status: completed + - id: hakari-out + content: Remove workspace-hack crate, hakari.toml, 49 manifest lines, root workspace dep, CI hakari steps, hakari comments; refresh Cargo.lock + status: completed + - id: xtask-docs + content: Drop workspace-hack allowances from build-xtask product and tidy-wiring rules and tests; update Invariants docs and guard comments + status: completed + - id: sweep + content: Per-package cargo check sweep (default and all-targets/all-features); add missing features crate by crate; commit with hakari removal + status: completed + - id: feature-diff + content: Diff shipped closures against baseline; restore runtime features (reqwest system-proxy); confirm ring and Engine tokio checks + status: completed + - id: ci-merge + content: Replace the four Windows test steps with one nextest command; delete the Linux duplicate race-test step + status: completed + - id: verify + content: Run the Testing Plan exit criteria and the merged Windows command locally; CI green; dispatch release-workshop.yml on the branch + status: completed +isProject: false +--- + +# Remove hakari and cut the Workshop test runs to one + + + +## Product Requirements + +The promptforge repository uses cargo-hakari's `workspace-hack` crate to give every workspace crate one shared third-party feature set. That compiles test-only features into the shipped gateway and Workshop binaries, puts the async and HTTP stack under the sans-I/O Engine crates, and carries Workshop-only features into the gateway. This change removes hakari entirely, deletes the `headless` feature of `workshop-server`, and folds the Windows Workshop CI job's five test commands into one. Shipped runtime behavior stays the same, apart from the intended removals. + +- Problem and users: + - Users: the repository owner, the agents that build, test, and change promptforge, and CI. All paths below are relative to the promptforge repository root. + - `crates/workspace-hack/Cargo.toml:69` and `:142` list tokio with `test-util` as a normal and a build dependency. Every direct request for `test-util` is a dev-dependency: `crates/promptforge-internal/engine/Cargo.toml:44`, `crates/gateway/app/Cargo.toml:158`, `crates/workshop/server/Cargo.toml:83`, `crates/workshop/support/Cargo.toml:28`, `crates/workshop/status/Cargo.toml:17`, and `crates/gateway/stt/engine/Cargo.toml:16`. Release builds compile it anyway. + - Six of the seven Engine crates depend on `workspace-hack`: `crates/promptforge/Cargo.toml:18`, plus the `workspace-hack.workspace = true` lines in `crates/promptforge-internal/{engine,lua,model-client,parser,types}/Cargo.toml`. The seventh, `crates/promptforge-internal/vfs`, declares no dependencies at all. So `cargo build -p promptforge-types` compiles tokio, reqwest, hyper, and Workshop GUI crates. + - The hack has already forced one workaround. `tauri-plugin-updater`'s default `rustls-tls` feature would have put `ring` into every member, the gateway included (`Cargo.toml:242-251`). + - The Windows `check-workshop` job runs five test commands on different package sets (`.github/workflows/ci.yml:182-210`). The Linux `check-workshop-linux` job repeats five race tests (`.github/workflows/ci.yml:265-272`). +- Goals: + - No `workspace-hack` crate, no `.config/hakari.toml`, and no `cargo-hakari` in any manifest, workflow, or tool source. + - Every workspace crate compiles on its own with only the features it declares. + - The Windows Workshop job runs its tests as one command. + - The Linux Workshop job runs no test that the main Linux test job already runs. + - The shipped gateway and Workshop behave at runtime as they do today. The exceptions are losing tokio `test-util` and losing crates the binary never used. +- Non-goals: + - Merging commands in the `docs`, `clippy`, or `test` jobs. + - Moving `workshop-server` and `workshop-server-api` into the workspace-wide `--all-features` runs. + - Adding a standing CI check that compiles each crate alone. +- Success criteria: + - `rg -n --hidden "hakari|workspace-hack|workspace_hack" --glob '!vibe/**' --glob '!.git/**'` from the repository root returns nothing, `Cargo.lock` included. Without `--hidden`, rg skips `.github/` and `.config/`. + - The normal closure of `promptforge-types` (`cargo tree --locked -p promptforge-types -e normal --prefix none`) has no `tokio` line. `cargo tree -i tokio` can't express this: it exits with an error when tokio isn't in the crate's graph at all. + - In the gateway's `cargo tree --locked -p gateway -e normal -f "{p} {f}" --prefix none` output, the tokio line has no `test-util`. + - The Windows `check-workshop` job has exactly one test step, and that step still runs the five race tests on Windows. + - `ci.yml` is green on the branch. A dispatched `release-workshop.yml` run builds all five targets. +- Constraints: + - CI runs cargo with `--locked`, so `Cargo.lock` changes are committed with the change. + - The Workshop desktop crate compiles only after the featureless gateway is built and staged as Tauri's `externalBin` sidecar (`.github/workflows/ci.yml:168-172`; `vibe/2026-10-11-5-easy-fixes.md:226`). + - The gateway links exactly one rustls crypto backend, aws-lc-rs. The CI guard at `.github/workflows/ci.yml:353-371` stays. + - Shipping targets: `x86_64-pc-windows-msvc`, `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, `x86_64-apple-darwin`, and `aarch64-apple-darwin` (`.config/hakari.toml:15-21`). That file is removed, so this list is the record. +- Open questions: None + +## Functional Specification + +The change shows up in the build and CI behavior: which features each build compiles, which CI steps run, and which tests run on which platform. `workshop-server` loses its `headless` build shape, so it always bundles the UI and needs Node.js to build. The shipped binaries keep their runtime behavior. Hakari was quietly giving the gateway one runtime feature, OS proxy detection, and that feature is now declared explicitly. + +- Actors and workflows: + - A developer or agent running `cargo build`, `check`, or `test` with `-p ` gets that crate's declared features, unified only with the other packages selected in the same command. + - CI `check-workshop` (Windows), in order: + - Build the featureless gateway. + - Stage the sidecar. + - Clippy over `workshop`, `workshop-server`, and `workshop-server-api`. + - The one merged test command. + - The `workshop-server` private-items docs. + - Remove the sidecar. + - Clean-tree check. + - CI `check-workshop-linux`: build the featureless gateway, stage the sidecar, build `workshop`, remove the sidecar, then the clean-tree check. + - CI `supply-chain`: `cargo deny`, `cargo audit`, and the ring guard. +- Inputs and outputs: + - The merged Windows test command selects six packages: `workshop`, `workshop-server`, `workshop-server-api`, `workshop-workspace`, `gateway-api-discovery`, and `gateway`. + - It runs every test of the four Workshop crates plus the five named race tests. Its exact text is in Technical Design. +- States and validation: + - `workshop-server` has no `headless` feature. Its build script always bundles the UI, and its asset routes always serve the embedded bundle. +- Errors and recovery: + - A crate that relied on a feature hakari supplied fails to compile alone. Fix it by declaring the feature on that crate's own dependency line, or on its dev-dependency line when only tests need it. + - The merged Windows command may fail to compile the gateway's test targets under `--no-default-features --features gateway/test-fixtures`. If a few `cfg` gates fix it, add them. Otherwise move the gateway into a second command (`-p gateway --no-default-features --features test-fixtures --test it` with the gateway half of the filter) and report two commands. +- Security and privacy behavior: + - The gateway keeps aws-lc-rs as its only rustls backend. + - The Workshop stays free of `ring` while `tauri-plugin-updater` keeps `default-features = false` (`Cargo.toml:242-251`). + - The gateway keeps honoring the Windows registry and macOS system proxy settings, because reqwest's `system-proxy` feature becomes explicit. +- Acceptance criteria: + - The Windows job runs the same tests as today: all tests of `workshop`, `workshop-server`, `workshop-server-api`, and `workshop-workspace` (the Windows path-jail tests included), plus the five race tests. + - The headless-only tests in `crates/workshop/server/src/routes/assets-headless-tests.rs` are deleted along with the feature. + - The per-crate check sweep passes for every workspace member. + - The shipped-feature diff has been reviewed. Every remaining loss is either expected or recorded as accepted in the commit message. + + + + +## Technical Design + +Feature unification goes back to cargo's default: within one command, across the packages that command selects. The `workspace-hack` crate is removed, along with its config, every dependency edge to it, and the build-xtask rules and docs that name it. The Windows CI job's test runs become one nextest command whose filter reproduces today's coverage. The workspace reqwest pin gains `system-proxy`, so the gateway keeps its proxy behavior. + +- Architecture: + - Hakari folds dev-dependency features into the normal dependencies of every member. The evidence is tokio `test-util`, which is listed as normal in the hack and only ever requested by dev-dependencies (Product Requirements). Without hakari, a feature reaches a crate only through its own manifest or through the other packages in the same cargo command. + - Dependency features still unify across all packages selected in one command. This is the cargo behavior that lets the merged Windows command share one build (Cargo reference, features page, "Feature unification"). +- Modules and interfaces: + - build-xtask's product-boundary matrix (`crates/build-xtask/src/product.rs:15-23`, `:159-161`, `:231-236`, `:276-284`) changes as follows: + - Outside their family, Harness crates may name only `promptforge` and `promptforge-plugin`. Any unaffiliated crate is a breach. + - Plugin crates may name only `promptforge-plugin`, `shared-*` crates, and outside libraries. + - build-xtask's ceiling-wiring check loses its special case for a crate without a build script. The affected code is the `WORKSPACE_HACK` and `LINE_LIMIT` constants, the doc line, the branch, and `workspace_hack_violations` (`crates/build-xtask/src/tidy-wiring.rs:13-17`, `:25`, `:40-47`, `:193-213`). The `else` body of the branch stays. + - workshop-server's asset layer becomes `EmbeddedAssets` alone. The `AssetServer` trait only existed to choose between `EmbeddedAssets` and `NoopAssets`, so it collapses into `EmbeddedAssets`, and `ui_asset` loses its server argument (`crates/workshop/server/src/assets.rs:66-141`, `crates/workshop/server/src/routes/assets.rs:11-24`). `NoopAssets` and its test `the_noop_asset_layer_serves_nothing` are deleted. +- File and public API changes: + - Removed from the repository: + - `crates/workspace-hack/` + - `.config/hakari.toml` + - `crates/workshop/server/src/routes/assets-headless-tests.rs` + - Root `Cargo.toml`: + - Remove the `workspace-hack` workspace dependency and its comment (`:75-76`). + - Add `system-proxy` to the reqwest pin (`:89`) with a one-line reason. + - Reword the `tauri-plugin-updater` comment (`:242-251`) to say its `rustls-tls` default would put `ring` into the Workshop binary. + - Member manifests: delete the line `workspace-hack.workspace = true` from every member `Cargo.toml` under `crates/` that has it. There are 49, each with no adjoining comment; `rg -l '^workspace-hack\.workspace = true' crates` lists them. + - Comments that name hakari or the hack: + - `crates/build-ceiling/Cargo.toml:9-12` + - `crates/promptforge-internal/vfs/Cargo.toml:9-12` + - `crates/build-xtask/src/engine_deps.rs:22-23` + - `crates/build-xtask/src/test_support_leak.rs:34-35` + - `crates/build-xtask/src/harness_bans.rs:11-12` + - `## Invariants` docs that list `workspace-hack` as an allowed dependency: + - `crates/promptforge-plugin/src/lib.rs:22-23` + - `crates/plugin-mcp/src/lib.rs:57-58` + - `crates/plugin-user-input/src/lib.rs:68-69` + - `crates/plugin-web/src/lib.rs:48-49` + - `crates/promptforge-internal/types/src/lib.rs:31` + - build-xtask tests: + - `crates/build-xtask/src/product/harness_tests.rs`: delete `a_harness_crate_depending_on_workspace_hack_passes` (`:53-67`) and update the expected messages at `:101` and `:122-124`. + - `crates/build-xtask/src/product/plugin_tests.rs`: update the fixture at `:19`, `PLUGIN_RULE` at `:29`, and the passing test at `:32-39`. + - `crates/build-xtask/src/tidy-wiring.rs`: update the fixture at `:248` and `:265-266`, and delete the test at `:337-346`. + - workshop-server: + - `Cargo.toml:65-68`: the `headless` feature and its comment. + - `build.rs`: the `CARGO_FEATURE_HEADLESS` branch (`:17-19`), `empty_asset_dir` (`:36-51`), and the module-doc sentences about headless (`:7-10`). + - `src/assets.rs`. + - `src/routes/assets.rs`: the test-module gates at `:180-186` become plain `#[cfg(test)]`. + - `src/serve-tests.rs:58-66`: the UI index assertion becomes unconditional. + - `.github/workflows/ci.yml`: + - `supply-chain`: drop `cargo-hakari` from the install list (`:342`), delete the `cargo hakari verify` step (`:350-351`), and drop "or a hakari regeneration" from the ring-guard comment (`:355-356`). + - `check-workshop`: replace the steps at `:182-196` and `:203-210` with the merged step below. + - `check-workshop-linux`: delete the race-test step (`:265-272`). + - `Cargo.lock`: only the `workspace-hack` package and its dependency edges are removed. No versions change. + - Crate manifests that relied on hakari gain the explicit features the per-crate sweep finds. + - No public Rust API changes. The facade listing `crates/promptforge/public-api.txt` is unaffected. +- Merged Windows test step, replacing the four steps named above: + + ```yaml + # One build runs every Windows-only test: the Workshop crates' suites + # (the workspace jail's Windows paths included) and the gateway's + # process-ownership races, the only tests the filter selects from the + # gateway and gateway-api-discovery suites. + - name: Test (Windows) + shell: bash + run: >- + cargo nextest run --locked --no-default-features --features gateway/test-fixtures + -p workshop -p workshop-server -p workshop-server-api -p workshop-workspace + -p gateway-api-discovery -p gateway + -E 'package(workshop) | package(workshop-server) | package(workshop-server-api) + | package(workshop-workspace) + | (package(gateway-api-discovery) & test(a_process_lifetime_lease_recovers_after_its_owner_is_terminated)) + | (package(gateway) & binary(it) & (test(simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff) + | test(workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot) + | test(a_direct_launch_recovers_the_lease_from_a_terminated_owner) + | test(a_silent_process_lease_owner_causes_a_bounded_console_only_failure)))' + ``` + + - **Feature flags.** `--no-default-features` turns off defaults for every selected package, and `gateway/test-fixtures` enables `test-fixtures` on the gateway alone (Cargo reference, features page, "Command-line feature options"). The four Workshop crates have empty defaults (`crates/workshop/desktop/Cargo.toml:59-60`, `crates/workshop/server/Cargo.toml:64`, `crates/workshop/server-api/Cargo.toml:9-10`, `crates/workshop/workspace/Cargo.toml:9-10`). `gateway-api-discovery` has no `default` at all (`crates/gateway-api-discovery/Cargo.toml:28-32`). + - **workshop-workspace.** Its only feature is `test-fixtures`, which its own dev-dependency on itself already enables (`crates/workshop/workspace/Cargo.toml:15`, `:36`). So the old `--all-features` run changed nothing. + - **Filter matching.** In nextest, `package()` and `binary()` default to glob matching, which needs the full name to match, and `test()` defaults to substring matching (nextest filterset reference, "Default matchers"). `package(workshop)` therefore matches only `workshop`. + - **No sidecar needed.** The race tests start the gateway through `CARGO_BIN_EXE_promptforge-gateway` (`crates/gateway/app/tests/it/boot/lease.rs:31`, `:62`; `crates/gateway/app/tests/it/support/process.rs:84`). The discovery test re-launches its own test executable (`crates/gateway-api-discovery/tests/it/main.rs:201`). + - **What gets built.** The gateway's only test targets are its library unit tests and `it`; it has no examples or benches (`crates/gateway/app/Cargo.toml`). Its feature-specific test modules are gated, for example `crates/gateway/app/src/boot.rs:391-392` and `crates/gateway/app/tests/it/main.rs:38-45`. `crates/gateway/app/src/main-logging-tests.rs` gates per item. +- Data, persistence, failure, security, and privacy constraints: + - **Proxy behavior.** + - reqwest's `system-proxy` feature enables hyper-util's `client-proxy-system` (reqwest 0.13.5 `Cargo.toml:76-77`). + - hyper-util's `Matcher::from_system()` always reads `ALL_PROXY`, `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`. The feature adds only the macOS system proxy configuration and the Windows registry proxy settings (hyper-util `src/client/proxy/matcher.rs:86-111`, `:228-249`). + - A reqwest client with no explicit proxy configuration installs that system matcher (reqwest `src/async_impl/client.rs:313-314`, `:424-427`; reqwest `src/proxy.rs:514-526`). + - **Gateway clients.** + - The gateway's cloud, artifact, HF, and STT-config clients set no proxy: `crates/gateway/protocol/src/http_util.rs:25-65`, `crates/gateway/local/src/artifacts.rs:272`, `crates/gateway/local/src/runtime/hf_sidecar.rs:37`, `crates/gateway/cloud-providers/src/main.rs:110`, and `crates/gateway/config/src/config/stt.rs:470`. + - Only loopback clients call `.no_proxy()`: `crates/gateway/local/src/server.rs:388-389`, `crates/gateway/local/src/dialect.rs:251-252`, and `crates/gateway/local/src/chat_templates/live.rs:246-247`. + - No workspace manifest asks reqwest for `system-proxy`. The pin at `Cargo.toml:89` lacks it, and the per-crate reqwest lines add only `blocking` (`crates/gateway/local/Cargo.toml:17`; dev-only in `crates/gateway/config/Cargo.toml:20`) and `gzip`/`brotli` (`crates/plugin-web/Cargo.toml:17`). The gateway gets it today only through the hack (`crates/workspace-hack/Cargo.toml:53`). + - **Test-only features.** Once hakari is removed, tokio `test-util` is compiled only into builds that include dev-dependencies. + + + + +## Testing Plan + +Beyond the usual gates, three checks show the change is correct. Every crate compiles alone. The shipped binaries' feature sets differ from today's only in expected or accepted ways. And the merged Windows command runs the same tests as the five commands it replaces. Code that only builds on Linux or macOS is covered by CI and a dispatched release build. + +- Unit: + - `cargo test -p build-xtask`, covering the updated product-boundary and ceiling-wiring tests. + - `cargo nextest run --locked -p workshop-server` after the `headless` removal. +- Integration and end-to-end: + - **Feature baseline and diff.** Before any manifest change, save as scratch the sorted, de-duplicated output of `cargo tree --locked -p -e normal --target -f "{p} {f}" --prefix none`, for `gateway` and `workshop` on each of the five shipping targets. After the change, rerun the same commands and diff. + - Expected: tokio loses `test-util`, and crates the binary never uses disappear. + - Restore: anything that changes runtime behavior. + - Accept: losses that only affect performance or API surface, such as reqwest `gzip` and `brotli` in the gateway. List them in the commit message. + - **Per-crate sweep.** With the sidecar staged, run `cargo check --locked -p ` and `cargo check --locked -p --all-targets --all-features` for each member `` named by `cargo metadata --no-deps --format-version 1`. + - **Merged Windows command.** Run it locally with the sidecar staged. `cargo nextest list` with the same arguments must show the five race tests and every test of the four Workshop crates. + - **Cross-platform build.** Dispatch `.github/workflows/release-workshop.yml` on the branch. A dispatched run builds the gateway and the Workshop for all five targets. It uploads only temporary Actions artifacts and never reaches the publish job, which is gated to pushes of `workshop-v*` tags (`.github/workflows/release-workshop.yml:16-20`, `:71-87`, `:146-192`, `:483-485`). +- Regression, security, and performance: + - `cargo tree --locked -p gateway -e normal -i ring` prints nothing, and so does the same command with `-p workshop`. + - The normal closure (`cargo tree --locked -p -e normal --prefix none`) has no `tokio` line for `promptforge-types`, `promptforge-model-client`, `promptforge-parser`, `promptforge-lua`, `promptforge-engine`, and `promptforge`. + - `cargo tree --locked -p gateway -e normal -i reqwest -f "{p} {f}"` lists `system-proxy`. +- Exit criteria: all of the following pass. + - `cargo fmt --all --check` + - `CARGO_BUILD_WARNINGS=deny cargo clippy --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-targets --all-features` + - `CARGO_BUILD_WARNINGS=deny cargo clippy -p workshop -p workshop-server -p workshop-server-api --all-targets`, with the sidecar staged + - `cargo check -p gateway --no-default-features` + - `cargo nextest run --locked --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-features` + - The merged Windows command, with the sidecar staged + - With `RUSTDOCFLAGS="-D warnings"`: + - `cargo doc --workspace --no-deps --all-features --exclude workshop --exclude workshop-server --exclude workshop-server-api` + - `cargo doc -p promptforge --no-deps` + - `cargo doc -p harness --no-deps` + - `cargo doc --locked --no-deps --all-features -p promptforge-engine --document-private-items` + - `cargo doc --locked --no-deps -p workshop-server --document-private-items` + - `cargo test -p build-xtask` + - `node --test crates/workshop/ui/test/docs-claims.mjs` + - `cargo deny check` + - The per-crate sweep, the regression checks above, and the reviewed feature diff + - `rg -n --hidden "hakari|workspace-hack|workspace_hack" --glob '!vibe/**' --glob '!.git/**'` returns nothing + - `ci.yml` green on the branch, and the dispatched `release-workshop.yml` run green + + + + +## Decision Record + +- Decisions: + - **Remove hakari entirely, not partially.** + - Rationale: it compiled `test-util` into release binaries, put tokio, reqwest, hyper, and GUI crates under the Engine crates, and carried Workshop features into the gateway. It also needed a regenerate step on most manifest changes, and its rebuild savings were never measured. The heaviest CI steps are single `--workspace` commands, where cargo already unifies features (`.github/workflows/ci.yml:56`, `:85`, `:127`). + - User's words: "I want all the workspace hack out, I want all the hakari out". + - **Delete workshop-server's `headless` feature.** + - Rationale: only its own CI step uses it (`.github/workflows/ci.yml:196`). It removes code through `#[cfg(not(feature = "headless"))]`, so it can never share a build with the default shape. CI always installs Node.js. + - User's choice: "Delete `headless` - one test command". + - **One test command in the Windows Workshop job.** + - User's words: "I want all these different builds cut down to just 2 or preferably 1 if that's possible". + - **Delete the Linux Workshop job's race-test step.** + - Rationale: those tests don't use the sidecar, and the main Linux test job already runs them with `--all-features` (`.github/workflows/ci.yml:85`). + - Follows from the user's request to cut the duplicate builds. + - **Add `system-proxy` to the workspace reqwest pin.** + - Rationale: the gateway keeps honoring Windows registry and macOS proxy settings, which today it gets only through the hack (Technical Design). + - This follows the goal of unchanged shipped behavior; the user didn't state it. + - **Fix hakari-masked features on each crate's own dependency line, not on the workspace pin.** + - Rationale: this matches the repository's per-crate feature pattern, for example `crates/gateway/local/Cargo.toml:17`. + - **Run the remote checks through a `remove-hakari` branch and a pull request into `master`.** + - Rationale: `ci.yml` runs only on pushes to `master` or `main` and on pull requests (`.github/workflows/ci.yml:3-6`), and the run's commits shouldn't reach `master` before CI and the release build pass. + - User's choice: "Push a `remove-hakari` branch and open a PR to run CI, then dispatch the release build on that branch". + - **Collapse `AssetServer` into `EmbeddedAssets`, and delete `NoopAssets` along with its test.** + - Rationale: with `headless` gone, the trait has one implementation and `NoopAssets` exists only to test itself. +- Rejected alternatives: + - **List the Engine crates under hakari's `[final-excludes]`.** + - Reason: it fixes the Engine closure but leaves `test-util` in the shipped binaries. + - Revisit: none, since hakari is removed. + - **Exclude tokio and its HTTP stack from hakari's output.** + - Reason: it breaks easily on regeneration, gives up most of what hakari shares, and keeps the regenerate chore. + - Revisit: none. + - **Keep `headless` as a second test command, or turn it into an additive `ui` feature.** + - Reason: either way it is a second build shape. The user chose one command. + - Revisit: if building `workshop-server` without Node.js becomes a real need. + - **Keep the gateway race tests as their own command.** + - Reason: the merged command covers them. + - Revisit: kept as the fallback in Functional Specification, Errors and recovery. + - **Run the whole gateway and gateway-api-discovery suites on Windows.** + - Reason: that grows coverage beyond today's. + - Revisit: if Windows-specific gateway failures appear. +- Assumptions, risks, and notes: + - **Risk: features hakari was quietly supplying.** Some per-crate builds break where a crate uses a feature it never declared; the sweep finds them. The local Windows sweep doesn't cover code that only builds on Linux or macOS. That code is covered by `ci.yml` and the dispatched release build. + - **Risk: gateway test compile under the merged command.** It compiles the gateway's library unit tests under `--no-default-features --features gateway/test-fixtures` on Windows, a shape CI has never built. Feature-specific test modules are gated, so failures should be few. The fallback is two commands. + - **Risk: shared dependency features.** One command with six packages unifies dependency features across all of them, so Workshop tests see the gateway's dependency features and the reverse. The hack already gave every crate the union, so no test sees fewer features than today. + - **Risk: parallel race tests.** The race tests now run alongside the Workshop tests in one nextest run. They already run alongside the whole suite in the Linux test job (`.github/workflows/ci.yml:85`). + - **Assumption: cross-target resolution.** `cargo tree --target ` resolves on Windows for targets whose standard library isn't installed. If it doesn't, compute the baseline on matching CI runners, or later from the commit before the change. + - **Note: accepted losses.** Feature losses accepted after the diff are listed in the commit message. + - **Note: history stays.** The `vibe/` records that mention hakari are history and stay unchanged. + - **Note: macOS coverage.** `ci.yml` has no macOS job. macOS builds run only in `.github/workflows/release-workshop.yml` and `.github/workflows/nightly.yml`. + - **Note: ring in the Workshop.** It stays out only while `tauri-plugin-updater` keeps `default-features = false`, so the pin and its comment stay. + +### Deferred and Out of Scope + +- **Deferred: put `workshop-server` and `workshop-server-api` into the workspace-wide `--all-features` clippy, test, and docs runs.** With `headless` gone, nothing blocks this. Revisit after this change lands, since it touches the documented verification commands, `.githooks/pre-push:12`, and three CI jobs. +- **Out of scope:** merging commands in the `docs`, `clippy`, and `test` jobs. +- **Out of scope:** a standing CI check that compiles each crate alone. + + + + +## Project Survey + +- Status: complete +- Build command: `cargo build --locked -p gateway` (the workspace `default-members` is `crates/gateway/app` only); the Workshop desktop app builds explicitly with `cargo build --locked -p workshop`. Crate build scripts bundle the UIs, so a fresh clone needs `npm ci --prefix crates/workshop` and `npm ci --prefix crates/gateway/config-ui/ui` first (both `node_modules` are present locally). +- Focused test command pattern: `cargo nextest run --locked -p --all-features ` for any crate outside the Workshop trio (drop `--all-features` for `workshop`, `workshop-server`, `workshop-server-api`); one integration target: `cargo test --locked -p --test `. +- Component test command pattern: `cargo nextest run --locked -p --all-features`; for the Workshop trio `cargo nextest run --locked -p workshop -p workshop-server -p workshop-server-api`; structural checks `cargo test -p build-xtask`. +- Full-suite test command: `cargo nextest run --locked --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-features`, then `cargo nextest run --locked -p workshop -p workshop-server -p workshop-server-api`, then `node --test crates/workshop/ui/test/docs-claims.mjs`. UI suites: `npm test --workspaces --if-present` in `crates/workshop` and `npm test` in `crates/gateway/config-ui/ui`. +- Linter command: `CARGO_BUILD_WARNINGS=deny cargo clippy --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-targets --all-features` and `CARGO_BUILD_WARNINGS=deny cargo clippy -p workshop -p workshop-server -p workshop-server-api --all-targets` (PowerShell: set `$env:CARGO_BUILD_WARNINGS = "deny"`), plus the headless gate `cargo check -p gateway --no-default-features`. Supply chain (CI `supply-chain` job): `cargo deny check`, `cargo audit`, `cargo hakari verify`, and the `cargo tree --locked -p gateway -e normal -i ring --depth 0` single-rustls-backend check. Locally installed: cargo-nextest 0.9.128, cargo-hakari 0.9.38, cargo-deny 0.20.2, Node 24. +- Formatter check command: `cargo fmt --all --check` (also the `.githooks/pre-commit` hook). +- Docs command: `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features --exclude workshop --exclude workshop-server --exclude workshop-server-api`, plus `RUSTDOCFLAGS="-D warnings" cargo doc -p promptforge --no-deps` (facade, default features). CI also runs `cargo doc -p harness --no-deps`, `cargo doc --locked --no-deps --all-features -p promptforge-engine --document-private-items`, and `cargo doc --locked --no-deps -p workshop-server --document-private-items`. Facade surface: `cargo +nightly-2026-09-05 xtask api --check` (nightly pinned in `crates/build-xtask/src/api/toolchain.rs`). +- Test placement and naming conventions: + - Unit tests sit in a kebab sibling `foo-tests.rs` wired from `foo.rs` with `#[cfg(test)]`, `#[path = "foo-tests.rs"]`, `mod tests;`. A module with three or more test files moves them into `src//tests/` (for example `promptforge-internal/engine/src/execute/tests/`). + - Integration tests use one target per crate, `tests/it/main.rs` (most crates) or `tests/suite/main.rs` (`harness`, `promptforge`), declaring one `mod` per topic file; shared helpers go in `tests/common/`, data in `tests/fixtures/`, prompt fixtures in `tests/prompts/`. A few crates keep flat target files instead (`gateway-stt-engine`, `gateway-stt-backend-whisper`, `gateway-cloud-providers`, `build-workshop`). + - Test functions are snake_case sentences stating the behavior, such as `a_policy_refuses_an_undeclared_path_before_the_watcher_sees_it`. + - `.config/nextest.toml` defines `default` and `ci` profiles and a `heavy` test group for the STT crates. + - JavaScript tests are `node --test` `.mjs` files: `crates/workshop/ui/test/`, `tools/*.test.mjs`. +- Directory map: + - `crates/promptforge` is the Engine facade; `crates/promptforge-internal/` (manifestless container) holds the private Engine crates `types`, `engine`, `lua`, `parser`, `vfs`, `model-client`; `crates/promptforge-plugin` is the Plugin API. + - `crates/harness`, `crates/harness-internal/runner`, and `crates/harness-gateway-client` are the Harness. + - `crates/plugin-web`, `crates/plugin-mcp`, `crates/plugin-user-input` are linked Plugins. + - `crates/gateway/` (container) holds the gateway family: `app` (the `gateway` crate), `config`, `config-ui`, `local`, `logging`, `progress`, `protocol`, `routing`, `cloud-providers`, `web-search`, and `stt/{api,engine,backend-whisper,whisper-ffi}`. `crates/gateway-api-types` and `crates/gateway-api-discovery` are the gateway's cross-family wire types and discovery. + - `crates/workshop/` (container) holds the Workshop Host: Rust crates `desktop` (the Tauri `workshop` crate), `server`, `server-api`, `gateway`, `menu`, `protocol`, `registry`, `status`, `support`, `user-state`, `workspace`, `run-log`, `agents`, and an npm workspace (`ui`, `look`, `platform`) rooted at `crates/workshop/package.json`. + - `crates/shared-error-source` and `crates/shared-loopback` are shared Rust crates; `crates/shared-ui` is a TypeScript and CSS package excluded from the Cargo workspace. + - `crates/build-*` are tooling: `build-xtask` (`cargo xtask` alias: structural checks, API surface, site), `build-workshop` (`cargo workshop` alias: build orchestrator), `build-ui`, `build-user-guide`, `build-ceiling`, `build-llama-cuda`. + - `crates/workspace-hack` is the cargo-hakari feature-unification crate (generated `### BEGIN/END HAKARI SECTION` in its `Cargo.toml`, one-line `src/lib.rs`), configured by `.config/hakari.toml`. Its footprint: root `Cargo.toml` (`workspace-hack` entry in `[workspace.dependencies]` with its comment, and the `tauri-plugin-updater` comment), 49 member manifests with `workspace-hack.workspace = true`, explanatory comments in the `build-ceiling` and `promptforge-vfs` manifests, `.github/workflows/ci.yml` (`supply-chain` job installs cargo-hakari and runs `cargo hakari verify`; the ring-check comment names hakari), `build-xtask` (`WORKSPACE_HACK` allowances in `product.rs` with `product/harness_tests.rs` and `product/plugin_tests.rs`, the `WORKSPACE_HACK` line-limit check and its tests in `tidy-wiring.rs`, doc comments in `engine_deps.rs`, `harness_bans.rs`, `test_support_leak.rs`), and crate docs in `promptforge-plugin`, `plugin-web`, and `promptforge-types` `lib.rs`. + - Root config: `Cargo.toml`, `Cargo.lock`, `clippy.toml`, `deny.toml`, `rustfmt.toml`, `rust-toolchain.toml` (stable), `dist-workspace.toml` (cargo-dist); `.cargo/config.toml` (rust-lld and static CRT on Windows MSVC, `xtask` and `workshop` aliases); `.config/` (`hakari.toml`, `nextest.toml`); `.github/workflows/` (`ci.yml` plus release, nightly, site, and smoke workflows); `.githooks/` (pre-commit fmt; pre-push docs-claims, headless check, clippy, cargo deny); `.cursor/rules/` (Workshop rules). + - Non-code: `guide/` (user guide sources), `prompts/` (example prompts), `tools/` (Node scripts for gateway sidecar staging and TTS), `images/`, `vibe/` (plan archive), `local/` (gitignored operator config), `cabinet/`. +- Component boundaries (normal dependencies, from `cargo metadata`): + - Engine: `promptforge-types` and `promptforge-vfs` are leaves; `promptforge-model-client` uses `types`; `promptforge-lua` uses `model-client`, `types`, `vfs`; `promptforge-parser` uses `lua`, `types`; `promptforge-engine` uses all five; the `promptforge` facade re-exports all six. `promptforge-plugin` uses `types` and `vfs`. No Engine crate depends on the Harness or a Host. + - Plugins: each `plugin-*` crate depends only on `promptforge-plugin` (rule allows `shared-*`, `workspace-hack`, and outside libraries). + - Harness: `harness-runner` uses `promptforge` and `promptforge-plugin`; `harness` adds `harness-runner`; `harness-gateway-client` uses `harness`, `promptforge`, `plugin-web`. Outside their family Harness crates may name only `promptforge`, `promptforge-plugin`, and `workspace-hack`. + - Gateway: `gateway-api-types` is a leaf; `config` and `progress` use it; `protocol` uses `api-types` and `config`; `routing` uses `config` and `protocol`; `local` uses `config`, `progress`, `protocol`, `routing`; `stt/engine` and `stt/whisper-ffi` are leaves, `backend-whisper` uses them and `progress`, `stt/api` (`gateway-stt`) uses `config`, `local`, `progress`, and both STT crates; `web-search` uses `config` and `protocol`; `logging` uses `gateway-api-discovery`; `config-ui` uses `shared-loopback`; `cloud-providers` (a library plus the `shared-cloud-providers` binary) has no workspace dependents. The `gateway` app sits on top of the family. + - Workshop: `workshop-protocol` and `workshop-support` are leaves; `registry` uses `protocol`; `menu`, `status`, `user-state`, `workspace` use `protocol`, `registry`, `support` (the last two also `shared-error-source`); `workshop-gateway` uses those plus `gateway-api-types` and `gateway-api-discovery` (no other gateway crate); `run-log` uses `harness`; `agents` uses `harness`, `promptforge`, `plugin-user-input`; `workshop-server` sits over every Workshop crate plus the Harness, `harness-gateway-client`, all three Plugins, `promptforge`, `promptforge-plugin`; `workshop-server-api` wraps the server; `workshop` (desktop) uses `workshop-server-api` and `gateway-api-discovery`. + - `build-*` and `shared-*` crates depend on no workspace crate. `workspace-hack` depends on no workspace crate and is depended on by every member except itself, `promptforge-vfs`, and `build-ceiling` (the `final-excludes`). + - `cargo test -p build-xtask` enforces product and container boundaries, the Workshop tier graph, the `## Invariants` marker, and lint inheritance. +- Conventions summary: + - Every third-party dependency is declared once in root `[workspace.dependencies]`, usually with a comment explaining the pin or trimmed features, and members inherit it in dotted style (`name.workspace = true`); every crate has `[lints] workspace = true`. + - Lints are strict: clippy `all` and `pedantic` deny, `unwrap_used`, `expect_used`, `allow_attributes`, `allow_attributes_without_reason` deny, `unsafe_code` deny, `missing_docs` warn, all promoted by `CARGO_BUILD_WARNINGS=deny`. + - Crate roots open with `//!` docs stating purpose and allowed dependencies; 27 carry a `## Invariants` section. + - Source directories are flat; one or two related files become kebab siblings wired with `#[path]`, three or more become a subdirectory. + - The capitalized terms Engine, Harness, Host, and Plugin have fixed meanings, enforced by `crates/workshop/ui/test/docs-claims.mjs` across `AGENTS.md` files, crate docs, and `.cursor/rules`; nested `AGENTS.md` files add per-crate rules. + - Comments explain only non-obvious constraints; behavior changes ship with tests; error messages are written for model consumption. + - CI fails if any build dirties the tree, and the `ci-green` job must list every job in `needs:`. + + + + +## Execution Instructions + +Six steps in two components. Each step is one commit that leaves its own tests passing. The work items map to steps this way: deleting `headless` is Step 1, the CI change is Step 2, the hakari removal and the per-crate sweep are Step 3 (one commit), the baseline and the feature diff are Step 4, the build-xtask cleanup is Step 5, and verification is Step 6. + +Test commands are written for Windows PowerShell 5.1, the shell of the local checkout. It drops double quotes nested inside single-quoted arguments to native programs, so no command nests them. A command that sets `CARGO_BUILD_WARNINGS` or `RUSTDOCFLAGS` clears it again, so the setting never reaches a later command. Every command that builds `workshop` runs between the sidecar stage and remove commands, because the desktop crate compiles only with the featureless gateway staged as Tauri's `externalBin`. + +Components, in dependency order: + +1. One Workshop test build (Steps 1-2). It comes first because the per-crate sweep in Step 3 runs `--all-features` on `workshop-server`, which must no longer turn on the `headless` shape, and because it needs nothing from the hakari removal. Two pieces, built in sequence: + - The single build shape (Step 1). `headless` goes together with its one CI step, so no commit leaves CI asking for a deleted feature. + - The merged command (Step 2). The remaining Windows test steps become one nextest command, and the duplicate Linux race step goes. It follows Step 1 because one command can't also build the `headless` shape. +2. Hakari removal (Steps 3-6). It comes second because its sweep needs Step 1, and its last step reruns Step 2's command against the final manifests. Four pieces, built in sequence: + - Removal and sweep (Step 3), built jointly in one commit. Removing the hack breaks exactly the crates the sweep fixes, so neither half compiles alone. + - Feature restoration (Step 4). It follows Step 3 because it diffs the shipped closures against the parent of Step 3's commit. + - Rule and doc cleanup (Step 5). It follows Step 3 because `workspace_respects_the_product_boundary` (`crates/build-xtask/src/product/tests.rs`) and `every_workspace_crate_runs_the_ceiling_from_its_build_script` (`crates/build-xtask/src/tidy-tests.rs`) check the live workspace and fail while 49 members still name `workspace-hack`. It doesn't depend on Step 4. + - Cross-platform proof (Step 6). It comes last because it runs every exit criterion against the finished change, including CI and a dispatched release build. + + + +### Step 1: Delete workshop-server's `headless` feature [completed] + +- Component: One Workshop test build +- Tests: + - `cargo nextest run --locked -p workshop-server` + - `cargo build --locked -p gateway --no-default-features` + - `node tools/stage-gateway-sidecar.mjs stage --target x86_64-pc-windows-msvc --source target/debug/promptforge-gateway.exe` + - `$env:CARGO_BUILD_WARNINGS='deny'; cargo clippy -p workshop -p workshop-server -p workshop-server-api --all-targets; Remove-Item Env:CARGO_BUILD_WARNINGS` + - `$env:RUSTDOCFLAGS='-D warnings'; cargo doc --locked --no-deps -p workshop-server --document-private-items; Remove-Item Env:RUSTDOCFLAGS` + - `node tools/stage-gateway-sidecar.mjs remove --target x86_64-pc-windows-msvc` + - `cargo fmt --all --check` + - `if (rg -n headless crates/workshop/server) { throw 'workshop-server still names headless' }` + - `if (rg -n 'features headless' .github/workflows/ci.yml) { throw 'ci.yml still runs the headless shape' }` +- Commit: one commit with the summary `Delete workshop-server's headless feature`, a body, `Design:` lines, and a `Plan: vibe/` trailer naming the run's copy of this plan. +- Contract items: Technical Design, the asset-layer entry of "Modules and interfaces" and the `crates/workshop/server/src/routes/assets-headless-tests.rs` and workshop-server entries of "File and public API changes". +- Before editing: read the root `AGENTS.md`, `crates/workshop/AGENTS.md`, and `crates/workshop/server/AGENTS.md`. + +Artifacts: + +- `crates/workshop/server/Cargo.toml`: delete `headless = []` and its three-line comment (lines 65-68). `default = []` and `test-fixtures` stay. +- `crates/workshop/server/build.rs`: delete the `CARGO_FEATURE_HEADLESS` branch (lines 17-19) and `empty_asset_dir` (lines 36-51). The module doc ends at the `npm ci` sentence, without the headless sentences (lines 7-10). `main` runs `build_ceiling::check()` and then always builds the UI. +- `crates/workshop/server/src/assets.rs`: + - Delete the `AssetServer` trait (lines 66-78), `NoopAssets` and its impl (lines 106-120), and the test `the_noop_asset_layer_serves_nothing` (lines 189-201). + - `EmbeddedAssets` keeps `get` and `manifest` as inherent associated functions with no `self` parameter (`EmbeddedAssets::get(path)`, `EmbeddedAssets::manifest()`). The struct is a unit type, and clippy's pedantic `unused_self` is denied. + - `ui_asset(path, content_type, cache)` loses its `server: &dyn AssetServer` parameter and reads through `EmbeddedAssets::get`. + - Drop every `#[cfg(not(feature = "headless"))]` and the headless sentences of the module doc and the `UiAssets` doc. The test-module gate (line 143) becomes `#[cfg(test)]`. The three traversal tests and `the_cache_policies_render_their_header_values` stay. +- `crates/workshop/server/src/routes/assets.rs`: delete `asset_server()` (lines 11-24) and the `AssetServer` import. Each handler calls `assets::ui_asset` without a server argument, and `logical_asset` reads `assets::EmbeddedAssets::manifest()`. The `routes` doc names `EmbeddedAssets` where it now says `AssetServer`. The test wiring (lines 180-186) becomes `#[cfg(test)]`, `#[path = "assets-tests.rs"]`, `mod tests;`, with no `headless_tests` module. +- Delete `crates/workshop/server/src/routes/assets-headless-tests.rs`. +- `crates/workshop/server/src/serve-tests.rs`: in `the_server_boots_and_serves_the_ui_with_an_unreachable_gateway`, drop the headless comment and the `#[cfg(not(feature = "headless"))]` wrapper (lines 58-67), so the `/` request and its `200 OK` assertion always run. +- `.github/workflows/ci.yml`, job `check-workshop`: delete the `Test (workshop-server headless)` step and its three-line comment (lines 192-196). The step is the feature's only user (Decision Record), so it goes in this commit. Step 2 replaces the job's other test steps. + +Behavior: + +- `workshop-server` has no `headless` feature. Its build script always bundles the UI with esbuild, so building it needs Node.js 22 and `npm ci --prefix crates/workshop`, which CI always installs. Its asset routes always serve the embedded bundle. +- The headless-only tests are deleted with the feature, by design. Every other `workshop-server` test still runs and passes. +- No public Rust API changes. Every item touched is `pub(crate)`. + + + + + +### Step 2: Run the Windows Workshop tests as one nextest command [completed] + +- Component: One Workshop test build +- Tests: + - `cargo build --locked -p gateway --no-default-features` + - `node tools/stage-gateway-sidecar.mjs stage --target x86_64-pc-windows-msvc --source target/debug/promptforge-gateway.exe` + - `cargo nextest run --locked --no-default-features --features gateway/test-fixtures -p workshop -p workshop-server -p workshop-server-api -p workshop-workspace -p gateway-api-discovery -p gateway -E 'package(workshop) | package(workshop-server) | package(workshop-server-api) | package(workshop-workspace) | (package(gateway-api-discovery) & test(a_process_lifetime_lease_recovers_after_its_owner_is_terminated)) | (package(gateway) & binary(it) & (test(simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff) | test(workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot) | test(a_direct_launch_recovers_the_lease_from_a_terminated_owner) | test(a_silent_process_lease_owner_causes_a_bounded_console_only_failure)))'` + - `cargo nextest list --locked --no-default-features --features gateway/test-fixtures -p workshop -p workshop-server -p workshop-server-api -p workshop-workspace -p gateway-api-discovery -p gateway -E 'package(workshop) | package(workshop-server) | package(workshop-server-api) | package(workshop-workspace) | (package(gateway-api-discovery) & test(a_process_lifetime_lease_recovers_after_its_owner_is_terminated)) | (package(gateway) & binary(it) & (test(simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff) | test(workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot) | test(a_direct_launch_recovers_the_lease_from_a_terminated_owner) | test(a_silent_process_lease_owner_causes_a_bounded_console_only_failure)))'` + - `cargo nextest list --locked -p workshop -p workshop-server -p workshop-server-api` + - `cargo nextest list --locked -p workshop-workspace --all-features` + - `node tools/stage-gateway-sidecar.mjs remove --target x86_64-pc-windows-msvc` + - `cargo fmt --all --check` +- Commit: one commit with the summary `Run the Windows Workshop tests as one nextest command`, a body, `Design:` lines, and a `Plan: vibe/` trailer naming the run's copy of this plan. +- Contract items: Technical Design, the `check-workshop` and `check-workshop-linux` entries of "File and public API changes", the merged Windows test step, and its notes on feature flags, `workshop-workspace`, filter matching, the sidecar, and what gets built. +- Before editing: read the root `AGENTS.md`, `crates/workshop/AGENTS.md`, `crates/gateway/app/AGENTS.md`, and `crates/gateway-api-discovery/AGENTS.md`. + +Artifacts: + +- `.github/workflows/ci.yml`, job `check-workshop`: replace `Test (workshop, concurrent via nextest)`, `Test (workshop-workspace, Windows jail)` with its comment, and `Test Gateway process ownership races` (lines 182-190 and 203-210 before Step 1) with the `Test (Windows)` step exactly as written in Technical Design, comment and `shell: bash` included. It goes right after `Clippy (workshop)`. +- `.github/workflows/ci.yml`, job `check-workshop-linux`: delete `Test Gateway process ownership races` (lines 265-272 before Step 1). +- Gateway test code, only if the merged command fails to compile the gateway's library unit tests or its `it` target under `--no-default-features --features gateway/test-fixtures`: add the few missing `#[cfg(feature = "...")]` gates in `crates/gateway/app/src/` or `crates/gateway/app/tests/it/`, in the style of `crates/gateway/app/src/boot.rs:391-392` and `crates/gateway/app/tests/it/main.rs:38-45`. `crates/gateway/app/src/main-logging-tests.rs` gates per item. + +Behavior: + +- `check-workshop` on Windows runs, in order: Build featureless Gateway, Stage Gateway sidecar, Clippy (workshop), Test (Windows), Docs (workshop-server, private items), Remove Gateway sidecar, Clean tree. It has exactly one test step. +- `check-workshop-linux` runs: Build featureless Gateway, Stage Gateway sidecar, Build (workshop, Linux), Remove Gateway sidecar, Clean tree. The main Linux `test` job already runs the five race tests with `--all-features` (`.github/workflows/ci.yml:85`), so Linux coverage doesn't change. +- The merged command runs the same tests as the Windows job ran before Step 1, minus the deleted headless-only tests: + - every test of `workshop`, `workshop-server`, `workshop-server-api`, and `workshop-workspace`, including the workspace jail's Windows path tests (verbatim, UNC, junctions, alternate data streams, symlinks) + - `gateway-api-discovery`'s `a_process_lifetime_lease_recovers_after_its_owner_is_terminated` + - the gateway `it` tests `simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff`, `workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot`, `a_direct_launch_recovers_the_lease_from_a_terminated_owner`, and `a_silent_process_lease_owner_causes_a_bounded_console_only_failure` + - no other gateway or `gateway-api-discovery` test +- List check: the merged `cargo nextest list` shows exactly those five race tests from the gateway packages. For each of the four Workshop crates, it shows the same tests as the two plain `cargo nextest list` commands in Tests. +- Fallback: if a few `cfg` gates don't fix the gateway compile, split the step into two commands in the same `run:` block. The first command keeps the four Workshop crates and `gateway-api-discovery` with their half of the filter. The second is `cargo nextest run --locked -p gateway --no-default-features --features test-fixtures --test it` with the gateway half of the filter. Report "two commands" in the step's return and the commit body. +- Accepted by the Decision Record: one six-package command unifies dependency features across all six, and the race tests run in parallel with the Workshop tests, as they already do in the Linux `test` job. + + + + + +### Step 3: Remove hakari and declare the features it supplied [completed] + +- Component: Hakari removal +- Tests: + - `cargo build --locked -p gateway --no-default-features` + - `node tools/stage-gateway-sidecar.mjs stage --target x86_64-pc-windows-msvc --source target/debug/promptforge-gateway.exe` + - `foreach ($m in (cargo metadata --no-deps --format-version 1 --locked | ConvertFrom-Json).packages.name) { cargo check --locked -p $m; if ($LASTEXITCODE) { throw "cargo check -p $m failed" }; cargo check --locked -p $m --all-targets --all-features; if ($LASTEXITCODE) { throw "cargo check -p $m --all-targets --all-features failed" } }` + - `node tools/stage-gateway-sidecar.mjs remove --target x86_64-pc-windows-msvc` + - `cargo test -p build-xtask` + - `cargo deny check` + - `if (rg -n --hidden 'hakari|workspace-hack|workspace_hack' --glob '*.toml' --glob '*.yml' --glob 'Cargo.lock' --glob '!vibe/**' --glob '!.git/**') { throw 'hakari remains in a manifest, workflow, or Cargo.lock' }` +- Commit: one commit with the summary `Remove cargo-hakari and the workspace-hack crate`, a body that names every feature the sweep added and the code that needs it, `Design:` lines, and a `Plan: vibe/` trailer naming the run's copy of this plan. +- Contract items: Technical Design, "Architecture", and these entries of "File and public API changes": the removed `crates/workspace-hack/` and `.config/hakari.toml`, the root `Cargo.toml` `workspace-hack` entry and `tauri-plugin-updater` comment, the member manifests, the `build-ceiling` and `promptforge-vfs` manifest comments, the `supply-chain` job, `Cargo.lock`, and the manifests the sweep fixes. +- Before editing: read the root `AGENTS.md`. + +Artifacts: + +- Delete `crates/workspace-hack/` (its generated `Cargo.toml` and one-line `src/lib.rs`) and `.config/hakari.toml`. `.config/nextest.toml` stays. +- Root `Cargo.toml`: + - Delete the `workspace-hack` entry of `[workspace.dependencies]` and its comment (lines 75-76). + - In the `tauri-plugin-updater` comment (lines 243-250), replace the sentence "Through workspace-hack that feature would put `ring` back into every member, the gateway included." with one saying the plugin's `rustls-tls` default would put `ring` into the Workshop binary. The rest of the comment and `default-features = false, features = ["system-proxy", "zip"]` stay. + - The reqwest pin is Step 4's. +- Delete the line `workspace-hack.workspace = true` from each of the 49 member manifests that `rg -l '^workspace-hack\.workspace = true' crates` lists. None has an adjoining comment. +- `crates/build-ceiling/Cargo.toml` (lines 9-12): the comment becomes "No dependencies of any kind: the six crates that may not name a build-* crate include src/lib.rs with #[path] from their build scripts, so it must compile on std alone." It loses "not even workspace-hack" and the `.config/hakari.toml` sentence. +- `crates/promptforge-internal/vfs/Cargo.toml` (lines 9-12): keep the zero-dependency rule's first two sentences and drop the sentence beginning "workspace-hack is excluded". +- `.github/workflows/ci.yml`, job `supply-chain`: the install list becomes `tool: cargo-deny, cargo-audit` (line 342). Delete the `cargo hakari verify` step (lines 350-351). The ring-guard comment's parenthetical becomes "(through a reqwest bump or a Tauri plugin default)" (lines 355-356). The guard step itself (lines 353-371) is unchanged. +- `Cargo.lock`: refresh it once with `cargo update --workspace`, which updates only workspace members. `git diff HEAD -- Cargo.lock` must show only removed lines: the `workspace-hack` package block, the `"workspace-hack",` dependency entries, and any third-party package that only the hack reached. No line is added and no version changes. Name any removed third-party package in the commit body. +- Member manifests that relied on hakari gain the features the sweep finds. + +Sweep: + +- With the sidecar staged, run `cargo check --locked -p ` and `cargo check --locked -p --all-targets --all-features` for each member `` that `cargo metadata --no-deps --format-version 1` names. The Tests loop does this and stops at the first failure. 51 members remain once `workspace-hack` is gone. +- A failure means the crate used a feature hakari supplied. Declare it on that crate's own dependency line in its own `Cargo.toml`, for example `tokio = { workspace = true, features = ["macros"] }`, the pattern of `crates/gateway/local/Cargo.toml:17`. Use the dev-dependency line when only tests need it, and the matching `[target.'cfg(...)'.dependencies]` table when only target-specific code needs it. Never add it to the root `[workspace.dependencies]` pin (Decision Record). Rerun the loop until every member passes both checks. + +Behavior: + +- No `workspace-hack` crate, no `.config/hakari.toml`, and no `cargo-hakari` in any manifest, workflow, or `Cargo.lock`. The mentions left in build-xtask sources and crate docs are Step 5's. +- Every workspace crate compiles alone with only the features it declares. A `-p ` command gets that crate's features, unified only with the other packages the same command selects. +- tokio `test-util` is compiled only into builds that include dev-dependencies. +- `cargo test -p build-xtask` still passes. Its `workspace-hack` allowances stay until Step 5 and no longer match any crate. +- CI runs cargo with `--locked`, so the refreshed `Cargo.lock` lands in this commit. +- Security: the gateway still links only aws-lc-rs, and the ring guard stays. `tauri-plugin-updater` keeps `default-features = false`, which is what keeps `ring` out of the Workshop. +- This Windows sweep doesn't cover code that only builds on Linux or macOS. Step 6 covers it through CI and the release build. + + + + + +### Step 4: Restore the gateway's system proxy support and review the shipped feature diff [completed] + +- Component: Hakari removal +- Tests: + - `cargo check --locked -p gateway` + - `cargo check -p gateway --no-default-features` + - `$out = cargo tree --locked -p gateway -e normal -i reqwest -f "{p} {f}" --depth 0; if (-not ($out -match 'system-proxy')) { throw "the gateway's reqwest lacks system-proxy: $out" }` + - `$out = cargo tree --locked -p gateway -e normal -f "{p} {f}" --prefix none | Select-String '^tokio v'; if ($out -match 'test-util') { throw 'the gateway still compiles tokio test-util' }` + - `foreach ($p in 'gateway', 'workshop') { $out = cargo tree --locked -p $p -e normal -i ring; if ($LASTEXITCODE) { throw "cargo tree -p $p failed" }; if ($out) { throw "$p links ring: $out" } }` + - `foreach ($c in 'promptforge-types', 'promptforge-model-client', 'promptforge-parser', 'promptforge-lua', 'promptforge-engine', 'promptforge') { $out = cargo tree --locked -p $c -e normal --prefix none; if ($LASTEXITCODE) { throw "cargo tree -p $c failed" }; if ($out -match '^tokio v') { throw "$c still reaches tokio" } }` +- Commit: one commit with the summary `Keep the gateway's system proxy support without hakari`, a body that lists every restored feature and every accepted loss with its reason, `Design:` lines, and a `Plan: vibe/` trailer naming the run's copy of this plan. +- Contract items: Technical Design, the root `Cargo.toml` reqwest-pin entry of "File and public API changes", and the "Proxy behavior", "Gateway clients", and "Test-only features" constraints. +- Before editing: read the root `AGENTS.md` and `crates/gateway/app/AGENTS.md`. + +Artifacts: + +- Root `Cargo.toml`, the reqwest pin (line 89): its features become `["json", "query", "rustls", "stream", "system-proxy"]`. Add one comment line saying `system-proxy` keeps the gateway honoring the Windows registry and macOS system proxy settings. This goes on the workspace pin by decision, unlike the sweep fixes. +- Crate manifests, only for a further runtime-relevant loss the diff shows: the lost feature on that crate's own dependency line. +- `Cargo.lock`, only when the pin change leaves it stale under `--locked`: refresh it with `cargo update --workspace`. No third-party version changes. +- Scratch that is never committed: the listings and diffs, saved as scratch outside the repository, and a temporary worktree at `$env:TEMP\pf-feature-baseline`. + +Feature diff: + +- Baseline tree: `$base = git rev-parse "$(git log -1 --format=%H -- crates/workspace-hack/src/lib.rs)^"` names the parent of Step 3's commit, the last tree with hakari. Check it out with `git worktree add --detach "$env:TEMP\pf-feature-baseline" $base`. Steps 1 and 2 don't change the gateway's or the Workshop's feature closure, so this stands in for a baseline taken before any manifest change. +- For `gateway` and `workshop` on each shipping target (`x86_64-pc-windows-msvc`, `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, `x86_64-apple-darwin`, `aarch64-apple-darwin`), run `cargo tree --locked -p -e normal --target -f "{p} {f}" --prefix none` twice: once with `--manifest-path "$env:TEMP\pf-feature-baseline\Cargo.toml"` and once in the working tree. Drop each line's trailing ` (*)` marker, sort, de-duplicate, and save the results as the scratch files `before--.txt` and `after--.txt`. Compare each pair with `git diff --no-index`. +- If `cargo tree --target ` can't resolve a triple on Windows, stop and return blocked with the triple and the error. The plan's fallback is to compute that triple on a matching CI runner. +- Classify every difference: + - Expected: tokio loses `test-util`, and crates the binary never uses disappear. + - Restore: anything that changes runtime behavior. reqwest `system-proxy` in the gateway is already known. + - Accept: losses that only affect performance or API surface, such as reqwest `gzip` and `brotli` in the gateway. List each one in the commit body. + - A gained feature or crate is unexpected. Report it in the step's return. +- After restoring, rerun the after-listings so the final diff holds only expected and accepted losses. Then run `git worktree remove "$env:TEMP\pf-feature-baseline"`. + +Behavior: + +- The gateway keeps honoring the Windows registry and macOS system proxy settings. reqwest's `system-proxy` enables hyper-util's `client-proxy-system`. hyper-util's `Matcher::from_system()` always reads `ALL_PROXY`, `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`, and the feature adds only the macOS system configuration and the Windows registry settings. A reqwest client with no explicit proxy configuration installs that system matcher. +- The gateway's cloud, artifact, HF, and STT-config clients set no proxy (`crates/gateway/protocol/src/http_util.rs:25-65`, `crates/gateway/local/src/artifacts.rs:272`, `crates/gateway/local/src/runtime/hf_sidecar.rs:37`, `crates/gateway/cloud-providers/src/main.rs:110`, `crates/gateway/config/src/config/stt.rs:470`), so they pick it up. The loopback clients that call `.no_proxy()` are unaffected. +- No workspace manifest asked for `system-proxy` before this step. The gateway got it only through `crates/workspace-hack/Cargo.toml:53`. +- The shipped gateway and Workshop behave at runtime as before the change, apart from losing tokio `test-util` and crates the binary never used. +- The regression checks in Tests pass: no `ring` under the gateway or the Workshop, no tokio under the six Engine crates, `system-proxy` on the gateway's reqwest, and no `test-util` on the gateway's tokio. + + + + + +### Step 5: Drop the workspace-hack allowances from build-xtask and the crate docs [completed] + +- Component: Hakari removal +- Tests: + - `cargo test -p build-xtask` + - `$env:CARGO_BUILD_WARNINGS='deny'; cargo clippy -p build-xtask -p promptforge-plugin -p plugin-mcp -p plugin-user-input -p plugin-web -p promptforge-types --all-targets --all-features; Remove-Item Env:CARGO_BUILD_WARNINGS` + - `$env:RUSTDOCFLAGS='-D warnings'; cargo doc --locked --no-deps --all-features -p promptforge-plugin -p plugin-mcp -p plugin-user-input -p plugin-web -p promptforge-types; Remove-Item Env:RUSTDOCFLAGS` + - `node --test crates/workshop/ui/test/docs-claims.mjs` + - `cargo fmt --all --check` + - `if (rg -n --hidden 'hakari|workspace-hack|workspace_hack' --glob '!vibe/**' --glob '!.git/**') { throw 'hakari references remain' }` +- Commit: one commit with the summary `Drop the workspace-hack allowances from build-xtask`, a body, `Design:` lines, and a `Plan: vibe/` trailer naming the run's copy of this plan. +- Contract items: Technical Design, the build-xtask entries of "Modules and interfaces", and the comment, `## Invariants`, and build-xtask test entries of "File and public API changes". +- Before editing: read the root `AGENTS.md` and `crates/promptforge-internal/types/AGENTS.md`. + +Artifacts: + +- `crates/build-xtask/src/product.rs`: + - Module doc (lines 15-20): Harness crates outside their own family "may name only `promptforge` and `promptforge-plugin`, which rules out `build-*` and other unaffiliated crates". Plugin crates "may depend only on `promptforge-plugin`, `shared-*` crates, and outside libraries". + - Delete `WORKSPACE_HACK` and its doc (lines 159-161). + - In `boundary_breach`, the `(Family::Harness, Family::Build)` message becomes `harness crates must not depend on build-* crates; outside their family they may name only promptforge and promptforge-plugin`. The `(Family::Harness, Family::Unaffiliated)` arm loses its `if dep.package != WORKSPACE_HACK` guard, and its message becomes `harness crates must not depend on unaffiliated crates; outside their family they may name only promptforge and promptforge-plugin`. + - In `plugin_breach`, delete the comment at lines 276-277. The allow-list becomes `[PLUGIN_CONTRACT, package.package.as_str()]`, and the message becomes `plugin crates may depend only on promptforge-plugin, shared-* crates, and outside libraries`. +- `crates/build-xtask/src/product/harness_tests.rs`: delete `a_harness_crate_depending_on_workspace_hack_passes` (lines 52-67). The expected texts at lines 101 and 122 become the two new Harness messages. The assertion message at line 124 says unaffiliated crates are closed to harness crates. +- `crates/build-xtask/src/product/plugin_tests.rs`: the module doc (lines 1-5) drops `workspace-hack`. `plugin_web_naming` drops its `("workspace-hack", "workspace-hack")` fixture crate (line 19). `PLUGIN_RULE` (line 29) becomes the new Plugin message. The passing test (lines 31-40) becomes `a_plugin_crate_naming_the_contract_and_a_shared_crate_passes`, without its `workspace-hack` dependency line. +- `crates/build-xtask/src/tidy-wiring.rs`: + - Delete the `WORKSPACE_HACK` and `LINE_LIMIT` constants and their docs (lines 13-17). + - The `ceiling_wiring_violations` doc drops "and that no `workspace-hack` file is over the limit" (lines 24-25). + - The loop body becomes today's `else` body (lines 43-46), with no branch. Delete `workspace_hack_violations` (lines 192-213). `fs` stays imported for the manifest read, and `super::rust_files` keeps its other callers. + - Tests: the `write_wired` doc drops "and a `workspace-hack` with no build script" (lines 247-248), and its last two lines (265-266) go. Delete `a_workspace_hack_file_over_the_limit_fails` (lines 337-346). +- Guard comments in `crates/build-xtask/src/engine_deps.rs` (lines 22-23), `crates/build-xtask/src/test_support_leak.rs` (lines 34-35), and `crates/build-xtask/src/harness_bans.rs` (lines 11-12): keep "The check reads declared dependencies, not the resolved graph." and drop the clause about `workspace-hack` unification. The sentences after it stay. +- `## Invariants` docs, reflowed: + - `crates/promptforge-plugin/src/lib.rs` (lines 22-23): "May depend on: `promptforge-types` and `promptforge-vfs` among workspace crates." + - `crates/plugin-mcp/src/lib.rs` (lines 57-58), `crates/plugin-user-input/src/lib.rs` (lines 68-69), and `crates/plugin-web/src/lib.rs` (lines 48-49): "May depend on: `promptforge-plugin`, `shared-*` crates, and outside libraries." + - `crates/promptforge-internal/types/src/lib.rs` (line 31): "May depend on: no workspace crate." + +Behavior: + +- Outside their family, Harness crates may name only `promptforge` and `promptforge-plugin`. Any unaffiliated crate is a breach, `workspace-hack` included. +- Plugin crates may name only `promptforge-plugin`, `shared-*` crates, and outside libraries. +- The ceiling-wiring check holds every crate to a package-root `build.rs` that calls `build_ceiling::check()`, with no special case for a crate without a build script. +- The docs keep the fixed meanings of Engine, Harness, Host, and Plugin that `docs-claims.mjs` enforces. +- After this step, `rg -n --hidden "hakari|workspace-hack|workspace_hack" --glob '!vibe/**' --glob '!.git/**'` returns nothing, `Cargo.lock` included. The `vibe/` records stay unchanged as history. + + + + + +### Step 6: Pass every exit criterion on Windows, Linux, and macOS [completed] + +- Component: Hakari removal +- Tests: + - `cargo fmt --all --check` + - `$env:CARGO_BUILD_WARNINGS='deny'; cargo clippy --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-targets --all-features; Remove-Item Env:CARGO_BUILD_WARNINGS` + - `cargo check -p gateway --no-default-features` + - `cargo nextest run --locked --workspace --exclude workshop --exclude workshop-server --exclude workshop-server-api --all-features` + - `$env:RUSTDOCFLAGS='-D warnings'; cargo doc --workspace --no-deps --all-features --exclude workshop --exclude workshop-server --exclude workshop-server-api; Remove-Item Env:RUSTDOCFLAGS` + - `$env:RUSTDOCFLAGS='-D warnings'; cargo doc -p promptforge --no-deps; Remove-Item Env:RUSTDOCFLAGS` + - `$env:RUSTDOCFLAGS='-D warnings'; cargo doc -p harness --no-deps; Remove-Item Env:RUSTDOCFLAGS` + - `$env:RUSTDOCFLAGS='-D warnings'; cargo doc --locked --no-deps --all-features -p promptforge-engine --document-private-items; Remove-Item Env:RUSTDOCFLAGS` + - `cargo test -p build-xtask` + - `node --test crates/workshop/ui/test/docs-claims.mjs` + - `cargo deny check` + - `cargo build --locked -p gateway --no-default-features` + - `node tools/stage-gateway-sidecar.mjs stage --target x86_64-pc-windows-msvc --source target/debug/promptforge-gateway.exe` + - `$env:CARGO_BUILD_WARNINGS='deny'; cargo clippy -p workshop -p workshop-server -p workshop-server-api --all-targets; Remove-Item Env:CARGO_BUILD_WARNINGS` + - `cargo nextest run --locked --no-default-features --features gateway/test-fixtures -p workshop -p workshop-server -p workshop-server-api -p workshop-workspace -p gateway-api-discovery -p gateway -E 'package(workshop) | package(workshop-server) | package(workshop-server-api) | package(workshop-workspace) | (package(gateway-api-discovery) & test(a_process_lifetime_lease_recovers_after_its_owner_is_terminated)) | (package(gateway) & binary(it) & (test(simultaneous_direct_launches_leave_one_owner_and_one_clean_handoff) | test(workshop_launch_lock_and_direct_launch_do_not_deadlock_or_double_boot) | test(a_direct_launch_recovers_the_lease_from_a_terminated_owner) | test(a_silent_process_lease_owner_causes_a_bounded_console_only_failure)))'` + - `$env:RUSTDOCFLAGS='-D warnings'; cargo doc --locked --no-deps -p workshop-server --document-private-items; Remove-Item Env:RUSTDOCFLAGS` + - `foreach ($m in (cargo metadata --no-deps --format-version 1 --locked | ConvertFrom-Json).packages.name) { cargo check --locked -p $m; if ($LASTEXITCODE) { throw "cargo check -p $m failed" }; cargo check --locked -p $m --all-targets --all-features; if ($LASTEXITCODE) { throw "cargo check -p $m --all-targets --all-features failed" } }` + - `node tools/stage-gateway-sidecar.mjs remove --target x86_64-pc-windows-msvc` + - `$out = cargo tree --locked -p gateway -e normal -i reqwest -f "{p} {f}" --depth 0; if (-not ($out -match 'system-proxy')) { throw "the gateway's reqwest lacks system-proxy: $out" }` + - `$out = cargo tree --locked -p gateway -e normal -f "{p} {f}" --prefix none | Select-String '^tokio v'; if ($out -match 'test-util') { throw 'the gateway still compiles tokio test-util' }` + - `foreach ($p in 'gateway', 'workshop') { $out = cargo tree --locked -p $p -e normal -i ring; if ($LASTEXITCODE) { throw "cargo tree -p $p failed" }; if ($out) { throw "$p links ring: $out" } }` + - `foreach ($c in 'promptforge-types', 'promptforge-model-client', 'promptforge-parser', 'promptforge-lua', 'promptforge-engine', 'promptforge') { $out = cargo tree --locked -p $c -e normal --prefix none; if ($LASTEXITCODE) { throw "cargo tree -p $c failed" }; if ($out -match '^tokio v') { throw "$c still reaches tokio" } }` + - `if (rg -n --hidden 'hakari|workspace-hack|workspace_hack' --glob '!vibe/**' --glob '!.git/**') { throw 'hakari references remain' }` +- Commit: one commit holding the fixes that CI or the release build show are needed, plus the step's completion marker in the run's copy of this plan, with a body, `Design:` lines when code changes, and a `Plan: vibe/` trailer naming that copy. When both runs pass with no change, the commit holds only the completion marker, and the step says so in its return. +- Contract items: Technical Design, the "Test-only features" constraint and the manifests the sweep fixes. The Testing Plan's exit criteria are copied below. +- Before editing: read the root `AGENTS.md`. + +Artifacts: + +- The `remove-hakari` branch on `origin` (`git@github.com:vinniefalco/promptforge.git`), pushed with Steps 1-5, and a pull request from it into `master` opened with `gh pr create`. `ci.yml` runs only on pushes to `master` or `main` and on pull requests (`.github/workflows/ci.yml:3-6`), so the pull request is what runs it. +- A green `ci.yml` run for the pull request's head, followed with `gh run watch`. +- A dispatched `.github/workflows/release-workshop.yml` run on the same branch (`gh workflow run release-workshop.yml --ref remove-hakari`), also followed with `gh run watch`. +- Member manifests, only for a feature that Linux- or macOS-only code took from hakari: the feature goes on that crate's own dependency line, in the matching `[target.'cfg(...)'.dependencies]` table when only that target's code uses it, and never on the root pin. + +Behavior: + +- Every Tests command passes locally. Together they are the Testing Plan's exit criteria: the formatter, both clippy partitions, the featureless gateway check, the workspace test run, the merged Windows command with the sidecar staged, the five doc builds, build-xtask, the docs-claims scan, `cargo deny`, the per-crate sweep, the regression checks, and the reference scan. +- The reviewed feature diff is Step 4's commit body. This step doesn't redo it. +- `ci.yml` is green on the branch. The Windows `check-workshop` job has exactly one test step, and that step runs the five race tests on Windows. +- The dispatched `release-workshop.yml` run builds the gateway and the Workshop for all five targets and passes. A dispatched run uploads only temporary Actions artifacts and never reaches the publish job, which runs only on pushes of `workshop-v*` tags (`.github/workflows/release-workshop.yml:16-20`, `:71-87`, `:146-192`, `:483-485`). +- `ci.yml` has no macOS job, so macOS code is proven only by the release run (and `.github/workflows/nightly.yml`). +- When a fix lands, rerun the affected local Tests and push again, and both runs must be green on the fix commit. + + + +