Inheritance
A manifest is a tree, so a node’s policy applies to everything under it. The rules for how that works are short, and the asymmetry between allowances and prohibitions is the part worth reading twice.
Allowances accumulate down, and can be reset
Section titled “Allowances accumulate down, and can be reset”An imports.allow list is inherited: a node’s effective allowlist is its own entries plus
every ancestor’s. external behaves the same way, and omitting it inherits rather than
clearing.
A node’s allowlist compiles to a rule scoped to that node’s whole subtree — not to its
immediate files. A folder three levels down with no imports of its own is still covered by
the ancestor that stated one. Descendants that state their own allowlist are excluded from
the ancestor’s rule, transitively, so exactly one allowlist rule governs any given file:
the nearest one above it.
That is the mechanism behind the tightening you actually want, where each tier can only narrow:
"~/server/src/": imports: external: [effect] allow: ["~/server/src/**", "~/contracts/**"] children: "modules/{module}/": children: "domain/": # Narrower: the ancestor's allowances no longer apply. imports: reset: true external: [effect] allow: ["@/modules/{module}/domain/**"]members and surface on a folder
Section titled “members and surface on a folder”Neither is merged down the tree the way an allowlist is. Stated on a folder, a members or
surface rule covers the subtree — every file under the folder is selected by it — and a
descendant stating its own adds a second rule rather than replacing the first. Both fire.
A vocabulary is a statement about every file in a tier, and a narrower one below it is a
second statement, not an exemption.
name inherits the same way
Section titled “name inherits the same way”A naming convention is inherited by the whole subtree, and a descendant that states its own
replaces it. There is no reset for naming, because a convention is a single value rather
than a list: restating it is the reset.
"~/database/src/": name: { regex: "^(?:Database|[a-z0-9]+(?:-[a-z0-9]+)*)$" } children: "migrations/": { name: snake_case } # narrower, and stated "**/": { name: kebab-case }Prohibitions only accumulate
Section titled “Prohibitions only accumulate”A deny is emitted once, over the subtree of the node that declares it, and descendants
neither re-emit it nor can escape it.
This is why reset: true drops inherited allowances only. If reset also dropped
prohibitions, a nested node could make its own subtree quieter than its ancestors — and the
direction a mistake is dangerous in is the quiet one. As written, no arrangement of nodes
can produce a subtree that refuses less than the tier above it.
The corollary: an exemption to a prohibition is declared by the prohibition. deny.except
and deny.matchNot are the only ways out, and both are written by the author of the rule, in
the same breath, where a reader of the rule sees them. A tier cannot opt itself out of a
constraint someone else wrote about it.
deny is checked before allow
Section titled “deny is checked before allow”Within one node, a prohibition wins. A target that a deny matches is reported with that
deny’s message even if the allowlist would also have admitted it — which is the whole
reason to write one, since the allowlist alone would already refuse anything outside it. The
deny exists to say why this particular target is wrong, in a sentence the general
allowlist message cannot.
The three ways to widen
Section titled “The three ways to widen”Each is a distinct statement, each greppable, and none of them is silent.
| Switch | Says | Leaves in force |
|---|---|---|
reset: true |
this tier states its own allowlist from scratch | all inherited prohibitions |
unrestricted: true |
this tier has no allowlist yet | all inherited prohibitions |
layout: open |
this folder does not enumerate its file names | its imports policy, its subfolders |
unrestricted is required whenever a node states imports without an allow. You
cannot leave an allowlist off by accident; the manifest will not decode. That turns an
untightened tier into a sentence someone wrote and a reviewer saw, and makes
grep -c unrestricted architecture.yaml the adoption backlog.
partial: true
Section titled “partial: true”partial says “do not enumerate this folder’s own file list”. The node compiles no layout
rule, so its direct children are not checked against its children keys — but everything
else it states still applies to its whole subtree, and its child nodes still compile their
own rules normally:
"src/": partial: true imports: { external: [effect], allow: ["src/**"] } # ← still in force children: "deep/": message: "deep/ admits only *.ok.ts" children: { "*.ok.ts": {} } # ← still enforcedIt is the incremental-adoption switch: bring one branch of a large repository under policy without first having to describe every file sitting beside it.