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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ crates/workshop/look/**/*.png binary
# The event-log schema canary pins the version-1 file exactly as the log
# writer emits it (LF); autocrlf must not rewrite it on Windows checkouts.
crates/workshop/server/tests/it/observer/*.jsonl text eol=lf
# The Gateway discovery contract's golden fixtures are compared byte for
# byte against what the writer emits (LF); a checkout must not rewrite them.
crates/workshop/gateway-discovery/fixtures/*.json text eol=lf
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Seven terms have exactly one meaning each, everywhere in this repository: code c
- Behavior changes ship with tests in the same change. Preserve product and behavior tests during refactors.
- A Cargo feature gates a real constraint such as a toolchain requirement or heavy native build. It does not describe product shape. Feature-disabled builds must not leak optional types into core paths.
- Runtime and serve paths never compile native dependencies or invoke build tools.
- Long-running gateway work reports through `gateway-progress`, a private gateway family crate: a producer begins an activity with a text, replaces the text as work moves, and drops the guard when done. Consumers outside the family read only the `Progress` wire type from `gateway-api-types`.
- Long-running gateway work reports through `gateway-progress`, a private gateway family crate: a producer begins an activity with a text, replaces the text as work moves, and drops the guard when done. Consumers outside the family read only the same `{ busy, text }` wire shape, which each side declares for itself: the Gateway sends it, and a Host that shows it pins the field names with a golden JSON test.
- Unsafe code stays in its explicitly owned boundary.
- Comments explain a non-obvious constraint, ordering requirement, or workaround. Every platform or external-bug workaround cites its upstream issue URL in the explanatory comment.
- JSON that reaches a recorder or a replay comparison round-trips exactly - `to_value`, `to_string`, `from_str` yield an identical value, object keys stay canonical (sorted), numbers must be finite. Exact parsing (`float_roundtrip`) carries that guarantee; a value derived and then logged is additionally rounded to its meaningful precision at the source.
Expand Down
23 changes: 18 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "3"
members = ["crates/*", "crates/runvil-internal/types", "crates/runvil-internal/engine", "crates/runvil-internal/lua", "crates/runvil-internal/parser", "crates/runvil-internal/vfs", "crates/runvil-internal/model-client", "crates/gateway/app", "crates/gateway/cloud-providers", "crates/gateway/config", "crates/gateway/config-ui", "crates/gateway/local", "crates/gateway/logging", "crates/gateway/progress", "crates/gateway/protocol", "crates/gateway/routing", "crates/gateway/web-search", "crates/gateway/stt/api", "crates/gateway/stt/engine", "crates/gateway/stt/backend-whisper", "crates/gateway/stt/whisper-ffi", "crates/workshop/desktop", "crates/workshop/server", "crates/workshop/server-api", "crates/workshop/gateway", "crates/workshop/menu", "crates/workshop/protocol", "crates/workshop/registry", "crates/workshop/status", "crates/workshop/support", "crates/workshop/user-state", "crates/workshop/workspace", "crates/workshop/run-log", "crates/workshop/agents", "crates/harness-internal/runner", "crates/harness-internal/plugin-loader", "crates/harness-internal/test-plugin"]
members = ["crates/*", "crates/runvil-internal/types", "crates/runvil-internal/engine", "crates/runvil-internal/lua", "crates/runvil-internal/parser", "crates/runvil-internal/vfs", "crates/runvil-internal/model-client", "crates/gateway/app", "crates/gateway/cloud-providers", "crates/gateway/config", "crates/gateway/config-ui", "crates/gateway/local", "crates/gateway/logging", "crates/gateway/progress", "crates/gateway/protocol", "crates/gateway/routing", "crates/gateway/web-search", "crates/gateway/stt/api", "crates/gateway/stt/engine", "crates/gateway/stt/backend-whisper", "crates/gateway/stt/whisper-ffi", "crates/workshop/desktop", "crates/workshop/server", "crates/workshop/server-api", "crates/workshop/gateway", "crates/workshop/gateway-discovery", "crates/workshop/menu", "crates/workshop/protocol", "crates/workshop/registry", "crates/workshop/status", "crates/workshop/support", "crates/workshop/user-state", "crates/workshop/workspace", "crates/workshop/run-log", "crates/workshop/agents", "crates/harness-internal/runner", "crates/harness-internal/plugin-loader", "crates/harness-internal/test-plugin"]
# crates/shared-ui is not a Rust crate: it is the TypeScript+CSS package
# the Gateway config UI consumes (the Workshop uses its fork in
# crates/workshop/look), so the crates/* glob skips it.
Expand Down Expand Up @@ -44,6 +44,7 @@ shared-error-source = { path = "crates/shared-error-source" }
shared-loopback = { path = "crates/shared-loopback" }
gateway-protocol = { path = "crates/gateway/protocol" }
gateway-api-discovery = { path = "crates/gateway-api-discovery" }
workshop-gateway-discovery = { path = "crates/workshop/gateway-discovery" }
harness = { path = "crates/harness" }
harness-gateway-client = { path = "crates/harness-gateway-client" }
harness-runner = { path = "crates/harness-internal/runner" }
Expand Down
2 changes: 1 addition & 1 deletion crates/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Crates

This directory holds the workspace's crates. The crates at this level are the public and shared layer: the Engine's public API, the Harness and the pieces a Host plugs into it, the vocabulary and discovery code the gateway shares with its clients, shared middleware and error wrappers, and the build tooling. Each product family keeps its private crates in a container directory of its own, and code in one family reaches another family only through a crate at this level.
This directory holds the workspace's crates. The crates at this level are the public and shared layer: the Engine's public API, the Harness and the pieces a Host plugs into it, the vocabulary and protocol code each product family publishes, shared middleware and error wrappers, and the build tooling. Each product family keeps its private crates in a container directory of its own, and code in one family reaches another family only through a crate at this level.
7 changes: 7 additions & 0 deletions crates/build-xtask/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ toml.workspace = true

[dev-dependencies]
tempfile.workspace = true
# The cross-half discovery conformance check (tests/discovery_conformance.rs):
# it runs the Gateway's real writer and Workshop's forked reader against each
# other while both halves still live in this workspace. This is the one
# place allowed to name both, and it disappears when the Gateway moves to
# its own repository.
gateway-api-discovery = { workspace = true, features = ["test-fixtures"] }
workshop-gateway-discovery = { workspace = true, features = ["test-fixtures"] }

[build-dependencies]
build-ceiling.workspace = true
Expand Down
10 changes: 2 additions & 8 deletions crates/build-xtask/src/product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
//! workshop, or Harness crates.
//! - `gateway`/`gateway-*` crates must not depend on runvil,
//! workshop, or Harness crates.
//! - `workshop`/`workshop-*` crates must not depend on gateway crates,
//! except the family's public pair (`gateway-api-types`,
//! `gateway-api-discovery`).
//! - `workshop`/`workshop-*` crates must not depend on gateway crates.
//! - `harness`/`harness-*` crates must not depend on gateway, shared, or
//! workshop crates; outside their own family they may name only
//! `runvil` and `runvil-plugin`, which rules out `build-*`
Expand Down Expand Up @@ -141,9 +139,6 @@ const SERVER: &str = "workshop-server";
/// The runvil-family crates outside crates may depend on directly:
/// the Engine facade and the Plugin contract.
const PUBLIC_RUNVIL: [&str; 2] = ["runvil", "runvil-plugin"];
/// The gateway family's public pair: the only gateway crates workshop
/// crates may name.
const PUBLIC_GATEWAY: [&str; 2] = ["gateway-api-types", "gateway-api-discovery"];
/// The Harness family's facade: the one outside crate permitted into
/// `crates/harness-internal/`.
const HARNESS_FACADE: &str = "harness";
Expand Down Expand Up @@ -204,15 +199,14 @@ fn boundary_breach(package: &CrateInfo, dep: &CrateInfo) -> Option<String> {
return Some(reason.to_owned());
}
let (from, to) = (family(&package.package), family(&dep.package));
let public_gateway = PUBLIC_GATEWAY.contains(&dep.package.as_str());
let family_rule = match (from, to) {
(Family::Runvil, Family::Gateway | Family::Workshop | Family::Harness) => {
Some("runvil crates must not depend on gateway, workshop, or harness crates")
}
(Family::Gateway, Family::Runvil | Family::Workshop | Family::Harness) => {
Some("gateway crates must not depend on runvil, workshop, or harness crates")
}
(Family::Workshop, Family::Gateway) if !public_gateway => {
(Family::Workshop, Family::Gateway) => {
Some("workshop crates must not depend on gateway crates")
}
(Family::Harness, Family::Workshop) => {
Expand Down
6 changes: 4 additions & 2 deletions crates/build-xtask/src/product/harness_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use super::product_boundary_violations;
use super::test_support::write_crate;

#[test]
fn a_harness_crate_depending_on_the_gateway_public_types_is_reported() {
fn a_harness_crate_depending_on_a_gateway_crate_is_reported() {
// No gateway crate is reachable from the Harness family, the former
// `gateway-api-types` exception included.
let root = tempfile::TempDir::new().expect("tempdir");
write_crate(
root.path(),
Expand All @@ -20,7 +22,7 @@ fn a_harness_crate_depending_on_the_gateway_public_types_is_reported() {
assert!(
violations[0].starts_with("harness-sessions depends on gateway-api-types:")
&& violations[0].contains("harness crates must not depend on gateway crates"),
"the gateway public pair is closed to harness crates: {violations:?}"
"every gateway crate is closed to harness crates: {violations:?}"
);
}

Expand Down
44 changes: 24 additions & 20 deletions crates/build-xtask/src/product/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,27 +311,31 @@ fn an_unparseable_manifest_is_reported() {
}

#[test]
fn a_workshop_crate_depending_on_the_public_gateway_pair_passes() {
let root = tempfile::TempDir::new().expect("tempdir");
write_crate(
root.path(),
"workshop-server",
"workshop-server",
"[dependencies]\ngateway-api-types = { path = \"../gateway-api-types\" }\n\
gateway-api-discovery = { path = \"../gateway-api-discovery\" }\n",
);
write_crate(root.path(), "gateway-api-types", "gateway-api-types", "");
write_crate(
root.path(),
fn a_workshop_crate_depending_on_a_gateway_crate_is_reported() {
// The Workshop family has no gateway dependency at all: Workshop reaches
// a running Gateway through its own discovery and HTTP clients, never
// through a Gateway crate. The two former exceptions are covered.
for gateway in [
"gateway-api-types",
"gateway-api-discovery",
"gateway-api-discovery",
"",
);
let violations = product_boundary_violations(root.path());
assert!(
violations.is_empty(),
"the public gateway pair is legal for workshop crates: {violations:?}"
);
"gateway-routing",
] {
let root = tempfile::TempDir::new().expect("tempdir");
write_crate(
root.path(),
"workshop-server",
"workshop-server",
&format!("[dependencies]\n{gateway} = {{ path = \"../{gateway}\" }}\n"),
);
write_crate(root.path(), gateway, gateway, "");
let violations = product_boundary_violations(root.path());
assert_eq!(violations.len(), 1, "{gateway}: {violations:?}");
assert!(
violations[0].starts_with(&format!("workshop-server depends on {gateway}:"))
&& violations[0].contains("workshop crates must not depend on gateway crates"),
"{gateway} is refused for a workshop crate: {violations:?}"
);
}
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions crates/build-xtask/src/tidy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Tiered = (&'static str, &'static str);

/// Tier 0: vocabulary crates. No internal `workshop-*` dependencies.
const VOCABULARY: &[Tiered] = &[
("workshop-gateway-discovery", "gateway-discovery"),
("workshop-protocol", "protocol"),
("workshop-registry", "registry"),
("workshop-support", "support"),
Expand Down
5 changes: 3 additions & 2 deletions crates/build-xtask/src/unsafe_allowlist-tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The unsafe allowlist over this workspace, plus fixtures that each way of
//! relaxing `unsafe_code` outside the six owned crates fails at its line,
//! relaxing `unsafe_code` outside the owned crates fails at its line,
//! text that only spells the attribute passes, and an empty scan fails.

use std::path::Path;
Expand Down Expand Up @@ -39,13 +39,14 @@ fn the_workspace_relaxes_unsafe_code_only_in_its_owned_crates() {
}

#[test]
fn the_six_owned_crates_may_relax_unsafe_code_and_text_that_spells_it_passes() {
fn the_owned_crates_may_relax_unsafe_code_and_text_that_spells_it_passes() {
let root = tempfile::TempDir::new().expect("tempdir");
for dir in [
"gateway/app",
"gateway/stt/whisper-ffi",
"gateway-api-discovery",
"workshop/desktop",
"workshop/gateway-discovery",
"harness-internal/plugin-loader",
"harness-internal/test-plugin",
] {
Expand Down
5 changes: 3 additions & 2 deletions crates/build-xtask/src/unsafe_allowlist.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The unsafe allowlist: the workspace lint table denies `unsafe_code`,
//! and only the six crates that own an unsafe boundary may relax it. Any
//! and only the crates that own an unsafe boundary may relax it. Any
//! other `.rs` file that expects or allows `unsafe_code`, directly or
//! through `cfg_attr` at any depth, is a violation. Runs as part of
//! `cargo test -p build-xtask` and `cargo xtask tidy`.
Expand All @@ -18,11 +18,12 @@ use syn::{Meta, Token};

/// The crate directories, relative to the workspace root, that own an
/// unsafe boundary and may relax `unsafe_code` in the module that holds it.
const OWNED: [&str; 6] = [
const OWNED: [&str; 7] = [
"crates/gateway/app",
"crates/gateway/stt/whisper-ffi",
"crates/gateway-api-discovery",
"crates/workshop/desktop",
"crates/workshop/gateway-discovery",
"crates/harness-internal/plugin-loader",
"crates/harness-internal/test-plugin",
];
Expand Down
Loading
Loading