Skip to content

refactor(stella-protocol,stella-pipeline): derive the call-role family instead of hand-listing it (#1977) - #2035

Merged
macanderson merged 2 commits into
mainfrom
roles-all-const
Aug 7, 2026
Merged

refactor(stella-protocol,stella-pipeline): derive the call-role family instead of hand-listing it (#1977)#2035
macanderson merged 2 commits into
mainfrom
roles-all-const

Conversation

@macanderson

@macanderson macanderson commented Aug 7, 2026

Copy link
Copy Markdown
Owner

What & why

ALL_ROLES in crates/stella-pipeline/src/management_prompt/tests.rs was a
hand-maintained [ModelCallRole; 15], and its own doc comment conceded the gap:

Completeness is not compiler-checked here — that job belongs to the exhaustive
match in [management_system_block], which forces a new variant to declare its
prefix posture before this array matters.

That delegation held, but only by luck of ordering. When #1778 added
ModelCallRole::Research, the exhaustive match did fail the build — but the
array was a separate edit that nothing would have caught had the match not
happened to live in the same file. An array one variant short makes the #1855
shared-preamble parity witness silently test fewer roles than it claims to, and
the failure mode is a passing test, not a failing one.

stella-protocol now owns the enumeration of its own variants.

How it is provably total

There is no variant count anywhere — Rust cannot count enum variants on stable,
and a hand-written length is exactly the drift this replaces. Instead one
model_call_roles! token list feeds two expansions:

  • ModelCallRole::ALL, built from the list, and
  • an exhaustive match over ModelCallRole, checked against the list.

A variant added to the enum but not the declaration fails that match with
E0004, so the list is provably a superset of the variants; ALL is built from
the same list, so it is provably total. Neither half can drift from the other
because there is only one list.

Modelled on agent_event_tags!, which binds KNOWN_TYPE_TAGS to
AgentEvent the same way, in the same crate — this is that idiom applied to the
sibling enum, not a new pattern.

Why the enum moved file

event.rs was at 1497 of the 1500-line ratchet with no baseline entry, so
there was nowhere for the declaration to land. ModelCallRole moves to
event/call_role.rs; event.rs drops to 1446. The public path
stella_protocol::ModelCallRole is unchanged.

Zero wire diff

The doc comment on this enum is the description field of
docs/wire/agentevent.schema.json and its TypeScript twin. My first pass
rewrote it and leaked rustdoc-internal (super) / (super::AgentEvent) link
syntax into an artifact whose consumers have no Rust — the wire-schema guard
caught it (second commit). The published prose is now restored byte-for-byte,
so this refactor produces no wire diff at all, which is the proof it is
shape-preserving. Maintainer notes live in a non-doc comment and on the macro,
neither of which reaches the wire; the AgentEvent intra-doc link resolves from
the child module via a cfg(doc) import that a normal build never sees.

Closes #1977

The witness

  • This PR includes a witness test (fails on main, passes here), or
  • No witness needed

Two runtime tests ship in event/call_role.rs (ALL has no repeats and is in
declaration order; every role in ALL round-trips through serde). But the
real witness for this issue is a compile-time one, so it cannot live in the
test tree — verified by hand:

$ # add `ScratchProbe` to the enum, leave model_call_roles! alone
$ cargo build -p stella-protocol
error[E0004]: non-exhaustive patterns: `ModelCallRole::ScratchProbe` not covered
   --> crates/stella-protocol/src/event/call_role.rs:109:23
119 | / model_call_roles! {

On main the same edit compiles clean once the two stella-pipeline matches
are updated, leaving ALL_ROLES at 15 and the parity witness quietly covering
one role fewer. That difference is the whole issue.

The gate

  • cargo fmt --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace
  • make wire-schema — clean, zero diff
  • RUSTDOCFLAGS="-D warnings" cargo doc — clean
  • make guards-fast — all green (file-size, god-files, gate-parity, …)
  • Docs updated where behavior changed (doc comments; no flags changed)
  • Closes #1977 appears both here and as a commit trailer

Nothing left behind

crates/stella-pipeline/src/pipeline/raw_usage.rs's management_bounds is
named in the issue as the same pairing, but it carries no array — only an
exhaustive match — so it is already safe and needed no change.

The same shape does exist in ~12 other places in the workspace (a hand-written
pub const ALL: [X; N] beside an exhaustive match that cannot force the array to
grow): stella-tui deck.rs/envelope.rs/views/engine.rs/theme.rs/deck_ui.rs,
stella-diag level.rs, stella-graph lang.rs, stella-serve observe/event.rs,
stella-core extensions.rs, stella-cli settings.rs. Filed as #2031
rather than swept into this PR, which is deliberately one enum — that issue also
raises whether twelve near-identical macros should instead be one shared
enum_all! in a leaf crate, which is a design call, not a mechanical sweep.

Ground-rule check

  • No I/O added to stella-core; no new deps (option 2 in the issue, a
    strum derive, was rejected — stella-protocol is types-only and
    dependency-light, and option 1 needs nothing)
  • No new outbound network calls
  • Cross-boundary type round-trips through serde (test included)

Anything reviewers should know?

The tripwire function every_variant_is_in_all deliberately has an empty body
and is never called at runtime — its exhaustive match is the assertion, and
the const _: () = … item below it forces evaluation so it cannot rot into dead
code. It looks strange on purpose; the doc comment says why.

Summary by Sourcery

Move the ModelCallRole enum into its own module and introduce a macro-driven enumeration to derive the complete role family, ensuring consumers iterate a compiler-checked ALL list rather than a hand-maintained array.

Enhancements:

  • Expose ModelCallRole::ALL as the canonical, declaration-ordered list of all call roles, backed by a macro that ties the enum variants to their enumeration via an exhaustive compile-time match.
  • Add tests around ModelCallRole::ALL to verify the role list has no duplicates, preserves declaration order, and that every role round-trips through serde.

Tests:

  • Replace the stella-pipeline management prompt parity witness to iterate ModelCallRole::ALL instead of a local ALL_ROLES array, so coverage automatically tracks the enum.

…y instead of hand-listing it

`ALL_ROLES` in stella-pipeline's management-prompt witness was a
hand-maintained `[ModelCallRole; 15]`, and its own doc comment conceded
completeness was not compiler-checked — it delegated that to the exhaustive
match in `management_system_block`. The delegation only held by luck of
ordering: when #1778 added `ModelCallRole::Research`, the match did fail the
build, but the array was a separate edit nothing would have caught. An array
one variant short makes the #1855 shared-preamble parity witness silently
test fewer roles than it claims, and the failure mode is a *passing* test.

`stella-protocol` now owns the enumeration of its own variants. A
`model_call_roles!` declaration feeds two expansions from one token list:
`ModelCallRole::ALL`, and an exhaustive `match` over the enum. A variant
added to the enum but not the declaration fails that match with E0004, so
the list is provably a superset of the variants; `ALL` is built from the same
list, so it is provably total. No variant count exists to fall out of date --
Rust cannot count variants on stable, and a hand-written length is the drift
this replaces. Modelled on `agent_event_tags!`, which binds `KNOWN_TYPE_TAGS`
to `AgentEvent` the same way in the same crate.

`ModelCallRole` moves to `event/call_role.rs` rather than growing `event.rs`,
which sat at 1497 of the 1500-line ratchet with no baseline entry; the parent
drops to 1446. The public path `stella_protocol::ModelCallRole` is unchanged.

Witness: adding a variant to the enum and not the declaration fails with
"E0004: non-exhaustive patterns: `ModelCallRole::ScratchProbe` not covered"
pointing at `model_call_roles!`. On the old code the same edit compiled clean
once the two pipeline matches were updated, leaving ALL_ROLES at 15.

Closes #1977
Moving the enum rewrote its doc comment, and that comment is the
`description` field of docs/wire/agentevent.schema.json and its TypeScript
twin — so the rustdoc-internal `(super)` / `(super::AgentEvent)` link syntax
I introduced shipped straight into an artifact whose consumers have no Rust.
The wire-schema guard caught it.

Restores the published prose byte-for-byte, so the refactor now produces a
zero wire diff — which is the proof it is shape-preserving. The maintainer
note about the E0004 tripwire moves to a non-doc comment and to
`model_call_roles!`, neither of which reaches the wire. The `AgentEvent`
intra-doc link resolves from the child module via a `cfg(doc)` import,
invisible to a normal build.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
stella-cli-docs Ignored Ignored Aug 7, 2026 4:36am

@sourcery-ai

sourcery-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors ModelCallRole so its "all roles" enumeration is derived in stella-protocol via a macro, moves the enum into its own module, exposes a compile-time exhaustiveness guard, and updates stella-pipeline tests to consume the new ModelCallRole::ALL instead of a hand-maintained array while preserving wire schema shape.

File-Level Changes

Change Details Files
Introduce a macro-based, single-source enumeration for ModelCallRole that derives the ALL slice and enforces compile-time exhaustiveness.
  • Add model_call_roles! macro that declares the role family and generates ModelCallRole::ALL as a &'static [Self].
  • Implement every_variant_is_in_all const fn using an exhaustive match over ModelCallRole and force its evaluation via a const item to trip on missing variants.
  • Add tests ensuring ALL has no duplicates, matches declaration order, and every role round-trips through serde JSON.
crates/stella-protocol/src/event/call_role.rs
Relocate ModelCallRole into a dedicated module without changing its public path or wire-facing documentation, keeping the wire schema byte-for-byte identical.
  • Move the ModelCallRole enum definition from event.rs into new event/call_role.rs and re-export it via pub use call_role::ModelCallRole.
  • Add cfg(doc) import of AgentEvent to keep intra-doc links working from the new module.
  • Ensure doc comments remain unchanged so generated agentevent.schema.json and TypeScript schema descriptions are identical (zero wire diff).
crates/stella-protocol/src/event.rs
crates/stella-protocol/src/event/call_role.rs
docs/wire/agentevent.schema.json
TypeScript schema file (generated)
Update stella-pipeline management_prompt tests to enumerate roles via ModelCallRole::ALL instead of a local constant array.
  • Remove the hand-maintained ALL_ROLES [ModelCallRole; 15] constant from management_prompt tests.
  • Change management_system_blocks() to iterate ModelCallRole::ALL (iter().copied()) when building the (role, system block) pairs.
  • Adjust witness comments to reflect that the parity check now covers the enum’s own role family rather than a potentially short local copy.
crates/stella-pipeline/src/management_prompt/tests.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#1977 Replace the hand-maintained ALL_ROLES array in crates/stella-pipeline/src/management_prompt/tests.rs with a derived enumeration tied to ModelCallRole so the test’s role family cannot silently drift from the enum.
#1977 Have stella-protocol own the enumeration of ModelCallRole variants by exposing a total, compiler-checked family (e.g., ModelCallRole::ALL) from the enum’s defining crate.
#1977 Eliminate the unsafe pattern of an exhaustive match plus a hand-listed role array in crates/stella-pipeline/src/pipeline/raw_usage.rs (or confirm there is no such drifting array so the file is already safe).

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@macanderson
macanderson merged commit 9b502f1 into main Aug 7, 2026
16 checks passed
@macanderson
macanderson deleted the roles-all-const branch August 7, 2026 04:41
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.

ALL_ROLES is a hand-maintained array that can silently under-test the role family

1 participant