a2b57d2 docs(plan): V1 — «de 5 linjene» var ikke homogene; :214 er ikke en literal d63e45d docs(plan): okf-versjonssjekken utført — hypotesen falsifisert på to stale premisser ef31dda docs(plan): V1 §4.2 — pin + id + sitering avgjort, og ratifiseringsgaten funnet 35220f7 docs(plan): V1 §4.1 — serialiseringsformen er bundet av :158, så 6 sider er invariant 8a7d430 docs(plan): operatøren avgjorde V1 (O2) og B1 (O1) — B1 utført, V1 gated f306c7b docs(plan): V1 §5.1 — konsument-kostnaden målt @ 8a14137, O3-raden priset ab0ea8f docs(plan): innboksrunden — V1 utvidet med tre målte funn, §7.2-siteringen ref-bundet 84a3010 docs(plan): §7.2 — MCP-spørsmålet er avgjort av frossen tekst, S2.2 og S2.4 er ugated e984d51 docs(plan): D-B ankret, D-A#3s årsak er repo-avhengig, V1 utvidet med oppstrøms-evidens 3de702b docs(plan): V1 §6 — rekkevidde-forbeholdet korrigert, og funnet under funnet 249425b docs(plan): amendment-underlag — D-A#3s ÅRSAK rettet, drift ikke avvik 4876970 docs(plan): V1-underlag — generated-feltets form etter OKF v0.2 3f18ca7 docs(plan): B1/D4 operator question — nav-golden's normative status, four costed options 29ad8ca docs(plan): amendment-underlag — frossen tekst per køpunkt, med målt fasit-effekt a67a243 docs(plan): ordering-axes ruling — three distinct axes, as an interpretation record 381d9e5 docs(plan): guard adoption plan pins no version — v0.2 header was stale 54ca0ff docs(plan): D3 ratified — status vocabulary landed in coord register fe6b998 docs(plan): D3 status vocabulary — canonical 7-token set for roll-up register c66ccc3 docs(plan): D2 register-form fix — two-output model resolves ÅS#5 b641741 docs(examples): nav-golden fixture class — bundle → read-context goldens 9801d35 docs(spec): method-spec §3 Step 1 — Q3 navigation contract (hierarchy) bfa5a9b docs(spec): ingest-spec — land ratified D1 stamp model 5f046ab docs(plan): Q3 finding — method-spec §3 silently forbids hierarchical bundles a013e8b docs(plan): F1 direction — free-text sources belong in ingest-spec door A 84191c6 docs(plan): record commons↔implementation-repo coordination boundary cae9972 docs(plan): llm-ingestion-guard adoption — planned at future untrusted ingest boundaries git-subtree-dir: shared git-subtree-split: a2b57d20fc0953c19776c21ce1311f234386a590
8 KiB
Ordering axes — three distinct axes already present in the frozen spec
Status: INTERPRETATION of frozen spec text. This document changes no normative text and introduces no new requirement. Where this document and the spec disagree, the spec wins and this document is the thing that is wrong. It is a reading aid, not a contract.
Issued: 2026-07-25. Applied by at least one downstream implementation as binding for its own work before this record existed — which is why the record exists.
Scope: the disambiguation is across the two specs. Each axis is already stated, normatively, in one of them; nothing here is derived, inferred or extended. What was missing was a single place saying that these are three different things and which one governs a given piece of code.
1. Why this exists
Three separate implementations reached, independently, for "the ordering rule" and each picked a different one — twice arriving at code that looked correct and sat on the wrong contract. That failure mode is expensive precisely because it is quiet: an implementation on the wrong axis is usually deterministic, passes its own tests, and produces plausible output. Nothing fails until another consumer depends on the property the right axis guaranteed.
The specs are not at fault. Each axis is stated clearly where it belongs. The gap is that no single document said "there are three, they are not interchangeable, here is how to tell which one you are standing on."
2. The three axes
| Axis | Question it answers | Normative source | |
|---|---|---|---|
| A | Membership | Which files does a re-materialization replace? | ingest-spec.md:175-177 |
| B | Generation / index-link order | In what order are generated files and their index links emitted? | ingest-spec.md:178-181, §6 (:183) |
| C | Navigation order | In what order does a reader traverse a bundle? | method-spec.md:76-78 |
Axis A — membership, read from disk. The set of files a re-materialization replaces is exactly
those the bundle already carries the stamp for, "read from disk (§3), never a caller-supplied
file list; a caller can therefore never name a file for deletion" (:175-177). This is a safety
property, not an ordering one: it is what keeps re-materialization from becoming a caller-controlled
delete.
Axis B — generation order, supplied by the caller. "The ORDER of generated files and their
index links (§6) is the manifest's extraction order — supplied by the caller, never filesystem
enumeration order" (:178-181). This is a determinism property: output must not vary with
directory iteration order.
Axis C — navigation order, first-seen link order. "Traversal is depth-first in first-seen
link order and fully deterministic (the root index first, then each link followed in the order it
appears, recursively)" (method-spec.md:76-78). This governs a reader walking an existing
bundle. It says nothing about how that bundle came to be ordered.
A and B are deliberately split by the spec itself — :175 labels the split load-bearing — because
fusing them would trade a safety property for a determinism property. B and C are adjacent and
easily confused: both are "order", but one describes an act of writing and the other an act of
reading.
3. The test — which axis am I on?
Ask what the code does, not what it is called:
- Does it decide what to delete or replace? → A. The answer comes from disk. A caller-supplied list is a defect regardless of how convenient it is.
- Does it emit files or index entries? → B. The order comes from the caller's extraction order. Any order the code chooses for itself is wrong — including a deterministic one.
- Does it read an existing bundle and visit its files? → C. The order comes from the links, depth-first, first seen.
The trap is that "generator" and "reader" are roles, not file names. A module that both writes an index and reads one is on both B and C, and owes each its own rule.
4. Worked applications
An index generator that sorts its entries alphabetically. It emits index entries, so it is on B. Alphabetical order is deterministic, which is what makes this hard to see — but B does not ask for a deterministic order, it asks for the caller's order. Alphabetical is therefore deterministically wrong, not merely unlucky, and the fix is to thread the caller's extraction order through, not to substitute a different self-chosen sort. Attributing such a generator to axis A ("it is about membership, so ordering is a benign detail") is the specific mis-read that lets the sort survive review.
A convention that says a directory index is a "directory enumeration". Ambiguous on exactly this
axis, and worth resolving in the text rather than in the reader's head. If it means the index file
contains an authored enumeration of that directory's children, it is compatible with C. If it means
the reader enumerates the directory to navigate, it contradicts method-spec.md:82-83, which says
an intermediate directory is "navigated only through the links its own files carry, never by
directory enumeration." Prefer wording that cannot be read the second way.
A re-materializer narrowing what it deletes. On A. Note that A's text scopes membership by
the stamp, and the stamp's identity field is {manifest filename stem}@{h} (ingest-spec.md:168-170)
where {h} is a hash of the manifest's bytes. Matching on the stem rather than the full reference is
what lets an edited manifest reclaim its own prior output, since {h} changes on every edit.
(Separately: ingest-spec.md:171-174 assumes one manifest per bundle in version 1 and names the
multi-manifest case an extension point. Code that scopes ownership per-manifest is implementing that
extension point and runs ahead of the frozen text — see the amendment queue, not this document.)
5. Axis B does not travel alone
An implementation adopting B inherits §6's idempotence with it, not afterwards:
- linking is idempotent by target — a link whose target is already present is never added twice
(
ingest-spec.md:187-190); - on re-materialization, index links whose target is an ingest-owned file removed in that run MUST be
removed, and all other links — curated and promoted — are preserved verbatim (
:191-193).
Taking the ordering rule without the idempotence rule produces duplicate links on the second run. The two are one package.
6. What this document does not say
- It does not rule on any axis outside these three. Installation root, bundle root and source root are a different family of distinctions and are not addressed here.
- It does not make the multi-manifest extension point written, ratified, or safe to assume.
- It does not bind any implementation's internal design — only the observable properties the specs already require.
7. Verification
Testable criteria, so this document can be checked rather than believed:
- V1 (the anchors are real and still say what is quoted):
grep -n 'Membership from disk' ingest-spec.md→:175;grep -n 'never filesystem enumeration order' ingest-spec.md→:179;grep -n 'depth-first in first-seen' method-spec.md→:76. Verified 2026-07-25. - V2 (no normative text was changed by this document):
git show --statfor the commit adding this file touchesdocs/plan/only — neveringest-spec.md,method-spec.mdorexamples/. - V3 (the axes are genuinely distinct, not three phrasings of one rule): A is sourced from disk, B from the caller, C from link order in the file. Substituting any one for another changes observable behaviour: A→B would let a caller name files for deletion; B→C would make output order depend on a reader's traversal; C→B would make navigation depend on how the bundle was generated.
- V4 (§6 idempotence is not separable from B):
grep -n 'idempotent by target' ingest-spec.md→ a hit inside §6, i.e. in the same section B points at for index links.