Skip to content

feat(policy): read backend's .rafter/config.yml + flat-shape compat (sable-c1c)#154

Merged
Rome-1 merged 1 commit into
mainfrom
sable-c1c-cli-config-fallback
Jun 1, 2026
Merged

feat(policy): read backend's .rafter/config.yml + flat-shape compat (sable-c1c)#154
Rome-1 merged 1 commit into
mainfrom
sable-c1c-cli-config-fallback

Conversation

@Rome-1
Copy link
Copy Markdown
Collaborator

@Rome-1 Rome-1 commented Jun 1, 2026

Summary

Backend triage (hq-4tcey) confirmed rafter-backend reads policy at .rafter/config.yml (subdir + config.yml) with exclude_paths / custom_patterns flat at the top level, while the CLI canonical is .rafter.yml with scan.* nested. Customers writing either shape get honored by only one tool — exactly what bit the recent customer (.rafter.yml worked locally after #152, silently no-op'd remotely).

This PR is the CLI half of the bilateral fix:

  • CLI now reads .rafter/config.yml indefinitely (no deprecation — backend's file path stays a first-class location).
  • CLI accepts the backend flat shape (top-level exclude_paths / custom_patterns) alongside the canonical nested form, in either file.

Backend pairs this with their own work to add .rafter.yml fallback + scan.* schema compat. Once both ship, customers can write either file with either shape and both tools honor it.

Precedence

For each directory in the walk-up from cwd:

.rafter.yml   →  .rafter.yaml   →  .rafter/config.yml   →  .rafter/config.yaml
   (canonical dotfile wins if multiple files exist)

Within a single file, nested scan.exclude_paths wins over top-level exclude_paths if both are present (no merge — explicit precedence keeps debugging straightforward).

What's accepted in each shape

Shape Where it works Notes
scan.exclude_paths (nested) .rafter.yml, .rafter/config.yml CLI canonical
exclude_paths (top-level) .rafter.yml, .rafter/config.yml Backend flat-shape compat
scan.custom_patterns (nested) both CLI canonical
custom_patterns (top-level) both Backend flat-shape compat

Top-level exclude_paths and custom_patterns are added to VALID_TOP_LEVEL_KEYS so they no longer trigger Warning: Unknown policy key … on validate.

Tests

Suite Cases Status
node/tests/policy-loader.test.ts (new sable-c1c block) 8 Pass
python/tests/test_policy.py::TestBackendCompat 7 Pass
node/tests/policy-{loader,merge,validation}.test.ts (regression) 40 Pass
python/tests/test_policy.py full suite (regression) 25 Pass

Coverage hits: subdir-only discovery, both .yml and .yaml extensions, dotfile-wins precedence, top-level schema accepted on both file paths, nested-wins-on-collision, no warnings for compat keys.

Pairs with

Tracking

  • Bead: sable-c1c (P1 feature, claimed by me)
  • Sibling beads:
    • sable-yz0 (P0 bug) — scan.exclude_paths enforcement (PR fix(scan): honor .rafter.yml scan.exclude_paths on both engines (sable-yz0) #152, pending merge)
    • sable-s2t (P1) — rerouted: R-XXXXX hashing lives in github-action / juno, not rafter-backend
    • sable-5vo (P1) — closed: backend confirmed which file/schema they honor; this PR is the answer
    • sable-r9h (P3) — follow-up: pattern-length cap for ReDoS defense-in-depth

🤖 Generated with Claude Code

…sable-c1c)

Backend triage (hq-4tcey, closed) confirmed rafter-backend reads policy
at .rafter/config.yml (subdir + config.yml) with exclude_paths /
custom_patterns flat at the top level, while the CLI canonical is
.rafter.yml with scan.* nested. Customers writing either shape get
honored by only one tool — the customer's .rafter.yml worked locally
(after sable-yz0) and silently no-op'd on the remote cloud scan.

Bilateral alignment plan:
  - Backend adds .rafter.yml fallback with scan.* schema compat
    (their preferred direction).
  - CLI (this PR) reads .rafter/config.yml indefinitely and accepts
    the backend flat shape alongside the canonical nested form. No
    deprecation — both shapes supported permanently.

Two changes in policy-loader.ts / policy_loader.py:

1. findPolicyFile / find_policy_file walks all four candidates in
   precedence order: .rafter.yml > .rafter.yaml > .rafter/config.yml
   > .rafter/config.yaml. The canonical dotfile wins if both shapes
   exist (matching prior CLI behavior for users who already wrote
   .rafter.yml).

2. mapPolicy / _map_policy accepts top-level `exclude_paths` and
   `custom_patterns` (backend flat shape) and folds them into
   policy.scan.* — but only if the nested form wasn't already set.
   Nested form takes precedence on collision. Top-level compat keys
   are added to VALID_TOP_LEVEL_KEYS so they don't trigger
   "Unknown policy key" warnings on validate.

Tests: 8 Node + 7 Python new assertions covering subdir-only discovery,
extension variants, dotfile-wins precedence, top-level schema accepted
on both file paths, nested wins on collision, no warnings for compat
keys. Broader regression sweeps (25 Python + 16 Node) still pass.

Paired with sable-yz0 (PR #152), this unblocks customers writing
either CLI-shape or backend-shape policy. Once rafter-backend adds the
.rafter.yml fallback (their side of the bilateral fix), both tools
honor either file in either shape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Rome-1 Rome-1 merged commit a244d20 into main Jun 1, 2026
@Rome-1 Rome-1 deleted the sable-c1c-cli-config-fallback branch June 1, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant