Extend architecture test: denylist scope + stepdown rule check#117
Open
Extend architecture test: denylist scope + stepdown rule check#117
Conversation
Two upgrades to `src/architecture_tests.rs`: 1. **`is_enforced_file` flips from allowlist to denylist.** Was a fixed set of filenames (`routes.rs`, `service.rs`, `repo.rs`, etc.) which silently missed any helper file with a different name (`landing.rs`, `qr.rs`, `stripe_webhook.rs`, future additions). Now: enforce every `.rs` under `src/` except `models.rs`, `lib.rs`, `main.rs`, `architecture_tests.rs`, and `*_tests.rs`. New files inherit enforcement automatically. 2. **New `stepdown_rule_at_depth_zero` test.** Codifies the public-vs- private half of the stepdown rule: in any enforced file, no free private `fn` may appear before the first free public/`pub(crate)` `fn` at depth 0. The caller-above-callee half needs a real call graph and is still review-only. The denylist flip exposed pre-existing violations the old allowlist quietly missed. Two categories: - **Impl containers** that needed `impl_container!` markers — the obvious ones (migrations m001-m004, CdpFacilitator, ThreatFeed, RateLimiter, RiftMcp, RepoResourceCounts) are marked in this PR. - **Real data-type extractions** that need `models.rs` migration — 10 files tracked in a new `PUB_TYPES_CLEANUP_BACKLOG` const with per-file TODO comments. Each entry can be cleaned up independently in follow-up PRs; the backlog is the lever to chip away at. Stepdown rule fixes (3 small reorders to land green): - `api/mod.rs`: `serve_rift_js` moved below `router` to a Helpers section - `services/links/service.rs`: `to_doc_value` moved below `build_canonical_link_url` to file bottom - `services/webhooks/dispatcher.rs`: `compute_hmac` (pub(crate)) moved above `cached_find_active_for_event` (private) so public surface comes first CLAUDE.md updated to describe both rules and the `pub(crate)` carve-out. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
4 tasks
saltyskip
added a commit
that referenced
this pull request
May 1, 2026
Stacks on PR #117. The backlog list introduced there held 10 files of pre-existing pub-data-type violations. This PR works through every entry so the backlog is empty. **Billing slice — moved to services/billing/models.rs:** - handoff.rs: BillingIntent, BillingTier (+ impl), BillingHandoffError, HandoffOutcome, BillingHandoffConfig - stripe_client.rs: StripeConfig (+ impl), StripeError (+ Display), CheckoutSession, HandoffCheckoutOpts, PortalSession, WebhookVerifyError (+ Display) - limits.rs: PlanLimits - repos/event_counters.rs: EventCounterDoc (EventCountersRepo marked impl_container) - repos/stripe_webhook_dedup.rs: StripeDedupDoc (StripeWebhookDedupRepo marked impl_container) Source files now `pub use` from models.rs to keep external import paths stable. BillingHandoffService also marked as impl_container. **Core slice — new core/models.rs:** - 4 webhook payloads (ClickEventPayload, AttributionEventPayload, ConversionEventPayload, WebhookPayload) extracted from webhook_dispatcher.rs. The trait stays. - core/config.rs::Config marked as impl_container (env-loaded settings with from_env constructor — same pattern as repo containers). **Single-struct top-level files — converted to directories:** - error.rs → error/{mod.rs (the IntoResponse impl), models.rs (ErrorResponse, AppError)}. mod.rs re-exports both, so `crate::error::ErrorResponse` keeps working everywhere. - app.rs → app/{mod.rs (re-export), models.rs (AppState)}. Same re-export pattern; `crate::app::AppState` unchanged. **MCP renamed:** - mcp/tools.rs → mcp/models.rs. The file already held DTO-shaped tool input types — name now matches the pattern. `mcp/server.rs` and `mcp/mod.rs` updated. **Architecture test:** PUB_TYPES_CLEANUP_BACKLOG drops to `&[]`. Every `.rs` under `src/` (except models.rs, lib.rs, main.rs, architecture_tests.rs, *_tests.rs) is now enforced and clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Two upgrades to
src/architecture_tests.rsthat close the gaps surfaced by review:1.
is_enforced_fileflips from allowlist to denylistWas a fixed set of filenames (
routes.rs,service.rs,repo.rs, etc.) which silently missed any helper file with a different name.landing.rs,qr.rs,stripe_webhook.rs, and any future helper file slipped through unchecked.Now: enforce every
.rsundersrc/exceptmodels.rs,lib.rs,main.rs,architecture_tests.rs, and*_tests.rs. New files inherit enforcement automatically.2. Stepdown rule check (Clean Code, Robert C. Martin)
stepdown_rule_at_depth_zeroflags any free privatefnthat appears before the first free public (orpub(crate))fnat depth 0 in the same file. Methods insideimplblocks aren't checked — they're nested.The caller-above-callee half of the rule needs a real call graph and isn't mechanically checked; it stays review-only.
What the denylist flip exposed
The old allowlist was quietly missing real violations. Categorized:
Impl containers needing markers (fixed in this PR):
migrations/m001_auth_split.rs::M001AuthSplit(and m002, m003, m004)core/cdp.rs::CdpFacilitatorcore/threat_feed.rs::ThreatFeedcore/rate_limit.rs::RateLimitermcp/server.rs::RiftMcpservices/billing/repos/resource_counts_adapter.rs::RepoResourceCountsReal data-type extractions (deferred to backlog):
Tracked in a new
PUB_TYPES_CLEANUP_BACKLOGconst with per-file// TODOcomments. The backlog is debt to chip away at:core/config.rscore/models.rscore/webhook_dispatcher.rscore/models.rserror.rsmcp/tools.rsmcp/models.rsapp.rsservices/billing/handoff.rsbilling/models.rs(BillingHandoffService is impl container)services/billing/stripe_client.rsbilling/models.rsservices/billing/limits.rsbilling/models.rsservices/billing/repos/event_counters.rsservices/billing/repos/stripe_webhook_dedup.rsStepdown rule fixes (3 small reorders)
api/mod.rs:serve_rift_js(helper) was aboverouter(the public fn) → moved to a Helpers section at the bottomservices/links/service.rs:to_doc_value(private helper) was abovebuild_canonical_link_url(pub fn) → moved to file bottomservices/webhooks/dispatcher.rs:compute_hmac(pub(crate)) was belowcached_find_active_for_event(private) → reordered so public surface comes firstCLAUDE.md updates
pub(crate)carve-out explicitly (the rule is about external API surface, not crate-internal scaffolding)Test plan
cargo fmt -- --checkcargo clippy --all-targets -- -D warningscargo test— 112 lib + 145 integration tests pass (was 107; +5 from new arch test parser unit tests)Why a backlog instead of fixing everything
This PR is meant to land the enforcement infrastructure cleanly. Fixing every pre-existing violation (15+ files, ~30 types) in one PR would balloon the diff and make review harder. The backlog approach makes the debt visible and tractable: each file is a self-contained follow-up.
🤖 Generated with Claude Code