inward-dependencies lint crashes on reroute/; take roles from a map, not the folder #947

Closed
opened 2026-10-05 12:28:54 +00:00 by jared · 3 comments
Owner

Problem

bin/lint-inward-dependencies crashes on os-sdlc.
It reads the layer of a constant from the first path segment of its file. The folder reroute/ is not in ROLE_LAYER, so Hash#fetch raises.
ADR-0272 retired the role directories, so the path no longer gives the role. The lint has crashed since 2026-10-01 (d3f4888f added lib/os_sdlc/reroute/draft.rb) and nobody saw it for four days.

Observed

cd plugins/os-sdlc && bundle exec ruby bin/lint-inward-dependencies
lib/os_sdlc/lint/inward_dependencies.rb:107:in 'Hash#fetch': key not found: "reroute" (KeyError)

add_role_entry (line 39) and SourceFile#role (line 65) both take split("/").first of the path. crosses_inward? (line 107) calls role_layer.fetch(ref_role).

Reproduce

cd plugins/os-sdlc && bundle exec ruby bin/lint-inward-dependencies

Approach

Decision: G2 option A, pass-scoped. The role comes from the role_by_const: map (the existing input) in every place. The folder-derived map stays as the default when no map is given. The bin script may read the crc ledger as the map source: the G2 document (step 1) says to key the ledger role column by new_constant, or by constant when the verdict is keep (ledger format: plugins/os-sdlc/skills/crc-refactor/references/ledger.md). Add the ledger input as an optional bin argument. Do not change the default.

Required work:

  1. SourceFile#role looks up the constant that the file defines in the role_by_const map. It does not read the path. — check: absent split("/").first in plugins/os-sdlc/lib/os_sdlc/lint/inward_dependencies.rb
  2. A constant with no role prints one line, unmapped <path> <Constant>, and does not raise KeyError. — check: present unmapped in plugins/os-sdlc/lib/os_sdlc/lint/inward_dependencies.rb
  3. The walker skips the constant path of a class or module node that only reopens its parent class or module.
  4. bin/lint-inward-dependencies accepts an optional ledger path and builds role_by_const from the ledger role column (its values are the ROLE_LAYER keys). — check: present role_by_const in plugins/os-sdlc/bin/lint-inward-dependencies
  5. Add a test that runs the lint over a fixture tree with a folder that is not in ROLE_LAYER and asserts that no exception is raised. — check: present unmapped in plugins/os-sdlc/tests/lint/inward_dependencies_test.rb

Rejected: suffix convention (G2 candidate 1) — renames concepts and conflicts with A15 and ADR-0272.
Rejected: LAYER declaration in each class — about 360 one-line claims that nothing checks.
Rejected: retire the lint (option C) — crc gate 3 loses its outward-reference signal.

Acceptance criteria

  1. Given a lib tree with a folder that is not in ROLE_LAYER, when the lint runs, then it raises no exception.
  2. Given a constant with no entry in role_by_const, when the lint runs, then the output has one unmapped <path> <Constant> line for it.
  3. Given a file that only reopens its parent class, when the lint runs, then the output has no reference line for the reopening.
  4. Given a ledger path, when bin/lint-inward-dependencies runs, then it takes each role from the ledger.

Owner sketch

OsSdlc::InwardDependencies owns the role of a constant. It keeps its constructor and gains one collaborator for the ledger.

class OsSdlc::InwardDependencies
  def initialize(root:, role_by_const: nil)  # nil falls back to the folder-derived map
  def outward   # reference lines, then one "unmapped <path> <Constant>" line per constant with no role
end
class OsSdlc::InwardDependencies::SourceFile
  def role(role_by_const)  # role_by_const[defined constant]; no path parsing
end
  • SourceFile#outward_references asks role(role_by_const) and prints unmapped instead of calling role_layer.fetch.
  • bin/lint-inward-dependencies reads the optional ledger path, builds role_by_const from the ledger, and passes it to InwardDependencies.new.

Non-goals

Checking the layer rule between crc passes. Jared rules on that separately (G2 memo 1).

Origin

  • Trigger: lens challenge G2, commit 4b8a1730. Running bundle exec ruby bin/lint-inward-dependencies in plugins/os-sdlc raised the KeyError.
  • Improvised this session: none
  • Chain: crash on reroute ← SourceFile#role reads the first path segment ← the lint was written for the ADR-0222 role directories, and ADR-0272 retired them. Stop: DESIGN (ADR-0272 item 3; docs/crc/os-adr/lens-challenge-g2-layer-signal.md).
  • Root candidate: #935 (the lint redesign named in ADR-0272 item 3)
  • Where: plugins/os-sdlc/lib/os_sdlc/lint/inward_dependencies.rb
  • Session: ab599e53-dbf5-4953-b8bf-c598a9c5c16e
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-crc-os-adr-2026-10-04/ab599e53-dbf5-4953-b8bf-c598a9c5c16e.jsonl
## Problem `bin/lint-inward-dependencies` crashes on os-sdlc. It reads the layer of a constant from the first path segment of its file. The folder `reroute/` is not in `ROLE_LAYER`, so `Hash#fetch` raises. ADR-0272 retired the role directories, so the path no longer gives the role. The lint has crashed since 2026-10-01 (d3f4888f added `lib/os_sdlc/reroute/draft.rb`) and nobody saw it for four days. ## Observed ``` cd plugins/os-sdlc && bundle exec ruby bin/lint-inward-dependencies ``` ``` lib/os_sdlc/lint/inward_dependencies.rb:107:in 'Hash#fetch': key not found: "reroute" (KeyError) ``` `add_role_entry` (line 39) and `SourceFile#role` (line 65) both take `split("/").first` of the path. `crosses_inward?` (line 107) calls `role_layer.fetch(ref_role)`. ## Reproduce ``` cd plugins/os-sdlc && bundle exec ruby bin/lint-inward-dependencies ``` ## Approach Decision: G2 option A, pass-scoped. The role comes from the `role_by_const:` map (the existing input) in every place. The folder-derived map stays as the default when no map is given. The bin script may read the crc ledger as the map source: the G2 document (step 1) says to key the ledger `role` column by `new_constant`, or by `constant` when the verdict is `keep` (ledger format: `plugins/os-sdlc/skills/crc-refactor/references/ledger.md`). Add the ledger input as an optional bin argument. Do not change the default. Required work: 1. `SourceFile#role` looks up the constant that the file defines in the `role_by_const` map. It does not read the path. — check: absent `split("/").first` in `plugins/os-sdlc/lib/os_sdlc/lint/inward_dependencies.rb` 2. A constant with no role prints one line, `unmapped <path> <Constant>`, and does not raise `KeyError`. — check: present `unmapped` in `plugins/os-sdlc/lib/os_sdlc/lint/inward_dependencies.rb` 3. The walker skips the constant path of a `class` or `module` node that only reopens its parent class or module. 4. `bin/lint-inward-dependencies` accepts an optional ledger path and builds `role_by_const` from the ledger `role` column (its values are the `ROLE_LAYER` keys). — check: present `role_by_const` in `plugins/os-sdlc/bin/lint-inward-dependencies` 5. Add a test that runs the lint over a fixture tree with a folder that is not in `ROLE_LAYER` and asserts that no exception is raised. — check: present `unmapped` in `plugins/os-sdlc/tests/lint/inward_dependencies_test.rb` Rejected: suffix convention (G2 candidate 1) — renames concepts and conflicts with A15 and ADR-0272. Rejected: `LAYER` declaration in each class — about 360 one-line claims that nothing checks. Rejected: retire the lint (option C) — crc gate 3 loses its outward-reference signal. ## Acceptance criteria 1. Given a lib tree with a folder that is not in `ROLE_LAYER`, when the lint runs, then it raises no exception. 2. Given a constant with no entry in `role_by_const`, when the lint runs, then the output has one `unmapped <path> <Constant>` line for it. 3. Given a file that only reopens its parent class, when the lint runs, then the output has no reference line for the reopening. 4. Given a ledger path, when `bin/lint-inward-dependencies` runs, then it takes each role from the ledger. ## Owner sketch `OsSdlc::InwardDependencies` owns the role of a constant. It keeps its constructor and gains one collaborator for the ledger. ```ruby class OsSdlc::InwardDependencies def initialize(root:, role_by_const: nil) # nil falls back to the folder-derived map def outward # reference lines, then one "unmapped <path> <Constant>" line per constant with no role end class OsSdlc::InwardDependencies::SourceFile def role(role_by_const) # role_by_const[defined constant]; no path parsing end ``` - `SourceFile#outward_references` asks `role(role_by_const)` and prints `unmapped` instead of calling `role_layer.fetch`. - `bin/lint-inward-dependencies` reads the optional ledger path, builds `role_by_const` from the ledger, and passes it to `InwardDependencies.new`. ## Non-goals Checking the layer rule between crc passes. Jared rules on that separately (G2 memo 1). ## Origin - Trigger: lens challenge G2, commit 4b8a1730. Running `bundle exec ruby bin/lint-inward-dependencies` in plugins/os-sdlc raised the `KeyError`. - Improvised this session: none - Chain: crash on `reroute` ← `SourceFile#role` reads the first path segment ← the lint was written for the ADR-0222 role directories, and ADR-0272 retired them. Stop: DESIGN (ADR-0272 item 3; docs/crc/os-adr/lens-challenge-g2-layer-signal.md). - Root candidate: #935 (the lint redesign named in ADR-0272 item 3) - Where: plugins/os-sdlc/lib/os_sdlc/lint/inward_dependencies.rb - Session: ab599e53-dbf5-4953-b8bf-c598a9c5c16e - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-crc-os-adr-2026-10-04/ab599e53-dbf5-4953-b8bf-c598a9c5c16e.jsonl
Author
Owner

Work started on branch crc-os-adr-2026-10-04 (persistent worktree) via /os-sdlc:implement.

Work started on branch crc-os-adr-2026-10-04 (persistent worktree) via /os-sdlc:implement.
Author
Owner

Dropped follow-up: map edge for test-writer unexpected_pass after a behavior-verifier fail. It treats a symptom; the root is the behavior-verifier failing without a probe (now filed).

Dropped follow-up: map edge for test-writer unexpected_pass after a behavior-verifier fail. It treats a symptom; the root is the behavior-verifier failing without a probe (now filed).
Author
Owner

Resolution

Done: inward-dependencies lint takes each file's role from role_by_const, prints 'unmapped ' instead of raising KeyError, skips reopen-only class/module nodes; bin/lint-inward-dependencies accepts an optional crc ledger TSV and builds role_by_const from its role column (InwardDependencies::Ledger).

Evidence: Commit d4ff3c09 on branch crc-os-adr-2026-10-04 (implementation 29, 20 dispatches, one orchestrator reroute after a wrong verifier verdict). 7/7 lint tests green, suite green twice, contract audit pass. bin/lint-inward-dependencies on real os-sdlc lib now exits 0 and prints 'unmapped lib/os_sdlc/reroute/draft.rb Reroute::Draft' (crashed with KeyError before).

Follow-ups: #950 behavior-verifier can fail without a probe; #951 escalation procedure has no recovery for a wrong verdict; map unexpected_pass edge DROPPED by skeptic (root is #950). Branch not yet merged to main: merge waits for the user's decision on the whole crc-os-adr branch.

## Resolution **Done:** inward-dependencies lint takes each file's role from role_by_const, prints 'unmapped <path> <Constant>' instead of raising KeyError, skips reopen-only class/module nodes; bin/lint-inward-dependencies accepts an optional crc ledger TSV and builds role_by_const from its role column (InwardDependencies::Ledger). **Evidence:** Commit d4ff3c09 on branch crc-os-adr-2026-10-04 (implementation 29, 20 dispatches, one orchestrator reroute after a wrong verifier verdict). 7/7 lint tests green, suite green twice, contract audit pass. bin/lint-inward-dependencies on real os-sdlc lib now exits 0 and prints 'unmapped lib/os_sdlc/reroute/draft.rb Reroute::Draft' (crashed with KeyError before). **Follow-ups:** #950 behavior-verifier can fail without a probe; #951 escalation procedure has no recovery for a wrong verdict; map unexpected_pass edge DROPPED by skeptic (root is #950). Branch not yet merged to main: merge waits for the user's decision on the whole crc-os-adr branch.
jared closed this issue 2026-10-05 12:56:49 +00:00
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#947
No description provided.