Sdlc/Sequel/RegistryOutsideEntryPoint: match the Sequel Db by constant path, carve out by shape not path #1381

Open
opened 2026-10-25 21:23:38 +00:00 by jared · 5 comments
Owner

Problem

The cop Sdlc/Sequel/RegistryOutsideEntryPoint matches any constant named Db and skips code by path, not by shape.
It flags a non-Sequel Db and misses the real registry reached through method(:connect_project!).

Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md).
Smell analysis: docs/lint-lessons/analyses/sdlc-sequel-registry-outside-entry-point.md.

Observed

Counterexample 1: the cop flags a non-Sequel registry.

Cache::Db.for(:users)

Counterexample 2: a non-test class under tests/ is skipped by the **/tests/**/* glob (registry_outside_entry_point.rb:14-17).
Counterexample 3: Db.method(:connect_project!) is missed. The real uses are plugins/os-sdlc/lib/os_sdlc/boot.rb:7 and plugins/os-sdlc/lib/os_sdlc/boot/project_bind.rb:6; both are in the entry layer (Boot), so they must not fire.
The allowed path **/controller/**/* matches no directory under plugins/os-sdlc/lib; the real entry layer is lib/os_sdlc/boot.rb and lib/os_sdlc/boot/.
0 hits on the repo (2026-10-28, 1885 files).

Reproduce

n/a: the cop matcher is at plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb:23-29; run the cop on the snippets above.

Approach

Carve-outs go in the cop matcher, never in a config Exclude list or a test-path exclusion.
Match (const {nil? cbase (const {nil? cbase} :OsSdlc)} :Db) and Db.method(<registry symbol>).
Replace the stale **/controller/**/* entry-layer glob with the real entry layer, Boot (**/os_sdlc/boot.rb, **/os_sdlc/boot/**/*), so that Boot's injected Db.method(:connect_project!) default does not fire.

Required work:

  1. Match Db by constant path (bare, ::Db, or OsSdlc::Db) — check: present :OsSdlc in plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb
  2. Match Db.method(<registry symbol>) — check: present :method in plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb
  3. Replace the stale controller glob with the Boot entry layer — check: absent **/controller/**/* in plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb

Acceptance criteria

  1. Given Cache::Db.for(:users) in a query file, when the cop runs, then it reports no offense.
  2. Given OsSdlc::Db.method(:connect_project!) in a query file outside the entry layer, when the cop runs, then it reports an offense.
  3. Given Db.db in lib/os_sdlc/controller/entry.rb, when the cop runs, then it reports an offense (no controller/ folder is an entry point).

Owner sketch

class RuboCop::Cop::Sdlc::Sequel::RegistryOutsideEntryPoint
  def_node_matcher :registry_call?   # Db matched by constant path; adds Db.method(<registry symbol>)
  def on_send(node)
end

Deferred: needs Jared's ruling (same question as #1450)

The original approach also asked to replace the **/tests/**/* and **/*_test.rb allowances with a shape carve-out (for example "inside a Minitest::Test subclass"), with the criterion "a non-test class under tests/ that calls the registry reports an offense".
On main, the non-test classes under tests/ that call the registry are test infrastructure called by test cases: tests/test_helper.rb (RollbackPerTest), tests/support/subagent_stop_scenario.rb, tests/support/subagent_start_scenario.rb, tests/support/cli_next_instruction_helpers.rb, tests/support/repeated_next_row_check.rb, plus DispatchTestCase and UnboundDbTestCase (indirect Minitest::Test subclasses).
Whether a helper class called by an entry point (a test case, or Boot::Commands) is itself an entry point is the open ruling on #1450. This ticket does not decide it; the test globs stay until that ruling.

Origin

  • Trigger: #1325 zero-false-positive audit of the Sdlc cops
  • Improvised this session: none
  • Chain: false positive and miss ← constant matched by name, carve-out by path ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb)
  • Root candidate: none found; searched RegistryOutsideEntryPoint
  • Where: plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb
  • Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl
    VERDICT: CREATE
## Problem The cop `Sdlc/Sequel/RegistryOutsideEntryPoint` matches any constant named `Db` and skips code by path, not by shape. It flags a non-Sequel `Db` and misses the real registry reached through `method(:connect_project!)`. Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md). Smell analysis: `docs/lint-lessons/analyses/sdlc-sequel-registry-outside-entry-point.md`. ## Observed Counterexample 1: the cop flags a non-Sequel registry. ```ruby Cache::Db.for(:users) ``` Counterexample 2: a non-test class under `tests/` is skipped by the `**/tests/**/*` glob (`registry_outside_entry_point.rb:14-17`). Counterexample 3: `Db.method(:connect_project!)` is missed. The real uses are `plugins/os-sdlc/lib/os_sdlc/boot.rb:7` and `plugins/os-sdlc/lib/os_sdlc/boot/project_bind.rb:6`; both are in the entry layer (`Boot`), so they must not fire. The allowed path `**/controller/**/*` matches no directory under `plugins/os-sdlc/lib`; the real entry layer is `lib/os_sdlc/boot.rb` and `lib/os_sdlc/boot/`. 0 hits on the repo (2026-10-28, 1885 files). ## Reproduce n/a: the cop matcher is at `plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb:23-29`; run the cop on the snippets above. ## Approach Carve-outs go in the cop matcher, never in a config Exclude list or a test-path exclusion. Match `(const {nil? cbase (const {nil? cbase} :OsSdlc)} :Db)` and `Db.method(<registry symbol>)`. Replace the stale `**/controller/**/*` entry-layer glob with the real entry layer, `Boot` (`**/os_sdlc/boot.rb`, `**/os_sdlc/boot/**/*`), so that `Boot`'s injected `Db.method(:connect_project!)` default does not fire. Required work: 1. Match `Db` by constant path (bare, `::Db`, or `OsSdlc::Db`) — check: present `:OsSdlc` in `plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb` 2. Match `Db.method(<registry symbol>)` — check: present `:method` in `plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb` 3. Replace the stale controller glob with the Boot entry layer — check: absent `**/controller/**/*` in `plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb` ## Acceptance criteria 1. Given `Cache::Db.for(:users)` in a query file, when the cop runs, then it reports no offense. 2. Given `OsSdlc::Db.method(:connect_project!)` in a query file outside the entry layer, when the cop runs, then it reports an offense. 3. Given `Db.db` in `lib/os_sdlc/controller/entry.rb`, when the cop runs, then it reports an offense (no `controller/` folder is an entry point). ## Owner sketch ```ruby class RuboCop::Cop::Sdlc::Sequel::RegistryOutsideEntryPoint def_node_matcher :registry_call? # Db matched by constant path; adds Db.method(<registry symbol>) def on_send(node) end ``` ## Deferred: needs Jared's ruling (same question as #1450) The original approach also asked to replace the `**/tests/**/*` and `**/*_test.rb` allowances with a shape carve-out (for example "inside a Minitest::Test subclass"), with the criterion "a non-test class under `tests/` that calls the registry reports an offense". On main, the non-test classes under `tests/` that call the registry are test infrastructure called by test cases: `tests/test_helper.rb` (`RollbackPerTest`), `tests/support/subagent_stop_scenario.rb`, `tests/support/subagent_start_scenario.rb`, `tests/support/cli_next_instruction_helpers.rb`, `tests/support/repeated_next_row_check.rb`, plus `DispatchTestCase` and `UnboundDbTestCase` (indirect Minitest::Test subclasses). Whether a helper class called by an entry point (a test case, or `Boot::Commands`) is itself an entry point is the open ruling on #1450. This ticket does not decide it; the test globs stay until that ruling. ## Origin - Trigger: #1325 zero-false-positive audit of the Sdlc cops - Improvised this session: none - Chain: false positive and miss ← constant matched by name, carve-out by path ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb) - Root candidate: none found; searched RegistryOutsideEntryPoint - Where: plugins/os-sdlc/lib/os_sdlc/cops/sequel/registry_outside_entry_point.rb - Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl VERDICT: CREATE
Author
Owner

Work started on branch ticket-1381 (worktree .claude/worktrees/ticket-1381). Ticket body corrected first: cop path is cops/sequel/; test-glob shape carve-out deferred to the #1450 ruling.

Work started on branch ticket-1381 (worktree .claude/worktrees/ticket-1381). Ticket body corrected first: cop path is cops/sequel/; test-glob shape carve-out deferred to the #1450 ruling.
Author
Owner

Resolution

Done: RegistryOutsideEntryPoint matches Db by constant path only (Db, ::Db, OsSdlc::Db; Cache::Db passes), flags Db.method(), and replaces the stale /controller//* glob with the Boot entry layer (boot.rb, boot/). Message and lesson now say 'only db and boot'. Smell analysis section 6 updated.

Evidence: Pipeline implementation 206 complete (8 dispatches, 8 min 13 s, 0 retries); main e2588c72 pushed (48928fdd + e2588c72). Suite 2865 runs, 0 failures. Cop over plugins/os-sdlc: 0 hits before, 0 after (1885 files). True-case probe: Db.db in lib/os_sdlc/query fires. Regression tests: Db.method(:connect_project!) in boot.rb and boot/project_bind.rb passes.

Follow-ups: Original AC3 (test-glob shape carve-out) not done: the non-test classes under tests/ that reach the registry are test support called by test cases; whether such helpers are entry points is the #1450 ruling. Predicted row: Boot (boot.rb, boot/) is the entry layer.

Needs review: Jared: rule on #1450 (are helper classes called by an entry point, incl. tests/support classes and Boot::Commands thin classes, entry points?). Then replace the /tests//* and **/*_test.rb globs by shape. Also confirm predicted row: Boot is the entry layer.

## Resolution **Done:** RegistryOutsideEntryPoint matches Db by constant path only (Db, ::Db, OsSdlc::Db; Cache::Db passes), flags Db.method(<registry symbol>), and replaces the stale **/controller/**/* glob with the Boot entry layer (boot.rb, boot/). Message and lesson now say 'only db and boot'. Smell analysis section 6 updated. **Evidence:** Pipeline implementation 206 complete (8 dispatches, 8 min 13 s, 0 retries); main e2588c72 pushed (48928fdd + e2588c72). Suite 2865 runs, 0 failures. Cop over plugins/os-sdlc: 0 hits before, 0 after (1885 files). True-case probe: Db.db in lib/os_sdlc/query fires. Regression tests: Db.method(:connect_project!) in boot.rb and boot/project_bind.rb passes. **Follow-ups:** Original AC3 (test-glob shape carve-out) not done: the non-test classes under tests/ that reach the registry are test support called by test cases; whether such helpers are entry points is the #1450 ruling. Predicted row: Boot (boot.rb, boot/) is the entry layer. Needs review: Jared: rule on #1450 (are helper classes called by an entry point, incl. tests/support classes and Boot::Commands thin classes, entry points?). Then replace the **/tests/**/* and **/*_test.rb globs by shape. Also confirm predicted row: Boot is the entry layer.
Author
Owner

Note from session a48658d5-b566-40ce-8257-ebeabf56f7e1: ruling 134 (docs/decisions/rulings.tsv) deleted the registry check of Sdlc/Sequel/RegistryOutsideEntryPoint that this ticket describes; Boot is deleted on main (031244657). The cop now holds only the lookup-by-id check; #1684 renames it. This ticket is a close-or-rewrite candidate.

Note from session a48658d5-b566-40ce-8257-ebeabf56f7e1: ruling 134 (docs/decisions/rulings.tsv) deleted the registry check of Sdlc/Sequel/RegistryOutsideEntryPoint that this ticket describes; Boot is deleted on main (031244657). The cop now holds only the lookup-by-id check; #1684 renames it. This ticket is a close-or-rewrite candidate.
Author
Owner

Ruling 134 deleted the registry check that this ticket concerns. The one remainder of this ticket (replace the test globs in DEFAULT_ALLOWED_PATHS by shape) moved to #1684. This ticket stays open.

Ruling 134 deleted the registry check that this ticket concerns. The one remainder of this ticket (replace the test globs in `DEFAULT_ALLOWED_PATHS` by shape) moved to #1684. This ticket stays open.
Author
Owner

Update 2026-11-12: #1684 is closed (cop renamed to Sdlc/Sequel/LookupByIdOutsideInitialize, a2996eb8e). The one remainder of this ticket was not done there and returns here: replace the test globs in DEFAULT_ALLOWED_PATHS (lib/os_sdlc/cops/sequel/lookup_by_id_outside_initialize.rb) by shape. It needs a ruling on the shape; #1450 calls it a separate ruling. One candidate, 'inside a Minitest::Test subclass', would make the support classes under test/support report offenses.

Update 2026-11-12: #1684 is closed (cop renamed to Sdlc/Sequel/LookupByIdOutsideInitialize, a2996eb8e). The one remainder of this ticket was not done there and returns here: replace the test globs in DEFAULT_ALLOWED_PATHS (lib/os_sdlc/cops/sequel/lookup_by_id_outside_initialize.rb) by shape. It needs a ruling on the shape; #1450 calls it a separate ruling. One candidate, 'inside a Minitest::Test subclass', would make the support classes under test/support report offenses.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1381
No description provided.