feat: add denial-classification seam to keeper - #36
Open
mfw78 wants to merge 6 commits into
Open
Conversation
Contributor
Author
|
Deferred red-team finding filed: #40 (RunReport buckets by RetryAction, so a DropOnRepeat retirement tick counts as retried, never dropped). Shepherd adoption of the seam is tracked in nullislabs/shepherd#669. |
mfw78
force-pushed
the
feat/quote-staleness-ledger
branch
from
August 4, 2026 23:26
2cf2177 to
df47837
Compare
mfw78
force-pushed
the
feat/denial-classification-seam
branch
from
August 4, 2026 23:26
c757661 to
0256e81
Compare
This was referenced Aug 6, 2026
mfw78
force-pushed
the
feat/quote-staleness-ledger
branch
from
August 6, 2026 14:52
df47837 to
08b9fd8
Compare
Hang denial classification off the Venue marker as a provided static classify_denied, defaulting to the coarse every-denial-drops mapping. The keeper carries it as a plain fn pointer (DenialClassifier), so Keeper and reconcile stay non-generic over the venue: Keeper::for_venue takes both the id and the classification from the marker, reconcile takes the classifier as a parameter, and the free retry_action::<V> delegates its denied arm to the marker. A softened denial still releases its journal reservation. The grace is a watch-level ledger the Retrier keeps, and the reconcile pass has no repeat counter, so holding the reservation would re-POST the same refused body every tick forever. Releasing hands the watch back to the next poll, where the Retrier grants one block and then retires it. So a future EIP-1271 signer on the generic sweep inherits the one-block grace instead of dropping on the first refusal, and it still terminates. No venue symbol enters videre-sdk; the seam is name-free. Shepherd adoption is filed as nullislabs/shepherd#669. Implements #11. AI Assistance: Claude Code used for implementation and tests.
The client form of #[videre_sdk::venue] rejected any item in the impl body, so a marker declared through the macro could not override the new Venue::classify_denied. Pass the body through and reject only a hand-written ID or Body, which the attribute still supplies. Split the shape checks out of expand so they are testable without a module.toml next to the manifest. AI Assistance: Claude Code used for implementation and tests.
…ition The reconcile pass matched the rate-limit hint itself and recomputed the park window that retry_action already derives, and the guard test wrote the mapping a third time. Route all three through refusal_disposition, which folds a fault under the classifier into the nexum-sdk Disposition. This also gives a classified denial its backoff. A venue that classifies a denial to RetryAction::Backoff previously fell through to the leave-it-RESERVED arm, so the pass ignored the window and re-POSTed the refused body on every tick. AI Assistance: Claude Code used for implementation and tests.
RUSTDOCFLAGS=-D warnings fails on a public item linking to the private refusal_disposition. Name it in plain code font instead. AI Assistance: Claude Code used for the fix.
mfw78
force-pushed
the
feat/denial-classification-seam
branch
from
August 6, 2026 15:30
0256e81 to
3d53f31
Compare
Deleting the override splat from the marker expansion kept the suite green: the shape gate is tested, the emission is not, so a venue's classify_denied could silently revert to the coarse default and resurrect the bug the seam exists to fix. Split the emission out of expand so it is testable without a module.toml next to the manifest, and assert the override survives it. Rename the test fixtures off the cow venue name while here. AI Assistance: Claude Code used for the test and the split.
A venue implementer sees RetryAction and can reach for TryNextBlock on a denial it expects to clear. Only a drop-class or backoff action retires or paces a stranded reservation, so that choice pins the marker RESERVED and re-POSTs the refused body on every tick. Say so at the seam. AI Assistance: Claude Code used for the doc.
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.
What
Venue::classify_denied(detail: &str) -> RetryActionincrates/videre-sdk/src/client.rs, a provided method that defaults toRetryAction::Dropfor every denial, letting a venue marker override how itsdeniedfaults fold into the generic keeper retry path.DenialClassifier(fn(&str) -> RetryAction) throughKeeperincrates/videre-sdk/src/keeper.rs:Keeper::newdefaults to the coarseDROP_DENIEDmapping,Keeper::for_venue::<V>binds both the venue id andV::classify_deniedso a keeper cannot route to one venue while folding another's denials, andKeeper::with_classifierallows an explicit override.refusal_dispositionhelper against the newDispositionenum (Release/Park { until }/ left pending), replacing the previous three separately-derived branches, soreconcileand the retry-action mapping can no longer disagree.crates/videre-macros/src/venue_marker.rs(and a smalllib.rstouch) to keep the emitted venue marker impl body open, so a marker's macro-generated impl can carry an overriddenclassify_deniedalongside the macro's own generated items; adds a test pinning that a marker override reaches the emitted impl.classify_denied: only a drop-class action (DroporDropOnRepeat) retires a stranded reservation,TryNextBlockleaves the reservationRESERVEDand reconcile re-POSTs the refused body every tick.Why
The generic keeper reconcile path previously treated every non-accepted, non-signing-required refusal with one fixed terminal/transient split, so a venue had no way to tell the keeper that a particular denial reason should back off, drop, or be retried differently from the coarse default.
classify_deniedgives venues that hook without makingKeeperorreconcilegeneric over the venue (the classifier is carried as a plainfnpointer), and collapsing the refusal handling into onerefusal_dispositioncloses the gap where the rate-limit arm, the terminal check, and the retry-action mapping could previously drift out of sync.Closes #11
Testing
All commands run in the repo's
nix developshell (rustc 1.94.0, cargo-nextest 0.9.127) on a clean checkout offeat/denial-classification-seam(3096cae), based onorigin/feat/quote-staleness-ledger. Touched crates:videre-sdk,videre-macros(5 files, +485/-68; noCargo.toml/Cargo.lockdelta).cargo clippy -p videre-sdk -p videre-macros --all-targets --all-features -- -D warnings- clean, no warnings.cargo test --doc -p videre-sdk -p videre-macros --all-features-videre_macros0 passed,videre_sdk0 passed (no doctests declared); ok.RUSTDOCFLAGS='-D warnings' cargo doc --no-deps -p videre-sdk -p videre-macros- clean, docs generated.just build-modules(required becausevidere-macroschanged) -echo-venue,echo-client,echo-keeper,flaky-venueall built forwasm32-wasip2 --release.cargo nextest run -p videre-sdk -p videre-macros- full pass.AI Assistance
Implementation by claude-fable-5, red-team review by claude-opus-5, PR description by claude-sonnet-5.