CI formats, lints and rustdocs no shipped guest crate — all four are outside the workspace and outside every gate #276

Closed
opened 2026-09-15 15:46:52 +02:00 by buildagent · 1 comment
Member

Found while building de.h-dv.svelte (#268). Pre-existing, not introduced by that work, and it affects code that ships inside signed packages operators install.

Measured

Four guest crates each declare their own [workspace] table and appear in the root Cargo.toml's exclude list:

crate own [workspace] in root exclude
crates/guest/example yes yes
crates/guest/ruby yes yes
crates/guest/timeline yes yes
crates/guest/svelte yes yes

That is correct and deliberate — they are cdylibs with panic = "abort" for wasm32-unknown-unknown, and the root exclude comment says plainly that "a host cargo build --workspace has no business linking either, and cargo would try."

The consequence is the part nothing states: cargo fmt --all from the repository root does not see a single file under any of them, and neither does cargo clippy --workspace --all-targets or cargo doc --workspace. Those three commands are the entire formatting/linting/doc gate in ci.yml.

Then the workflows:

  • .forgejo/workflows/ci.yml — mentions crates/guest exactly ONCE, at line 644, inside a COMMENT about crates/guest/example/build.sh. No step reads any guest source.
  • .forgejo/workflows/ci-windows.yml — zero mentions.
  • .forgejo/workflows/release.yml — two mentions, both packing paths.
  • No test under crates/indexer/tests/ formats, lints or rustdocs a guest.

So: nothing in CI formats, lints, or rustdocs any shipped guest crate.

How it was demonstrated rather than reasoned about

During #268 the svelte lane appended a deliberately misformatted function to crates/guest/svelte/src/expr.rs:

  • cargo fmt --all -- --check run from the CRATE directory — RED.
  • the same command run from the repository ROOT — the file is not seen at all.

That is the whole gap in one mutation.

Why it matters more than the usual "CI does not lint X"

crates/guest/ruby, crates/guest/timeline and now crates/guest/svelte are the SOURCE of extractor.wasm inside de.h-dv.ruby, de.h-dv.timeline and de.h-dv.svelte. Those artifacts are signed with the first-party key, published as release assets, pinned by digest, and installed into a machine-wide store. They are also the code that runs inside the sandbox on an operator's files.

A clippy lint that CI never runs on a package's extractor is a lint that never ran on the thing this project asks operators to trust most. The current state depends entirely on whoever last touched a guest having remembered to run the three commands by hand, from the right directory — which is a convention, not a gate, and this repository has a long record of what happens to those.

It also interacts badly with the dev loop: a contributor who runs the documented cargo fmt --all -- --check from the root gets a GREEN that means nothing about the crate they just edited.

What this does NOT claim

That any guest is currently unformatted or carries a clippy warning. Every guest lane to date has run the three commands by hand from the crate directory, and #268's did so and recorded the exit codes. The claim is about what is GATED, not about the present state of the tree.

Options

  1. A CI step per guest crate. Explicit, and it grows by one block per guest. The set is small and changes rarely.
  2. One CI step that DERIVES the set from the root Cargo.toml's exclude list (or from crates/guest/*/Cargo.toml) and loops fmt/clippy/doc over it. Preferred on this repository's own precedent: GRAMMAR_BUILDS and WASM_ARTIFACTS are both lists-plus-a-loop for exactly this reason, and grammar_provenance.rs's comment says why — "a THIRD grammar arrives with three more copies, and the fix for a weakness found in one of them has to be applied by hand in each."
  3. A source-shape gate in crates/indexer/tests/ asserting every directory matching crates/guest/*/Cargo.toml is named by a CI step, derived on both sides so a guest added without a step is red on the day it is added. This is the shape ci_disk_preflight.rs already uses for the free-disk step, and it closes the "a new guest silently escapes" hole that options 1 and 2 leave open.

2 and 3 are complementary rather than alternatives: the loop does the work, the gate proves the loop's population is the real one.

Note for whoever takes this: the guests need --target wasm32-unknown-unknown for clippy, and they are no_std. The #268 lane ran cargo clippy --release --target wasm32-unknown-unknown -- -D warnings and RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items from inside the crate; both are clean today, so a step added now starts green.

Found while building `de.h-dv.svelte` (#268). Pre-existing, not introduced by that work, and it affects code that ships inside signed packages operators install. ## Measured Four guest crates each declare their own `[workspace]` table and appear in the root `Cargo.toml`'s `exclude` list: | crate | own `[workspace]` | in root `exclude` | | :-- | :-- | :-- | | `crates/guest/example` | yes | yes | | `crates/guest/ruby` | yes | yes | | `crates/guest/timeline` | yes | yes | | `crates/guest/svelte` | yes | yes | That is correct and deliberate — they are `cdylib`s with `panic = "abort"` for `wasm32-unknown-unknown`, and the root `exclude` comment says plainly that "a host `cargo build --workspace` has no business linking either, and cargo would try." The consequence is the part nothing states: **`cargo fmt --all` from the repository root does not see a single file under any of them**, and neither does `cargo clippy --workspace --all-targets` or `cargo doc --workspace`. Those three commands are the entire formatting/linting/doc gate in `ci.yml`. Then the workflows: * `.forgejo/workflows/ci.yml` — mentions `crates/guest` exactly ONCE, at line 644, inside a COMMENT about `crates/guest/example/build.sh`. No step reads any guest source. * `.forgejo/workflows/ci-windows.yml` — zero mentions. * `.forgejo/workflows/release.yml` — two mentions, both packing paths. * No test under `crates/indexer/tests/` formats, lints or rustdocs a guest. So: **nothing in CI formats, lints, or rustdocs any shipped guest crate.** ## How it was demonstrated rather than reasoned about During #268 the svelte lane appended a deliberately misformatted function to `crates/guest/svelte/src/expr.rs`: * `cargo fmt --all -- --check` run from the CRATE directory — **RED**. * the same command run from the repository ROOT — **the file is not seen at all**. That is the whole gap in one mutation. ## Why it matters more than the usual "CI does not lint X" `crates/guest/ruby`, `crates/guest/timeline` and now `crates/guest/svelte` are the SOURCE of `extractor.wasm` inside `de.h-dv.ruby`, `de.h-dv.timeline` and `de.h-dv.svelte`. Those artifacts are signed with the first-party key, published as release assets, pinned by digest, and installed into a machine-wide store. They are also the code that runs inside the sandbox on an operator's files. A clippy lint that CI never runs on a package's extractor is a lint that never ran on the thing this project asks operators to trust most. The current state depends entirely on whoever last touched a guest having remembered to run the three commands by hand, from the right directory — which is a convention, not a gate, and this repository has a long record of what happens to those. It also interacts badly with the dev loop: a contributor who runs the documented `cargo fmt --all -- --check` from the root gets a GREEN that means nothing about the crate they just edited. ## What this does NOT claim That any guest is currently unformatted or carries a clippy warning. Every guest lane to date has run the three commands by hand from the crate directory, and #268's did so and recorded the exit codes. The claim is about what is GATED, not about the present state of the tree. ## Options 1. **A CI step per guest crate.** Explicit, and it grows by one block per guest. The set is small and changes rarely. 2. **One CI step that DERIVES the set** from the root `Cargo.toml`'s `exclude` list (or from `crates/guest/*/Cargo.toml`) and loops fmt/clippy/doc over it. Preferred on this repository's own precedent: `GRAMMAR_BUILDS` and `WASM_ARTIFACTS` are both lists-plus-a-loop for exactly this reason, and `grammar_provenance.rs`'s comment says why — "a THIRD grammar arrives with three more copies, and the fix for a weakness found in one of them has to be applied by hand in each." 3. **A source-shape gate** in `crates/indexer/tests/` asserting every directory matching `crates/guest/*/Cargo.toml` is named by a CI step, derived on both sides so a guest added without a step is red on the day it is added. This is the shape `ci_disk_preflight.rs` already uses for the free-disk step, and it closes the "a new guest silently escapes" hole that options 1 and 2 leave open. 2 and 3 are complementary rather than alternatives: the loop does the work, the gate proves the loop's population is the real one. Note for whoever takes this: the guests need `--target wasm32-unknown-unknown` for clippy, and they are `no_std`. The #268 lane ran `cargo clippy --release --target wasm32-unknown-unknown -- -D warnings` and `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items` from inside the crate; both are clean today, so a step added now starts green.
Author
Member

Closed by PR #287 (bea410f), options 2 + 3 as suggested: .forgejo/scripts/guest_gates.sh does the loop, crates/indexer/tests/ci_guest_gates.rs proves the loop's population is the real one.

Verified by dispatch, not by shape alone: CI run #5372 on 7a5fd70 — 14/14 jobs green, 1h33m02s. The new job cost ~2.5 min and did not extend the lane. Its runner log shows it graded all four crates, each getting clippy (Finished release) and rustdoc (Documenting … → Generated …/index.html).

Two corrections to this issue's text, recorded because both were load-bearing and a future reader will hit them:

1. The exclude table is wrong, and it is the trap. This issue states all four guests appear in the root Cargo.toml's exclude list, and offers deriving the set from it as the first form of option 2. Only example and ruby are there. timeline and svelte are outside the workspace purely by virtue of their own [workspace] table, which is what actually excludes a crate — exclude is optional for one that carries it.

Run as a mutation, the exclude-derived discovery grades two of the four crates, silently, and the half it drops contains svelte — the newest guest, and precisely the one a new gate exists to catch. Discovery therefore keys on the [workspace] table, and guest_gates_population_is_not_derivable_from_the_exclude_list pins the difference so it cannot be "simplified" back.

2. "Both are clean today, so a step added now starts green" is false for rustdoc. True of fmt and clippy on all four. rustdoc was RED on two, and the gate is green only because they were fixed first:

  • crates/guest/ruby/src/lib.rs — [`extract`] is ambiguous, naming both mod extract and pub extern "C" fn extract.
  • crates/guest/timeline/src/lib.rs — [`OUT_LEN`] is a private item linked from the public docs of SRC_OFFSET, resolving only under --document-private-items and 404ing in any other render.

Both were demoted to citations per the one intra-doc rule; neither was #[allow]ed.

One addition for whoever touches a guest next. The note here prescribes cargo clippy --release --target wasm32-unknown-unknown -- -D warnings, and the omission of --all-targets turns out to be structural rather than incidental: these crates are no_std with their own #[panic_handler], so the lib-test target links std's and fails with error[E0152]: found duplicate lang item panic_impl before any lint runs. Measured on all four — every one fails with the flag, every one is clean without it. The script says so at the call site.

🤖 Generated with Claude Code

https://claude.ai/code/session_0126PDDLB4wNHxKXvWM1VNmu

Closed by PR #287 (`bea410f`), options 2 + 3 as suggested: `.forgejo/scripts/guest_gates.sh` does the loop, `crates/indexer/tests/ci_guest_gates.rs` proves the loop's population is the real one. **Verified by dispatch**, not by shape alone: CI run [#5372](https://git.h-dv.de/h-dv/code-index/actions/runs/851) on `7a5fd70` — 14/14 jobs green, 1h33m02s. The new job cost ~2.5 min and did not extend the lane. Its runner log shows it graded all four crates, each getting clippy (`Finished release`) and rustdoc (`Documenting …` → `Generated …/index.html`). Two corrections to this issue's text, recorded because both were load-bearing and a future reader will hit them: **1. The `exclude` table is wrong, and it is the trap.** This issue states all four guests appear in the root `Cargo.toml`'s `exclude` list, and offers deriving the set from it as the first form of option 2. **Only `example` and `ruby` are there.** `timeline` and `svelte` are outside the workspace purely by virtue of their own `[workspace]` table, which is what actually excludes a crate — `exclude` is optional for one that carries it. Run as a mutation, the `exclude`-derived discovery grades **two of the four** crates, silently, and the half it drops contains `svelte` — the newest guest, and precisely the one a new gate exists to catch. Discovery therefore keys on the `[workspace]` table, and `guest_gates_population_is_not_derivable_from_the_exclude_list` pins the difference so it cannot be "simplified" back. **2. "Both are clean today, so a step added now starts green" is false for rustdoc.** True of fmt and clippy on all four. rustdoc was **RED on two**, and the gate is green only because they were fixed first: - `crates/guest/ruby/src/lib.rs` — ``[`extract`]`` is ambiguous, naming both `mod extract` and `pub extern "C" fn extract`. - `crates/guest/timeline/src/lib.rs` — ``[`OUT_LEN`]`` is a private item linked from the public docs of `SRC_OFFSET`, resolving only under `--document-private-items` and 404ing in any other render. Both were demoted to citations per the one intra-doc rule; neither was `#[allow]`ed. **One addition for whoever touches a guest next.** The note here prescribes `cargo clippy --release --target wasm32-unknown-unknown -- -D warnings`, and the omission of `--all-targets` turns out to be structural rather than incidental: these crates are `no_std` with their own `#[panic_handler]`, so the lib-test target links std's and fails with `error[E0152]: found duplicate lang item panic_impl` before any lint runs. Measured on all four — every one fails with the flag, every one is clean without it. The script says so at the call site. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0126PDDLB4wNHxKXvWM1VNmu
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#276
No description provided.