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.
Context
Follow-up from #368. When a zone uses
autoDiscover, a parent rule like{ "from": "features", "allow": ["shared"] }expands to one rule perdiscovered 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-levelsrc/features/index.tsbarrel ends up in zonefeatureswith allow[shared], and importing children producesfeatures → features/<child>false positives.
The Bulletproof preset works around this by leaving
patternsempty on thefeatureszone, which keeps top-level files unclassified. That fixes thebarrel 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-50and pinned bybulletproof_top_level_features_file_is_unrestrictedincrates/core/tests/integration_test/boundary_violations.rs.Proposal
Differentiate parent rule expansion from child rule expansion:
Parent rule (the rule whose
fromis the literal group name and whichis kept as a fallback when patterns is non-empty): expand
allowto includeALL discovered child zone names AS WELL AS the original allow list. So
features(the parent fallback zone) can re-export auth/billing without aviolation.
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:
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 barrelscan 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. Theexpansion logic does the rest.
What changes
expand_auto_discoverincrates/config/src/config/boundaries.rsneeds totrack which rules are 'parent' (their
fromis the literal group name ANDthe parent zone is being kept as a fallback) versus 'child' (their
fromisan auto-discovered child). Only parent rules get the children-included allow
list expansion.
Estimated ~30-50 LOC change, plus tests for:
patterns: ['src/features/**']on thefeatures zone to enable strict mode for top-level features files, with
no false positive on the barrel.
Cross-references
T-shirt size: M.