docs(plan): record the Door C facet decisions, the mutation result, and the harness that lied
Four decisions: Door C projects and never derives (D-C1), the projection is key-agnostic so it is not about numbering (D-C2), an unrenderable value drops the facet and never the concept (D-C3), and a faceted entry is refreshed because it is the first entry that can go stale (D-C4). Also records a measurement about the measuring. The mutation harness first reported 11 of 11 killed; the number was entirely false, because it invoked pytest with an unrecognised `--timeout` flag and every run exited non-zero. The negative control caught it by being reported killed when it provably could not be. Honest result is 9 of 11, with both survivors analysed as equivalent mutants and the one real gap they exposed (profile threading pinned only by coincidence) closed with a direct test. Closes open item 6 of the Door B record, which asked exactly the question this answers: whose claim does the frontmatter make.
This commit is contained in:
parent
1f7d3502b8
commit
d35bcb21ec
2 changed files with 202 additions and 4 deletions
|
|
@ -256,7 +256,167 @@ change — rather than as "not possible".
|
|||
(`entries_match_directory`) in the same call, because `STRUCTURED_V1` does
|
||||
not set it. Not attempted; no consumer has asked.
|
||||
|
||||
6. **No profile is applied to Door C.** Import writes concepts verbatim and
|
||||
derives nothing, unchanged. Deriving structure for content another sender
|
||||
wrote would put our inference into their bytes; if that is wanted it needs a
|
||||
decision about whose claim the frontmatter makes.
|
||||
6. ~~No profile is applied to Door C.~~ **Closed** — see the Door C section
|
||||
below. The objection stated here is the one the answer is built on: Door C
|
||||
PROJECTS the sender's declared facts and derives nothing.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Door C: the index carries the sender's facts, and only theirs
|
||||
|
||||
Ordered by vegnormal-okf (`20260826T224500Z-873805419`) on a measurement, not a
|
||||
preference. A pre-registered reasoning bake-off on 2026-08-27 ran the same
|
||||
bundle through two index shapes:
|
||||
|
||||
| arm | index shape | hits |
|
||||
| --- | --- | --- |
|
||||
| B | `DEFAULT` profile's `index.md` | **0 of 8** |
|
||||
| — | faceted index over the same frontmatter | **25 of 29** |
|
||||
|
||||
Same bundle, same concept files, same model. The `DEFAULT` arm did not answer
|
||||
wrongly; it **abstained**, because the fact it needed was never in the file it
|
||||
was given. Measured on their `index.md`: 30 974 characters describing 269
|
||||
requirements, with **0 occurrences** of any of the eight facts (title, date,
|
||||
status, supersedes, erstatter, req_number, seksjon, kravtype) that the concepts
|
||||
in the same bundle carry.
|
||||
|
||||
`FacetPolicy` and `STRUCTURED_V1` already did this. They did not reach Door C.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D-C1. Door C PROJECTS, and never DERIVES
|
||||
|
||||
Door B derives structure from documents this repo's own operator dropped. Door C
|
||||
merges documents a **third party** wrote, verbatim, and the entire door is built
|
||||
on refusing to put anything of ours inside their bytes.
|
||||
|
||||
Running the deriver here would not have broken the verbatim guarantee where
|
||||
anyone would look for it. The concept file would still be byte-identical. It
|
||||
would have broken it in the index entry *about* the concept — where our
|
||||
inference is printed next to their name and reads as **their** claim. Forging an
|
||||
attribution is not a smaller failure than editing a file; it is a quieter one.
|
||||
|
||||
So every facet in a Door C entry is a value the sender wrote in their own
|
||||
frontmatter, copied. Nothing is inferred from their body, their filename, or
|
||||
their neighbours in the bundle. Where the sender carries `derived`, **their**
|
||||
list travels unchanged — so a reader can still see which of the sender's facts
|
||||
the sender inferred, a distinction we would erase by adding inferences of our
|
||||
own beside them. That is the whole ownership stamp at this door: the concept is
|
||||
verbatim, and so is the index's account of what the concept claims.
|
||||
|
||||
`resolve_structure` is deliberately not called. It computes supersession across
|
||||
a whole document set, and at this door that set is somebody else's.
|
||||
|
||||
### D-C2. The projection is key-agnostic, which is why it is not about numbering
|
||||
|
||||
The loop asks the policy which keys to carry and never what a key *means*. It
|
||||
reads a value only to check the policy can render it.
|
||||
|
||||
Operator directive 2026-08-27, verbatim: *"alt vi lager rundt OKF publiseres på
|
||||
Forgejo og skal virke for alt av innhold"* — so a design that only works for
|
||||
numbered norms is wrong even when it scores well on N100/N200/N500. Nothing in
|
||||
`_project_facets` can key off a numbering scheme, and the suite says so with an
|
||||
unnumbered Norwegian meeting note (`status` + `date`, no number, no parent, no
|
||||
version) and with a document declaring none of the policy's keys, which renders
|
||||
the bare link rather than a separator with nothing after it.
|
||||
|
||||
The same property pays a second time: a consumer whose concepts are named by
|
||||
UUID gets `title` into the index by **naming the key** in their own facet
|
||||
policy, with no change here. Door C's index label is the concept path, so for
|
||||
those senders the title travels as a facet or not at all.
|
||||
|
||||
### D-C3. An unrenderable value drops the FACET, never the CONCEPT — and is reported
|
||||
|
||||
The policy refuses a value carrying its own separator or joiner rather than
|
||||
escaping it. At Door B that refuses the document, and rightly: the value is one
|
||||
we derived and the operator can fix the source.
|
||||
|
||||
At Door C it must not. This door judges no shape and refuses no sender on form —
|
||||
that is what the module docstring already commits to — so refusing a merge over
|
||||
a semicolon in someone else's frontmatter is precisely the failure it names.
|
||||
Dropping it silently is the other failure: the sender made a claim our index
|
||||
does not show. So the facet is dropped, the concept merges verbatim, and the
|
||||
drop is reported per concept and key in `ImportResult.unrendered_facets` — an
|
||||
advisory over the merged set, like `unverified_references`, never a fifth
|
||||
bucket.
|
||||
|
||||
### D-C4. A faceted entry is refreshed, because it is the first one that can go stale
|
||||
|
||||
Found by measurement during the work, not predicted. A flat entry carries a
|
||||
label and a target, both stable, so it can never disagree with the file it
|
||||
points at, and `link_in_index`'s early return on a present target is exactly
|
||||
right. An entry carrying the concept's **facts** can disagree.
|
||||
|
||||
The path is reachable: the collision gate refuses an updated concept outright,
|
||||
so the operator's only route to accept an update is to remove the merged file —
|
||||
which the refusal message itself instructs — and import again. Measured after
|
||||
doing so: the concept file said `gjeldende` while the index still said `utkast`.
|
||||
|
||||
An index that contradicts the bundle it indexes is worse than one that says
|
||||
nothing, because the consumer reads the index and stops. A faceted entry for a
|
||||
present target is now re-rendered in place, keyed on the policy's entry pattern
|
||||
and the parsed target — never on a substring, so a curated line that merely
|
||||
mentions the target survives verbatim, line ending included. With `facets=None`
|
||||
none of this runs, so every unfaceted caller emits the bytes it always did.
|
||||
|
||||
This is the one addition beyond the order's three edits. It is here because it
|
||||
is a divergence *this change introduced*, and shipping a measured file/index
|
||||
contradiction would have been worse than the scope it costs.
|
||||
|
||||
## Verification
|
||||
|
||||
Suite **695 → 709**; `ruff check`, `ruff format --check`, `mypy --strict` clean.
|
||||
`import_bundle`'s `profile` is keyword-only and defaults to `DEFAULT`, so the
|
||||
171 branch bases built through this door emit the bytes they always did — pinned
|
||||
by a test that feeds the DEFAULT path the same frontmatter that *would* produce
|
||||
a facet tail under a faceted profile.
|
||||
|
||||
### Mutation testing, and the harness that lied first
|
||||
|
||||
11 mutations of the new code, plus a permanent **negative control** (a
|
||||
behaviour-preserving local rename that must SURVIVE).
|
||||
|
||||
The control earned its place on its first run. The harness invoked
|
||||
`pytest --timeout=120`; `pytest-timeout` is not installed, so every run exited
|
||||
non-zero and every mutant was reported **killed — 11 of 11**. The number was
|
||||
entirely false: zero mutations had actually been evaluated. The control is what
|
||||
exposed it, by being reported killed when it provably could not be. A clean
|
||||
sweep is worthless until the harness has been shown able to report a survivor.
|
||||
|
||||
The harness now asserts the suite passes on **unmutated** source before a single
|
||||
"killed" is believed, and asserts per mutant that the anchor matched exactly
|
||||
once and that the mutation landed on disk.
|
||||
|
||||
Honest result: **9 of 11 killed.** Both survivors are equivalent mutants, not
|
||||
gaps:
|
||||
|
||||
- **M1** (`if not value` → `if value is None`) — `FacetPolicy.render` already
|
||||
filters falsy values (`profiles.py:423`), so the guard in `_project_facets` is
|
||||
redundant with the policy's own. The observable property is now pinned
|
||||
directly anyway: a key the sender declared empty produces no facet.
|
||||
- **M8** (drop the `changed` guard before writing) — writes byte-identical
|
||||
content.
|
||||
|
||||
**M10** — reverting the `profile` threading in `import_filename` — survived the
|
||||
first honest run, and that one *was* a gap: every profile shipped today reuses
|
||||
`DEFAULT.paths`, so no existing test could tell a threaded profile from a
|
||||
hard-coded `DEFAULT`. The property was held by coincidence. A profile owning its
|
||||
own `import_prefix` now states it directly, and M10 is killed.
|
||||
|
||||
## What was not achieved
|
||||
|
||||
1. **`title` is not in `STRUCTURED_V1`'s facet set.** Widening it would move
|
||||
Door B's index bytes, so it stays a caller's key to name. For a sender whose
|
||||
concept paths are UUIDs this is the difference between a title in the index
|
||||
and none — flagged to vegnormal-okf rather than decided here.
|
||||
2. **Door C's index is an append plus a per-target refresh, not a projection of
|
||||
the whole bundle** (contrast D-S4). A concept removed from the bundle by hand
|
||||
leaves its entry behind. Reprojection would mean enumerating the directory,
|
||||
which no profile has asked this door to do.
|
||||
3. **Parent-prefix resolution is still deferred**, unchanged and for the
|
||||
unchanged reason: "unique prefix match" must be defined against a real
|
||||
corpus. `vegnormal-okf` has that corpus; the count is theirs to take.
|
||||
4. **The before/after number is not ours to produce.** Whether the faceted index
|
||||
is worth its cost on a real corpus is answered by re-running the same arm
|
||||
with the same scoring, on their data.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue