Skip to content

Commit b104b87

Browse files
committed
reconcile: reject a projected marker in the corpus, and check form per root
20-contract.md claimed the index raises DuplicateRegionId when an id appears in both marker forms 'anywhere in the repository'. Nothing enforced it for the corpus, and the gap was live: dropping ':declared:' from both markers of mirror-AttributeState made the region invisible and the run still reported Valid, exit 0, with the obligation guarding the wisdom defect silently gone. A both-forms rule would not have caught that - the id never appears in declared form - so the rule is that a projected marker in the corpus is MalformedRegion at all. The corpus has no projector: SS1 makes every module read-only. - Read-SpecSet.ps1: the pattern gains the projected form; seeing one fails the run - Read-SpecSet.Tests.ps1: the typo is rejected, the declared form still accepted - 20-contract.md: both sites narrowed to what is checked, and the half that stays open named - an id declared in the corpus and projected outside it, which CP9 puts beyond the spec-set checker and the glob table puts beyond IdCollision Decision recorded in 90-decisions.md and design/state/decisions/.
1 parent 40d52cc commit b104b87

6 files changed

Lines changed: 100 additions & 5 deletions

design/20-contract.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,12 @@ Authors write these; no code declares them, so this is their only home. The four
480480
*Authored records* above. Binding on the corpus:
481481

482482
- A region's opening and closing markers must match and must not nest.
483-
- A `(document, id)` pair must be unique, and one id must not appear in both marker forms anywhere
484-
in the repository.
483+
- A `(document, id)` pair must be unique within the corpus.
484+
- **Every region in the corpus is declared, and a projected marker anywhere in it is a finding.**
485+
The corpus has no projector — SS1 makes every module read-only — so a rendered region cannot
486+
legitimately appear here, and treating one as merely unrecognised is how an obligation
487+
disappears without trace: drop `:declared:` from both of a region's markers and the region
488+
ceases to exist, the obligation with it, on a run that still reports `Valid`.
485489
- A region must have a non-empty body.
486490
- `provisional-register` occurs exactly once across the whole corpus.
487491

@@ -742,10 +746,26 @@ clean. A declaration the index skipped is a declaration no check examined.
742746
|---|---|---|
743747
| `UnreadableDocument` | A corpus file cannot be opened or decoded as UTF-8 | Fix the file; check encoding, per `agent.md` on CP1252 imports |
744748
| `UnknownDeclarationForm` | A fence contains a construct the restricted grammar does not accept | Extend the grammar, or rewrite the declaration into a known form |
745-
| `MalformedRegion` | A marker is unclosed, mismatched, or nested | Fix the markers |
746-
| `DuplicateRegionId` | A `(document, id)` repeats, or one id appears in both marker forms anywhere in the repository | Rename one region or make the form consistent |
749+
| `MalformedRegion` | A marker is unclosed, mismatched, nested, or written in the projected form, which the corpus has no writer for | Fix the markers |
750+
| `DuplicateRegionId` | A `(document, id)` repeats within the corpus | Rename one region |
747751
| `CorpusNotFound` | `-CorpusPath` does not resolve to a directory | Fix the invocation |
748752

753+
**`MalformedRegion` covers the projected form as well as the unbalanced ones, and the name
754+
reading narrower than what it checks is the price** — paid deliberately, for the third time in
755+
this document, on the reasoning that widened `AnchorMissing` and `EnforcementUnevidenced` rather
756+
than splitting them: the check, the remedy, and the reason are the same in every case, and a
757+
second reason would have split one rule across two names for nothing.
758+
759+
**What this does not reach, stated rather than left to be found: an id declared in the corpus and
760+
projected outside it.** `AGENTS.md` § *Marked regions* makes form consistency repository-wide and
761+
`90-decisions.md` (2026-08-21, marked-region identity) settled it, but no checker applies it
762+
across both roots. `IdCollision` enforces it over the design-state document set, which
763+
§ *Artifacts of a unit kind* never resolves into `docs/docs/games/`; the spec-set checker cannot
764+
reach the other direction either, because CP9 keeps it to exactly one corpus root and widening
765+
that is a contract amendment rather than a slice's call. The exposure is small and worth naming:
766+
the corpus's ids are `mirror-`, `provisional-`, `lifecycle-` prefixed and nothing outside it
767+
projects under those names. The rule stands; what is checked is each root against itself.
768+
749769
`UnknownDeclarationForm` becoming frequent is the countable condition that reverses
750770
`90-decisions.md` (2026-08-20, restricted grammar). When status 2 stops meaning "look at this" and
751771
starts meaning "run it again", the real parser has become correct.

design/90-decisions.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,40 @@ Append-only. Newest at the top. The rejected alternatives are the point — with
99

1010
---
1111

12+
### 2026-08-31 — A projected marker in the corpus is a finding; form consistency is checked per root
13+
Context: `20-contract.md` claimed in two places — § *The marker vocabulary* and the
14+
`DuplicateRegionId` row — that an id appearing in both marker forms "anywhere in the repository"
15+
is raised by the spec-set index. Nothing enforced it for the corpus: `Get-DeclaredRegions` matched
16+
only the declared form, and `IdCollision`, which does enforce the policy, runs over the
17+
design-state document set that § *Artifacts of a unit kind* never resolves into
18+
`docs/docs/games/`. The gap was not theoretical. Dropping `:declared:` from both markers of
19+
`mirror-AttributeState` made the region invisible, took the obligation guarding the `wisdom`
20+
defect the brief is named for with it, and the run still reported `Valid`, exit 0, with the
21+
obligation count quietly down from two to one.
22+
Chosen: A projected-form marker anywhere in the corpus is `MalformedRegion`, not a both-forms
23+
collision. A both-forms rule would not have caught this at all — the typo drops `:declared:` from
24+
the start and the end together, so the id never appears in declared form and there is nothing to
25+
collide with. The corpus has no projector (SS1 makes every module read-only), so a rendered region
26+
there is always wrong and can be rejected on its own. `MalformedRegion` absorbs it rather than a
27+
new reason being minted, on the reasoning that widened `AnchorMissing` and `EnforcementUnevidenced`
28+
rather than splitting them. The two contract sites are narrowed to what is actually checked —
29+
`(document, id)` uniqueness within the corpus — and the half that stays open is named: an id
30+
declared in the corpus and projected outside it is checked by neither root, because CP9 keeps the
31+
spec-set checker to one corpus root. The 2026-08-21 policy is untouched; only who enforces it
32+
where is now written down truthfully.
33+
Rejected: Correcting the contract and leaving the index alone — prose only, no code, and rejected
34+
because it writes the silent retirement of an obligation down as accepted when the fix is one
35+
alternative in the pattern the file already owns (SS2) plus a test. Also rejected: adding
36+
`docs/docs/games/*.md` to § *Artifacts of a unit kind*'s document row so `IdCollision` covers the
37+
corpus, which would make the repository-wide claim literally true — rejected because it raises
38+
`UnrecordedArtifact` on all eight corpus files at once, and changes the installed design-state
39+
path's policy for another path's benefit, the shape `90-decisions.md` (2026-08-30, the content
40+
path is contracted as a third scope) already rejected by name when it declined to make
41+
`src/campaigns/` a unit kind.
42+
Reversibility: cheap — one regex alternative, one guard, one test, and three prose edits.
43+
44+
---
45+
1246
### 2026-08-31 — The `lifecycle-` marker vocabulary is reserved to the derived concept set
1347
Context: `Test-SpecSet.ps1`'s concept check raises a third finding the contract's § *Checks* table
1448
does not list — a `lifecycle-` region whose name is outside the derived concept set — and S6.5

design/state-index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ This document is a navigation view generated from `design/state/`. Edit the reco
158158
| decision/2026-08-30-the-engine-s-published-surface-is-enforced-by-a-check-not-only-by-prose | `unit/document/design-10-design`, `unit/document/design-20-contract` |
159159
| decision/2026-08-30-the-game-content-path-enters-design-through-a-design-pass-before-it-is-contracted-or-sliced | `unit/document/design-10-design`, `unit/document/design-20-contract`, `unit/document/design-30-slices` |
160160
| decision/2026-08-31-a-length-narrowing-on-a-collection-field-is-cp10-compliant-s21-3-is-fully-met ||
161+
| decision/2026-08-31-a-projected-marker-in-the-corpus-is-a-finding-form-consistency-is-checked-per-root ||
161162
| decision/2026-08-31-cp2-and-cp3-are-scoped-to-production-sources-and-the-cp3-check-is-tightened-to-match ||
162163
| decision/2026-08-31-the-lifecycle-marker-vocabulary-is-reserved-to-the-derived-concept-set ||
163164
<!-- decision-affects:end -->
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# decision/2026-08-31-a-projected-marker-in-the-corpus-is-a-finding-form-consistency-is-checked-per-root
2+
Date: 2026-08-31
3+
Anchor: 2026-08-31 — A projected marker in the corpus is a finding; form consistency is checked per root
4+
Status: accepted
5+
6+
## Claim
7+
A marker written in the projected form anywhere in the corpus is `MalformedRegion`. The corpus has
8+
no projector — SS1 makes every module read-only — so a rendered region cannot legitimately appear
9+
there, and leaving it merely unrecognised let a region disappear silently: dropping `:declared:`
10+
from both of a region's markers retired its obligation on a run that still reported `Valid`. A
11+
both-forms collision rule would not have caught that, because the id then never appears in
12+
declared form at all. `MalformedRegion` absorbs the case rather than a new reason being minted,
13+
on the reasoning that widened `AnchorMissing` and `EnforcementUnevidenced`. Form consistency is
14+
checked per root: `IdCollision` over the design-state document set, the spec-set index over the
15+
corpus, and an id declared in one and projected in the other is reached by neither — CP9 keeps
16+
the spec-set checker to a single corpus root, so `20-contract.md` names that bound instead of
17+
claiming a reach it does not have.

tools/Read-SpecSet.Tests.ps1

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,22 @@ Describe 'Read-SpecSetIndex' {
6565
$index = Read-SpecSetIndex -CorpusPath $corpus
6666
$index.State | Should -Be 'NotEvaluated'; $index.Reason | Should -Be 'DuplicateRegionId'
6767
}
68+
It 'a projected-form marker anywhere in the corpus yields MalformedRegion' {
69+
# The corpus has no projector - every module is read-only (SS1) - so a rendered region
70+
# cannot legitimately appear here. Left unlooked-for, dropping ':declared:' from both
71+
# of a region's markers retires that obligation with no trace: before this was
72+
# rejected, the fixture below indexed zero obligations and the run reported Valid.
73+
$corpus = Join-Path $TestDrive 'projected-form'; New-Item -ItemType Directory -Path $corpus | Out-Null
74+
Set-Content -LiteralPath (Join-Path $corpus '01-fixture.md') -Value "# Fixture`n`nList: <!-- mirror-Foo:start -->a, b<!-- mirror-Foo:end -->" -NoNewline
75+
$index = Read-SpecSetIndex -CorpusPath $corpus
76+
$index.State | Should -Be 'NotEvaluated'; $index.Reason | Should -Be 'MalformedRegion'; $index.Line | Should -BeGreaterThan 0
77+
}
78+
It 'still accepts the declared form of the same region, so the rejection is the form and not the id' {
79+
$corpus = Join-Path $TestDrive 'declared-form'; New-Item -ItemType Directory -Path $corpus | Out-Null
80+
Set-Content -LiteralPath (Join-Path $corpus '01-fixture.md') -Value "# Fixture`n`nList: <!-- mirror-Foo:declared:start -->a, b<!-- mirror-Foo:declared:end -->" -NoNewline
81+
$index = Read-SpecSetIndex -CorpusPath $corpus
82+
$index.State | Should -Be 'Indexed'; $index.MirrorObligations.Count | Should -Be 1
83+
}
6884
It 'S3.4: a mirror region naming an open declaration is still indexed as an obligation' {
6985
$corpus = Join-Path $TestDrive 'open-decl'; New-Item -ItemType Directory -Path $corpus | Out-Null
7086
$content = @'

tools/Read-SpecSet.ps1

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,20 @@ function Get-SpecSetLineNumber {
165165
function Get-DeclaredRegions {
166166
param([string] $Text, [string] $DocumentPath)
167167

168-
$pattern = '<!--\s*(?<sid>[A-Za-z][\w.-]*):declared:start\s*-->|<!--\s*(?<eid>[A-Za-z][\w.-]*):declared:end\s*-->'
168+
# Both marker forms are matched, though only the declared one may appear here. The corpus
169+
# has no projector - every module is read-only (SS1), so nothing can write a rendered
170+
# region into it - which makes a projected marker in a corpus document always wrong, and
171+
# silently so if it is not looked for: dropping ':declared:' from both markers of a region
172+
# retires that obligation without a trace, and the run still reports Valid. The id
173+
# character class excludes ':', so the projected alternative cannot match the declared form.
174+
$pattern = '<!--\s*(?<sid>[A-Za-z][\w.-]*):declared:start\s*-->|<!--\s*(?<eid>[A-Za-z][\w.-]*):declared:end\s*-->|<!--\s*(?<pid>[A-Za-z][\w.-]*):(?:start|end)\s*-->'
169175
$regions = [System.Collections.Generic.List[object]]::new()
170176
$seenIds = [System.Collections.Generic.HashSet[string]]::new()
171177
$openId = $null; $openLine = 0; $openBodyStart = 0
172178

173179
foreach ($m in [regex]::Matches($Text, $pattern)) {
174180
$line = Get-SpecSetLineNumber -Text $Text -Index $m.Index
181+
if ($m.Groups['pid'].Success) { return [pscustomobject]@{ Failure = 'MalformedRegion'; Line = $line } }
175182
if ($m.Groups['sid'].Success) {
176183
if ($openId) { return [pscustomobject]@{ Failure = 'MalformedRegion'; Line = $line } }
177184
$openId = $m.Groups['sid'].Value; $openLine = $line; $openBodyStart = $m.Index + $m.Length

0 commit comments

Comments
 (0)