portfolio-optimiser/docs/okf-konsum-kontrakter.md
Kjell Tore Guttormsen baae7507a9 feat(okf): den erklaerte bundle_id-en er identiteten - mountet er en filsystem-tilfeldighet
S7a-3 pkt. 1. Til i dag NEKTET reconcile_bundle_id en base som erklaerte en id
katalogen ikke bar. Maalt mot K2 - den foerste leverte basen som erklaerer sin
egen id (618 av 630 konseptfiler + rot-index, alle "k2-trinn1-20260903", levert
som "K2-bundle-20260903") - betydde det at basen ikke kunne aapnes slik den var
levert, og at eneste botemiddel var aa montere den paa nytt for haand, en gang
per leveranse. PM-beslutning: konsumenten slakker.

- Erklaert vinner (B1s rekkefoelge uroert), avviket REGISTRERES:
  ResolvedBundleId.mount + ProvenanceStamp.bundle_id_source +
  DryRunReport.bundle_id_source + run.bundle_id_notice (None ved enighet).
  Stempel-feltet er PAAKREVD uten default: None er en VERDI (veg-stien).
- Det som fortsatt nekter er den EKTE kollisjonen: to KONSEPTER i en base som
  erklaerer ULIKE id-er (okf.assert_declared_ids_agree, kalt ved hver doer som
  aapner en base). Rot-index er IKKE med i enighets-settet - konsept-slaar-index
  er en presedens-regel, saa en index i utakt er fallbacken som taper.
- KONSEKVENS, ikke scope-krype: explore._bundle_index loeser naa den erklaerte
  id-en. Den brukte Path(raw).name mens dispatcheren brukte reconcile...id; med
  erklaert-vinner ville explore() myntet approaches som navngir MOUNTET mens
  dispatcheren ruter paa ERKLAERINGEN - en utforskning med uruterbart mandat.

Load-bearing MAALT: 10 mutasjoner alle roede mot HELE suiten, groenn kontroll
1243 passed / 5 skipped og golden demo-transcript.stdout byte-uendret
(shasum -a 1 = ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
M1 1 / M2 1 / M3 1 / M4 9 / M5 2 / M6 1 / M7 1 / M8 2 / M9 2 / M11 1.

Tre armer i test_bundle_id_reconciliation_loadbearing er SKREVET OM (ikke
slettet) - de pinnet nekten beslutningen fjernet. (j) ble skarpere enn den den
erstattet: erklaert id ruter, mountet nektes.

Kontrakt: docs/okf-konsum-kontrakter.md § 3.1. Invariantrad i CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:47:58 +02:00

9.1 KiB
Raw Blame History

OKF consumption contracts

This file is the source. Three facts below span a producer (llm-ingestion-okf) and this consumer, so neither side can hold the other to them from inside its own plan file. Docstrings, tests and the invariant ledger in CLAUDE.md point here; they do not restate the rule. A second copy of a contract is the drift this repository has a rule against.

The producer's half is recorded in that repository's docs/plan/office-intake.md §§ 45 and docs/consumption-contract.md. Ratified at programme level 2026-09-02. Written in English because the counterparty half is, and a contract split across two languages is a contract nobody reads whole.

Every number below comes from a command, and each command is written next to its number.

§ 1. The falsification threshold — present and not unverified

A falsification verdict may rest on a concept only when both hold:

evidence_for(path).state == "present"   AND   tier != "unverified"

Everything else is reported and explicitly discounted, never silently excluded. The report carries the triple (state, reason, items_seen): a concept that was dropped and a concept that was read-and-discounted are different facts, and only the second is honest about what was actually read.

The threshold is expressed in exactly one placeokf.admits_falsification(evidence) -> bool. A threshold spelled inline at each caller is a threshold nobody can find, and a second copy is free to drift from the first.

author, usage_count and last_modified are deliberately NOT required, and the denominator is written down rather than implied. SPEC § 5.1 names six keys on a sources entry — resource, id, title, author, usage_count, last_modified — and the producer writes two of them:

# Grep-able anchor, never a line number — a line anchor into another repository's file rots
# on that repository's next edit, and this one has to survive being read from outside.
grep -A11 'Each `sources` entry:' ~/repos/_okf-canonical/SPEC.md      # the six entry keys
grep -rh '^sources:' ~/repos/llm-ingestion-okf/examples/
#   -> sources: [{ id: golden-v0-2-sales, resource: fixture }]
#      (1 of the 26 markdown files under examples/ carries the key at all)

Measured 2026-09-02 against producer 62b6192. 2 of 6, not 2 of 5 — an earlier draft of this contract carried the smaller denominator, which would have understated how much of § 5.1 the producer leaves unwritten. Requiring the three credibility signals would make the threshold unreachable in practice while looking strict on paper: the worst of both. The threshold names only what is actually written.

usage_window is a sibling of sources, not an entry key, and is outside the six.

§ 2. The adjudication state — absence is unknown, never absent

A concept carries its adjudication state in the frontmatter key adjudication, whose value set is closed: proposed or adjudicated.

Observation State
adjudication: proposed proposed
adjudication: adjudicated adjudicated
the key is not present unknown
any other value refused by name — never mapped into the vocabulary

The third row is the whole contract. A concept that does not carry the key means we did not learn whether this was adjudicated (an older bundle), and collapsing that into absentit was not adjudicated — is a verdict on missing evidence presented as evidence of absence. It is the same defect as collapsing unreadable into absent one layer up, and the same defect the ungiven-verdict work removed from RunResult.verdict.

The fourth row is validation, never repair: an out-of-vocabulary value is an error, not an extension point, and mapping it to unknown would silently invent the very state this contract exists to keep honest.

Honesty limit, stated: this consumer's fixtures are hand-written to the contract above, not produced by llm-ingestion-okf. Integration against the producer's own golden is later work and is not claimed here.

§ 3. Identity across bundles — the pair (bundle_id, concept_id)

A concept ID is bundle-local and stable. Identity across bundles is the pair (bundle_id, concept_id) — never the concept ID alone.

bundle_id resolves in a fixed order, and the resolved value carries which source answered:

Order Source origin
1 the concept's own frontmatter declared-concept
2 the bundle root's index.md declared-index
3 neither declared it — the mount basename is used mount-derived

Three origins, not a boolean. A caller that cannot tell "the concept said so" from "we fell back twice" has been handed a stamp it cannot audit.

A base with no readable index.md is not "undeclared". The existing navigation fail-fast propagates unchanged; reading an unreadable base as "no declaration" is the tolerant-read defect that widens an answer instead of refusing it. Only a base whose index is readable and declares nothing falls through to the next row.

Measured today: every base in this repo resolves mount-derived. No shipped index.md declares the key:

find shared -name index.md | wc -l          # -> 7
grep -l 'bundle_id' $(find shared -name index.md) | wc -l   # -> 0

So this contract changes which sources are consulted, not what happens when none of them declares anything — which is why adopting it moves no existing call site.

§ 3.1. A declared id BEATS the mount; the mount is a filesystem accident

Amended 2026-09-03 (S7a-3 pkt. 1), and the amendment is a consumer slacken. Until then, a base that declared an id its mount did not carry was refused (BundleIdMismatch) at every door. The first delivered corpus that declares its own id — K2, measured 2026-09-03: 618 of 630 concept files plus the root index.md, all saying k2-trinn1-20260903, delivered in a directory named K2-bundle-20260903 — could therefore not be opened as delivered. The only remedy was to re-mount it under the declared name by hand, once per delivery, forever.

That is a consumer refusing a producer's legitimate output over a directory name. The rule is now:

  • The declared id is the identity. The mount's basename answers only when nothing is declared.
  • A disagreement is RECORDED, never refused: ResolvedBundleId.mount carries the overridden directory, ProvenanceStamp.bundle_id_source / DryRunReport.bundle_id_source carry the whole resolution into the artefacts, and the run prints one warning line naming both values.
  • A base is addressed by its declared id at BOTH doors. The exploration catalogue (list_bundles / read_bundle / read_file / quick_validate) and the mandate dispatcher resolve the same id. This is a consequence of the slacken rather than a separate decision: while a disagreeing declaration was refused the two could not differ, and if they differed now, an exploration's own minted mandate would be unroutable.

What still refuses, because no mount name can settle it: two concepts inside one base declaring different ids. Such a base cannot say what it is, and an artefact stamped from it would name one of two corpora at random. The check runs at every door that OPENS a base.

The root index.md is NOT part of that agreement check — decision B1 applied a second time. Concept-beats-index is a precedence rule, so an index out of step with its concepts is the fallback losing, not a collision; folding the index in would newly refuse exactly the K2-shaped bases this amendment exists to admit.

Honesty limits, stated. (1) Without a named concept the root index.md answers, so a base whose index declares X while its concepts declare Y resolves to X — that is B1's resolution order, unchanged. (2) Two differently named directories that both declare the same id now collide in the dispatcher's "two knowledge bases share the id" refusal; that branch used to require two mounts with the same basename and is no longer defensive.

There is no cross-bundle link form. A consumer holding several bundles partitions per bundle; the producer emits no syntax for pointing from inside one bundle into another, and adding one is out of scope until a consumer states the need.

§ 4. What these three do not settle

  • The wire form of sources is the producer's to fix. Flow-sequence-of-mappings is the normative emission form; block form remains conformant OKF but outside the accepted subset. A known defect stands on the producer side: llm-ingestion-guard 1.2.0 rejects a flow sequence of mappings for sources. That is a known producer-side gate defect, not a consumer error, and this consumer does not wait on it.
  • The trust tiers themselves (§ 5.3 of the SPEC) are inferred, never stored — no credibility score is written by either side.
  • Nothing here wires a library primitive into a run's stdout. The reader reads; the caller decides. Both halves of that split predate these contracts and are unchanged by them.