fix(okf): close the inert ingest-stamp guard, then land okf 0.8.5 -- and read the block sources form all four bases actually write
P13 measured this lift and REFUSED it, because okf >=0.8.5 emits the ownership
stamp as the V1 flow mapping `generated: { by: process:okf-ingest, at: ... }`
where 0.3.2 emitted `true`, and `_carries_complete_ingest_stamp` read the new
form as NOT a stamp -- write_concept_file's forgery refusal would have shipped
DISARMED with the whole fail-closed suite green. That blocker is closed first,
red-first, and then the pin moves.
ROW 1, THE SECURITY HALF. `_claims_ingest_ownership` widens the predicate from
"reads as boolean True" to "claims ingest ownership", of which the boolean is
the pre-V1 spelling. The recogniser for the new half is `decode_flow_value` --
the module's ONE flow decoder, the same argument write_concept_file already
makes for `verified`: the writer refuses exactly what the reader can read. A
value the decoder REFUSES is therefore not an ownership claim and writes
through, which is what keeps this from collapsing into "any non-empty
generated". Two arms red before the fix; no YAML library introduced.
THE PIN. okf v0.3.2 -> v0.8.5, guard v0.3.4 -> v1.4.0 spelled `tag =`, not
`rev =`, and not the declared floor 1.2.0 -- both P13 premises hold and the
reason now lives next to the pin in pyproject.toml. The ":40" comment is
corrected: okf has ONE runtime dependency, the guard, and that is what binds
the two lines together. 27/27 imported names resolve across five modules.
THE GOLDENS, REGENERATED AS A DECISION. Seven concept files across four
examples/ingest-golden-* bundles, one line each. Two were regenerated by the
REAL materializer; the other five are derived (http/sql/mcp cannot materialize
outside the tests' stubs) and then MEASURED -- all four golden suites compare
byte for byte against what the stubs produce, and all four are green. The four
`generated == "true"` asserts now read ONE source, conftest.
expected_generated_stamp: four literals for one emitter fact are four places a
later release can leave half-corrected, which is exactly how the pre-V1 form
survived until P13 measured it. tests/test_okf.py keeps its literal on purpose
-- that one round-trips a CURATED half-stamp through our own writer.
THE BLOCK READER. Measured with the full denominator: all four delivered
knowledge bases write `sources` as a BLOCK sequence and none in flow form
(n100 446/446, n200 1133/1133, n500 270/270, r761 2756/2756 = 4605/4605), and
`evidence_for` reported `unreadable` on 4605 of 4605 -- the falsification layer
had no address for any document in any base. `okf.decode_block_mappings` is the
second CARRIER of one grammar, never a second grammar: colon-SPACE separator,
unquote_scalar, duplicate keys refused, SPEC 5.2's actor rule applied. okf's
consume.read_sources was READ for the form and not called; po calls no okf
reader, which is measured and deliberate. After: 4605 present / 4605 entries.
Reading is not a licence to WRITE -- the emitter is untouched and both writers
still refuse what decode_flow_value refuses.
THREE FINDINGS. (1) The first block reader INVENTED data on `- { k: v }` items
-- SPEC-canonical, and the shape tests/golden/block-form-provenance writes for
`verified` -- decoding it as `{'{ id': '...'}`. No arm caught it: the 5.2 actor
rule shielded the fixture by accident. Closed with a flow-decoder branch and
four new arms. (2) One of my own arms was VACUOUS, found by my own mutation M5:
it claimed to prove the colon-SPACE rule and stayed green under first-colon,
because the two rules agree on every delivered value. Renamed, labelled, and
the claim moved to the arm that actually witnesses it. (3) OPEN, and it needs
the operator: the commons-owned worked example declares its second concept
`unreadable`/`block-sequence`, which is now false for po. `shared/` is
pull-only, so closing it needs a commons amendment; the test asserts the
divergence instead of skipping it, keeping the discriminating half (the example
says two entries were seen and the reader returns exactly two).
NINE EXISTING ARMS REWRITTEN, NONE WEAKENED. All nine pinned "the block form is
unreadable" -- the behaviour this order changes. Each keeps its claim on a
specimen that is still unreadable for a reason of its own (5.2: an entry naming
no actor), or pins the REVERSED direction where the old arm stood so the change
cannot be silent. Two got STRONGER: multi-verified.md was authored for "a reader
keeping the last entry reports machine-confirmed for a concept a human signed",
and that could not be tested while the form was unreadable. Three node ids were
renamed; nothing was removed in substance.
Suite 1582 -> 1606 passed / 5 skipped. Both demo goldens byte-unchanged
(ea8c534... / ede3e2f..., shasum -a 1 of the CONTENT, never the git blob id).
ruff check / ruff format / mypy green. shared/ untouched.
Six mutations, all red against the WHOLE suite, each with its own signature:
row 1 detached (2) / block reader detached (17) / flow-item branch detached (7)
/ a stray indented line folds into an INVENTED entry (4) / separator becomes the
first colon (1 -- and that is finding 2) / the stamp expectation reverts to
"true" (4).
Order: 20260912T195112Z-995611104-from-.claude
Record: docs/2026-09-12-p13b-okf-bump.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
afdd9e0692
commit
fed69790ac
25 changed files with 964 additions and 89 deletions
280
docs/2026-09-12-p13b-okf-bump.md
Normal file
280
docs/2026-09-12-p13b-okf-bump.md
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
# P13b — okf-bumpen v0.3.2 → v0.8.5 LANDET, stempelvakten lukket, blokk-`sources` lest
|
||||
|
||||
Dato: 2026-09-12. Ordre `20260912T195112Z-995611104-from-.claude` (operatørsvar på P13-E).
|
||||
Forutgående måling: [`docs/2026-09-12-p13-okf-pin-r761.md`](2026-09-12-p13-okf-pin-r761.md).
|
||||
**Ingen modellkall, ingen Azure, NOK 0.**
|
||||
|
||||
---
|
||||
|
||||
## § 0 — Hva som ER målt og hva som IKKE er det
|
||||
|
||||
**Målt i denne økten**
|
||||
|
||||
| # | Påstand | Hvordan |
|
||||
|---|---|---|
|
||||
| 1 | (P1) Stempelvakten var inert på V1-formen | direkte kall på `_carries_complete_ingest_stamp`: `true`→True, `yes`→True, `{ by: …, at: … }`→**False** |
|
||||
| 2 | (P2) Alle fire bundler skriver `sources` i BLOKK-form | `grep -rl`, hele nevneren: **4605 av 4605**, 0 flat |
|
||||
| 3 | Evidens-tilstand FØR: `unreadable` på 4605 av 4605 | `evidence_for` over `context_files` i alle fire |
|
||||
| 4 | Evidens-tilstand ETTER: `present` på 4605 av 4605, 4605 oppføringer | samme kjøring |
|
||||
| 5 | 27/27 importerte navn resolverer på okf 0.8.5 / guard 1.4.0 | `hasattr` over fem moduler |
|
||||
| 6 | Suite 1582 → **1606 passed / 5 skipped** | `uv run pytest -q` |
|
||||
| 7 | Begge demo-goldener byte-uendret | `shasum -a 1` av INNHOLDET |
|
||||
| 8 | De sju golden-konseptfilene stemmer mot materialisatoren | de fire `test_ingest_golden_*`-suitene grønne |
|
||||
| 9 | Seks mutasjoner, alle røde, hver med egen signatur | § 4 |
|
||||
|
||||
**IKKE målt**
|
||||
|
||||
- **Ingen levende modell, ingen betalt kjøring.** Ingenting her sier hva en modell gjør med en
|
||||
base hvis proveniens nå er lesbar.
|
||||
- **Ingen ekte ingest-kjøring mot et levende manifest** utover suitens fixturer og stubber.
|
||||
- **Guard-kalibreringen** (`_ACCEPTED_DISPOSITION = "warn"`) er bekreftet kun gjennom
|
||||
`test_ingest_content_gate_loadbearing`s egne dokumenter — guardens CHANGELOG holder dispositions
|
||||
eksplisitt utenfor semver-frysen, så et bredere korpus er uprøvd.
|
||||
- **De fem http/sql/mcp-golden-linjene ble AVLEDET, ikke materialisert** — se § 2.3.
|
||||
- **Den nye lesertilstanden er ikke wiret inn i `run_project`.** `evidence_for` er fortsatt en
|
||||
bibliotek-primitiv (B4-regelen, urørt); at 4605 dokumenter nå har en adresse endrer ingen kjøring
|
||||
av seg selv.
|
||||
|
||||
---
|
||||
|
||||
## § 1 — Premissene, målt før noe ble bygget
|
||||
|
||||
### P1 — stempelvakten var inert
|
||||
|
||||
```
|
||||
literal true : True
|
||||
yes : True
|
||||
V1 flow : False <-- formen okf >=0.8.5 faktisk skriver
|
||||
```
|
||||
|
||||
### P2 — alle fire bundler er blokkform, hele nevneren
|
||||
|
||||
| Base | Konsepter | `sources`-nøkkel | BLOKK | FLAT |
|
||||
|---|---|---|---|---|
|
||||
| n100-2023 | 446 | 446 | **446** | 0 |
|
||||
| n200-2024 | 1133 | 1133 | **1133** | 0 |
|
||||
| n500-2024 | 270 | 270 | **270** | 0 |
|
||||
| r761-2025 | 2756 | 2756 | **2756** | 0 |
|
||||
| **SUM** | **4605** | **4605** | **4605** | **0** |
|
||||
|
||||
Ordren oppga «400 av de 400 første i hver». Den fulle nevneren er målt her og stemmer med tallet
|
||||
`llm-ingestion-okf` selv oppga i sin v0.8.5-FYI (2756 + 446 + 1133 + 270 = 4605).
|
||||
|
||||
---
|
||||
|
||||
## § 2 — Utført, rad for rad
|
||||
|
||||
### 2.1 Rad 1 — stempelvakten (sikkerhetsbærende, FØRST)
|
||||
|
||||
**Rød først:** fire nye armer i `tests/test_ingest_stamp_fail_closed_loadbearing.py`; to av dem
|
||||
røde mot den uendrede vakten (de to andre beskriver uendret oppførsel og var grønne, som de skal).
|
||||
|
||||
`_claims_ingest_ownership` er ny og er hele endringen: predikatet utvides fra «leses som boolsk
|
||||
True» til «hevder ingest-eierskap», der booleanen er pre-V1-stavemåten.
|
||||
**Gjenkjenneren for den nye halvdelen er `decode_flow_value`** — modulens ENE flow-dekoder, aldri en
|
||||
andre kopi (kø-(p), og samme argument `write_concept_file` alt gjør for `verified`: skriveren nekter
|
||||
nøyaktig det leseren kan lese). En verdi dekoderen REFUSERER er derfor ikke et eierskapskrav og
|
||||
skrives gjennom — det er dét som holder regelen fra å kollapse til «enhver ikke-tom `generated`».
|
||||
|
||||
Ingen YAML-bibliotek innført (po har fortsatt 0 `import yaml`).
|
||||
|
||||
### 2.2 Rad 6–7 — pinnene
|
||||
|
||||
```toml
|
||||
llm-ingestion-okf = { git = "…/llm-ingestion-okf.git", rev = "v0.8.5" }
|
||||
llm-ingestion-guard = { git = "…/llm-ingestion-pipeline-security.git", tag = "v1.4.0" }
|
||||
```
|
||||
|
||||
`tag =`, ikke `rev =`, og ikke 1.2.0 — begge P13-premissene står, og begrunnelsen er nå skrevet inn
|
||||
i `pyproject.toml` ved siden av pinnen. `pyproject.toml:40`-kommentaren er rettet: okf-kjernen har
|
||||
ÉN runtime-avhengighet, guarden, og det er dét som binder de to linjene sammen.
|
||||
|
||||
Etter `uv lock && uv sync`: okf **0.8.5** (`64661c7`), guard **1.4.0** (`d19de8c`), **27/27** navn.
|
||||
|
||||
### 2.3 Rad 3–5 og 8 — goldenene, assertene, prosaen, vakten
|
||||
|
||||
**De to `ingest-golden-file`-filene ble regenerert av den EKTE materialisatoren** og diffen er én
|
||||
linje hver:
|
||||
|
||||
```diff
|
||||
-generated: true
|
||||
+generated: { by: process:okf-ingest, at: 2026-07-03T12:00:00Z }
|
||||
```
|
||||
|
||||
**De fem øvrige ble AVLEDET** (http/sql/mcp kan ikke materialiseres utenfor testenes stubber — de
|
||||
krever `PORTEFOLJE_SQL_DSN` respektive nettverks-opt-in), med hver bundles egen `ingested-at.txt`
|
||||
som `at`-verdi. **Avledningen er deretter MÅLT, ikke antatt:** alle fire `test_ingest_golden_*`
|
||||
sammenligner byte for byte mot det stubbene faktisk materialiserer, og alle fire er grønne. Hadde
|
||||
tidsstempelet eller formen vært feil ett eneste sted, ville suiten stått rød.
|
||||
|
||||
De fire assertene på `generated == "true"` leser nå **ÉN kilde**: `conftest.expected_generated_stamp
|
||||
(ingested_at)`. Fire literaler for ett emitter-faktum er fire steder en senere okf-utgivelse kan
|
||||
etterlate halvrettet — det er nøyaktig slik pre-V1-formen overlevde i fire asserts til P13 målte
|
||||
den. `tests/test_okf.py`s `generated: true` er BEVISST urørt: den runder en KURATERT halv-stempel
|
||||
gjennom po sin egen skriver og er et annet faktum enn hva okf emitterer.
|
||||
|
||||
Prosa: `ingest_mcp.py:29`, `okf.py`s nekt-melding. Vakten (`tests/test_okf_version_guard.py`) pinner
|
||||
nå 0.8.5 / 1.4.0, og nekt-meldingen er skrevet om fra «her er kostnaden ved å løfte» til «re-mål hva
|
||||
denne utgivelsen EMITTERER som §7-stempel, og sjekk at `_claims_ingest_ownership` ser det» — fordi
|
||||
en tredje stavemåte ville gjort vakten inert igjen uten at én test ble rød.
|
||||
|
||||
### 2.4 Steg 4 — blokk-`sources`-leseren
|
||||
|
||||
`okf.decode_block_mappings` er ny og er den ANDRE bæreren av ÉN grammatikk, aldri en andre
|
||||
grammatikk: par-separatoren er kolon-MELLOMROM (`_find_pair_separator`), navn og verdier gjennom
|
||||
`unquote_scalar`, duplikate nøkler nektet, og §5.2-regelen (en `verified`-oppføring må navngi en
|
||||
aktør) gjelder her også. okf sin `consume.read_sources` ble LEST for formen og ikke kalt — po kaller
|
||||
ingen okf-leser, som er målt og bevisst (P13 § 1a).
|
||||
|
||||
**To nekter er BEHOLDT og er filens diskriminatorer:** `block-mapping` (ingen `- `-oppføring åpnet)
|
||||
og `unsupported-flow`. Uten dem ville en leser utvidet til «alt innrykket er oppføringer» bestått
|
||||
hver positiv arm mens den fant på oppføringer dokumentet ikke har.
|
||||
|
||||
| Base | FØR | ETTER |
|
||||
|---|---|---|
|
||||
| n100-2023 (446) | 446 `unreadable` / 0 oppføringer | **446 `present` / 446 oppføringer** |
|
||||
| n200-2024 (1133) | 1133 `unreadable` / 0 | **1133 `present` / 1133** |
|
||||
| n500-2024 (270) | 270 `unreadable` / 0 | **270 `present` / 270** |
|
||||
| r761-2025 (2756) | 2756 `unreadable` / 0 | **2756 `present` / 2756** |
|
||||
| **SUM (4605)** | **4605 / 0** | **4605 / 4605** |
|
||||
|
||||
Fixturen er en **byte-identisk kopi av en EKTE levert konseptfil**
|
||||
(`tests/fixtures/p13b-block-sources/n100-krav-4-2-5-1-3.md`, `shasum -a 1` `8c2952d1…`, lest fra
|
||||
`vegnormal-okf`, aldri skrevet der). En håndskrevet tilnærming ville bevist at leseren håndterer den
|
||||
formen jeg forestilte meg.
|
||||
|
||||
**Lesing er ikke lov til å SKRIVE:** `materialize._render_sources` i okf emitterer fortsatt flow,
|
||||
og `write_concept_file`/`verified_field` nekter fortsatt nøyaktig det `decode_flow_value` nekter —
|
||||
skriveren nekter altså fortsatt det leseren ikke kan lese, som er egenskapen rundturs-gaten finnes
|
||||
for.
|
||||
|
||||
---
|
||||
|
||||
## § 3 — Tre funn under arbeidet
|
||||
|
||||
### F1 — min egen leser fant på data (funnet ved å måle, ikke av en rød arm)
|
||||
|
||||
Første blokk-leser dekodet `- { id: a, resource: x }` — en blokk-sekvens hvis ITEMS er FLOW-mappinger,
|
||||
SPEC-kanonisk og nøyaktig formen `tests/golden/block-form-provenance` skriver for `verified` — som
|
||||
`{'{ id': 'a, resource: x }'}`. **En nøkkel som ikke er en nøkkel**, altså «oppfinn en oppføring
|
||||
dokumentet ikke har»-feilen min egen docstring forbyr. **Ingen arm fanget den:** på `verified` nektet
|
||||
§5.2-regelen den av en helt annen grunn, så fixturen som bærer formen var skjermet ved et uhell.
|
||||
Lukket med en egen gren som sender item-et gjennom `decode_flow_value`, og med fire nye armer
|
||||
(dekoding, rekkefølge, blandet bærer nektet, malformet item nektet). M3 er mutasjonen.
|
||||
|
||||
### F2 — en av mine egne armer var vakuøs, funnet av min egen mutasjon
|
||||
|
||||
`test_a_value_carrying_a_colon_is_split_on_colon_space_only` påsto å bevise kolon-MELLOMROM-regelen.
|
||||
**M5 (separator → FØRSTE kolon) lot den stå GRØNN**: første kolon i `resource: https://…` og i
|
||||
`title: N100:2023` ER den kolon-mellomrom finner, så de to reglene er enige om hver verdi formet som
|
||||
en levert. Armen som faktisk skiller dem er `test_an_item_with_no_pair_separator_is_refused` — under
|
||||
første-kolon dekodes `- https://a.example/d` til `{'https': '//a.example/d'}`, en nøkkel oppfunnet av
|
||||
et URL-skjema. Armen er BEHOLDT som regresjonsvakt og OMDØPT + merket ærlig; kolon-MELLOMROM-påstanden
|
||||
er flyttet til armen som bærer den.
|
||||
|
||||
### F3 — commons-eksempelet erklærer nå noe som er usant for po
|
||||
|
||||
`shared/skills/falsification-reviewer/references/example-evidence.json` (commons, **pull-only**)
|
||||
erklærer konsept 2 som `state: unreadable, reason: block-sequence, items_seen: 2` for en SPEC §5.1
|
||||
blokk-sekvens. po leser den nå som to oppføringer. **Divergensen kan ikke lukkes herfra** — det
|
||||
krever et commons-amendment.
|
||||
|
||||
`test_the_worked_example_round_trips_through_the_real_readers` er skrevet om til å **asserte
|
||||
divergensen i stedet for å hoppe over den**, og beholder den diskriminerende halvdelen: eksempelet
|
||||
sier to oppføringer ble sett, og po sin leser returnerer nøyaktig to — så en leser som mistet en
|
||||
oppføring, beholdt én, eller fant på en nøkkel som ikke er en nøkkel, faller fortsatt her.
|
||||
|
||||
**Dette er en åpen sak for operatøren, ikke lukket av denne økten.**
|
||||
|
||||
---
|
||||
|
||||
## § 4 — Mutasjonene
|
||||
|
||||
Alle mot HELE suiten, én per kjøring, restaurert fra scratchpad + `shasum -c`.
|
||||
Grønn kontroll: **1606 passed / 5 skipped**, begge goldener byte-uendret.
|
||||
|
||||
| # | Mutasjon | Røde | Signatur |
|
||||
|---|---|---|---|
|
||||
| M1 | `_claims_ingest_ownership` tilbake til literalene | **2** | KUN de to nye V1-stempel-armene — altså er rad 1 gatet av seg selv |
|
||||
| M2 | blokk-leseren frakoblet `read_provenance` | **17** | hele blokk-lesersuiten + fem eldre B4-armer, altså uavhengige vitner |
|
||||
| M3 | flow-mapping-item faller til par-løkka (F1-defekten) | **7** | de fire nye flow-item-armene + tre eldre |
|
||||
| M4 | en frittstående innrykket linje folder inn i en OPPFUNNET oppføring | **4** | begge `block-mapping`-armene + to eldre dekoder-armer |
|
||||
| M5 | separator = FØRSTE kolon i stedet for kolon-MELLOMROM | **1** | bare-skalar-armen ALENE — og dét er F2 |
|
||||
| M6 | `expected_generated_stamp` tilbake til literalen `"true"` | **4** | nøyaktig de fire ingest-assertene som leser den ene kilden |
|
||||
|
||||
---
|
||||
|
||||
## § 5 — Suite-regnskap
|
||||
|
||||
| | Node-ider | Kommentar |
|
||||
|---|---|---|
|
||||
| Før P13b | 1582 passed / 5 skipped | P13s kontroll |
|
||||
| Etter | **1606 passed / 5 skipped** | +24 |
|
||||
|
||||
De 24: +4 V1-stempel-armer · +18 blokk-leser-armer · +2 B4-armer
|
||||
(`test_an_unreadable_document_yields_NO_tier`, `test_the_committed_block_form_fixture_is_now_READ`).
|
||||
|
||||
**Ikke et strengt supersett — tre node-ider er OMDØPT, ingen fjernet i substans:**
|
||||
|
||||
| Gammelt navn | Nytt navn | Hvorfor |
|
||||
|---|---|---|
|
||||
| `test_a_block_form_document_is_unreadable_and_says_WHY` | `test_an_undecodable_document_is_unreadable_and_says_WHY` | spesimenet måtte flytte; påstanden er uendret |
|
||||
| `test_a_two_entry_block_document_yields_NO_tier` | `test_a_two_entry_block_document_keeps_the_human_sign_off` | påstanden er nå den fixturen ble AUTORERT for |
|
||||
| `test_a_value_carrying_a_colon_is_split_on_colon_space_only` | `test_a_value_carrying_a_colon_survives_whole` | F2 |
|
||||
|
||||
**Ni eksisterende armer er skrevet om, ingen svekket.** Alle ni pinnet «blokkformen er uleselig» —
|
||||
nøyaktig oppførselen ordren endrer. Hver av dem beholder sin påstand og har fått enten (a) et
|
||||
spesimen som fortsatt er uleselig av en grunn av sitt eget (SPEC §5.2: en oppføring som navngir ingen
|
||||
aktør, som holder både reason-tokenet `block-sequence` og et valgbart antall), eller (b) den motsatte
|
||||
påstanden pinnet der den gamle sto, så retningsendringen ikke kan være stille. To av dem ble
|
||||
STERKERE: `multi-verified.md` ble autorert for «en leser som beholder siste oppføring rapporterer
|
||||
maskin-bekreftet for et konsept et menneske signerte», og den påstanden kunne ikke testes så lenge
|
||||
formen var uleselig — nå asserteres rekkefølgen og `human-reviewed`.
|
||||
|
||||
---
|
||||
|
||||
## § 6 — Honesty limits
|
||||
|
||||
1. **Ingen betalt kjøring, ingen levende modell.** At 4605 dokumenter nå har en lesbar adresse er en
|
||||
egenskap ved leseren, ikke et bevis på at noen kjøring blir bedre.
|
||||
2. **De fem avledede golden-linjene** er verifisert av testene mot stubbene, ikke av en materialisering
|
||||
jeg kjørte selv.
|
||||
3. **F3 er ÅPEN.** Commons-eksempelet erklærer fortsatt noe usant for po, og `shared/` er pull-only.
|
||||
4. **`evidence_for` er ikke wiret inn i kjørestien.** B4-regelen står: systemet leser, kalleren
|
||||
avgjør. Ingen `run_project`-oppførsel endres av denne økten.
|
||||
5. **Guard 1.4.0s kalibrering** er bekreftet på en smal nevner (§ 0).
|
||||
6. **`_claims_ingest_ownership` gjenkjenner to stavemåter.** En TREDJE ville gjøre vakten inert igjen,
|
||||
og ingen test i suiten ville bli rød — det er dét vaktens nekt-melding nå sier høyt.
|
||||
7. **Blokk-formen er LEST, aldri SKREVET.** po emitterer fortsatt ingen blokkform, og skriverne
|
||||
nekter den uendret.
|
||||
|
||||
---
|
||||
|
||||
## § 7 — Reproduksjon
|
||||
|
||||
```bash
|
||||
# premissene
|
||||
uv run python -c "
|
||||
from portfolio_optimiser.okf import _carries_complete_ingest_stamp as f
|
||||
print(f({'generated':'true','ingest_manifest':'m'}), f({'generated':'{ by: a, at: b }','ingest_manifest':'m'}))"
|
||||
B=~/repos/vegnormal-okf/build/ferdig
|
||||
for b in n100-2023 n200-2024 n500-2024 r761-2025; do
|
||||
echo "$b konsepter=$(find $B/$b -name '*.md' ! -name index.md | wc -l) \
|
||||
blokk=$(grep -rl -E '^sources:[[:space:]]*$' $B/$b --include='*.md' | grep -vc '/index.md$') \
|
||||
flat=$(grep -rl -E '^sources:[[:space:]]*\[' $B/$b --include='*.md' | grep -vc '/index.md$')"
|
||||
done
|
||||
|
||||
# evidens-tilstand foer/etter (samme skript, kjoert paa hver side av endringen)
|
||||
# se docs/2026-09-12-p13-okf-pin-r761.md for navigasjonsmaalingen
|
||||
|
||||
# bumpen
|
||||
uv lock && uv sync
|
||||
uv run python -c "import importlib.metadata as m; print(m.version('llm-ingestion-okf'), m.version('llm-ingestion-guard'))"
|
||||
|
||||
# portene
|
||||
uv run pytest -q # 1606 passed / 5 skipped
|
||||
uv run ruff check . --exclude scratchpad
|
||||
uv run ruff format --check . --exclude scratchpad
|
||||
uv run mypy src
|
||||
shasum -a 1 tests/golden/demo-transcript.stdout tests/golden/demo-transcript.stderr
|
||||
```
|
||||
|
|
@ -5,7 +5,7 @@ source_system: prosjekt-arkiv
|
|||
source_query: costs.csv
|
||||
ingested_at: 2026-07-03T12:00:00Z
|
||||
ingest_manifest: manifest@24109f486b9ef708
|
||||
generated: true
|
||||
generated: { by: process:okf-ingest, at: 2026-07-03T12:00:00Z }
|
||||
---
|
||||
|
||||
| item | cost_nok | note |
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ source_system: prosjekt-arkiv
|
|||
source_query: edge.csv
|
||||
ingested_at: 2026-07-03T12:00:00Z
|
||||
ingest_manifest: manifest@24109f486b9ef708
|
||||
generated: true
|
||||
generated: { by: process:okf-ingest, at: 2026-07-03T12:00:00Z }
|
||||
---
|
||||
|
||||
| id | value |
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ source_system: status-api
|
|||
source_query: report
|
||||
ingested_at: 2026-07-04T12:00:00Z
|
||||
ingest_manifest: manifest@8a8ae7a7a4d1cfc2
|
||||
generated: true
|
||||
generated: { by: process:okf-ingest, at: 2026-07-04T12:00:00Z }
|
||||
---
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ source_system: status-api
|
|||
source_query: status
|
||||
ingested_at: 2026-07-04T12:00:00Z
|
||||
ingest_manifest: manifest@8a8ae7a7a4d1cfc2
|
||||
generated: true
|
||||
generated: { by: process:okf-ingest, at: 2026-07-04T12:00:00Z }
|
||||
---
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ source_system: docs-mcp
|
|||
source_query: cost_docs
|
||||
ingested_at: 2026-08-03T12:00:00Z
|
||||
ingest_manifest: manifest@18577a264477cd11
|
||||
generated: true
|
||||
generated: { by: process:okf-ingest, at: 2026-08-03T12:00:00Z }
|
||||
---
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ source_system: portefolje-db
|
|||
source_query: SELECT id, item, amount, note FROM costs ORDER BY id
|
||||
ingested_at: 2026-07-04T12:00:00Z
|
||||
ingest_manifest: manifest@a4b891aea55df97b
|
||||
generated: true
|
||||
generated: { by: process:okf-ingest, at: 2026-07-04T12:00:00Z }
|
||||
---
|
||||
|
||||
| id | item | amount | note |
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ source_system: portefolje-db
|
|||
source_query: SELECT k, v FROM meta ORDER BY k
|
||||
ingested_at: 2026-07-04T12:00:00Z
|
||||
ingest_manifest: manifest@a4b891aea55df97b
|
||||
generated: true
|
||||
generated: { by: process:okf-ingest, at: 2026-07-04T12:00:00Z }
|
||||
---
|
||||
|
||||
| k | v |
|
||||
|
|
|
|||
|
|
@ -37,7 +37,10 @@ dependencies = [
|
|||
# mypy refuses to parse under `python_version = 3.10` (python/mypy#18701) — breaking
|
||||
# `uv run mypy src` outright. Raise both floors together or not at all.
|
||||
"numpy>=2.0,<2.3",
|
||||
"llm-ingestion-okf", # Door A ingest (§4–§6) — the shared implementation of shared/ingest-spec.md; zero runtime deps, MAF-free (D7)
|
||||
# Door A ingest (§4–§6) — the shared implementation of shared/ingest-spec.md, MAF-free (D7).
|
||||
# NOT "zero runtime deps" since 0.4.0: llm-ingestion-guard is okf's one runtime dependency,
|
||||
# and okf >=0.8.5 declares `>=1.2,<2.0` for it. That is why the two lines below move together.
|
||||
"llm-ingestion-okf",
|
||||
"llm-ingestion-guard",
|
||||
# U14 (økt 55, operatørbeslutning 23.08): the tracing seam's ONE new runtime dependency.
|
||||
# `opentelemetry-api` was already here transitively via agent-framework-core, but the SDK is
|
||||
|
|
@ -65,11 +68,16 @@ portfolio-optimiser-demo = "portfolio_optimiser.simulation:main"
|
|||
# verified in consumer CI): git pin against the public Forgejo repo — reproducible for every
|
||||
# consumer, uv.lock pins the exact commit behind the tag. Bump the rev on a new library tag.
|
||||
[tool.uv.sources]
|
||||
llm-ingestion-okf = { git = "https://git.fromaitochitta.com/open/llm-ingestion-okf.git", rev = "v0.3.2" }
|
||||
llm-ingestion-okf = { git = "https://git.fromaitochitta.com/open/llm-ingestion-okf.git", rev = "v0.8.5" }
|
||||
# Pinned to a TAG, never a floating ref — a security component whose version can move under a
|
||||
# `uv sync` is a gate that can stop gating without a local diff (the okf `generated`-literal
|
||||
# lesson, measured 2026-08-09). Bumping this is a decision, not maintenance.
|
||||
llm-ingestion-guard = { git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git", rev = "v0.3.4" }
|
||||
# `tag =`, never `rev =`, and never a lower 1.x: okf >=0.8.5 pins this library itself via its own
|
||||
# [tool.uv.sources] `tag = "v1.4.0"`, and uv treats `rev = "v1.4.0"` as a DIFFERENT url from
|
||||
# `tag = "v1.4.0"` at the same value — both spellings were measured to fail resolution as
|
||||
# "conflicting URLs" (P13 § 2a). The declared floor okf asks for is >=1.2, but 1.2.0 is not
|
||||
# choosable for the same reason.
|
||||
llm-ingestion-guard = { git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git", tag = "v1.4.0" }
|
||||
|
||||
# Dev tooling as a PEP 735 dependency-group (uv includes it by default in `uv sync`/`uv run`),
|
||||
# so the documented bare `uv sync` + `uv run pytest` workflow installs it without `--extra`.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ have forgotten, as a fourth family:
|
|||
never a manifest field, and it refuses BEFORE any transport call);
|
||||
- the §8 ``max_rows`` cap, enforced as an ERROR rather than a silent truncation;
|
||||
- §5 verbatim fenced-body rendering, including the code-fence-marker refusal;
|
||||
- the §7 ``generated: true`` + ``ingest_manifest`` provenance stamp;
|
||||
- the §7 ``generated`` + ``ingest_manifest`` provenance stamp (``generated`` is the V1 mapping
|
||||
``{ by: <actor>, at: <timestamp> }`` as of okf 0.8.5; it was the literal ``true`` under 0.3.2);
|
||||
- the §4 rule that credentials resolve at run time from the environment, never from the manifest.
|
||||
|
||||
**MAF-free (D7-portable).** MCP is an open protocol; ``agent_framework`` is one framework's runtime.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import posixpath
|
|||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Final, Literal
|
||||
|
||||
from portfolio_optimiser.ir import CostBaseline, CostBaselineLine
|
||||
|
|
@ -271,6 +272,80 @@ def _decode_flow_mapping(item: str, raw: str, key: str | None) -> dict[str, str]
|
|||
return entry
|
||||
|
||||
|
||||
def decode_block_mappings(
|
||||
continuation: Sequence[str], *, key: str | None = None
|
||||
) -> tuple[dict[str, str], ...] | None:
|
||||
"""Decode a BLOCK sequence of mappings into entries, or ``None`` when it cannot be read.
|
||||
|
||||
``None`` means "this reader cannot decode it", NEVER "there is nothing here": an indented line
|
||||
before any ``- `` opens no entry, and folding it into one would invent an entry the document
|
||||
does not carry. The caller turns ``None`` into the ``UnreadableProvenance`` it already returned.
|
||||
|
||||
**The second carrier of the same shape, never a second grammar.** The pair separator is
|
||||
colon-SPACE via ``_find_pair_separator``, names and values go through ``unquote_scalar``,
|
||||
duplicate keys are refused, and the one key-specific rule SPEC §5.2 imposes (a ``verified``
|
||||
entry must name an actor) applies here too — all of it the SAME rule ``_decode_flow_mapping``
|
||||
applies to the flow carrier. Two grammars would be two answers to one question, and a delivered
|
||||
base would read differently depending on which spelling its producer chose. The colon-SPACE part
|
||||
is load-bearing rather than stylistic: every delivered ``resource`` is a URL, so a reader
|
||||
splitting on the FIRST colon would truncate all 4605 of them at ``https``.
|
||||
|
||||
Measured 2026-09-12: all four delivered knowledge bases write ``sources`` in this form and none
|
||||
in flow form (n100 446/446, n200 1133/1133, n500 270/270, r761 2756/2756). Reading it is not a
|
||||
licence to WRITE it — ``write_concept_file``/``verified_field`` still refuse exactly what
|
||||
``decode_flow_value`` refuses, so the emission rule and the round-trip gate are untouched.
|
||||
|
||||
Gated by ``tests/test_block_sources_reader_loadbearing.py``."""
|
||||
entries: list[dict[str, str]] = []
|
||||
from_flow = False
|
||||
for line in continuation:
|
||||
if not line.strip():
|
||||
continue
|
||||
item = line.strip()
|
||||
opened = item.startswith("- ")
|
||||
if opened:
|
||||
entries.append({})
|
||||
item = item[2:].strip()
|
||||
from_flow = False
|
||||
elif not entries:
|
||||
return None
|
||||
if item.startswith("{"):
|
||||
# A FLOW mapping as the item. Decoded by ``decode_flow_value`` — the ONE decoder —
|
||||
# rather than by the pair loop below, which would read ``{ id`` as a key and hand back
|
||||
# an entry the document does not carry. Measured 2026-09-12: this is the shape
|
||||
# ``tests/golden/block-form-provenance`` writes for ``verified``, and the pair loop
|
||||
# produced ``{'{ id': 'a, resource: ... }'}`` for it.
|
||||
if not opened:
|
||||
return None
|
||||
try:
|
||||
decoded = decode_flow_value(item, key=key)
|
||||
except FlowDecodeError:
|
||||
return None
|
||||
if len(decoded) != 1:
|
||||
return None
|
||||
entries[-1] = dict(decoded[0])
|
||||
from_flow = True
|
||||
continue
|
||||
if from_flow:
|
||||
# A bare pair continuing an entry that was opened as a flow mapping. Mixing the two
|
||||
# spellings within one entry is refused rather than merged: merging would decide
|
||||
# silently which carrier wins for a document that used both.
|
||||
return None
|
||||
at = _find_pair_separator(item)
|
||||
if at < 0:
|
||||
return None
|
||||
name = unquote_scalar(item[:at])
|
||||
value = unquote_scalar(item[at + len(_FLOW_PAIR_SEPARATOR) :])
|
||||
if name in entries[-1]:
|
||||
return None
|
||||
entries[-1][name] = value
|
||||
if not entries:
|
||||
return None
|
||||
if key == "verified" and any(not entry.get("by") for entry in entries):
|
||||
return None
|
||||
return tuple(entries)
|
||||
|
||||
|
||||
def decode_flow_value(raw: str, *, key: str | None = None) -> tuple[dict[str, str], ...]:
|
||||
"""Decode the accepted single-line flow subset into a tuple of entries.
|
||||
|
||||
|
|
@ -707,6 +782,14 @@ def read_provenance(
|
|||
# inside a value is text, not an item, and counting it would inflate the number the caller
|
||||
# acts on.
|
||||
items = sum(1 for ln in continuation if ln.strip().startswith("- "))
|
||||
# P13b: the block sequence of mappings is SPEC §5.1's own example and the form all four
|
||||
# delivered bases actually write (4605/4605, measured). It is decoded by the second carrier
|
||||
# of the one grammar; a block MAPPING (no item opened) and anything the grammar refuses
|
||||
# still come back unreadable, which is what keeps "we could not read it" from quietly
|
||||
# becoming "there was nothing to read".
|
||||
block = decode_block_mappings(continuation, key=key)
|
||||
if block is not None:
|
||||
return block
|
||||
return UnreadableProvenance(
|
||||
file=str(path),
|
||||
key=key,
|
||||
|
|
@ -1236,10 +1319,37 @@ Widening past what a YAML reader actually resolves would over-block curated cont
|
|||
pipeline ever produces, on a form nothing downstream would honour as the stamp either."""
|
||||
|
||||
|
||||
def _claims_ingest_ownership(value: str) -> bool:
|
||||
"""Whether a ``generated`` value claims ingest ownership, in EITHER spelling the pinned writer
|
||||
has used.
|
||||
|
||||
Two forms, one claim. The pre-V1 spelling is a YAML boolean (``_YAML_TRUE_LITERALS``). The V1
|
||||
spelling is the provenance mapping ``{ by: <actor>, at: <timestamp> }`` — measured 2026-09-12 as
|
||||
what ``llm-ingestion-okf`` >=0.8.5 writes into every generated concept, where 0.3.2 wrote
|
||||
``true``. A detector that knew only the boolean read the V1 form as NOT a stamp, so the forgery
|
||||
refusal in ``write_concept_file`` went inert on exactly the output of the writer it guards
|
||||
against, with the whole fail-closed suite green (P13, ``docs/2026-09-12-p13-okf-pin-r761.md``
|
||||
§ 2c′). Widening the predicate from "reads as boolean True" to "claims ownership" is what makes
|
||||
the guard survive the emitter, rather than the emitter's current spelling.
|
||||
|
||||
The recogniser for the V1 half is ``decode_flow_value`` — the module's ONE flow decoder, never a
|
||||
second copy of the rule (the kø-(p) precedent, and the same argument ``write_concept_file``
|
||||
already makes for ``verified``: the writer refuses exactly what the reader can read). A value the
|
||||
decoder REFUSES is therefore not an ownership claim and writes through, which is what keeps this
|
||||
from collapsing into "any non-empty ``generated``"."""
|
||||
literal = value.strip().strip('"').lower()
|
||||
if literal in _YAML_TRUE_LITERALS:
|
||||
return True
|
||||
try:
|
||||
return bool(decode_flow_value(value.strip(), key="generated"))
|
||||
except FlowDecodeError:
|
||||
return False
|
||||
|
||||
|
||||
def _carries_complete_ingest_stamp(frontmatter: dict[str, str]) -> bool:
|
||||
"""Whether ``frontmatter`` carries BOTH halves of the ingest ownership stamp: a ``generated``
|
||||
value a YAML reader would read as boolean ``True`` (``_YAML_TRUE_LITERALS``) together with a
|
||||
non-empty ``ingest_manifest`` reference (ingest-spec §7).
|
||||
value claiming ingest ownership (``_claims_ingest_ownership``) together with a non-empty
|
||||
``ingest_manifest`` reference (ingest-spec §7).
|
||||
|
||||
FAIL-CLOSED on the value literal: the field previously matched only the exact string ``"true"``,
|
||||
so a pinned ingest writer emitting any other YAML-1.1 truthy form (``yes``, ``on``) would have
|
||||
|
|
@ -1249,9 +1359,8 @@ def _carries_complete_ingest_stamp(frontmatter: dict[str, str]) -> bool:
|
|||
field, and a verbatim round-trip of one half must keep working. Values are compared the way
|
||||
``parse_frontmatter`` yields them (line-oriented strings, quotes retained), so surrounding quotes
|
||||
and case are normalised away here."""
|
||||
generated = str(frontmatter.get("generated", "")).strip().strip('"').lower()
|
||||
manifest = str(frontmatter.get("ingest_manifest", "")).strip().strip('"')
|
||||
return generated in _YAML_TRUE_LITERALS and bool(manifest)
|
||||
return _claims_ingest_ownership(str(frontmatter.get("generated", ""))) and bool(manifest)
|
||||
|
||||
|
||||
def write_concept_file(bundle_dir: str, name: str, frontmatter: dict[str, str], body: str) -> Path:
|
||||
|
|
@ -1275,7 +1384,8 @@ def write_concept_file(bundle_dir: str, name: str, frontmatter: dict[str, str],
|
|||
if _carries_complete_ingest_stamp(frontmatter):
|
||||
raise IngestStampError(
|
||||
"refusing to write a curated concept file carrying the COMPLETE ingest ownership stamp "
|
||||
"(generated: true + ingest_manifest); only the ingest materializer may claim it "
|
||||
"(a `generated` ownership claim — `true` or the V1 `{ by: ..., at: ... }` mapping — "
|
||||
"together with ingest_manifest); only the ingest materializer may claim it "
|
||||
"(ingest-spec §3) — either field alone is permitted"
|
||||
)
|
||||
resolved = Path(safe_resolve(bundle_dir, name))
|
||||
|
|
|
|||
|
|
@ -241,3 +241,17 @@ def assert_full_run_contract(result: RunResult, outbox_dir: str | Path, run_id:
|
|||
"the deterministic validator never decided — the run did not reach the gate with a "
|
||||
f"parsed candidate (validator_decision={result.provenance.validator_decision!r})"
|
||||
)
|
||||
|
||||
|
||||
#: P13b: the §7 ownership stamp as the PINNED WRITER emits it. ONE copy, shared by the four ingest
|
||||
#: tests that assert the emitted value, because four literals for one emitter fact are four places a
|
||||
#: later okf release can leave half-corrected (the kø-(p) rule, and exactly how the pre-V1 form
|
||||
#: survived in four asserts until P13 measured it). The actor token is okf's, not ours: measured
|
||||
#: 2026-09-12 against llm-ingestion-okf 0.8.5, which writes `{ by: process:okf-ingest, at: <the
|
||||
#: ingested_at the caller passed> }` where 0.3.2 wrote the literal `true`.
|
||||
#:
|
||||
#: A curated half-stamp written BY THIS REPO (`okf.write_concept_file`) is a different fact and keeps
|
||||
#: its own literal — `tests/test_okf.py` round-trips `generated: true` and must not be pointed here.
|
||||
def expected_generated_stamp(ingested_at: str) -> str:
|
||||
"""The exact ``generated`` value okf's materializer writes for ``ingested_at``."""
|
||||
return f"{{ by: process:okf-ingest, at: {ingested_at} }}"
|
||||
|
|
|
|||
26
tests/fixtures/p13b-block-sources/n100-krav-4-2-5-1-3.md
vendored
Normal file
26
tests/fixtures/p13b-block-sources/n100-krav-4-2-5-1-3.md
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
type: Krav
|
||||
title: Krav 4.2.5.1—3 Gangfelt og tilrettelagte kryssingspunkter
|
||||
description: Ved fartsgrense 40 og 50 km/t skal gangfelt etableres dersom:●Antall fotgjengere > 20 og antall kjøretøy > 200 i dimensjonerende time ●Antall fotgjengere > 10…
|
||||
kravtype: skal
|
||||
normal: N100
|
||||
utgave: N100:2023
|
||||
req_number: Krav 4.2.5.1—3
|
||||
kravdato: 2021-06-22
|
||||
hjemmel: forskrift om anlegg av offentlig veg, jf. vegloven § 13
|
||||
fraviksmyndighet: ikke uttalt i kilden
|
||||
status: stable
|
||||
trust_tier: unverified
|
||||
seksjon: 4.2.5.1
|
||||
seksjonstittel: Gangfelt og tilrettelagte kryssingspunkter
|
||||
ingested_at: 2026-09-08T12:00:00Z
|
||||
source_sha256: c58e8bbc5fa9a5400c111e51b04c05f2cfd9edabd884ef5352a486fdab2cb5ab
|
||||
source_element_id: id-2b69893f-e462-4a75-d1c3-b93a7dbf1667
|
||||
sources:
|
||||
- resource: https://viewers.vegnorm.vegvesen.no/api/nisosts/859984?languageCode=nb
|
||||
title: N100:2023
|
||||
---
|
||||
|
||||
## Krav
|
||||
|
||||
Ved fartsgrense 40 og 50 km/t skal gangfelt etableres dersom:●Antall fotgjengere > 20 og antall kjøretøy > 200 i dimensjonerende time ●Antall fotgjengere > 10 og antall kjøretøy > 800 i dimensjonerende time
|
||||
250
tests/test_block_sources_reader_loadbearing.py
Normal file
250
tests/test_block_sources_reader_loadbearing.py
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
"""P13b: a BLOCK sequence of mappings is provenance po can read, not provenance po cannot.
|
||||
|
||||
RED-FIRST, measured 2026-09-12 with the full denominator: every concept file in all four delivered
|
||||
knowledge bases writes ``sources`` as a BLOCK sequence — n100 446/446, n200 1133/1133, n500 270/270,
|
||||
r761 2756/2756, **4605 of 4605, and 0 in flow form**. ``read_provenance`` answered
|
||||
``UnreadableProvenance(reason="block-sequence")`` for every one of them, so ``evidence_for`` reported
|
||||
``state="unreadable"`` on 4605 of 4605 documents: the falsification layer had no address for any
|
||||
document in any base po is about to be stress-tested against.
|
||||
|
||||
**This widens the READER and nothing else.** The producer still EMITS flow (measured: okf's
|
||||
``materialize._render_sources`` is unchanged in 0.8.5), so no bundle bytes move, and
|
||||
``write_concept_file``/``verified_field`` still refuse what the flow decoder refuses — the writer
|
||||
keeps refusing exactly what the reader could not read, which is the property the round-trip gate
|
||||
exists for. What changes is that a form the producer's own SPEC §5.1 documents, and that four
|
||||
delivered bases actually use, stops being reported as unreadable.
|
||||
|
||||
**Two refusals are KEPT, and they are this file's discriminators.** ``block-mapping`` (an indented
|
||||
continuation with no ``- `` item) and ``unsupported-flow`` (a single-line value the flow decoder
|
||||
refuses) must still come back as ``UnreadableProvenance``. Without those arms a reader widened to
|
||||
"anything indented is entries" would pass every positive arm here while inventing entries the
|
||||
document does not have — the repo's vacuous-gate class.
|
||||
|
||||
The grammar is po's OWN, never okf's imported: the pair separator is colon-SPACE
|
||||
(``_find_pair_separator``, the rule ``decode_flow_value`` already uses), because two separator rules
|
||||
in one module are two answers to one question. okf's ``consume.read_sources`` was read for the FORM
|
||||
and not called — po calls no okf reader, which is measured and deliberate (P13 § 1a).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from portfolio_optimiser import okf
|
||||
|
||||
_FIXTURES = Path(__file__).resolve().parent / "fixtures" / "p13b-block-sources"
|
||||
|
||||
#: A byte-identical copy of a REAL delivered concept file (n100-2023, `krav/N100/
|
||||
#: id-2b69893f-…md`, sha1 8c2952d1…). Read from the producer's tree, never written there. A
|
||||
#: hand-written approximation would prove the reader handles what I imagined the form to be.
|
||||
_REAL = _FIXTURES / "n100-krav-4-2-5-1-3.md"
|
||||
|
||||
|
||||
def _write(tmp_path: Path, frontmatter: str) -> Path:
|
||||
path = tmp_path / "c.md"
|
||||
path.write_text(f"---\n{frontmatter}\n---\n\nbody\n", encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def test_the_real_delivered_concept_file_yields_its_address() -> None:
|
||||
"""The measurement this whole row exists for, against the actual bytes on disk."""
|
||||
entries = okf.read_provenance(_REAL, "sources")
|
||||
assert isinstance(entries, tuple), f"still unreadable: {entries!r}"
|
||||
assert entries == (
|
||||
{
|
||||
"resource": "https://viewers.vegnorm.vegvesen.no/api/nisosts/859984?languageCode=nb",
|
||||
"title": "N100:2023",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_evidence_for_reports_present_on_the_real_file() -> None:
|
||||
"""The three-state answer a falsification verdict acts on, end to end. ``tier`` stays ``None``
|
||||
because ``sources`` is not the key SPEC §5.3 tiers — the B4 rule, unchanged by this widening."""
|
||||
evidence = okf.evidence_for(_REAL, "sources")
|
||||
assert evidence.state == "present"
|
||||
assert evidence.items_seen == 1
|
||||
assert evidence.tier is None
|
||||
assert okf.evidence_notice(evidence) is None
|
||||
|
||||
|
||||
def test_a_two_entry_block_sequence_keeps_both_entries(tmp_path: Path) -> None:
|
||||
"""More than one address, so the positive arm cannot pass on a reader that returns the first."""
|
||||
path = _write(
|
||||
tmp_path,
|
||||
"type: concept\n"
|
||||
"sources:\n"
|
||||
" - resource: https://a.example/doc?q=1\n"
|
||||
" title: A\n"
|
||||
" - resource: https://b.example/doc\n"
|
||||
" id: b\n",
|
||||
)
|
||||
assert okf.read_provenance(path, "sources") == (
|
||||
{"resource": "https://a.example/doc?q=1", "title": "A"},
|
||||
{"resource": "https://b.example/doc", "id": "b"},
|
||||
)
|
||||
|
||||
|
||||
def test_the_key_after_a_block_sequence_is_still_read(tmp_path: Path) -> None:
|
||||
"""The continuation must stop at the first unindented line, or the reader swallows the rest of
|
||||
the frontmatter into the last entry."""
|
||||
path = _write(
|
||||
tmp_path,
|
||||
"type: concept\nsources:\n - resource: https://a.example/d\nstatus: stable\n",
|
||||
)
|
||||
assert okf.read_provenance(path, "sources") == ({"resource": "https://a.example/d"},)
|
||||
assert okf.parse_frontmatter(path)["status"] == "stable"
|
||||
|
||||
|
||||
def test_a_value_carrying_a_colon_survives_whole(tmp_path: Path) -> None:
|
||||
"""``title: N100:2023`` is one pair and the value keeps its own colon, as does a URL's ``://``.
|
||||
|
||||
**This arm is a REGRESSION GUARD, not a discriminator, and the distinction was measured rather
|
||||
than assumed.** It was written claiming to prove the colon-SPACE rule, and mutation M5 (separator
|
||||
changed to the FIRST colon) left it GREEN: the first colon in ``resource: https://…`` and in
|
||||
``title: N100:2023`` is the same one colon-SPACE finds, so the two rules agree on every value
|
||||
shaped like a delivered one. The arm that actually tells them apart is
|
||||
``test_an_item_with_no_pair_separator_is_refused`` — under first-colon, ``- https://a.example/d``
|
||||
decodes to ``{'https': '//a.example/d'}``, a key invented out of a URL scheme. Kept because it
|
||||
pins the values four delivered bases actually carry; labelled honestly because a test that
|
||||
cannot separate two implementations proves nothing about them."""
|
||||
path = _write(
|
||||
tmp_path,
|
||||
"type: concept\nsources:\n - resource: https://x.example/a\n title: N100:2023\n",
|
||||
)
|
||||
assert okf.read_provenance(path, "sources") == (
|
||||
{"resource": "https://x.example/a", "title": "N100:2023"},
|
||||
)
|
||||
|
||||
|
||||
# --- the KEPT refusals: the discriminators ----------------------------------------------------
|
||||
|
||||
|
||||
def test_a_block_mapping_is_still_unreadable(tmp_path: Path) -> None:
|
||||
"""No ``- `` item, so no entry was opened. Folding it into one would invent an entry the
|
||||
document does not carry."""
|
||||
path = _write(tmp_path, "type: concept\nverified:\n by: someone\n at: 2026-01-01\n")
|
||||
result = okf.read_provenance(path, "verified")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert result.reason == "block-mapping"
|
||||
|
||||
|
||||
def test_an_indented_line_before_any_item_is_refused(tmp_path: Path) -> None:
|
||||
"""The sharp case: a continuation that LOOKS like a sequence but opens with a bare pair. Refused
|
||||
rather than folded, which is where a naive block reader invents an entry.
|
||||
|
||||
**A predicted reason was FALSIFIED by the measurement and is written down as measured.** This
|
||||
arm expected ``block-mapping``; the answer is ``block-sequence``, because the caller's reason is
|
||||
chosen by whether any ``- `` item is PRESENT, and one is — it simply does not decode. That is
|
||||
the honest label: ``block-mapping`` is reserved for a continuation carrying no item at all
|
||||
(the arm above), and relabelling this one would make the two indistinguishable."""
|
||||
path = _write(
|
||||
tmp_path,
|
||||
"type: concept\nsources:\n title: stray\n - resource: https://a.example/d\n",
|
||||
)
|
||||
result = okf.read_provenance(path, "sources")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert result.reason == "block-sequence"
|
||||
assert result.items_seen == 1
|
||||
|
||||
|
||||
def test_an_item_with_no_pair_separator_is_refused(tmp_path: Path) -> None:
|
||||
"""``- just-a-scalar`` names a resource without saying so — the same refusal the flow decoder
|
||||
makes for a bare scalar entry, so the two carriers cannot disagree about what an entry is.
|
||||
|
||||
**This is the ONE arm that witnesses the colon-SPACE rule** (measured by mutation M5): a reader
|
||||
splitting on the FIRST colon reads this item as ``{'https': '//a.example/d'}``, inventing a key
|
||||
out of a URL scheme instead of refusing. Every delivered ``resource`` is a URL, so that reader
|
||||
would mint 4605 such keys rather than fail once."""
|
||||
path = _write(tmp_path, "type: concept\nsources:\n - https://a.example/d\n")
|
||||
result = okf.read_provenance(path, "sources")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert result.reason == "block-sequence"
|
||||
|
||||
|
||||
def test_an_unsupported_flow_value_is_still_unreadable(tmp_path: Path) -> None:
|
||||
"""The other kept refusal: a single-line value the flow decoder rejects never reaches the block
|
||||
path at all."""
|
||||
path = _write(tmp_path, "type: concept\nsources: [not-a-mapping]\n")
|
||||
result = okf.read_provenance(path, "sources")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert result.reason == "unsupported-flow"
|
||||
|
||||
|
||||
def test_an_absent_key_is_still_none(tmp_path: Path) -> None:
|
||||
"""Absence is ``None``, never an empty tuple — the F2 principle one layer down, and the control
|
||||
that keeps "unreadable" and "absent" from collapsing into each other."""
|
||||
path = _write(tmp_path, "type: concept\ntitle: t\n")
|
||||
assert okf.read_provenance(path, "sources") is None
|
||||
|
||||
|
||||
def test_the_flow_form_still_decodes(tmp_path: Path) -> None:
|
||||
"""Control: widening the reader to the block form must not cost the flow form, which is what
|
||||
every commons-owned fixture and the producer's own emitter still write."""
|
||||
path = _write(tmp_path, "type: concept\nsources: [{ id: a, resource: https://a.example/d }]\n")
|
||||
assert okf.read_provenance(path, "sources") == ({"id": "a", "resource": "https://a.example/d"},)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ["verified", "sources", "attester"])
|
||||
def test_the_block_reader_is_key_agnostic(tmp_path: Path, key: str) -> None:
|
||||
"""Whatever key carries the sequence, the same grammar reads it — never a hard-coded
|
||||
``{id, resource}``, which would refuse the very shapes this seam exists for (the B4 rule)."""
|
||||
path = _write(tmp_path, f"type: concept\n{key}:\n - by: a\n at: 2026-01-01\n")
|
||||
assert okf.read_provenance(path, key) == ({"by": "a", "at": "2026-01-01"},)
|
||||
|
||||
|
||||
# --- the FLOW-mapping item: found by measuring my own reader, not by a failing arm -------------
|
||||
#
|
||||
# A block sequence whose ITEMS are flow mappings (``- { id: a, resource: x }``) is SPEC-canonical
|
||||
# and is exactly what ``tests/golden/block-form-provenance`` writes for ``verified``. The first
|
||||
# block reader decoded it as ``{'{ id': 'a, resource: https://x/y }'}`` — a key that is not a key —
|
||||
# which is the "invent an entry the document does not carry" failure its own docstring forbids. No
|
||||
# arm caught it: on ``verified`` the §5.2 "must name an actor" rule refused it for an unrelated
|
||||
# reason, so the fixture that carries the shape was shielded by accident. Measured, then fixed.
|
||||
|
||||
|
||||
def test_a_block_item_that_is_a_flow_mapping_is_decoded_by_the_flow_decoder(tmp_path: Path) -> None:
|
||||
"""One decoder, two carriers: the item goes through ``decode_flow_value``, so the block and flow
|
||||
spellings of the same entry cannot come back different."""
|
||||
path = _write(
|
||||
tmp_path, "type: concept\nsources:\n - { id: a, resource: https://x.example/y }\n"
|
||||
)
|
||||
assert okf.read_provenance(path, "sources") == ({"id": "a", "resource": "https://x.example/y"},)
|
||||
|
||||
|
||||
def test_two_flow_mapping_items_keep_their_order(tmp_path: Path) -> None:
|
||||
"""The order-sensitive case the fixture's own prose calls the whole point: a reader that kept
|
||||
the last entry it saw would report the wrong actor first."""
|
||||
path = _write(
|
||||
tmp_path,
|
||||
"type: concept\nverified:\n"
|
||||
" - { by: human:a, at: 2026-09-02T09:00:00Z }\n"
|
||||
" - { by: process:b, at: 2026-09-02T10:00:00Z }\n",
|
||||
)
|
||||
assert okf.read_provenance(path, "verified") == (
|
||||
{"by": "human:a", "at": "2026-09-02T09:00:00Z"},
|
||||
{"by": "process:b", "at": "2026-09-02T10:00:00Z"},
|
||||
)
|
||||
|
||||
|
||||
def test_a_flow_item_with_a_bare_continuation_is_refused(tmp_path: Path) -> None:
|
||||
"""Mixing the two spellings within one entry is refused rather than merged — merging would
|
||||
decide, silently, which carrier wins for a document that used both."""
|
||||
path = _write(
|
||||
tmp_path,
|
||||
"type: concept\nsources:\n - { id: a, resource: https://x.example/y }\n title: T\n",
|
||||
)
|
||||
result = okf.read_provenance(path, "sources")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert result.reason == "block-sequence"
|
||||
|
||||
|
||||
def test_a_malformed_flow_item_is_refused_not_guessed(tmp_path: Path) -> None:
|
||||
"""The discriminator against the defect this block exists for: an item the flow decoder refuses
|
||||
must come back unreadable, never as a key that is not a key."""
|
||||
path = _write(tmp_path, "type: concept\nsources:\n - { id a, resource x }\n")
|
||||
result = okf.read_provenance(path, "sources")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert result.reason == "block-sequence"
|
||||
|
|
@ -133,11 +133,24 @@ def test_the_three_non_present_states_are_unchanged_for_any_key(tmp_path: Path)
|
|||
absent = okf.evidence_for(_concept(tmp_path), key="adjudication")
|
||||
assert (absent.state, absent.tier, absent.items_seen) == ("absent", None, 0)
|
||||
|
||||
# P13b made the block sequence of mappings READABLE (measured: 4605 of 4605 concepts in the
|
||||
# four delivered bases write exactly that form). The specimen therefore moved to one that is
|
||||
# still unreadable for a reason of its own — an item the flow decoder refuses — so this arm
|
||||
# keeps asserting what it always asserted: an unreadable value is never tiered.
|
||||
blocked = _concept(
|
||||
tmp_path,
|
||||
"---\ntype: concept\nsources:\n - id: n100\n resource: vegnormal\n---\n\nInnhold.\n",
|
||||
"---\ntype: concept\nsources:\n - { id n100, resource vegnormal }\n---\n\nInnhold.\n",
|
||||
)
|
||||
unreadable = okf.evidence_for(blocked, key="sources")
|
||||
assert unreadable.state == "unreadable"
|
||||
assert unreadable.tier is None
|
||||
assert unreadable.reason == "block-sequence"
|
||||
|
||||
# The reverse direction, pinned in the same arm so the widening cannot regress silently.
|
||||
readable = _concept(
|
||||
tmp_path,
|
||||
"---\ntype: concept\nsources:\n - id: n100\n resource: vegnormal\n---\n\nInnhold.\n",
|
||||
)
|
||||
present = okf.evidence_for(readable, key="sources")
|
||||
assert present.state == "present"
|
||||
assert present.tier is None # `sources` is not the key SPEC §5.3 tiers — the B4 rule stands
|
||||
|
|
|
|||
|
|
@ -132,15 +132,29 @@ def test_the_worked_example_round_trips_through_the_real_readers(tmp_path: Path)
|
|||
# because ``FalsificationEvidence.tier`` is only meaningful for the ONE key SPEC §5.3
|
||||
# tiers. Branching on the primitive's own documented three-way return is test-level
|
||||
# dispatch, not a second copy of production logic.
|
||||
# P13b: the example's UNREADABLE concept is now READ, and that is a live divergence
|
||||
# between this repo and the commons-owned artefact rather than a defect on either side.
|
||||
# The example declares `state: unreadable, reason: block-sequence, items_seen: 2` for a
|
||||
# SPEC §5.1 block sequence; po widened its reader to that carrier because all four
|
||||
# delivered knowledge bases write it and nothing else (measured 2026-09-12: n100 446/446,
|
||||
# n200 1133/1133, n500 270/270, r761 2756/2756 = 4605/4605, 0 in flow form). `shared/` is a
|
||||
# PULL-ONLY subtree, so the declaration cannot be corrected from here: closing this needs a
|
||||
# commons amendment, and the divergence is asserted rather than skipped so it cannot sit
|
||||
# unnoticed until someone reads the prose.
|
||||
#
|
||||
# What still round-trips is the COUNT, and that is the discriminating half: the example
|
||||
# says two entries were seen and po's reader returns exactly two, so a reader that lost an
|
||||
# entry, kept one, or invented a key that is not a key still fails here.
|
||||
result = okf.read_provenance(path, "sources")
|
||||
assert isinstance(result, tuple), concept.concept_id
|
||||
assert len(result) == concept.items_seen, concept.concept_id
|
||||
if concept.state == "present":
|
||||
assert isinstance(result, tuple), concept.concept_id
|
||||
assert len(result) == concept.items_seen, concept.concept_id
|
||||
assert concept.reason is None, concept.concept_id
|
||||
else:
|
||||
assert isinstance(result, okf.UnreadableProvenance), concept.concept_id
|
||||
assert result.reason == concept.reason, concept.concept_id
|
||||
assert result.items_seen == concept.items_seen, concept.concept_id
|
||||
assert concept.reason == "block-sequence", (
|
||||
"the only DECLARED-unreadable shape po now reads is the block sequence; any other "
|
||||
f"reason is a real regression, got {concept.reason!r} for {concept.concept_id}"
|
||||
)
|
||||
|
||||
# The verified half goes through the SHIPPED ``evidence_for`` on its DEFAULT key — the
|
||||
# path where the tier derivation is the one §5.3 defines.
|
||||
|
|
@ -150,6 +164,8 @@ def test_the_worked_example_round_trips_through_the_real_readers(tmp_path: Path)
|
|||
seen_states.add(concept.state)
|
||||
|
||||
assert seen_states == {"present", "unreadable"}, (
|
||||
"the example must exercise BOTH a readable and an unreadable concept -- one of each is what "
|
||||
f"makes the round trip discriminating, got {sorted(seen_states)}"
|
||||
"the example must DECLARE both a readable and an unreadable concept -- one of each is what "
|
||||
f"makes the round trip discriminating, got {sorted(seen_states)}. Since P13b po READS both "
|
||||
"(the declared-unreadable one is a SPEC 5.1 block sequence); the declaration is the "
|
||||
"commons artefact's and can only be corrected there, see the comment above"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -60,11 +60,18 @@ def test_block_form_bundle_renders_the_captured_bytes() -> None:
|
|||
)
|
||||
assert [f.name for f in bundle.files] == ["index.md", "attested.md", "multi-verified.md"]
|
||||
|
||||
# (2) The decoder REFUSES the same document's `sources`, on the very same fixture. A refusal
|
||||
# that reached navigation would be a coupling a correct design never creates.
|
||||
# (2) The decoder READS the same document's block `sources`, on the very same fixture — the
|
||||
# direction REVERSED by P13b, and the fixture's own body is what asked for it ("a reader that
|
||||
# only understands the flow shorthand loses the provenance of this concept entirely"). The
|
||||
# coexistence claim is unchanged and is now the stronger one: a decoder that reads this
|
||||
# document did not make navigation any less tolerant of it, and did not move a byte of (3).
|
||||
sources = okf.read_provenance(Path(bundle_dir) / "attested.md", "sources")
|
||||
assert isinstance(sources, okf.UnreadableProvenance)
|
||||
assert sources.reason == "block-sequence"
|
||||
assert sources == (
|
||||
{
|
||||
"resource": "https://example.invalid/fixture-cost-baseline",
|
||||
"id": "fixture-cost-baseline",
|
||||
},
|
||||
)
|
||||
|
||||
# (3) And the bytes have not moved. This clause is the one a no-op cannot fake: tolerant
|
||||
# navigation was ALREADY today's behaviour, so clauses (1) and (2) alone are green on a tree
|
||||
|
|
@ -79,6 +86,22 @@ def test_block_form_bundle_renders_the_captured_bytes() -> None:
|
|||
_ENERGI_BUNDLE = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"
|
||||
|
||||
|
||||
def _unreadable_document(tmp_path: Path, entries: int = 1) -> Path:
|
||||
"""A document whose ``verified`` is a block sequence the decoder REFUSES, with a chosen count.
|
||||
|
||||
P13b widened the reader to the block sequence of mappings (measured: 4605 of 4605 concepts in
|
||||
the four delivered bases write it, and po reported every one of them unreadable). The arms below
|
||||
need a specimen that is still unreadable for a reason of its own, or they would be asserting
|
||||
against a state the reader no longer produces. This one refuses on SPEC §5.2 — an entry naming
|
||||
no ``by`` actor — which keeps the reason token ``block-sequence`` and lets the count be chosen,
|
||||
so the triple those arms assert on is unchanged rather than relabelled.
|
||||
"""
|
||||
path = tmp_path / f"unreadable-{entries}.md"
|
||||
items = "".join(f" - at: 2026-01-0{n + 1}T00:00:00Z\n" for n in range(entries))
|
||||
path.write_text(f"---\ntype: concept\nverified:\n{items}---\n\nInnhold.\n", encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def _present_document(tmp_path: Path) -> Path:
|
||||
"""A REAL flow-form document, produced by our own writer — never a mock.
|
||||
|
||||
|
|
@ -122,28 +145,62 @@ def test_a_document_without_the_key_is_absent_and_carries_no_tier() -> None:
|
|||
assert evidence.items_seen == 0
|
||||
|
||||
|
||||
def test_a_block_form_document_is_unreadable_and_says_WHY() -> None:
|
||||
def test_an_undecodable_document_is_unreadable_and_says_WHY(tmp_path: Path) -> None:
|
||||
"""The third state, and the arm asserts the REASON, not merely the state.
|
||||
|
||||
A collapsed ``unreadable`` → ``absent`` is a verdict on missing evidence presented as evidence
|
||||
of absence. Asserting only ``state != "present"`` would stay green against exactly that
|
||||
collapse, so the reason token is what this arm pins.
|
||||
|
||||
The specimen moved off the committed fixture when P13b made the block sequence READABLE. What
|
||||
the arm claims is unchanged — the third state still exists and still carries its reason — and
|
||||
the specimen now refuses on a rule of its own (SPEC §5.2, an entry naming no actor) rather than
|
||||
on the carrier having no reader.
|
||||
"""
|
||||
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "attested.md")
|
||||
evidence = okf.evidence_for(_unreadable_document(tmp_path))
|
||||
assert evidence.state == "unreadable"
|
||||
assert evidence.reason == "block-sequence"
|
||||
assert evidence.items_seen == 1
|
||||
|
||||
|
||||
def test_a_two_entry_block_document_yields_NO_tier() -> None:
|
||||
"""A tier here would be the measured second-entry-wins defect surfacing.
|
||||
def test_the_committed_block_form_fixture_is_now_READ(tmp_path: Path) -> None:
|
||||
"""The direction P13b reversed, pinned where the old arm stood so the change cannot be silent.
|
||||
|
||||
``multi-verified.md`` carries a human sign-off FIRST and a process entry SECOND. A reader that
|
||||
limped past the block form and kept the last entry it saw would report ``machine-confirmed``
|
||||
for a concept a human signed — downgrading the tier with nothing failing. The honest answer to
|
||||
an unreadable value is no tier at all.
|
||||
``attested.md`` was the repo's specimen for "the reader cannot read this". It is the SPEC §5.1
|
||||
block form, which all four delivered knowledge bases write on 4605 of 4605 concepts, and it now
|
||||
yields its single verification entry with the actor intact."""
|
||||
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "attested.md")
|
||||
assert evidence.state == "present"
|
||||
assert evidence.reason is None
|
||||
assert evidence.items_seen == 1
|
||||
assert evidence.entries[0]["by"] == "process:fixture-check"
|
||||
assert evidence.tier == "machine-confirmed"
|
||||
|
||||
|
||||
def test_a_two_entry_block_document_keeps_the_human_sign_off() -> None:
|
||||
"""The defect this fixture was AUTHORED for, finally checkable rather than merely avoided.
|
||||
|
||||
``multi-verified.md`` carries a human sign-off FIRST and a process entry SECOND. Until P13b the
|
||||
block form came back unreadable, so "no tier at all" was the honest answer — and the
|
||||
second-entry-wins defect the fixture's body describes could not be tested for, only sidestepped.
|
||||
Now that the form is read, the claim is the real one: both entries survive IN ORDER and the tier
|
||||
is the human one. A reader keeping the last entry it saw would report ``machine-confirmed`` for
|
||||
a concept a human signed, with nothing failing.
|
||||
"""
|
||||
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "multi-verified.md")
|
||||
assert evidence.state == "present"
|
||||
assert evidence.items_seen == 2
|
||||
assert [entry["by"] for entry in evidence.entries] == [
|
||||
"human:fixture-reviewer",
|
||||
"process:fixture-nightly",
|
||||
]
|
||||
assert evidence.tier == "human-reviewed"
|
||||
|
||||
|
||||
def test_an_unreadable_document_yields_NO_tier(tmp_path: Path) -> None:
|
||||
"""The half of the arm above that P13b did NOT change: the honest answer to a value that could
|
||||
not be read is no tier at all, never a tier derived from what the reader limped past."""
|
||||
evidence = okf.evidence_for(_unreadable_document(tmp_path, entries=2))
|
||||
assert evidence.state == "unreadable"
|
||||
assert evidence.tier is None
|
||||
assert evidence.items_seen == 2
|
||||
|
|
@ -154,10 +211,13 @@ def test_evidence_notice_is_None_when_there_is_nothing_to_say(tmp_path: Path) ->
|
|||
assert okf.evidence_notice(okf.evidence_for(_present_document(tmp_path))) is None
|
||||
|
||||
|
||||
def test_evidence_notice_prints_the_reason_TOKEN_itself() -> None:
|
||||
def test_evidence_notice_prints_the_reason_TOKEN_itself(tmp_path: Path) -> None:
|
||||
"""No second display vocabulary. A prose translation here would be free to drift from
|
||||
``ProvenanceReason``, and the drifted copy is the one the operator would read."""
|
||||
notice = okf.evidence_notice(okf.evidence_for(_GOLDEN_DIR / "bundle" / "attested.md"))
|
||||
``ProvenanceReason``, and the drifted copy is the one the operator would read.
|
||||
|
||||
The specimen moved from the committed fixture to ``_unreadable_document`` because P13b made the
|
||||
fixture READABLE; the token and the count asserted are unchanged."""
|
||||
notice = okf.evidence_notice(okf.evidence_for(_unreadable_document(tmp_path)))
|
||||
assert notice is not None
|
||||
assert "block-sequence" in notice
|
||||
assert "items_seen=1" in notice
|
||||
|
|
@ -166,9 +226,12 @@ def test_evidence_notice_prints_the_reason_TOKEN_itself() -> None:
|
|||
# --- Amendment A: the K5 threshold ------------------------------------------------------------
|
||||
|
||||
|
||||
def test_admits_falsification_refuses_a_state_that_is_not_present() -> None:
|
||||
"""AMENDMENT A, first conjunct — a verdict may not rest on evidence that was never read."""
|
||||
assert not okf.admits_falsification(okf.evidence_for(_GOLDEN_DIR / "bundle" / "attested.md"))
|
||||
def test_admits_falsification_refuses_a_state_that_is_not_present(tmp_path: Path) -> None:
|
||||
"""AMENDMENT A, first conjunct — a verdict may not rest on evidence that was never read.
|
||||
|
||||
Both non-present states are exercised: unreadable (the specimen, since P13b made the committed
|
||||
fixture readable) and absent (``index.md``, which declares no ``verified`` at all)."""
|
||||
assert not okf.admits_falsification(okf.evidence_for(_unreadable_document(tmp_path)))
|
||||
assert not okf.admits_falsification(okf.evidence_for(_GOLDEN_DIR / "bundle" / "index.md"))
|
||||
|
||||
|
||||
|
|
@ -193,14 +256,14 @@ def test_admits_falsification_ADMITS_the_positive_case(tmp_path: Path) -> None:
|
|||
assert okf.admits_falsification(okf.evidence_for(_present_document(tmp_path)))
|
||||
|
||||
|
||||
def test_a_discounted_concept_reports_the_TRIPLE_not_merely_the_refusal() -> None:
|
||||
def test_a_discounted_concept_reports_the_TRIPLE_not_merely_the_refusal(tmp_path: Path) -> None:
|
||||
"""AMENDMENT A — "why it was discounted" is the operative fact.
|
||||
|
||||
A dropped concept and a discounted one are different facts, and only the second is honest about
|
||||
what was read. Asserting that admission was denied says nothing about which of them happened;
|
||||
the triple ``(state, reason, items_seen)`` is what makes the difference legible.
|
||||
"""
|
||||
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "multi-verified.md")
|
||||
evidence = okf.evidence_for(_unreadable_document(tmp_path, entries=2))
|
||||
assert not okf.admits_falsification(evidence)
|
||||
assert (evidence.state, evidence.reason, evidence.items_seen) == (
|
||||
"unreadable",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ from typing import Any
|
|||
import pytest
|
||||
from llm_ingestion_okf import ManifestError
|
||||
|
||||
from conftest import expected_generated_stamp
|
||||
from portfolio_optimiser import okf
|
||||
from portfolio_optimiser.ingest import Extraction, materialize
|
||||
from portfolio_optimiser.verdicts import Verdict, bundle_candidate_features, promote_verdict
|
||||
|
|
@ -162,7 +163,7 @@ def test_every_generated_file_carries_the_provenance_layer(tmp_path: Path) -> No
|
|||
"generated",
|
||||
):
|
||||
assert key in fm, f"provenance key {key!r} missing from {path.name}"
|
||||
assert fm["generated"] == "true"
|
||||
assert fm["generated"] == expected_generated_stamp(_INGESTED_AT)
|
||||
assert fm["source_system"] == "prosjekt-arkiv"
|
||||
assert fm["ingested_at"] == _INGESTED_AT
|
||||
stem, _, digest = fm["ingest_manifest"].partition("@")
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import pytest
|
|||
|
||||
from llm_ingestion_okf import MaterializationError, SourceError
|
||||
|
||||
from conftest import expected_generated_stamp
|
||||
from portfolio_optimiser import okf
|
||||
from portfolio_optimiser.ingest import IngestError, materialize, read_csv, render_table
|
||||
|
||||
|
|
@ -197,7 +198,7 @@ def test_provenance_roundtrips_via_unchanged_parse_frontmatter(tmp_path: Path) -
|
|||
assert fm["source_system"] == "prosjekt-arkiv"
|
||||
assert fm["source_query"] == "costs.csv"
|
||||
assert fm["ingested_at"] == _INGESTED_AT # verbatim (§5)
|
||||
assert fm["generated"] == "true" # parse_frontmatter returns strings, never booleans
|
||||
assert fm["generated"] == expected_generated_stamp(_INGESTED_AT) # strings, never booleans
|
||||
expected = "manifest@" + hashlib.sha256(manifest_path.read_bytes()).hexdigest()[:16]
|
||||
assert fm["ingest_manifest"] == expected
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from conftest import expected_generated_stamp
|
||||
from portfolio_optimiser.ingest import (
|
||||
IngestError,
|
||||
NetworkGateError,
|
||||
|
|
@ -189,7 +190,9 @@ def test_mcp_source_materializes_a_bundle_through_the_http_family(tmp_path: Path
|
|||
assert [p.name for p in written] == ["ingest-cost-docs.md"]
|
||||
text = written[0].read_text(encoding="utf-8")
|
||||
assert "north,1200" in text, "extracted body must reach the concept file"
|
||||
assert "generated: true" in text, "§7 honesty stamp must survive the MCP path"
|
||||
assert f"generated: {expected_generated_stamp('2026-08-02T00:00:00Z')}" in text, (
|
||||
"§7 honesty stamp must survive the MCP path"
|
||||
)
|
||||
|
||||
|
||||
def test_mcp_source_is_refused_without_the_per_run_network_optin(tmp_path: Path) -> None:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from typing import Any
|
|||
|
||||
import pytest
|
||||
|
||||
from conftest import expected_generated_stamp
|
||||
from portfolio_optimiser import okf
|
||||
from portfolio_optimiser.ingest import (
|
||||
IngestError,
|
||||
|
|
@ -192,7 +193,7 @@ def test_sql_manifest_materializes_with_provenance(
|
|||
assert len(written) == 1
|
||||
fm = okf.parse_frontmatter(written[0])
|
||||
assert fm["source_system"] == "db"
|
||||
assert fm["generated"] == "true"
|
||||
assert fm["generated"] == expected_generated_stamp(_INGESTED_AT)
|
||||
assert fm["source_query"] == "SELECT item, amount FROM costs ORDER BY item"
|
||||
body = written[0].read_text(encoding="utf-8")
|
||||
assert "| item | amount |" in body
|
||||
|
|
|
|||
|
|
@ -75,3 +75,66 @@ def test_half_stamp_remains_legal_manifest_alone(tmp_path) -> None:
|
|||
def test_unstamped_file_written_as_before(tmp_path) -> None:
|
||||
okf.write_concept_file(str(tmp_path), "c.md", {"type": "reference"}, "d\n")
|
||||
assert (tmp_path / "c.md").exists()
|
||||
|
||||
|
||||
# --- P13b row 1: the V1 provenance form of the SAME stamp -------------------------------------
|
||||
#
|
||||
# RED-FIRST, measured 2026-09-12: ``llm-ingestion-okf`` v0.8.5 emits the ownership stamp as a flow
|
||||
# MAPPING — ``generated: { by: process:okf-ingest, at: <ingested_at> }`` — where v0.3.2 emitted the
|
||||
# literal ``true``. ``_YAML_TRUE_LITERALS`` cannot see that form (measured: ``True`` on the literal,
|
||||
# ``False`` on the mapping), so the forgery refusal went INERT on exactly the form the pinned writer
|
||||
# now produces, with this whole file GREEN. That is the trap this file's own docstring was written
|
||||
# for, arriving by a spelling it did not anticipate.
|
||||
#
|
||||
# The predicate widens from "reads as boolean True" to "claims ingest ownership", of which the
|
||||
# boolean is the pre-V1 spelling. The recogniser for the new half is ``decode_flow_value`` — the ONE
|
||||
# decoder, the same argument ``write_concept_file`` already makes for ``verified``: the writer
|
||||
# refuses exactly what the reader can read, and a second copy of the rule would be free to drift.
|
||||
|
||||
_V1_STAMP = "{ by: process:okf-ingest, at: 2026-07-03T12:00:00Z }"
|
||||
|
||||
|
||||
def test_v1_flow_mapping_form_is_recognized_as_the_stamp(tmp_path) -> None:
|
||||
"""The form okf >=0.8.5 actually emits, measured against the golden bundles it materialises."""
|
||||
fm = {"type": "reference", "generated": _V1_STAMP, "ingest_manifest": "bygg@0123456789abcdef"}
|
||||
with pytest.raises(okf.IngestStampError):
|
||||
okf.write_concept_file(str(tmp_path), "forged.md", fm, "body\n")
|
||||
assert not (tmp_path / "forged.md").exists() # refused, not silently repaired
|
||||
|
||||
|
||||
def test_v1_flow_sequence_form_is_recognized_too(tmp_path) -> None:
|
||||
"""SPEC §5.2's one-element MUST reads a bare mapping as a one-element list, so the sequence
|
||||
spelling of the same claim cannot be the way past the gate. Fail-closed by construction."""
|
||||
fm = {
|
||||
"type": "reference",
|
||||
"generated": f"[{_V1_STAMP}]",
|
||||
"ingest_manifest": "bygg@0123456789abcdef",
|
||||
}
|
||||
with pytest.raises(okf.IngestStampError):
|
||||
okf.write_concept_file(str(tmp_path), "forged.md", fm, "body\n")
|
||||
assert not (tmp_path / "forged.md").exists()
|
||||
|
||||
|
||||
def test_v1_half_stamp_remains_legal(tmp_path) -> None:
|
||||
"""The half-stamp rule is UNCHANGED by the widening: a provenance mapping alone, with no
|
||||
manifest, is still legitimate curated content and is written."""
|
||||
okf.write_concept_file(
|
||||
str(tmp_path), "a.md", {"type": "reference", "generated": _V1_STAMP}, "b\n"
|
||||
)
|
||||
assert (tmp_path / "a.md").exists()
|
||||
|
||||
|
||||
def test_an_undecodable_generated_value_is_not_the_stamp(tmp_path) -> None:
|
||||
"""The discriminator that keeps the new half from collapsing into "any non-empty value": a
|
||||
``generated`` the decoder REFUSES is not an ownership claim, so it writes through paired with a
|
||||
manifest exactly as ``1``/``y``/``maybe`` do above. Without this arm, a gate widened to
|
||||
``bool(generated)`` would pass every arm in this file."""
|
||||
for value in ("{ broken", "[not-a-mapping]", "{ nosep }"):
|
||||
name = f"curated-{abs(hash(value))}.md"
|
||||
okf.write_concept_file(
|
||||
str(tmp_path),
|
||||
name,
|
||||
{"type": "reference", "generated": value, "ingest_manifest": "bygg@0123456789abcdef"},
|
||||
"body\n",
|
||||
)
|
||||
assert (tmp_path / name).exists()
|
||||
|
|
|
|||
|
|
@ -12,14 +12,16 @@ A grep on ``pyproject.toml`` alone would be blind to an environment that drifted
|
|||
stale ``.venv``, a shadowing tool install), and a version assert alone would be blind to a lock that
|
||||
drifted from the declared pin. This guard is both halves.
|
||||
|
||||
**Why the pins are still 0.3.2 / 0.3.4 after P13 measured 0.8.5 / 1.4.0.** The lift was built and
|
||||
run in a worktree on 2026-09-12 and it is NOT green: okf 0.8.5 emits the V1 provenance stamp
|
||||
``generated: { by: process:okf-ingest, at: <ingested_at> }`` where 0.3.2 emitted ``generated: true``.
|
||||
Eight tests go red on that ONE line, and — the finding that decides it — ``okf.
|
||||
_carries_complete_ingest_stamp`` returns ``False`` for the new form while the whole suite stays
|
||||
green on that seam, so the ``write_concept_file`` forgery refusal would land DISARMED. The refusal
|
||||
messages below name that, because a pin whose reason lives only in prose is a pin the next session
|
||||
lifts without re-measuring. Full numbers: ``docs/2026-09-12-p13-okf-pin-r761.md``.
|
||||
**What the pinned pair costs, and why it is named in the refusal.** P13 measured the lift and
|
||||
REFUSED it; P13b closed the blocker and landed it. okf >=0.8.5 emits the V1 provenance stamp
|
||||
``generated: { by: process:okf-ingest, at: <ingested_at> }`` where 0.3.2 emitted ``generated: true``,
|
||||
and ``okf._carries_complete_ingest_stamp`` read the new form as NOT a stamp — the
|
||||
``write_concept_file`` forgery refusal went inert on exactly the output it guards against, with the
|
||||
whole fail-closed suite green. ``_claims_ingest_ownership`` now recognises BOTH spellings. Any
|
||||
further lift must re-measure that predicate against what the new release actually writes, because
|
||||
nothing in the suite would go red if a third spelling appeared. The refusal messages below name it,
|
||||
because a pin whose reason lives only in prose is a pin the next session lifts without
|
||||
re-measuring. Full numbers: ``docs/2026-09-12-p13-okf-pin-r761.md`` and ``…-p13b-okf-bump.md``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -35,17 +37,17 @@ _GUARD_DIST = "llm-ingestion-guard"
|
|||
#: The measured-green pins. ONE place each; the ``pyproject`` asserts DERIVE their expected strings
|
||||
#: from these (the kø-(p) rule) — two literals for one fact drift, and a drifted pin is a guard that
|
||||
#: stops guarding without a local diff.
|
||||
_OKF_PINNED = "0.3.2"
|
||||
_GUARD_PINNED = "0.3.4"
|
||||
_OKF_PINNED = "0.8.5"
|
||||
_GUARD_PINNED = "1.4.0"
|
||||
|
||||
_OKF_LIFT_COST = (
|
||||
"P13 measured the lift to 0.8.5 in a worktree: 27/27 imported names still resolve, both demo "
|
||||
"goldens stay byte-identical and ruff/mypy pass, but the emitted stamp moves from "
|
||||
"'generated: true' to the V1 flow mapping 'generated: { by: ..., at: ... }'. Eight tests go "
|
||||
"red on that one line (four examples/ingest-golden-* byte goldens over seven concept files, "
|
||||
"plus four 'generated == \"true\"' asserts), and okf._carries_complete_ingest_stamp reads the "
|
||||
"new form as NOT a stamp — so write_concept_file's forgery refusal would ship disarmed with "
|
||||
"the suite green. Close that first; see docs/2026-09-12-p13-okf-pin-r761.md."
|
||||
"Before lifting, re-measure what this release EMITS as the §7 ownership stamp and check "
|
||||
"okf._claims_ingest_ownership still recognises it. 0.3.2 wrote 'generated: true'; 0.8.5 writes "
|
||||
"the V1 flow mapping 'generated: { by: process:okf-ingest, at: ... }', and the detector that "
|
||||
"knew only the boolean left write_concept_file's forgery refusal INERT with the whole "
|
||||
"fail-closed suite green (P13 § 2c'). A third spelling would do the same. Then expect the four "
|
||||
"examples/ingest-golden-* byte goldens (seven concept files) and conftest."
|
||||
"expected_generated_stamp to move with it. See docs/2026-09-12-p13b-okf-bump.md."
|
||||
)
|
||||
|
||||
_GUARD_LIFT_COST = (
|
||||
|
|
@ -54,7 +56,8 @@ _GUARD_LIFT_COST = (
|
|||
"ingest._ACCEPTED_DISPOSITION reads 'warn' as the lowest tier a clean concept scores — "
|
||||
"re-measure it before lifting. Note also that okf >=0.8.5 pins this library itself via "
|
||||
"[tool.uv.sources] tag = 'v1.4.0', so a consumer cannot choose a lower 1.x, and must spell its "
|
||||
"own pin with tag= rather than rev= or uv refuses the resolution as conflicting URLs."
|
||||
"own pin with tag= rather than rev= or uv refuses the resolution as conflicting URLs — which "
|
||||
"is why pyproject.toml spells this one tag= and the okf one rev=."
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -89,19 +92,19 @@ def test_installed_guard_version_is_the_pinned_one() -> None:
|
|||
|
||||
|
||||
def test_okf_guard_trips_on_the_measured_lift_and_on_its_neighbours() -> None:
|
||||
"""``0.8.5`` is the release P13 actually measured; ``0.4.0`` is where the guard first became a
|
||||
hard runtime dependency of okf; ``0.8.4`` and ``0.9.0`` bracket the measured one, so a
|
||||
neighbouring tag cannot slip in under a range this pin does not have."""
|
||||
for other in ("0.4.0", "0.7.0", "0.8.4", "0.8.5", "0.9.0", "1.0.0"):
|
||||
"""``0.3.2`` is the pin P13b lifted FROM, so a revert cannot pass silently; ``0.4.0`` is where
|
||||
the guard first became a hard runtime dependency of okf; ``0.8.4``/``0.8.6`` bracket the pinned
|
||||
one, so a neighbouring tag cannot slip in under a range this pin does not have."""
|
||||
for other in ("0.3.2", "0.4.0", "0.7.0", "0.8.4", "0.8.6", "0.9.0", "1.0.0"):
|
||||
with pytest.raises(ValueError, match="write_concept_file"):
|
||||
assert_pinned_okf_version(other)
|
||||
|
||||
|
||||
def test_guard_version_guard_trips_on_the_lift_okf_would_force() -> None:
|
||||
"""``1.4.0`` is the tag okf 0.8.5 forces (it pins this library itself); ``1.2.0`` is the lowest
|
||||
1.x satisfying okf's declared ``>=1.2,<2.0`` and is NOT choosable for that reason; ``2.0.0`` is
|
||||
where the surface freeze ends."""
|
||||
for other in ("0.3.3", "1.2.0", "1.4.0", "2.0.0"):
|
||||
"""``0.3.4`` is the pin P13b lifted FROM; ``1.2.0`` is the lowest 1.x satisfying okf's declared
|
||||
``>=1.2,<2.0`` and is NOT choosable, because okf pins this library itself at ``v1.4.0``;
|
||||
``2.0.0`` is where the surface freeze ends."""
|
||||
for other in ("0.3.4", "1.2.0", "1.3.0", "2.0.0"):
|
||||
with pytest.raises(ValueError, match="calibration"):
|
||||
assert_pinned_guard_version(other)
|
||||
|
||||
|
|
@ -111,4 +114,5 @@ def test_pyproject_pins_both_tags() -> None:
|
|||
cannot drift apart."""
|
||||
pyproject = (Path(__file__).resolve().parents[1] / "pyproject.toml").read_text(encoding="utf-8")
|
||||
assert f'llm-ingestion-okf.git", rev = "v{_OKF_PINNED}" }}' in pyproject
|
||||
assert f'llm-ingestion-pipeline-security.git", rev = "v{_GUARD_PINNED}" }}' in pyproject
|
||||
# `tag =`, not `rev =`: measured, not stylistic. See _GUARD_LIFT_COST.
|
||||
assert f'llm-ingestion-pipeline-security.git", tag = "v{_GUARD_PINNED}" }}' in pyproject
|
||||
|
|
|
|||
|
|
@ -191,9 +191,15 @@ def test_the_parsed_dict_loses_what_the_accessor_recovers(tmp_path: Path) -> Non
|
|||
path = _write(tmp_path, "block")
|
||||
assert parse_frontmatter(path)["verified"] == ""
|
||||
|
||||
# P13b widened the accessor to the block sequence, so what it recovers is no longer the SHAPE
|
||||
# and the COUNT but the ENTRIES themselves. Amendment C's claim is unchanged and strictly
|
||||
# stronger: the line-oriented dict still loses this value entirely, and the accessor is still
|
||||
# the reason anything downstream can see it at all.
|
||||
provenance = read_provenance(path, "verified")
|
||||
assert isinstance(provenance, okf.UnreadableProvenance)
|
||||
assert (provenance.reason, provenance.items_seen) == ("block-sequence", 2)
|
||||
assert provenance == (
|
||||
{"by": "human:a", "at": "2026-01-01T00:00:00Z"},
|
||||
{"by": "process:b", "at": "2026-01-02T00:00:00Z"},
|
||||
)
|
||||
|
||||
|
||||
# --- Step 3: the flow-form decoder ------------------------------------------------------------
|
||||
|
|
@ -346,15 +352,29 @@ def test_a_single_entry_block_sequence_reports_shape_and_count() -> None:
|
|||
say which shape produced it. Together they are strictly more information than a single
|
||||
``block-form``/``multi-entry`` token, and neither half mislabels the other.
|
||||
"""
|
||||
result = okf.read_provenance(_FIXTURE_BUNDLE / "attested.md", "verified")
|
||||
# The specimen moved off the committed fixture when P13b made the block sequence READABLE. It
|
||||
# refuses on a rule of its own now (SPEC §5.2: an entry naming no actor), so the PAIR this arm
|
||||
# exists for is asserted on a value that still produces it.
|
||||
path = _fixture("---\ntype: concept\nverified:\n - at: 2026-01-01T00:00:00Z\n---\nbody\n")
|
||||
result = okf.read_provenance(path, "verified")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert (result.reason, result.items_seen) == ("block-sequence", 1)
|
||||
assert result.key == "verified"
|
||||
|
||||
# The reverse direction, in the same arm, so the widening cannot regress silently.
|
||||
assert okf.read_provenance(_FIXTURE_BUNDLE / "attested.md", "verified") == (
|
||||
{"by": "process:fixture-check", "at": "2026-09-02T08:00:00Z"},
|
||||
)
|
||||
|
||||
|
||||
def test_a_two_entry_block_sequence_counts_both() -> None:
|
||||
"""The same shape, a different count — which is what makes ``items_seen`` a real discriminator."""
|
||||
result = okf.read_provenance(_FIXTURE_BUNDLE / "multi-verified.md", "verified")
|
||||
path = _fixture(
|
||||
"---\ntype: concept\nverified:\n"
|
||||
" - at: 2026-01-01T00:00:00Z\n"
|
||||
" - at: 2026-01-02T00:00:00Z\n---\nbody\n"
|
||||
)
|
||||
result = okf.read_provenance(path, "verified")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert (result.reason, result.items_seen) == ("block-sequence", 2)
|
||||
|
||||
|
|
@ -425,12 +445,10 @@ def test_the_offending_text_is_carried_verbatim() -> None:
|
|||
"""``value`` is what the operator will edit, indentation included — never a normalised form
|
||||
that would send them looking for a string their file does not contain (the ``SkippedLink``
|
||||
rule, applied one layer down)."""
|
||||
path = _fixture(
|
||||
"---\ntype: concept\nverified:\n - { by: human:a, at: 2026-01-01T00:00:00Z }\n---\nbody\n"
|
||||
)
|
||||
path = _fixture("---\ntype: concept\nverified:\n - at: 2026-01-01T00:00:00Z\n---\nbody\n")
|
||||
result = okf.read_provenance(path, "verified")
|
||||
assert isinstance(result, okf.UnreadableProvenance)
|
||||
assert result.value == " - { by: human:a, at: 2026-01-01T00:00:00Z }"
|
||||
assert result.value == " - at: 2026-01-01T00:00:00Z"
|
||||
assert result.file == str(path)
|
||||
|
||||
|
||||
|
|
|
|||
15
uv.lock
generated
15
uv.lock
generated
|
|
@ -1102,13 +1102,16 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "llm-ingestion-guard"
|
||||
version = "0.3.4"
|
||||
source = { git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git?rev=v0.3.4#adf93e47fbcdbd76bdb9baf6393611230342397a" }
|
||||
version = "1.4.0"
|
||||
source = { git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git?tag=v1.4.0#d19de8cb3854fa8cedb732d6668261ee1afedacb" }
|
||||
|
||||
[[package]]
|
||||
name = "llm-ingestion-okf"
|
||||
version = "0.3.2"
|
||||
source = { git = "https://git.fromaitochitta.com/open/llm-ingestion-okf.git?rev=v0.3.2#f14c075a6508257253c0df318f3e10b2fae51d39" }
|
||||
version = "0.8.5"
|
||||
source = { git = "https://git.fromaitochitta.com/open/llm-ingestion-okf.git?rev=v0.8.5#64661c7198fbfb0166a53620ef0978bc80360904" }
|
||||
dependencies = [
|
||||
{ name = "llm-ingestion-guard" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
|
|
@ -1606,8 +1609,8 @@ requires-dist = [
|
|||
{ name = "agent-framework-orchestrations", specifier = ">=1.1.1" },
|
||||
{ name = "anyio", specifier = ">=4.14" },
|
||||
{ name = "azure-identity", specifier = ">=1.25" },
|
||||
{ name = "llm-ingestion-guard", git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git?rev=v0.3.4" },
|
||||
{ name = "llm-ingestion-okf", git = "https://git.fromaitochitta.com/open/llm-ingestion-okf.git?rev=v0.3.2" },
|
||||
{ name = "llm-ingestion-guard", git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git?tag=v1.4.0" },
|
||||
{ name = "llm-ingestion-okf", git = "https://git.fromaitochitta.com/open/llm-ingestion-okf.git?rev=v0.8.5" },
|
||||
{ name = "mcp", specifier = ">=1.28.0" },
|
||||
{ name = "numpy", specifier = ">=2.0,<2.3" },
|
||||
{ name = "opentelemetry-sdk", specifier = ">=1.42,<2" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue