portfolio-optimiser-claude/src/portfolio_optimiser_claude
Kjell Tore Guttormsen d691510163 fix(okf): nested frontmatter keys must not forge the type that gates verdict exclusion
The frontmatter parser is a line-oriented approximation of YAML with no nesting
model, and it flattened INDENTED keys into the same mapping. Two measured
defects followed, the second load-bearing:

1. Sibling blocks sharing an inner key COLLIDED. Given OKF §10's canonical
   Attested Computation shape, `executor.resource` vanished silently and
   `attester.resource` was promoted to a top-level `resource`. No error raised.

2. An indented `type:` OVERWROTE the column-0 one, making the verdict-exclusion
   gate in `bundle_context` forgeable. A file declaring `type: verdict` at
   column 0, carrying any nested block with a `type:` in it, rendered its body
   straight into the read-context — defeating the §11 seam whose own docstring
   claimed "a mislabelled or injected edge cannot smuggle a verdict into the
   context". The type CHECK was there; the VALUE it checked was writable.

This is spec-legal input, not malformed input: method-spec §2 calls it YAML
frontmatter, and ingest-spec §7 (`:153`, `:216`) says unknown keys MAY follow
the stamp and ride through navigation.

Fix: only column-0 keys participate; indented lines are skipped, never
flattened. Nested blocks become OPAQUE — stated as a limitation in the
docstring, not dressed up as a nesting model we do not have (§1 honesty rule).

Single parse site, so the fix covers the class: `hitl.py:157` (verdict_id,
gated on .type) and `experience.py:137` (realization_rate, expected_actual)
all read through `ConceptFile.frontmatter`; `promotion.py` only emits, from a
fixed template.

Golden-neutral by measurement: 0 indented frontmatter lines across all 17
frontmatter-bearing files in `shared/examples/`.

Both tests written RED first and confirmed to die on the SEAM assertion, not on
a collateral one — the forgery test rendered `## project: Seed` before the fix.
Each negative carries a standalone positive control (sessions 17-18: a control
can itself hide behind a preceding assert).

Provenance: hypothesis received from llm-ingestion-okf, who measured the
flattening in their own parser and flagged that ours shares the form. They had
NOT measured our side. The type-clobber variant is ours, found by measuring the
neighbourhood rather than only the reported case.

Suite 688 -> 690.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJmse16bEkaSBtvXhncEUc
2026-08-01 19:53:03 +02:00
..
data feat(portfolio): K6 — pre-run cost simulation, priced what-if (parity row 18) [skip-docs] 2026-07-23 23:01:01 +02:00
__init__.py feat(scaffold): S5 — D7 sibling scaffold: SDK dep, fail-fast startup contracts, CLAUDE.md 2026-07-03 06:06:03 +02:00
artifacts.py fix(run): S10 del 2 — post-mortem: stopp-artefakt, SDK-isolasjon, raw-JSON-direktiv 2026-07-03 10:49:51 +02:00
budget.py feat(portfolio): C3.5 — pre-call run-total USD budget belt (parity row 16/31) [skip-docs] 2026-07-23 22:45:37 +02:00
contracts.py feat(portfolio): K6 — pre-run cost simulation, priced what-if (parity row 18) [skip-docs] 2026-07-23 23:01:01 +02:00
costsim.py feat(portfolio): K6 — pre-run cost simulation, priced what-if (parity row 18) [skip-docs] 2026-07-23 23:01:01 +02:00
experience.py feat(learning): S9 — D7 læringssløyfe: verdict-inbox, fail-closed promoteringsgate, artefakt-sourced persona 2026-07-03 07:36:15 +02:00
goals.py feat(ledger): K1 — savings ledger + goal contract (parity rows 2-3) 2026-07-17 04:00:14 +02:00
hitl.py feat(portfolio): K10 — notification/notifier seam, opt-in webhook egress (parity row 23) [skip-docs] 2026-07-24 20:16:56 +02:00
inbox.py feat(ingest): adopt llm-ingestion-okf as Door A implementation (first consumer) 2026-07-16 20:46:51 +02:00
ingest.py feat(ingest): adopt llm-ingestion-okf as Door A implementation (first consumer) 2026-07-16 20:46:51 +02:00
ir.py fix(validator): C2.6 — finiteness hardening, Infinity can no longer vacuously clear the gate (closes R-2) 2026-07-16 20:10:12 +02:00
ledger.py fix(ledger): normalize every load rejection to ValueError at the ledger's own entrance 2026-07-25 15:29:27 +02:00
loop.py feat(portfolio): C3.5 — pre-call run-total USD budget belt (parity row 16/31) [skip-docs] 2026-07-23 22:45:37 +02:00
notify.py feat(portfolio): K10 — notification/notifier seam, opt-in webhook egress (parity row 23) [skip-docs] 2026-07-24 20:16:56 +02:00
okf.py fix(okf): nested frontmatter keys must not forge the type that gates verdict exclusion 2026-08-01 19:53:03 +02:00
outbox.py feat(portfolio): K5 — outbox persistence, run_id-named pairs (parity row 7) [skip-docs] 2026-07-23 22:31:18 +02:00
persona.py feat(learning): S9 — D7 læringssløyfe: verdict-inbox, fail-closed promoteringsgate, artefakt-sourced persona 2026-07-03 07:36:15 +02:00
portfolio.py feat(portfolio): K11 — per-run value report, pure projection over the three layers (parity row 25) [skip-docs] 2026-07-25 06:25:02 +02:00
preflight.py feat(portfolio): K7 — SDK/API preflight, offline pre-spend boundary (parity row 20) [skip-docs] 2026-07-24 01:34:18 +02:00
promotion.py feat(inbox): C2.5 — inbox hardening + SDK version guard (closes C-F7, C-N3, R-6) 2026-07-16 20:26:41 +02:00
provenance.py feat(portfolio): stamp the producing SDK build in provenance (wiki-advisory F1) [skip-docs] 2026-07-25 06:57:30 +02:00
run.py feat(run): stamp the drill's SDK build in the dry-run run-config [skip-docs] 2026-07-25 15:35:16 +02:00
run_s10.py fix(run): S10 del 2 — post-mortem: stopp-artefakt, SDK-isolasjon, raw-JSON-direktiv 2026-07-03 10:49:51 +02:00
sdk_client.py feat(portfolio): stamp the producing SDK build in provenance (wiki-advisory F1) [skip-docs] 2026-07-25 06:57:30 +02:00
simulation.py feat(simulation): K4 — closed-loop two-run simulation binds §11 'Closed loop' (closes R-1) 2026-07-17 03:47:05 +02:00
validator.py feat(validator): S6 — deterministic backbone: typed IR, golden-frozen validator, provenance stamp 2026-07-03 06:27:40 +02:00
valuereport.py test(valuereport): bind _SHARE_DIGITS to its MEASURED band, not to itself 2026-07-26 15:28:20 +02:00