Skip to content

boundaries: parent rule should auto-allow discovered child zones (strict mode for autoDiscover) #372

@BartWaardenburg

Description

@BartWaardenburg

Context

Follow-up from #368. When a zone uses autoDiscover, a parent rule like
{ "from": "features", "allow": ["shared"] } expands to one rule per
discovered child:

  • features/auth → [shared]
  • features/billing → [shared]

This correctly enforces sibling-feature isolation (auth cannot import billing).
But it has a side-effect: the parent zone itself (when kept as a fallback via
patterns) inherits the same allow list, so a top-level
src/features/index.ts barrel ends up in zone features with allow
[shared], and importing children produces features → features/<child>
false positives.

The Bulletproof preset works around this by leaving patterns empty on the
features zone, which keeps top-level files unclassified. That fixes the
barrel case but means non-barrel top-level files (e.g. src/features/types.ts)
are also unclassified and unrestricted. That trade-off is documented at
crates/config/src/config/boundaries.rs:35-50 and pinned by
bulletproof_top_level_features_file_is_unrestricted in
crates/core/tests/integration_test/boundary_violations.rs.

Proposal

Differentiate parent rule expansion from child rule expansion:

  • Parent rule (the rule whose from is the literal group name and which
    is kept as a fallback when patterns is non-empty): expand allow to include
    ALL discovered child zone names AS WELL AS the original allow list. So
    features (the parent fallback zone) can re-export auth/billing without a
    violation.

  • Child rule (generated rules for each discovered child): keep the
    original allow list with <group> expanded to siblings.

    Wait, that re-introduces the sibling-imports-sibling problem. The right
    shape is: child rules use the ORIGINAL allow list (without expanding <group>
    to siblings). So:

    • Parent rule features → [shared] becomes:
      • features → [features/auth, features/billing, shared] (parent gets all children + original allow)
      • features/auth → [shared] (child keeps original allow exactly)
      • features/billing → [shared] (child keeps original allow exactly)

    Sibling isolation still fires (auth allow is just [shared]), and barrels
    can re-export children (features allow includes both children).

Why no new schema fields

The user writes { "name": "features", "autoDiscover": ["src/features"] }
plus { "from": "features", "allow": ["shared"] }. Same as today. The
expansion logic does the rest.

What changes

expand_auto_discover in crates/config/src/config/boundaries.rs needs to
track which rules are 'parent' (their from is the literal group name AND
the parent zone is being kept as a fallback) versus 'child' (their from is
an auto-discovered child). Only parent rules get the children-included allow
list expansion.

Estimated ~30-50 LOC change, plus tests for:

  • Parent barrel can import children (allowed via parent rule's expanded allow).
  • Children cannot import siblings (child rule's allow unchanged).
  • Children can still import everything the parent's allow listed.
  • Bulletproof preset can re-add patterns: ['src/features/**'] on the
    features zone to enable strict mode for top-level features files, with
    no false positive on the barrel.

Cross-references

T-shirt size: M.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions