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
21 changes: 19 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ covers how to route by job. What is specific to Underlay:
does not ship `check:agent-instructions`.
- Fall back to raw `oxide`, `bun`, or `vitest` only when the needed operation is
not represented in `effigy.toml`. Rust goes through `oxide` (Cargo with
managed build storage), not `cargo`; `rust:check:cargo`, `rust:clippy:cargo`
and `rust:test:cargo` are the plain-Cargo fallback if `oxide` misbehaves.
managed build storage), never plain `cargo`; a tool that runs Cargo itself or
needs its own target directory runs `oxide exec -- <tool>`. No selector keeps
a plain-Cargo `:cargo` sibling. The only escape is an operator decision —
`EFFIGY_OXIDE_ESCAPE=cargo` on a routed surface that honours it, or Cargo by
hand outside Effigy — never a silent fallback.
- First-time local bring-up from outside this repo:
`effigy bootstrap git@github.com:inflatable-cookie/underlay.git`

Expand All @@ -90,6 +93,7 @@ effigy fmt:rust:check # oxide fmt --all --check; read-only
effigy fmt:rust # oxide fmt --all; mutates the workspace
effigy fmt:ts -- ts/src/client/format.ts # format the named TypeScript files
effigy fmt:ts:check -- ts/src/client/format.ts # read-only formatting check
effigy check:coverage-route # fake-tool proof of the coverage wrapper's Oxide route
effigy test:unit # vitest, filter {args} to the touched code
effigy test:components # vitest component config
effigy test --plan # when the test shape is what you need to know
Expand All @@ -108,6 +112,19 @@ These selectors do not include Svelte formatting or run automatically in QA.
`underlay-db` Postgres integration tests need a running Docker runtime and are
`#[ignore]`d by default; see `README.md` for the bring-up.

## Scratch Placement

- Agent and proof scratch goes in a unique directory below
`/Volumes/Builds/scratch/underlay/`. If the volume is not mounted, refuse and
report it; never fall back to `$TMPDIR`, `$HOME`, a drive root, or the
checkout, and never create a mount point that pretends to be the volume.
- Delete only scratch your own run created; leave unknown state where it is.
- Proof output records key presence, never secret values, and no generic `HOME`
override substitutes for the scratch rule.
- Product-managed temp files are not agent scratch; leave runtime behaviour and
guest `/tmp` files alone.
- Owner: `docs/knowledge/contracts/123-local-rust-routing-and-scratch-placement.md`.

## Documentation Rules

- Consumer-facing product docs live in `docs/guides/`, `docs/usage/`,
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,7 @@ Some `underlay-db` tests spin up Postgres via `testcontainers`.
### Coverage

- Install tarpaulin: `oxide install cargo-tarpaulin`
- Run coverage check: `rust/scripts/check-coverage.sh`
- Run coverage check: `rust/scripts/check-coverage.sh`, which launches the tool
through `oxide exec -- cargo tarpaulin --workspace --out Xml`
- Prove the wrapper's dispatch, output and exit behaviour without running
Tarpaulin: `effigy check:coverage-route`
4 changes: 2 additions & 2 deletions docs/guides/041-rust-module-splitting.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ pub use crate::postgres_scheduled::Notifier;
2. Create the new file with the extracted code.
3. Replace the inline code with a module reference or import.
4. Update `lib.rs` with `mod` declarations and re-exports.
5. `cargo check -p <crate> --all-features` — fix compile errors.
6. `cargo test -p <crate> --all-features` — verify all tests pass.
5. `oxide check -p <crate> --all-features` — fix compile errors.
6. `oxide test -p <crate> --all-features` — verify all tests pass.
7. Commit as a single change for easy revert.

## Reference
Expand Down
1 change: 1 addition & 0 deletions docs/knowledge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Every topic has exactly one owning file. Link to it; don't restate it.
| Public Rust API stability | [contracts/122-rust-public-api-inventory.md](contracts/122-rust-public-api-inventory.md) |
| Compatibility and consumer rollout | [contracts/023-release-and-compatibility-rollout.md](contracts/023-release-and-compatibility-rollout.md) |
| How we release | [contracts/release.md](contracts/release.md) |
| Local Rust routing and agent/proof scratch | [contracts/123-local-rust-routing-and-scratch-placement.md](contracts/123-local-rust-routing-and-scratch-placement.md) |
| Writing style | [contracts/writing-style.md](contracts/writing-style.md) |
| Retired concepts | [retired.toml](retired.toml) |
| Open questions | [questions.md](questions.md) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,13 @@ Rules:
### Coverage tooling

Install Tarpaulin with `oxide install cargo-tarpaulin`. The coverage script
still checks for `cargo-tarpaulin` and runs
`cargo tarpaulin --workspace --out Xml`; Tarpaulin's instrumentation, profile,
and platform behavior is not qualified through Oxide, so its execution remains
a Cargo exception.
still requires `cargo-tarpaulin` and runs the tool as
`oxide exec -- cargo tarpaulin --workspace --out Xml`: Tarpaulin invokes Cargo
itself, so its inner `cargo` word belongs to the tool, and the `oxide exec`
lease is routing only. Arguments, thresholds and exit behaviour stay in
`rust/scripts/check-coverage.sh`. Routing does not qualify Tarpaulin's
instrumentation, profile or platform behaviour. See
[`123-local-rust-routing-and-scratch-placement.md`](123-local-rust-routing-and-scratch-placement.md).

### Underlay repository TypeScript formatting

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Contract: Local Rust Routing and Scratch Placement

Status: active
Owner: repo maintainers
Depends on: `022-testing-posture-and-shared-harnesses.md`, `120-tooling-testing-and-contract-artifacts.md`

## Purpose

Durable rules for two local-host facts about this checkout:

- which command runs Rust for ordinary local work
- where agent and proof scratch lives

Operator ruling: Tom, 2026-10-25, relayed by Oxide Supervisor. It supersedes the
earlier postponed exceptions and the pre-formal-v0.15 retention of `:cargo`
escape tasks. It does not retroactively qualify earlier recorded results and it
does not authorise replays of them.

This contract covers this repository. Consumer apps keep their own manifests and
their own commands; nothing here asks a consumer to install, adopt or rewrite
anything.

## Sources of Truth

- [`effigy.toml`](../../../effigy.toml) — the effective local route catalogue
- [`rust/scripts/check-coverage.sh`](../../../rust/scripts/check-coverage.sh) —
external tool routed through `oxide exec`
- [`rust/scripts/test-check-coverage.sh`](../../../rust/scripts/test-check-coverage.sh)
— fake-tool proof of that route
- [`scripts/test-consumer-conformance-fixtures.sh`](../../../scripts/test-consumer-conformance-fixtures.sh)
and [`scripts/auth-webauthn-manual-dossier.sh`](../../../scripts/auth-webauthn-manual-dossier.sh)
— proof scratch placement
- [`AGENTS.md`](../../../AGENTS.md), [`rust/AGENTS.md`](../../../rust/AGENTS.md)

## Rust routing

Ordinary local Rust work runs through Oxide.

- Modeled commands: `oxide check|test|clippy|build|run|fmt`. Effigy selectors
`rust:build`, `rust:check`, `rust:clippy`, `rust:test`, `fmt:rust` and
`fmt:rust:check` spell Oxide directly.
- A tool that runs Cargo itself, or needs its own target directory or
`CARGO_TARGET_DIR`, runs `oxide exec -- <tool> …`. The inner `cargo` word
belongs to that tool. Tarpaulin is the current case.
- Oxide owns build storage, so it refuses `--target-dir` and the build-dir
config keys.
- The `oxide init` block in `AGENTS.md` is managed; do not restate it.

No selector keeps a plain-Cargo `:cargo` sibling. Their removal is the point:
they read as supported local routes and quietly wrote build output into the
checkout's `target/`.

Escape posture:

- The only escapes are operator decisions, recorded on the task that uses them.
- `EFFIGY_OXIDE_ESCAPE=cargo` applies to a routed surface that honours it, such
as Effigy's own oxide-selector routes. Underlay's catalog routes spell `oxide`
directly, so they do not read that variable.
- Running Cargo by hand outside Effigy is the manual escape.
- No custom escape wrapper, no silent fallback, no automatic retry on Cargo. A
missing or refusing Oxide fails the run loudly.

## Remaining Cargo words by class

The current ordinary local Rust route inventory contains no unexplained Cargo
invocation. Cargo still appears where it is the subject or the host:

- hosted CI: `.github/workflows/` and CI examples in guides run Cargo, because
hosted runners have no Oxide.
- generic consumer recipes: guide snippets that `cd apps/api` and run a consumer
app's own workspace. These are consumer examples, not this checkout's routes.
- Cargo as subject or metadata: `cargo-tarpaulin` and other `cargo-*` binary
names, dependency and crate names, `[package]`/`[workspace]` manifest schema,
`cargo:` build-script directives, release notes and changelog entries about
Cargo, and workspace-shape checks that read `Cargo.toml`.
- product and fixture behavior: scripts, samples and fixtures whose behavior is
the thing being proved, plus container/VM guests that build inside their own
target directory.
- historical records: release, upgrade and sweep records that quote what a past
run executed, and guides that declare themselves a historical reference for
retired posture (for example `205-legacy-migration-framework.md`). They are
evidence, not live instructions. Recheck trigger: reviving such a guide as a
current route means re-routing its commands through Oxide before it can be
followed again.

These are classified, not pending migrations. Do not mechanically rewrite them.

## Scratch placement

Agent and proof scratch belongs on the Builds volume, never in a home
directory, a drive root, or the checkout.

- Place scratch in a unique directory below
`/Volumes/Builds/scratch/underlay/`, for example
`mktemp -d /Volumes/Builds/scratch/underlay/<label>.XXXXXXXX`.
- If the volume is unavailable, refuse and report it. Do not fall back to
`$TMPDIR`, `$HOME`, a drive root or the checkout, and do not create a
directory or link that pretends the volume is mounted.
- Delete only scratch your own run created. Preserve unknown state.
- Evidence records key presence and outcomes, never raw secret values.
- A generic `HOME` override is not a scratch strategy.
- Product-managed temp files are not agent scratch. Runtime algorithms and guest
`/tmp` files stay as they are; this rule governs the agent/proof surface, not
shipped behavior.
- The retired `~/ur.*` fixtures do not come back.
- Small historical scratch was archived to
`/Volumes/Builds/home-scratch-archive/2026-10-25/`. That is a location fact
only: it grants no cleanup, replay, adoption or relocation authority.

## Proof

- `effigy check:coverage-route` proves the coverage wrapper's Oxide dispatch,
argument pass-through, stdout/stderr and exit preservation, threshold
handling, and the missing-`cargo-tarpaulin` refusal, with fake executables and
no real coverage run.
- `effigy check:consumer-conformance-fixtures` exercises the scratch placement
end to end when the Builds volume is mounted.
- `scripts/auth-webauthn-manual-dossier.sh` needs live consumer apps, so it is
covered here by syntax and placement review only; running it is a real
qualification run, not a routine check.

## Related

- [`022-testing-posture-and-shared-harnesses.md`](022-testing-posture-and-shared-harnesses.md)
owns testing posture and the coverage threshold.
- [`120-tooling-testing-and-contract-artifacts.md`](120-tooling-testing-and-contract-artifacts.md)
owns shared tooling boundaries.
- [`023-release-and-compatibility-rollout.md`](023-release-and-compatibility-rollout.md)
owns consumer-visible rollout.
1 change: 1 addition & 0 deletions docs/knowledge/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ machine-readable policy files.
| [119-helper-search-and-lookup-route-catalogue.md](119-helper-search-and-lookup-route-catalogue.md) | M | Shared helper-route naming and bounded envelope contract for lookup, search, suggest, detect, requirements, and status routes |
| [120-tooling-testing-and-contract-artifacts.md](120-tooling-testing-and-contract-artifacts.md) | M | Shared Rust/TS test harnesses, migration/schema devtools, guardrail scanners, and machine-readable contract-artifact boundary |
| [122-rust-public-api-inventory.md](122-rust-public-api-inventory.md) | M | Rust public API classification and first typed-boundary migration gates for the `g06` platform-contract transition |
| [123-local-rust-routing-and-scratch-placement.md](123-local-rust-routing-and-scratch-placement.md) | L | Local-host Oxide routing for this checkout, the remaining Cargo uses by class, and agent/proof scratch placement on the Builds volume |

## Index

Expand Down
12 changes: 7 additions & 5 deletions effigy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,18 @@ check = "cd {repo} && bun x svelte-check --tsconfig ./ts/tsconfig.json"
"check:workspace-shape" = "cd {repo} && bun x vitest run ts/tests/tools/workspace-shape.test.ts ts/tests/tools/merge-pr-closeout-cleanup.test.ts"
"check:env-authority" = "cd {repo} && bun x vitest run ts/tests/tools/env-authority.test.ts"
"check:consumer-conformance-fixtures" = "cd {repo} && bash scripts/test-consumer-conformance-fixtures.sh"
"check:coverage-route" = "cd {repo} && bash rust/scripts/test-check-coverage.sh"
# Rust goes through Oxide (Cargo with managed, budgeted build storage off the
# laptop SSD). The `:cargo` selectors are the plain-Cargo fallback if Oxide
# misbehaves; they write to the checkout's `target/`.
# laptop SSD). No selector keeps a plain-Cargo `:cargo` sibling: the only
# escape is an operator decision (`EFFIGY_OXIDE_ESCAPE=cargo` on a routed
# surface that honours it, or Cargo by hand outside Effigy), never a silent
# fallback. A tool that runs Cargo itself or needs its own target directory
# goes through `oxide exec -- <tool>`. See
# docs/knowledge/contracts/123-local-rust-routing-and-scratch-placement.md.
"rust:build" = "cd {repo} && oxide build --workspace"
"rust:check" = "cd {repo} && oxide check --workspace --all-features"
"rust:clippy" = "cd {repo} && oxide clippy --workspace --all-targets -- -D warnings"
"rust:test" = "cd {repo} && oxide test --workspace --all-features"
"rust:check:cargo" = "cd {repo} && cargo check --workspace --all-features"
"rust:clippy:cargo" = "cd {repo} && cargo clippy --workspace --all-targets -- -D warnings"
"rust:test:cargo" = "cd {repo} && cargo test --workspace --all-features"
"fmt:rust" = "cd {repo} && oxide fmt --all"
"fmt:rust:check" = "cd {repo} && oxide fmt --all --check"
"fmt:ts" = "cd {repo} && ./node_modules/.bin/prettier --write {args}"
Expand Down
11 changes: 8 additions & 3 deletions rust/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ tooling (`underlay-devtools`, `underlay-testing`).
## Validation

```bash
effigy rust:check # cargo check --workspace --all-features
effigy rust:clippy # cargo clippy --workspace --all-targets -- -D warnings
effigy rust:test # cargo test --workspace --all-features
effigy rust:check # oxide check --workspace --all-features
effigy rust:clippy # oxide clippy --workspace --all-targets -- -D warnings
effigy rust:test # oxide test --workspace --all-features
```

Ordinary local Rust routes go through Oxide; there is no plain-Cargo `:cargo`
sibling. A tool that runs Cargo itself (Tarpaulin) runs
`oxide exec -- <tool> …`. See
`../docs/knowledge/contracts/123-local-rust-routing-and-scratch-placement.md`.

`underlay-db`'s Postgres integration target is `#[ignore]`d and needs a Docker
runtime:

Expand Down
2 changes: 1 addition & 1 deletion rust/crates/underlay-audit/src/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//!
//! ```bash
//! UNDERLAY_TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres \
//! cargo test -p underlay-audit -- --ignored
//! oxide test -p underlay-audit -- --ignored
//! ```

use serde_json::json;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! ```bash
//! UNDERLAY_TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres \
//! cargo test -p underlay-auth-state-postgres -- --ignored
//! oxide test -p underlay-auth-state-postgres -- --ignored
//! ```
//!
//! Without `UNDERLAY_TEST_DATABASE_URL`, `TestDb` falls back to a testcontainer
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/underlay-db/tests/postgres_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use underlay_db::{create_pool, drop_schemas, validate_schema_name, DbConfig, Des
// colima start
//
// Run with:
// cargo test -p underlay-db --test postgres_integration -- --ignored
// oxide test -p underlay-db --test postgres_integration -- --ignored

use testcontainers::runners::SyncRunner;
use testcontainers::Container;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//!
//! ```bash
//! UNDERLAY_TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres \
//! cargo test -p underlay-jobs-postgres --lib -- --ignored
//! oxide test -p underlay-jobs-postgres --lib -- --ignored
//! ```
//!
//! This adapter hardcodes the `platform` schema, so per-test schema isolation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//!
//! ```bash
//! UNDERLAY_TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres \
//! cargo test -p underlay-media-postgres --lib -- --ignored
//! oxide test -p underlay-media-postgres --lib -- --ignored
//! ```
//!
//! The fixture builds the four media tables from the *adapter's* column usage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! ```bash
//! UNDERLAY_TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres \
//! cargo test -p underlay-ratelimit --features postgres --lib -- --ignored
//! oxide test -p underlay-ratelimit --features postgres --lib -- --ignored
//! ```
//!
//! This is the test the `g08.008` card promised: two backend instances (two
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//!
//! ```bash
//! UNDERLAY_TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres \
//! cargo test -p underlay-security-alerts -- --ignored
//! oxide test -p underlay-security-alerts -- --ignored
//! ```

use chrono::{Duration, Utc};
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/underlay-testing/src/tests/test_db_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;

// Docker-backed tests are ignored by default.
// Run with: cargo test -p underlay-testing --features db -- --ignored
// Run with: oxide test -p underlay-testing --features db -- --ignored

#[tokio::test]
#[ignore]
Expand Down
8 changes: 6 additions & 2 deletions rust/scripts/check-coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ set -euo pipefail
# Local prerequisites:
# - cargo-tarpaulin installed (or let this script tell you how)
#
# Tarpaulin runs Cargo itself, so it is launched through `oxide exec`, the
# supported route for a tool that owns its Cargo invocation and target
# directory. Arguments, thresholds and output handling stay here.
#
# CI is expected to have a Docker runtime available.

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
Expand All @@ -21,14 +25,14 @@ fi
MIN_OVERALL=50

echo "Running tarpaulin (workspace)…"
OUTPUT="$(cargo tarpaulin --workspace --out Xml 2>&1)" || {
OUTPUT="$(oxide exec -- cargo tarpaulin --workspace --out Xml 2>&1)" || {
echo "$OUTPUT" >&2
exit 1
}

# Extract overall % from the final summary line, e.g.
# "54.01% coverage, 606/1122 lines covered"
OVERALL_PCT="$(echo "$OUTPUT" | grep -Eo '^[0-9]+(\.[0-9]+)?% coverage' | tail -1 | cut -d% -f1)"
OVERALL_PCT="$(echo "$OUTPUT" | grep -Eo '^[0-9]+(\.[0-9]+)?% coverage' | tail -1 | cut -d% -f1 || true)"

if [[ -z "$OVERALL_PCT" ]]; then
echo "Failed to parse tarpaulin output:" >&2
Expand Down
Loading
Loading