Skip to content

refactor: drop single-impl traits, fix all 49 doc-link warnings, dedupe docs - #544

Merged
Destynova2 merged 7 commits into
mainfrom
refactor/drop-single-impl-traits
Aug 6, 2026
Merged

refactor: drop single-impl traits, fix all 49 doc-link warnings, dedupe docs#544
Destynova2 merged 7 commits into
mainfrom
refactor/drop-single-impl-traits

Conversation

@Destynova2

@Destynova2 Destynova2 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Second pass of the audit. #543 removed the dead mocks; this removes the abstraction they existed to serve, fixes every broken doc link, and collapses the duplicated docs.

Code: the five single-impl traits

DlpPipeline, RequestRouter, AuditWriter, EventTap, SpendTracking each had exactly one implementation, and every method in it delegated straight to the concrete type.

Four of the five were never used as dyn or as a generic bound anywhere. The concrete type was already the field type in AppState:

Trait AppState field Actual type
RequestRouter router Router
AuditWriter audit_log Option<Arc<AuditLog>>
EventTap tap_sender Option<Arc<TapSender>>
DlpPipeline (n/a) DlpEngine

SpendTracking was the one real Box<dyn>, at server/mod.rs:152, but also had a single implementor. It becomes Mutex<SpendTracker>. Its default method bodies existed only so test mocks could no-op, and those mocks went in #543.

Tracer and ProviderAvailability stay. Each has two real implementations and is genuinely polymorphic at server/mod.rs:406-411.

traits.rs: 458 to 120 lines.

Docs: 49 broken intra-doc links to zero

Every one was a dead reference in published API documentation. Failure modes: links from a module-level //! block to an item in that same module (rustdoc needs an absolute path there), enum variants written as bare names, links to private items from public docs, and two bare URLs in clap help text. Two were simply wrong, pointing at functions on types that do not exist.

Adds deny(broken_intra_doc_links) and deny(private_intra_doc_links). A warning nobody reads is not a signal; a dead link now fails the build the way missing_docs already does.

Docs: two entry points became one

  • docs/README.md and docs/index.md were both indexes of the same directory. Nothing linked to README, and the two had drifted. Merged the eight links only README carried, then deleted it.
  • tutorials/quickstart.md was a strict subset of getting-started.md and had gone stale, claiming the perf preset configures "Anthropic + OpenAI + Gemini" when it is Anthropic OAuth with no fallback. The three-command path is now an "In a hurry" section of the one remaining tutorial.
  • The ADR table in index.md was hand-maintained and wrong in five places (0018 and 0019 marked proposed though accepted, 0004 and 0028 not shown as superseded, 0027/0028/0029 missing). Regenerated from each ADR's status field.
  • Linked four docs no index reached: protocol fidelity matrix, OTLP exemplars, design/, and the hedge-billing protocol ADR-0020 depends on.

ADR-0018

Carried 378 lines of sprint material after the decision: target schemas for a schema that does not exist, an RE-0..RE-8 flowchart, effort estimates in LoC, pseudocode, migration examples. Nothing references any of it. Kept context, drivers, options, decision, consequences, references, glossary, open questions. 805 to 427 lines.

Usage: one command instead of two

grob completions <shell> printed to stdout; grob setup-completions detected the shell and installed. A new user had to read both help texts to find out which they wanted. The shell argument is now optional. setup-completions stays as a hidden alias, so nothing breaks. 25 visible root commands become 24.

Deliberately not done

  • Unifying the two circuit breakers. Not duplicates: security::CircuitBreakerRegistry is per provider and backs ProviderAvailability; routing::circuit_breaker is per endpoint with lock-free atomics on the hot path. Different granularities, both live.
  • Embedding ProviderBase in GeminiProvider. Worth ~40 LOC, but Gemini's api_key is Option<SecretString> where ProviderBase's is SecretString. Making a shared field optional to suit one provider costs more than the duplication.

Verification

1727 tests pass; clippy clean with --all-features --all-targets and --no-default-features; doctests pass; cargo doc emits zero warnings (was 49); fmt and markdownlint clean; both completion paths exercised by hand.

Base automatically changed from refactor/drop-dead-trait-mocks to main August 6, 2026 07:29
Each of DlpPipeline, RequestRouter, AuditWriter, EventTap and SpendTracking
had exactly one implementation whose every method delegated to the concrete
type. Four of the five were never used as `dyn` or as a generic bound
anywhere: the concrete type was already the field type in AppState. The
abstraction cost 320 lines and bought nothing.

SpendTracking was the only real `Box<dyn>`, at server/mod.rs:152, but it
too had a single implementor. It becomes `Mutex<SpendTracker>`. Its default
method bodies existed to keep test mocks no-op, and those mocks were deleted
in the previous commit.

Two methods lived only inside a trait impl and are now inherent:
`DlpEngine::scan_input_enabled`/`scan_output_enabled` (only exercised by
mutation-killing tests) and `SpendTracker::provider_breakdown` (called by
rpc/budget_ns.rs).

Tracer and ProviderAvailability stay: each has two real implementations and
is genuinely polymorphic at server/mod.rs:406-411.

traits.rs: 458 -> 120 lines. 1727 tests pass, clippy clean with all features
and with --no-default-features, doctests pass.
An ADR records a decision and its rationale. ADR-0018 carried 378 lines of
sprint material after the decision: target TOML schemas for a schema that
does not exist yet, an RE-0..RE-8 phase flowchart, per-phase effort estimates
in LoC, pseudocode sketches for five primitives, a full speculative schema
reference, and before/after migration examples. None of it is referenced
from anywhere else in the tree.

Kept: context, drivers, the five options with pros and cons, the decision,
consequences, confirmation, internal and external references, the biomimetic
glossary (it explains why the code uses engineering names, not animal ones),
and the open questions.

805 -> 427 lines. markdownlint clean.
The README files and the feature matrix still advertised the five traits
deleted in the previous commit. AGENTS.md claimed '7+ traits enable testing
via mock implementations', which was the justification for an abstraction
layer whose mocks had zero call sites.

Also fixes an inverted claim in src/security/README.md: it described the
security registry as per-endpoint and the routing breaker as active. It is
the other way round. The two breakers coexist deliberately at different
granularities, so the note now says which is which and why neither
subsumes the other.
cargo doc emitted 49 warnings, every one a dead reference in published API
documentation. The failure modes were: links from a module-level //! block to
an item in that same module (rustdoc needs an absolute path there), links to
enum variants written as bare names, links to private items from public docs,
and two bare URLs in clap help text.

Some were simply wrong: prompt_injection.rs pointed at a scan function on a
type that does not exist, and dlp/mod.rs referenced scan_end_of_stream without
its type.

Two module headers listed their own submodules in prose. rustdoc already
generates that index, so the list was duplicated maintenance that had drifted;
removed from shared/mod.rs, kept as plain text in routing/mod.rs where the
surrounding prose carries real information.

Adds deny(broken_intra_doc_links) and deny(private_intra_doc_links). A warning
nobody reads is not a signal; this makes a dead link fail the build the way
missing_docs already does.

49 warnings -> 0. 1727 tests pass, 24 doctests pass, clippy clean.
docs/ had two entry points. docs/README.md was a flat link list, docs/index.md
had the same links plus context, a request-flow diagram, and task-oriented
tables. Nothing in the repository linked to docs/README.md, and the two had
already drifted apart. Merged the eight links only README carried into
index.md, then deleted README.

Same pattern for tutorials: quickstart.md was a strict subset of
getting-started.md and had gone stale, claiming the perf preset configures
'Anthropic + OpenAI + Gemini' when it is Anthropic OAuth with no fallback at
all. The three-command path now opens getting-started.md as an 'In a hurry'
section, so there is one tutorial instead of two that disagree.

The ADR table in index.md was maintained by hand and was wrong in five places:
0018 and 0019 were marked proposed though both are accepted, 0004 and 0028 did
not show they were superseded, and 0027, 0028, 0029 were missing entirely. It
is now generated from the status field of each ADR.

Also linked four docs that no index reached: the protocol fidelity matrix, the
OTLP exemplars explanation, the design/ directory, and the hedge-billing
verification protocol that ADR-0020 depends on.
Two root commands did the same job. 'grob completions <shell>' printed a
script to stdout, 'grob setup-completions' detected the shell and installed
it, and a new user had to read both help texts to learn which one they wanted.

The shell argument is now optional: 'grob completions' installs for the
detected shell, 'grob completions zsh' still writes to stdout for packaging
or a custom path. Both code paths are unchanged, only the entry point merged.

'setup-completions' stays as a hidden alias, so scripts and muscle memory keep
working while the help text lists one command instead of two.

25 visible root commands become 24.
@Destynova2
Destynova2 force-pushed the refactor/drop-single-impl-traits branch from 53e04db to 50dc91c Compare August 6, 2026 07:45
@Destynova2
Destynova2 enabled auto-merge (squash) August 6, 2026 07:45
@Destynova2 Destynova2 changed the title refactor: drop the five single-impl traits and the ADR-0018 backlog refactor: drop single-impl traits, fix all 49 doc-link warnings, dedupe docs Aug 6, 2026
@Destynova2
Destynova2 merged commit e8494fd into main Aug 6, 2026
42 checks passed
@Destynova2
Destynova2 deleted the refactor/drop-single-impl-traits branch August 6, 2026 07:54
Destynova2 added a commit that referenced this pull request Aug 6, 2026
## 🤖 New release

* `grob`: 0.36.96 -> 0.36.97

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

##
[0.36.97](v0.36.96...v0.36.97)
- 2026-08-06

### Other

- drop single-impl traits, fix all 49 doc-link warnings, dedupe docs
([#544](#544))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Mutation testing (PR diff sample)

Informational — never blocks merge. Full matrix runs on main.

Metric Value
Status timed-out
Duration 1501 s
Total 61
Caught 0
Missed 0
Timeout 0
Unviable 0

Legend: clean (no survivors), missed (inspect artifact), timed-out (25 min cap reached).

Artifact: mutants-pr-results-8ff9d333b492d1f2087a8b152d3c64eb2cd28423.

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.

1 participant