feat(grammar)!: the run segment, last, and the position is the argument #9

Merged
administrator merged 1 commit from feat/the-run-segment into main 2026-10-28 19:47:15 +00:00
Collaborator

The rule was prose, and the slot was taken

The convention named a "run qualifier" three times (CONVENTION.md § env, § 6, § 14) and defined it nowhere — while qualifier is a single token already carrying real refinements. A rehearsal of the data lake produced one name twice:

see-prd-dsd-data-lake-r7f3-bkt-par      raw
see-prd-dsd-data-lake-r7f3-bkt-par      curated

So the run is its own segment, last:

see-prd-dsd-data-lake-raw-bkt-par-r7f3
see-prd-dsd-ops-forge-vm-01-r001

A rehearsal is a from-scratch realisation of an environment beside the real one: its resources keep that environment's own env — a faithful copy of production says prd — and the run tells two realisations apart.

Why last, and not where the ADR wrote it

position verdict
the qualifier's place the raw and the curated bucket of one rehearsal give one name. Eliminated.
after env (ADR-010 decision 5) readable, but the run sits where target sits — decodable only because a run holds a digit and a target code does not. An argument that expires in silence.
last nothing can follow the index, so no disjointness argument is needed at all. And where a name truncates, the hash is computed over the FULL name, so two runs still differ instead of merging.

ADR-010 wrote "after env" before the target segment existed at that position.

Three lints, all mutation-tested

The shape is fixed in the registry (grammar.run_regex: r + three hex digits) so the segment is recognised without being counted from either end. The lints refuse a shape a location code matches, a shape the two-digit index matches, and no shape at all.

OK   a run shape a location code also matches   also matches the location code 'ams'
OK   a run shape an index also matches          also matches the index '00'
OK   no run shape at all                        run_regex is missing: would accept any token

Two things it refuses, deliberately

The compact style refuses rather than dropping the run. 8 characters on a Proxmox SDN object, 5 eaten by the prefix: emitting the name without its run would hand two realisations one name on the one platform where the name is all there is. Consequence recorded in the README's known limits — those objects cannot be rehearsed today, they need a shorter shape or a second node.

A run outside the shape is refused by the module and by the policy, both reading the registry, so the two cannot disagree about what a run looks like.

One bug this found in passing

decode() reads the run first, because the segment is last: taking the index or the location before it hands them the run's token. Measured here — the location vanished from a rehearsed name until this was fixed. That is how a tail segment added without touching decode silently loses the two below it.

Verification

56 shared cases  (+3, both implementations agreeing)
75 module tests  (+5, two of them refusals)
53 policy tests  (+3)
14 lint mutations (+3), 0 silent

make check green. The prose that said "a run qualifier" now names the segment, in all four places.

Branched from main, so it does not stack on #8 — the two will conflict textually in conventions.yaml, CHANGELOG.md, README.md and lints.py, and whichever merges second gets rebased.

🤖 Generated with Claude Code

## The rule was prose, and the slot was taken The convention named a "run qualifier" **three times** (`CONVENTION.md` § env, § 6, § 14) and defined it nowhere — while `qualifier` is a single token already carrying real refinements. A rehearsal of the data lake produced one name twice: ``` see-prd-dsd-data-lake-r7f3-bkt-par raw see-prd-dsd-data-lake-r7f3-bkt-par curated ``` So the run is its own segment, **last**: ``` see-prd-dsd-data-lake-raw-bkt-par-r7f3 see-prd-dsd-ops-forge-vm-01-r001 ``` A rehearsal is a from-scratch realisation of an environment beside the real one: its resources keep that environment's own `env` — a faithful copy of production says `prd` — and the run tells two realisations apart. ## Why last, and not where the ADR wrote it | position | verdict | | --- | --- | | the qualifier's place | the raw and the curated bucket of one rehearsal give **one** name. Eliminated. | | after `env` (ADR-010 decision 5) | readable, but the run sits where `target` sits — decodable only because a run holds a digit and a target code does not. An argument that expires in silence. | | **last** | nothing can follow the index, so **no disjointness argument is needed at all**. And where a name truncates, the hash is computed over the FULL name, so two runs still differ instead of merging. | ADR-010 wrote "after env" before the `target` segment existed at that position. ## Three lints, all mutation-tested The shape is fixed in the registry (`grammar.run_regex`: `r` + three hex digits) so the segment is recognised without being counted from either end. The lints refuse a shape a **location code** matches, a shape the **two-digit index** matches, and **no shape at all**. ``` OK a run shape a location code also matches also matches the location code 'ams' OK a run shape an index also matches also matches the index '00' OK no run shape at all run_regex is missing: would accept any token ``` ## Two things it refuses, deliberately **The compact style refuses rather than dropping the run.** 8 characters on a Proxmox SDN object, 5 eaten by the prefix: emitting the name without its run would hand two realisations one name on the one platform where the name is all there is. Consequence recorded in the README's known limits — **those objects cannot be rehearsed today**, they need a shorter shape or a second node. **A run outside the shape is refused by the module and by the policy**, both reading the registry, so the two cannot disagree about what a run looks like. ## One bug this found in passing `decode()` reads the run **first**, because the segment is last: taking the index or the location before it hands them the run's token. Measured here — the location vanished from a rehearsed name until this was fixed. That is how a tail segment added without touching `decode` silently loses the two below it. ## Verification ``` 56 shared cases (+3, both implementations agreeing) 75 module tests (+5, two of them refusals) 53 policy tests (+3) 14 lint mutations (+3), 0 silent ``` `make check` green. The prose that said "a run qualifier" now names the segment, in all four places. > Branched from `main`, so it does not stack on #8 — the two will conflict textually in `conventions.yaml`, `CHANGELOG.md`, `README.md` and `lints.py`, and whichever merges second gets rebased. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(grammar)!: the run segment, last, and the position is the argument
Some checks failed
ci / check (pull_request) Has been cancelled
fb10649b9d
The convention named a "run qualifier" three times in prose and defined it
nowhere, while the qualifier slot was already taken by real refinements. A
rehearsal of the data lake produced ONE name twice:

  see-prd-dsd-data-lake-r7f3-bkt-par      raw
  see-prd-dsd-data-lake-r7f3-bkt-par      curated

So the run is its own segment, LAST:

  see-prd-dsd-data-lake-raw-bkt-par-r7f3
  see-prd-dsd-ops-forge-vm-01-r001

A rehearsal is a from-scratch realisation of an environment beside the real
one, so its resources keep that environment's own env - a faithful copy of
production says prd - and the run is what tells two realisations apart.

LAST, AND THAT IS THE WHOLE ARGUMENT. After env is where the consumer's own
ADR wrote it, before the target segment existed at that position; it stays
decodable there only because a run token holds a digit and a target code does
not, which is an argument that expires in silence. Nothing can follow the
index, so last needs no disjointness argument at all - and where a name
truncates, the hash is computed over the FULL name, so two runs still differ
instead of merging.

The shape is fixed (grammar.run_regex: r + three hex digits) so the segment is
recognised without being counted from either end, and THREE LINTS keep it
unmistakable: a shape a location code matches, a shape the two-digit index
matches, and no shape at all. All three mutation-tested.

A run tag and a run Kubernetes label come with it, emitted only where the
value exists - a "run." tag saying nothing would make the tag useless as a
filter, which is the rule target already follows.

AND THE COMPACT STYLE REFUSES RATHER THAN DROPPING IT. 8 characters on a
Proxmox SDN object, 5 eaten by the prefix: emitting the name without its run
would hand two realisations ONE name on the one platform where the name is all
there is to tell them apart. Recorded in the README's known limits, because it
means those objects cannot be rehearsed today.

decode() reads the run FIRST for the same reason the segment is last: taking
the index or the location before it would hand them the run's token. That is
how a tail segment added without touching decode silently loses the two below
it - measured here, the location vanished from a rehearsed name until this.

  56 shared cases (+3, both implementations agreeing)
  75 module tests (+5, two of them refusals)
  53 policy tests (+3)
  14 lint mutations (+3), 0 silent

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
administrator deleted branch feat/the-run-segment 2026-10-28 19:47:15 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
shared/infra-conventions!9
No description provided.