refactor(stella-protocol): generate the signal-consumer ledger from the tag table, making totality a compile error - #2737
Merged
Conversation
…riant declares what reads it "Produced and not consumed" is a repo-wide shape, and every instance so far was found by a bench run paying for it rather than by a test going red: flip.json written with nothing reading it (#1536); verify_done confirmations tallied but not feeding the halt, which cost solved_then_timeout four times on one certification panel before #2661 wired it; the flip transition still emitting nothing durable, so a shipped halt cannot be measured in the field. Each was fixed as whack-a-mole. This makes the class structural, copying content_free.rs element for element: a reviewed table, tests enforcing it from both sides, and negative controls proving the harness can fail. Every wire tag gets a row declaring a ConsumerPosture — Behavioral names the code that branches on it, Surfaced names the surfaces that select it, RecordedOnly and Unclassified each cite the issue where the gap is being decided. Unclassified sits under a down-only ratchet so a new variant cannot be filed away unread. Three design calls that differ from the issue's sketch, each forced by the code: - Posture and surfaces are orthogonal fields, not one enum. tool_result is both Behavioral and on the observatory whitelist; folding them together would have made #2707's parity test unsatisfiable on day one. - Surface names only the observatory and serve. The TUI and replay match AgentEvent exhaustively, so membership is compiler-guaranteed for every variant and a per-row claim would record nothing. - The ledger is a hand-maintained table rather than a third output of agent_event_tags!. That macro is the wire decoder's source of truth and event.rs sits 35 lines under the ratchet; both reasons are in the module doc so the next author does not re-litigate it. The tool_result row is the ledger earning its keep already: the intuitive consumer is loop detection, which is wrong — the detector reads the transcript, a different plane. The real event-plane consumer is the store's tool_calls projection. Refs #2701 Refs #2703
…TYPE_TAGS The symbol is in scope via the module's own use, so the bare intra-doc label already resolves and rustdoc's redundant-explicit-links lint rejects the spelled-out path. Caught by CI's doc-warnings step, which the local run of this change skipped.
…he tag table
Moves the variant table out of event.rs into event/tags.rs and extends it to
carry a ConsumerPosture per variant, so SIGNAL_CONSUMERS is generated from the
same list as type_tag() and KNOWN_TYPE_TAGS.
The point is where the enforcement lives. The ledger shipped as a
hand-maintained table checked by a test, which was strictly weaker than the
E0004 tripwire the tag list already had. It shipped that way for one reason:
event.rs sat 35 lines under the 1500-line ratchet with no baseline entry, and
the postures are worth roughly a hundred. Splitting the table out removes that
constraint — event.rs drops to 1350 lines and no baseline moved.
Adding an AgentEvent variant without declaring what consumes it is now a
compile error rather than a failing test:
error[E0004]: non-exhaustive patterns: `&AgentEvent::HaltFired { .. }` not covered
--> crates/stella-protocol/src/event/tags.rs:95:23
Three LedgerViolation kinds (MissingRow, UnknownTag, DuplicateRow) become
unrepresentable for the real ledger and are deliberately kept: audit_ledger
takes its inputs as parameters so the negative controls can hand it broken
input, and a check that cannot be shown to fail is a claim rather than a
check. The semantic rules it still enforces — issue-citation shape, a named
site for a Behavioral claim, posture/surfaces coherence — are judgements about
a row's content that no macro can hold an author to.
KNOWN_TYPE_TAGS keeps its path via a re-export, so crate::KNOWN_TYPE_TAGS and
every existing citation are unchanged.
Closes #2730
Refs #2701
Refs #2702
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
Contributor
Reviewer's GuideRefactors the AgentEvent tag table into a dedicated module that now also encodes consumer postures, generating SIGNAL_CONSUMERS, KNOWN_TYPE_TAGS, and type_tag() from a single macro-backed source of truth, so missing consumer rows become compile-time errors while preserving existing semantics and public APIs. Flow diagram for generating AgentEvent metadata from a single tags tableflowchart LR
tags_table["agent_event_tags! rows in event_tags"]
tags_table --> agent_event_tags_macro
agent_event_tags_macro --> AgentEvent_type_tag["AgentEvent::type_tag"]
agent_event_tags_macro --> KNOWN_TYPE_TAGS
agent_event_tags_macro --> SIGNAL_CONSUMERS
SIGNAL_CONSUMERS --> consumers_module["event::consumers::SIGNAL_CONSUMERS (re-export)"]
KNOWN_TYPE_TAGS --> event_module["event::KNOWN_TYPE_TAGS (re-export)"]
SIGNAL_CONSUMERS --> audit_ledger["audit_ledger"]
subgraph event_module_scope["event.rs"]
AgentEvent_type_tag
KNOWN_TYPE_TAGS
end
subgraph consumers_module_scope["consumers.rs"]
consumers_module
audit_ledger
end
subgraph tags_module_scope["tags.rs"]
tags_table
agent_event_tags_macro
SIGNAL_CONSUMERS
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…mer-tags-macro # Conflicts: # AGENTS.md # crates/stella-protocol/src/event.rs # crates/stella-protocol/src/event/consumers.rs
2 tasks
macanderson
added a commit
that referenced
this pull request
Aug 10, 2026
…build error (#2755) ## What & why Invariant #10 says the signal-consumer ledger's totality is enforced by tests — "adding a variant without declaring what consumes it is a red test." That was true when #2720 landed and stopped being true two commits later: #2737 generated the ledger rows from the tag table, so the check is now an `E0004` at `cargo build`. Caught by re-reading main after both merges landed. Per CLAUDE.md a stale doc is a bug, and this one is mine — I wrote the prose in #2720 and changed the mechanism in #2737 without chasing the claim. The correction is not cosmetic: it **understates** the guarantee, and it misdirects the next person adding an `AgentEvent` variant about where the failure will surface. It also tightens the closing "what it does not prove" paragraph, which is the part most worth keeping exact — totality is now compiler-enforced, while issue citation and posture coherence remain test-enforced. Saying "tests" for both blurs the line that paragraph exists to draw. ## The witness - [x] No witness needed — documentation only, correcting a claim about existing behavior. The behavior it now describes is witnessed in #2737, where a probe variant produces `error[E0004]: non-exhaustive patterns`. ## Deleted tests None. ## The gate - [x] `scripts/check-invariants.sh` — OK, 10 invariants, one normative home, 28 citations resolve Refs #2701 Refs #2730 ## Summary by Sourcery Documentation: - Clarify that adding an AgentEvent variant without a consumer now triggers a compile-time E0004 error rather than a failing test and tighten the distinction between compiler- and test-enforced guarantees in the invariant description.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#2720 has merged; this now targets
maindirectly. It was authored stacked on that PR, so the merge resolution here kept the generated ledger from this branch and theMissingRowmessage fix that landed on main with #2720.What & why
#2720 shipped the signal-consumer ledger as a hand-maintained table enforced by a test. That was strictly weaker than the
E0004tripwireagent_event_tags!already gave the tag list, and it shipped that way for exactly one reason, recorded in its module doc at the time:event.rssat 35 lines under the 1500-line ratchet with no baseline entry, and the postures are worth roughly a hundred.This removes that constraint. The variant table moves to
crates/stella-protocol/src/event/tags.rsand grows aConsumerPostureper row, soSIGNAL_CONSUMERSis generated from the same list astype_tag()andKNOWN_TYPE_TAGS.event.rsdrops from 1465 to 1350 lines. No baseline entry was added and none moved —stella-protocolstill has zero grandfathered files, which is the state to keep it in.The witness
Adding an
AgentEventvariant with no declared consumer is now a build failure, not a test failure. Verified by adding a probe variant and runningcargo build(notcargo test):That is the whole point of the change and the only evidence it landed, so it is quoted rather than asserted. (The probe used
HaltFired— the variant #2704 will actually add. It will hit this and need one row.)What generation does not buy
Three
LedgerViolationkinds —MissingRow,UnknownTag,DuplicateRow— become unrepresentable for the real ledger. They are deliberately kept, and the module doc now says why:audit_ledgertakes its ledger and tag list as parameters precisely so the negative controls can hand it broken input. Deleting the structural rules because one call site can no longer trip them would cost the harness its ability to prove itself with them — the vacuous-harness failurecontent_free.rsexists to prevent.The semantic half still runs and still matters: that every gap posture cites a real
#1234reference, that aBehavioralrow names somewhere to look, and that the posture agrees withsurfaces. Those are judgements about a row's content, and no macro can hold an author to them.Compatibility
KNOWN_TYPE_TAGSkeeps its path througheventvia a re-export, socrate::KNOWN_TYPE_TAGS,schema_export.rs,tests/wire_contract.rs, and every existing intra-doc citation are untouched. The "Adding a variant?" propagation comment — the map to the four compile-enforced and two silent downstream matchers — moved with the table rather than being stranded inevent.rsaway from what it describes.Deleted tests
None.
The gate
cargo build -p stella-protocolcargo test -p stella-protocol— 128 tests, all greencargo test --workspace— greenmake lint(clippy-D warnings, workspace, all targets)RUSTDOCFLAGS="-D warnings" cargo doc— green (run unpiped; a piped cargo doc can report a false green)scripts/check-file-size.sh— OK, nothing went over, no baseline changecargo fmt --allCloses #2730
Refs #2701
Refs #2702