# Placement is a grammar

> Where a file lives and what its name says follow one grammar, shown in the grammar]: a container, an optional subject, a concern, and then the file, as shown in…

Page: Methodology · Build
Canonical: https://banes-lab.com/disciplined-methodology/build#placement-is-a-grammar

This section is stop 39 of 102 in the learning route. Previous: [24 - Fail at the boundary](https://banes-lab.com/disciplined-methodology/build/fail-at-the-boundary.md). Next: [26 - It looked right](https://banes-lab.com/disciplined-methodology/verify/it-looked-right.md). It builds on [23 - The filesystem is the architecture](https://banes-lab.com/disciplined-methodology/build/the-filesystem-is-the-architecture.md).

Where a file lives and what its name says follow one grammar, shown in [I1·a the grammar](https://banes-lab.com/disciplined-methodology/build#placement-is-a-grammar-panel-a): a container, an optional subject, a concern, and then the file, as shown in [I1·b one tree](https://banes-lab.com/disciplined-methodology/build#placement-is-a-grammar-panel-b). The file's name ends with the concern of its folder, which is [concern-folder correspondence](https://banes-lab.com/records/arch/concern-folder-correspondence.md). Every word comes from a [closed vocabulary](https://banes-lab.com/records/arch/closed-vocabulary.md): [I1·c where a word goes](https://banes-lab.com/disciplined-methodology/build#placement-is-a-grammar-panel-c) shows how a word is placed, [I1·e the vocabulary](https://banes-lab.com/disciplined-methodology/build#placement-is-a-grammar-panel-e) shows how the words are declared, and [I1·d jurisdiction](https://banes-lab.com/disciplined-methodology/build#placement-is-a-grammar-panel-d) shows what the grammar claims and what it leaves alone. A file with two concerns is split rather than given a vague name, which is [one concern per file](https://banes-lab.com/records/arch/one-concern-per-file.md). Only an overlap between two tags for one concern that cannot be reduced takes the tag closer to the domain, following the precedence [the layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md) holds on the architecture page. The grammar is what turns [separation of concerns](https://banes-lab.com/records/arch/separation-of-concerns.md) from advice into a check, and the layer spine is the axis it uses to classify every concern.

### One legal path per file

Separation of concerns given as advice produces a different tree for every developer who follows it. A helper folder appears, then a utils folder, then a second helper folder inside a feature, and six months later you can't say where a new file goes, and neither can the model. Nothing parses a path, so a wrong placement fails no check and reads as a preference.

For this reason I treat placement as a grammar. A name is a claim about what the code does, and it is checked against the code, never against the old name. The vocabulary is closed and the path is parsed, rather than placement being reviewed by eye. In practice, the closed vocabulary and the roots it governs are declared, and a check parses every path against the grammar. A collision is resolved sideways with a variant, never downward with another folder. An undeclared word is a decision for the developer, worked down a ladder: an existing word first, then the is-a test, then the conclusion that the filename is wrong, then the conclusion that the file itself is wrong. Every file is created conformant, because there is no queue of files waiting to be converted.

To check this, pick a file at random and work out its path from its contents alone. If the derived path differs from the real one, one of them is wrong, and the grammar says which. Files that an ecosystem names for you keep their names. The grammar governs what you write, not what your tools require, and a tree carrying another system's ownership markers is never declared a governed root, because its names are identifiers that system resolves at runtime.

### Slots, not words

Words are resolved by their position rather than by their spelling, which is [positional slot resolution](https://banes-lab.com/records/arch/positional-slot-resolution.md). A word is read by the slot it lands in, so a concern tag can also serve as a subject: a registry of pools and a pool named base use the same word in two slots without ambiguity. The one restriction on the words themselves is that a subject never equals its own concern. A subject folder exists exactly when a container holds two or more sets of one concern that must not merge, because optional grouping would give classification two right answers and make placement impossible to check. [Sideways overflow](https://banes-lab.com/records/arch/sideways-overflow.md) handles the rest: a collision takes the filename's variant slot, breadth takes a sibling subject folder, and the [bounded nesting depth](https://banes-lab.com/records/arch/bounded-nesting-depth.md) is the reason both slots exist.

### Jurisdiction is declared

[Declared jurisdiction](https://banes-lab.com/records/arch/declared-jurisdiction.md) decides what the grammar reaches. Each key in the configuration is a governed root, and without a declaration there is no enforcement, so a tree outside the jurisdiction keeps its own names. A declaration is a claim that is checked against the disk: a root declared before its folder exists governs nothing and fails nothing, yet it reads as coverage. Material written elsewhere is declared once as an upstream root, and that one declaration exempts it from the naming, tense and reference checks together, because all three fail on such a tree and none of those failures is a defect in it.

### Judgement classifies, the check parses

Classification is a matter of judgement, while structure can be decided by a machine, and the tooling stops at the line between them. A check reports that a name does not parse or that a tag disagrees with its folder, but it never decides what a file is; the classification rule lives in the layer spine on the architecture page. Reshaping an existing tree is therefore a [manual identity migration](https://banes-lab.com/records/arch/manual-identity-migration.md), done one container at a time with the gate green between each, and the rest is described in [moves and renames](https://banes-lab.com/disciplined-methodology/verify/moves-and-renames.md).

### A vocabulary that proves itself

The vocabulary is one typed declaration, and its type is what makes it closed: the legal words for each slot are a union derived from the data rather than written out a second time, and a flat bucket is declared explicitly rather than inferred from the folder's shape.

The declaration also asserts its own [consistency](https://banes-lab.com/records/arch/consistency.md) when it compiles. A subject that is already a concern tag, a variant that is already a subject, or a concern whose layer lies outside the spine fails to compile, so the vocabulary cannot become inconsistent without the whole gate refusing to load. Every closed vocabulary here takes the same shape: a configuration that carries data and the proofs of its own consistency, and no reasoning.

I1·a the grammar

```text
folder = <container> | <subject> | <concern>       one word, never a dot
file   = <subject>.<concern>.<ext>
| <subject>.<variant>.<concern>.<ext>        only when two files would collide

depth  = container(1) → subject(2, optional) → concern(3) → file
a role may be skipped, never repeated, never revisited
the file's parent is always the concern folder
the file's concern tag equals its parent folder
```

I1·b one tree

```mermaid
flowchart TB
engine["engine · a container, one grouping axis"]
registries["registries · a concern folder, files of one role"]
page["page.registry.ts"]
route["route.registry.ts"]
form["form · a subject folder"]
panel["panel · a subject folder"]
fv["validators"]
pv["validators"]
field["field.validator.ts"]
layout["layout.validator.ts"]
engine --> registries --> page
registries --> route
engine --> form --> fv --> field
engine --> panel --> pv --> layout
form -. two sets of validators must not merge .- panel
```

I1·c where a word goes

```mermaid
flowchart LR
word["A word at a slot"]
role{"A role a file plays?"}
thing{"A thing the system has?"}
concern["The concern list · the file's tag"]
subject["The subject list · the first slot of a name"]
rejected["Rejected · a process, an adjective, a grouping label"]
split["The file gets its real role, or splits"]
word --> role
role -- registry, validator, renderer --> concern
role -- no --> thing
thing -- form, panel, route --> subject
thing -- timing, lazy, misc, helper --> rejected --> split
```

I1·d jurisdiction

```mermaid
flowchart TB
tree["A tree"]
declared{"Declared as a governed root?"}
governed["Every file inside resolves to one legal path"]
foreign{"Carries another system's ownership markers?"}
upstream{"Authored elsewhere?"}
left["Left alone · a grammar that does not claim a tree enforces nothing in it"]
refused["Refused as a root · its names are identifiers another runtime resolves"]
exempt["Declared once as upstream · exempt from naming, tense and reference checks together"]
tree --> declared
declared -- yes --> foreign
foreign -- yes --> refused
foreign -- no --> governed
declared -- no --> upstream
upstream -- yes --> exempt
upstream -- no --> left
```

I1·e the vocabulary

```typescript
export const LAYERS = ["domain", "application", "processing", "runtime", "infrastructure", "operations", "product"] as const;

export const taxonomy = {
containers: {
"<governed-root>": ["<container>", "<container>"],
},
specialContainers: {
"<governed-root>": ["<flat-bucket>"],
},
concerns: [
{ folder: "registries", tag: "registry", layer: "infrastructure" },
{ folder: "validators", tag: "validator", layer: "processing" },
{ folder: "strings", tag: "strings", layer: "product" },
],
subjects: ["base", "<domain-noun>", "<domain-noun>"],
variants: ["<facet>", "<facet>"],
grammar: {
separator: ".",
maxDepthFromRoot: 3,
compoundMarkers: ["test", "spec", "generated"],
},
} as const;

type Config = typeof taxonomy;
export type Subject = Config["subjects"][number];
export type Variant = Config["variants"][number];
export type ConcernTag = Config["concerns"][number]["tag"];
export type GovernedRoot = keyof Config["containers"];

type Assert<Name extends string, Overlap> = [Overlap] extends [never] ? true : [Name, Overlap];

export const NO_SUBJECT_CONCERN_OVERLAP: Assert<"subject is already a concern tag", Extract<Subject, ConcernTag>> = true;
export const NO_VARIANT_SUBJECT_OVERLAP: Assert<"variant is already a subject", Extract<Variant, Subject>> = true;
export const EVERY_LAYER_DECLARED: Assert<"concern layer is not in the spine", Exclude<Config["concerns"][number]["layer"], (typeof LAYERS)[number]>> = true;
```

## Links to

- [Concern-Folder Correspondence](https://banes-lab.com/records/arch/concern-folder-correspondence.md)
- [Closed Vocabulary](https://banes-lab.com/records/arch/closed-vocabulary.md)
- [One Concern Per File](https://banes-lab.com/records/arch/one-concern-per-file.md)
- [The layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md)
- [Separation of Concerns](https://banes-lab.com/records/arch/separation-of-concerns.md)
- [Positional Slot Resolution](https://banes-lab.com/records/arch/positional-slot-resolution.md)
- [Sideways Overflow](https://banes-lab.com/records/arch/sideways-overflow.md)
- [Bounded Nesting Depth](https://banes-lab.com/records/arch/bounded-nesting-depth.md)
- [Declared Jurisdiction](https://banes-lab.com/records/arch/declared-jurisdiction.md)
- [Manual Identity Migration](https://banes-lab.com/records/arch/manual-identity-migration.md)
- [Moves and renames](https://banes-lab.com/disciplined-methodology/verify/moves-and-renames.md)
- [Consistency](https://banes-lab.com/records/arch/consistency.md)

## Linked from

- [Three encodings](https://banes-lab.com/disciplined-methodology/start/three-encodings.md)
- [Documentation is code](https://banes-lab.com/disciplined-methodology/verify/documentation-is-code.md)
- [The layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md)
- [Containers on the spine](https://banes-lab.com/anatomy/reading/containers-on-the-spine.md)

## Evidence in the code

- [treeTab](https://banes-lab.com/source/tree/domain/converters/anatomy.converter.ts.md)
- [layersText](https://banes-lab.com/source/tree/domain/converters/anatomy.fragment.converter.ts.md)
