feat(navigation,validator): read_dir names the rung that reads a document, and a run can require its anchoring

F3 and F4, the two findings the S7 acid test (session 98) reported and left. The order required
both descriptions to be treated as PREMISES. One held; the other was felled before anything was
built on it.

F3 -- premise FELLED, asymmetry real. The order read arm C's two refused calls as "the path names
a document that EXISTS". Measured against the base that ran: its root holds 27 directories named
del-ii-bilag-N-... and 12 documents named inbox-del-ii-bilag-N-....md, and the requested path
matches NEITHER -- it is the directory naming convention applied to a document whose real name
carries an inbox- prefix. So the two live rounds were the UNKNOWN-path class, and this delivery
does NOT recover them (gated). What IS real: read_file on a directory has named read_dir since
session 95, while read_dir on a document named neither the rung nor the path.
okf.DocumentPathRefused closes that one direction -- a ValueError, a SIBLING of BundlePathNotFound
rather than a subclass, built from context_files (never files) and through the same in_dimension
predicate the listing uses, quoting the document's REAL name so what it hands back resolves.

F4 -- premise HELD, option (c) felled by measurement. All four live artefacts stamped
cost_baseline_anchored: False and each arm invented its cost codes. derive_cost_baseline refuses
against the delivered base: K2's price schedule is a pandoc SIMPLE table with ONE column header,
so making --derive-cost-baseline reachable there would mean inventing a rule for an unmeasured
form -- MAJOR-4's own honesty limit. Chose (b) over (a): --require-cost-baseline /
run_project(require_cost_baseline=...), OPT-IN and never default, so every bundle without a
cost-baseline.json runs unchanged. The gate sits where both branches have bound baseline and ABOVE
the dry-run cut, so it fires on the free trip too and, on the paid one, before the first model
call. Three CLI refusals by name, each with an rc-0 control.

12 mutations, all red against the WHOLE suite. Green control 1493/5 -> 1511/5 (+18 node-ids, 0
removed); golden demo-transcript.stdout BYTE-UNCHANGED (shasum -a 1 of the CONTENT =
ea8c534773acdbe41ae68f2c55724d69aaf8be4f). No paid run: both findings measured offline.

Measurement: docs/2026-09-08-f3-f4-nekten-og-forankringen.md
Order: 20260908T020419Z-5837110336-from-portfolio-optimiser

Co-Authored-By: Claude <Opus 5>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-08 05:32:46 +02:00
commit 9232f94041
8 changed files with 759 additions and 2 deletions

View file

@ -2102,6 +2102,75 @@ Python ≥3.10. MAF (`agent-framework-core` 1.16.0, `-orchestrations` 1.1.1 —
`ChatClientException` (Azure 400 etter tre `quick_validate`-nekter på rad) er RAPPORTERT, ikke
fikset — den ligger utenfor `main()`s nekt-tuppel og forlater CLI-en som traceback. Måling:
`docs/2026-09-07-prepass-mater-q5b-k2.md`.
- **`read_dir` på et DOKUMENT navngir rungen som leser det — og ordrens premiss ble FELT før noe
ble bygget på det (F3, 08.09, ordre `20260908T020419Z`):** ordren leste de to nektede live-kallene
(`read_dir('del-ii-bilag-6-teknisk-oppsett')` + `…-oppsett.md`, § 4 i syretesten) som «stien
navngir et dokument som FINNES». **MÅLT mot basen som kjørte** holder rotnivået 27 kataloger
`del-ii-bilag-N-…` og 12 dokumenter `inbox-del-ii-bilag-N-….md`; stien matcher **verken** — den er
katalog-navnekonvensjonen anvendt på et dokument hvis ekte navn bærer et `inbox-`-prefiks. De to
rundene var altså UKJENT-sti-klassen, og **denne leveransen gjenoppretter dem IKKE** (gatet av
`test_the_measured_live_path_is_still_the_unknown_class`); å resolvere dem ville vært å gjette
hvilket dokument en kaller MENTE — invensjon, ikke validering. **Det som ER ekte er asymmetrien:**
`read_file` på en katalog har siden økt 95 svart `DirectoryPathRefused` som navngir `read_dir`,
mens `read_dir` på et dokument svarte den generiske «has no directory» og navnga verken rungen
eller stien. `okf.DocumentPathRefused` lukker den ene retningen: en `ValueError`
(`BundlePathNotFound`/`DimensionScopeRefused`-presedensen) og en **SØSKEN av `BundlePathNotFound`,
aldri en subklasse** — «dette er et dokument» og «dette er ingenting» er ulike fakta, og en kaller
som switcher på det første skal ikke besvares av det andre. Oppslaget bygges av `context_files`,
ALDRI `files` (M2 → 1 rød: en suggestion fra vandringen ville navngitt et `type: verdict`-dokument
ved sti — i en NEKT — altså det ene laget ingen listing nevner og `read_file` avviser blankt), og
gjennom SAMME `in_dimension`-predikat listingen bruker (M3 → 1 rød: §4.1a invertert, å navngi et
dokument kjøringen straks ville nektet å åpne er S2cs «filter i navnet alene»). Dokumentets EKTE
navn siteres, aldri kallerens sti (M5 → 2 røde — `_index_excerpt`-regelen: en sti som aldri fantes
er verre enn ingen sti; armen mater den navngitte stien tilbake til `read_file`). De to grenene
deler BEVISST ingen ordlyd (M4 → 4 røde), og arm (g) i
`test_hierarchical_navigation_loadbearing` er SKREVET OM — ikke svekket — fra `ValueError` til
`BundlePathNotFound` ved navn, fordi begge nekter siterer kallerens sti og et treff på stien alene
ikke lenger kan skille dem. Load-bearing MÅLT (`tests/test_read_dir_wrong_rung_loadbearing.py`,
8 armer), **fem mutasjoner alle røde mot HELE suiten** + grønn kontroll **1511/5** og golden
BYTE-UENDRET (`shasum -a 1` av INNHOLDET = `ea8c534773acdbe41ae68f2c55724d69aaf8be4f`): M1 detach
dokument-grenen (4) · M2 bygg fra `files` (1) · M3 ignorer dimensjonen (1) · M4 kollaps de to
grenenes ordlyd (4) · M5 ekko kallerens sti (2). **Ærlighets-grenser, uttalt:** ingen LEVENDE
modell har lest den nye nekten, så at den endrer neste trekk er ikke bevist
(structured-output-grensens klasse); `read_dir`s verktøybeskrivelse er URØRT — den påstår
ingenting usant, og en utvidelse ville vært prosa uten en gate. Måling:
`docs/2026-09-08-f3-f4-nekten-og-forankringen.md` § 1.
- **En kjøring KAN kreve at gaten er forankret, og nekten koster ingenting (F4, 08.09, samme
ordre):** MÅLT på nytt mot de fire artefaktene den betalte S7-kjøringen etterlot: alle armer
stemplet `cost_baseline_anchored: False`, stage 0 ble hoppet over, og hver arm fant på kodene sine
(`ENGRAVE_MARK` · `RITB-HOURS`/`SYSINT-HOURS` · `RITB-consultancy`/`system-integrator` ·
`Material_Cost_Concrete`/`…_Steel`/`Construction_Heating_Fuel`). **Ordrens opsjon (c) er FELT AV
MÅLING:** `derive_cost_baseline` mot den leverte basen nekter — K2s prisskjema er en pandoc SIMPLE
table med ÉN kolonneoverskrift (`Prisskjema`) og hver verdi kollapset i den, så å gjøre
`--derive-cost-baseline` nåbart der ville krevd en regel for en form ingen har målt, som er
MAJOR-4s egen ærlighets-grense. **Valgt (b), ikke (a):** synligheten (`cost_baseline_notice`)
finnes og er ærlig, men den kan ikke stoppe et maskinlesbart artefakt som sier
`validator_decision: validated` over linjer ingenting forankret. `--require-cost-baseline` /
`run_project(require_cost_baseline=…)` er **OPT-IN, aldri default** — hver base uten
`cost-baseline.json`, altså hver commons-eid golden, kjører uendret, som er dét (a) beskyttet
(M12, gaten fyrer uansett flagg → **171 røde**). `UnanchoredRunRefused` er en `ValueError`
(`BundleIdMismatch`/`CostBaselineDerivationError`-presedensen): en argv som tar feil om hva denne
basen kan tilby hører på CLI-ens nekt-tuppel og hostings 400-arm, aldri krasj-kanalen. **Gaten bor
ÉTT sted — der BEGGE grener har bundet `baseline`, og OVER dry-run-kuttet** (M7 → 1 rød): den
frie turen er den billigste å lære det på, og på den betalte fyrer den før første modellkall ved
konstruksjon — armen asserterer NULL kall, aldri bare unntaket, fordi en nekt etter forbruket ser
identisk ut ved exit-koden (økt 57s regel). Tre CLI-nekter, hver med en rc-0-kontroll på en argv
som ellers ville blitt AKSEPTERT: krever `--bundle-dir` (veg-stien er forankret ved konstruksjon,
så der kunne flagget aldri fyre — et flagg som ikke kan fyre er en påstand flaten gjør om seg
selv, Fase-3-klassen), refusert i `--portfolio` VED NAVN (M9 → 1 rød med egen signatur: uten den
STARTER mutanten et porteføljepass og når modellen med `ChatClientException`, altså er nekten dét
som holder argv-en fra å koste noe) og i `report_forbidden` (report-modus returnerer OVER hver
dispatch, så en utelatelse er et stille DROPP — F4-gapets egen klasse). Load-bearing MÅLT
(`tests/test_require_cost_baseline_loadbearing.py`, 10 armer), **sju mutasjoner alle røde mot HELE
suiten** + samme grønne kontroll og golden: M6 detach gaten (3) · M7 gaten under dry-run-kuttet
(1) · M8 dropp fra `report_forbidden` (1) · M9 dropp fra portefølje-partisjonen (1) · M10 detach
`--bundle-dir`-kravet (1) · M11 detach fullkjørings-wiringen (1) · M12 gaten fyrer uansett flagg
(171). **Ærlighets-grenser, uttalt:** den hostede flaten er BEVISST urørt (feltet er i ingen av
hostings tre sett, så den generiske 400-en svarer og Fase 4es to halvdeler står — MAJOR-4s eget
valg gjentatt); stempelet sier fortsatt AT en kjøring var forankret, aldri HVILKEN av de tre
projeksjonene som forankret den; og ingen betalt kjøring er gjort — begge funn er målt offline mot
basen og artefaktene økt 98 etterlot. Måling:
`docs/2026-09-08-f3-f4-nekten-og-forankringen.md` § 2.
- **STATE.md er local-only** (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
- Prosess: Voyage-plugin (`/trekbrief → /trekplan → /trekexecute → /trekreview`) per større fase.