Skip to content

archcheck: block new catch-all crate names (common, utils, core, ...) - #407

Merged
ac-pplx merged 2 commits into
mainfrom
ac/archcheck-crate-names
Oct 16, 2026
Merged

ac-pplx merged 2 commits into
mainfrom
ac/archcheck-crate-names

Conversation

@ac-pplx

@ac-pplx ac-pplx commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

A new cargo arch rule, crate_names, that stops new crates from getting catch-all names.

What is checked.

  • Which crates: every workspace member as Cargo sees it, from cargo metadata --no-deps --offline run from the root. That covers the root Cargo.toml's members plus every in-workspace path dependency, which Cargo also makes a member.

  • Which names: each member's package name and its library target name ([lib] name or Cargo's default), lowercased and split on - and _.

  • What matches: a crate matches if any whole part is in banned_tokens:

    common, shared, util, utils, helper, helpers, misc, core

    So pplx-space-common-io, foo_core and a library named utils match; database, coreutils and encore don't.

  • Fail-closed (exit 2):

    • cargo metadata fails;
    • an empty token list;
    • a token that can't match a part (empty, uppercase, or containing -/_);
    • an invalid allow list.

    file_size and pattern_ratchet use the same member list.

allow. A list in [rules.crate_names] of exact package names exempt from the rule (e.g. a pplx-space-shared-memory). It starts empty.

  • Names must be lowercase and unique.
  • A name that is both allowed and baselined exits 2.

Baseline. A new [crate_names] table in architecture-baseline.toml maps package name → member path. It lists the 16 crates that match today:

  • common-core, common-svc, common-cli, common-grpc, common-grpc-lb, common-io, common-jobs, common-lru-reaper, common-proto-build, common-work-coalescer, common-connection-admission
  • net-common, execution-common, spaced-common
  • node-runtime-core, credential-injection-core

(all with the pplx-space- prefix). An entry covers only the crate at its path. A listed crate that moves needs a one-line edit to its entry, and a removed crate's name can't be reused at another path. The baseline diff only appends this table.

Behaviours.

  • no_new (required, ERROR):
    • A matching crate that isn't listed at its path. Shown on the name = line of its Cargo.toml, or on the [lib] name line when only the library name matches.
    • A [crate_names] entry the PR may not list, shown on the entry's line:
      • With the base's table (--base-baseline), any name the base lacks. Updating an existing entry's path doesn't count as new.
      • Without a copy of that table, only the names in a fixed bootstrap set compiled into archcheck (exactly the 16 above) may be listed. That covers a base that predates the rule, which is this PR's own case, and a base CI can't resolve.
      • A base table that doesn't parse counts as empty, with a warning.
  • stale_entries (advisory): an entry whose crate is gone, renamed or no longer matches, or an allow name that no longer applies.
  • policy_changed (advisory): with --base-architecture, each difference in [rules.crate_names] from the base's ARCHITECTURE.toml is reported on its line: tokens or allow names added or removed, modes changed, or the whole table new. The base copy is read leniently. Without the flag, the behaviour is skipped, and one line says so.
  • --write-baseline never adds an entry. It removes stale ones, leaves an entry whose crate moved for a deliberate edit, and always writes [crate_names], even when empty, after the other tables. Unlike file_size and pattern_ratchet, it prints no entries to paste, because a hand-added entry fails no_new. --write-baseline=crate_names updates that table alone.

Enforcement

  • CI: the archcheck step passes both the base architecture-baseline.toml and the base ARCHITECTURE.toml. It uses an empty file if the base lacks one, and warns with exactly what is skipped if the base can't be resolved.
  • reviewdog: no_new emits ERROR, so the -fail-level=error run fails the Rust clippy job in space-pull-ci. Every case is annotated in either filter mode:
    • a new crate's finding is on its own new Cargo.toml;
    • a renamed library's finding is on a manifest the PR edits; moving a listed crate changes the root Cargo.toml's members, where all 16 are, which switches reviewdog to nofilter;
    • a self-listed entry or an allow edit changes the baseline or ARCHITECTURE.toml, which switches reviewdog to nofilter.
  • Finding order (ci: annotate archcheck findings in changed files first #493): order-archcheck-findings.sh puts crate_names.policy_changed with the other base comparisons (no_new_entries, no_raised_caps) at the front of the advisory run. A stale [crate_names] entry counts as the PR's own when the PR changes the Cargo.toml at the entry's member path. Required findings are still annotated from archcheck's own report, in the run that fails the job.
  • tests/repository.rs fails on any required finding in the tree, including the one case file mode wouldn't annotate: a listed crate that is only a path dependency, moved without editing its manifest.

Why modules are out of scope

  • Catch-all modules haven't been appearing in the history.
  • A module's reach is limited to its crate.
  • file_size already bounds how much a module can hold.
  • Module name checks are easy to evade (util.rs → support.rs).

Combined with the shared count ratchet (#494) and finding order (#493)

Rebased onto main after #494, #493, #473 and the baseline and config edits (#504, #498, #417, #421, #413).

  • archcheck: share one per-file count ratchet between file_size and pattern_ratchet #494's structure is kept. CrateNames implements Rule directly, as the README's recipe says for a rule whose table isn't a count ratchet; its table maps names to member paths, not counts.
    • Rule::tables names crate_names, so the configured-table check and --write-baseline=<table> accept it.
    • Rule::baseline_rule makes Baseline::load_base read it. load_base reads the [crate_names] key from the parsed document on its own, records the rule as read (or why it can't be read), and BaseBaseline::crate_names() reports NotGiven, Unusable or NotRead like table() does.
    • The in-memory Baseline keeps [crate_names] beside its count tables. table_names() lists it last, table_len counts it, replace_table handles it, and write adds it after the other tables with its comment.
    • NotAdded's path/count become entry: Option<(String, usize)>, set in ratchet.rs; file_size.rs and pattern_ratchet.rs are exactly as on main.
  • Baseline and config: [rules.crate_names] and [crate_names] are appended after main's tables. [file_size] and the [pattern_ratchet.*] tables are byte-identical to main, including rootfs_copy's hand-written comments.
  • CI (ci: restore main's package caches so runs fetch only changed packages #473, ci: annotate archcheck findings in changed files first #493): the cache steps and the annotate step are as merged. Only the archcheck step's base-copy block changes, and its warning now says no_new_entries and no_raised_caps (every table's).
  • Tests: archcheck: share one per-file count ratchet between file_size and pattern_ratchet #494's tests are unchanged except for the always-present crate_names lines: two skip-line assertions, the unit-test configs (crate_names config is required) and every_configured_table_is_read_from_a_base_copy, which checks the [crate_names] names. Two new cases in test-order-archcheck-findings.sh fail on main's script.

Commits

  1. The rule, its config, the baseline table, cargo metadata member discovery, the bootstrap set, name-and-path matching, library names, allow, policy_changed and the README, fitted into archcheck: share one per-file count ratchet between file_size and pattern_ratchet #494's ratchet structure.
  2. CI: passes the base ARCHITECTURE.toml, fixes the warning and comment text, and orders crate_names findings in order-archcheck-findings.sh, with tests.

cargo metadata adds one member, pplx-space-credential-injection-store, a path dependency that isn't in members. Its files are all under the file_size limit, so the file_size and pattern_ratchet findings are identical to main's (1237 vs 1233 files checked).

Validation

  • cargo fmt --all --check, cargo clippy -p pplx-space-archcheck --all-targets -- -D warnings, cargo test -p pplx-space-archcheck (5 unit, 42 CLI, tests/repository.rs), test-order-archcheck-findings.sh and check-rust-error-policy.sh all pass.
  • actionlint: the only error is the self-hosted runner label, which main also reports. rustdoc -D warnings reports one error, a private intra-doc link to CountRule from archcheck: share one per-file count ratchet between file_size and pattern_ratchet #494, the same on main.
  • cargo arch exits 0 with 77 advisory findings and no required ones: file_size 2 no_crossing, 69 listed_no_growth, 6 stale_entries. The rdjson diagnostics are identical to main's binary on main. With both base flags against main, the only addition is the advisory policy_changed (whole policy new), which the ordering script puts first.
  • Each bypass, reproduced in a scratch worktree, exits 1 with a no_new ERROR, both with main's base copies and with no base:
    • a foo-utils crate added only as a path dependency;
    • a self-listed entry;
    • common-jobs moved to a new path under the same name;
    • a crate whose [lib] name = "utils".
  • --write-baseline writes the 16 crate_names entries.

This PR was written with AI assistance (Claude Code) and reviewed against the repository's public-review checklist.

🤖 Generated with Claude Code

@ac-pplx
ac-pplx force-pushed the ac/archcheck-crate-names branch 2 times, most recently from 2ef4493 to 5989905 Compare October 14, 2026 20:07
@ac-pplx
ac-pplx marked this pull request as ready for review October 14, 2026 20:28
Add a crate_names rule. Each workspace member's package name, and its
library target's name, is lowercased and split on - and _; the crate
matches if a whole part is a banned token (common, shared, util, utils,
helper, helpers, misc, core), unless `allow` in [rules.crate_names]
names the package exactly. `allow` starts empty; its names must be
lowercase and unique, and a name both allowed and baselined exits 2.

- no_new (required): a matching crate that the new [crate_names]
  baseline table does not list at its path, reported at the name = (or
  [lib] name) line of its Cargo.toml; and a [crate_names] entry a pull
  request may not list, reported at the entry's line. With
  --base-baseline, that is an entry whose name the base branch's table
  lacks; updating an existing entry's path is not a new entry. Without
  a base copy of the table (the base predates the rule, or can't be
  resolved), only a fixed set compiled into archcheck, the 16 crates
  that matched when the rule was added, may be listed. A base table
  that doesn't parse, or a base copy that isn't TOML, counts every entry
  as added.
- stale_entries (advisory): an entry whose crate is gone, renamed or no
  longer matches, and an allow name that no longer applies.
- policy_changed (advisory): with the new --base-architecture flag,
  every change to [rules.crate_names] compared with the base branch's
  ARCHITECTURE.toml (read leniently), on its line, so loosening the
  policy is visible in review.

Workspace members now come from `cargo metadata --no-deps --offline`,
run from the root, for every rule: Cargo also makes an in-workspace path
dependency a member, so a crate added only that way was never checked.
If cargo metadata fails, archcheck exits 2. This adds
pplx-space-credential-injection-store, whose files are all under the
file_size limit, so file_size findings are unchanged; pattern_ratchet
reads the same inventory and finds nothing new either.

The baseline lists the 16 crates that match today, in a [crate_names]
table after the other tables. --write-baseline never adds an entry:
it removes stale ones, leaves an entry whose crate moved for a
deliberate edit, lists no entries to paste (a hand-added one fails
no_new), and always writes the table, even when empty.
--write-baseline=crate_names updates that table alone.

crate_names fits the shared baseline structure of the count ratchet:
it implements Rule directly, not CountRule, since its table maps names
to member paths rather than capping a count. Rule::tables names its
crate_names table, so the configured-table check and
--write-baseline=<table> accept it, and Rule::baseline_rule makes
Baseline::load_base read it: the base reader takes the [crate_names] key
from the parsed document on its own, beside [file_size] and each
[pattern_ratchet.<instance>] table, and records it as read. The
in-memory Baseline keeps it beside the count tables and writes it last.
NotAdded's path and count become an optional entry, so the crate_names
lines offer nothing to paste. Rule::other_skips reports the
--base-architecture skip line. Config is
fail-closed: an empty token list, or a token that is empty, uppercase or
contains - or _, exits 2.

Module names are out of scope; the README explains why.
The archcheck step now also copies the base's ARCHITECTURE.toml, the
same way as its baseline, for crate_names.policy_changed. Its warnings
say exactly what is skipped when a base file can't be read: the
no_new_entries and no_raised_caps comparisons and policy_changed are
skipped, and crate_names.no_new allows only the crates listed when it
was added.

order-archcheck-findings.sh orders crate_names findings like the
others. policy_changed compares with the base's ARCHITECTURE.toml, so it
leads with no_new_entries and no_raised_caps instead of coming last in
report order. A stale [crate_names] entry is the pull request's own when
the pull request changes the Cargo.toml of the member path the entry
maps to (the key is a package name, not a file). Required findings are
still annotated from archcheck's own report, in the run that fails the
job. The README says so, and that every listed crate is a root
`members` entry, so moving one switches reviewdog to nofilter.
@ac-pplx
ac-pplx force-pushed the ac/archcheck-crate-names branch from 5989905 to 1eb58d8 Compare October 15, 2026 22:35
@ac-pplx
ac-pplx added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit a621b08 Oct 16, 2026
20 checks passed
@ac-pplx
ac-pplx deleted the ac/archcheck-crate-names branch October 16, 2026 04:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants