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
This commit is contained in:
Kjell Tore Guttormsen 2026-08-01 19:53:03 +02:00
commit d691510163
2 changed files with 77 additions and 1 deletions

View file

@ -56,6 +56,23 @@ def _parse_frontmatter_and_body(path: Path) -> tuple[dict[str, str], str]:
Unknown fields are preserved as strings. The ``type`` requirement is NOT applied
here it belongs to concept files, not to the index entry point.
ONLY COLUMN-0 KEYS participate. This parser is a line-oriented approximation of
YAML, not YAML: it has no nesting model, so an INDENTED line is skipped rather
than flattened into the same mapping. Nested block mappings are therefore OPAQUE
their inner keys are not readable here, and this is a stated limitation, not a
representation (§1 honesty rule). Frontmatter is spec-legal YAML (method-spec §2)
and MAY carry unknown structured keys that ride through navigation (ingest-spec
§7), so nested blocks are expected input, not malformed input.
Flattening was a defect on two counts, both measured. Sibling blocks sharing an
inner key COLLIDED ``executor.resource`` vanished silently while
``attester.resource`` was promoted to a top-level ``resource`` (OKF §10 Attested
Computation is exactly this shape). Worse, an indented ``type:`` overwrote the
column-0 one, making the verdict-exclusion gate in ``bundle_context`` FORGEABLE:
a file declaring ``type: verdict`` rendered into the read-context. The gate is
load-bearing (§11), so the value it reads must not be attacker- or
accident-writable from a nested block.
"""
lines = path.read_text(encoding="utf-8").splitlines()
if not lines or lines[0].strip() != "---":
@ -66,6 +83,8 @@ def _parse_frontmatter_and_body(path: Path) -> tuple[dict[str, str], str]:
if line.strip() == "---":
body_start = i + 1
break
if line[:1].isspace():
continue
key, sep, value = line.partition(":")
if sep:
frontmatter[key.strip()] = _strip_matching_quotes(value.strip())

View file

@ -17,7 +17,12 @@ from pathlib import Path
import pytest
from portfolio_optimiser_claude.okf import ConceptFile, bundle_context, navigate_bundle
from portfolio_optimiser_claude.okf import (
ConceptFile,
bundle_context,
navigate_bundle,
parse_concept_file,
)
BUNDLE = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"
NAV_GOLDENS = Path(__file__).resolve().parents[1] / "shared" / "examples"
@ -322,6 +327,58 @@ class TestVerdictLayerExclusion:
assert "## verdict" not in context
assert "## project: A" in context
def test_nested_block_cannot_forge_the_type_that_gates_exclusion(self, tmp_path: Path) -> None:
# The exclusion in `bundle_context` gates on `.type`, and `.type` is whatever
# the frontmatter parse produced. A line-oriented parse that lets an INDENTED
# `type:` reach the same dict makes the gate forgeable: the file still declares
# `type: verdict` at column 0, but a later nested key overwrites it and the
# verdict body renders. Spec-legal input — method-spec §2 calls this YAML
# frontmatter, and ingest-spec §7 (`:153`, `:216`) says unknown keys MAY follow
# and ride through navigation.
# RED if nested keys participate in the frontmatter mapping.
marker = "NESTED-FORGERY-MARKER-0.91"
bundle = _make_bundle(
tmp_path,
"Summary. See [v](v.md) and [a](a.md).",
{
"v.md": (
"---\ntype: verdict\ntitle: Seed\n"
"provenance:\n type: project\n---\n"
f"Signal: {marker}."
),
"a.md": "---\ntype: project\ntitle: A\n---\nBody A.",
},
)
# Positive control #1 — the file IS navigated, so a marker-absence below is the
# exclusion doing work, not an unreachable file. Stands alone, behind no other
# assert (session 17: a control can itself hide behind a preceding assertion).
assert "v.md" in [c.path.name for c in navigate_bundle(bundle)]
context = bundle_context(bundle)
# Positive control #2 — a rendered concept proves the context is non-empty and
# that `## {type}: {title}` is EXACTLY the form the negatives search for.
assert "## project: A" in context
assert marker not in context
assert "## project: Seed" not in context
def test_nested_block_does_not_collide_with_a_top_level_key(self, tmp_path: Path) -> None:
# Two sibling blocks sharing an inner key (OKF §10 Attested Computation:
# `executor.resource` + `attester.resource`) must not collapse into one
# top-level `resource`, silently losing the first and promoting the second.
# RED if indented keys land in the frontmatter mapping.
_write(
tmp_path / "c.md",
"---\ntype: project\ntitle: C\nresource: top-level-value\n"
"executor:\n resource: references/skills/run-on-bq.md\n"
"attester:\n resource: references/attesters/revenue.py\n---\nBody C.",
)
frontmatter = parse_concept_file(tmp_path / "c.md").frontmatter
# Positive control — a COLUMN-0 key of the very name under test survives the
# parse. Without it, "the nested value is absent" would also hold for a parser
# that dropped `resource` entirely.
assert frontmatter["resource"] == "top-level-value"
assert "references/attesters/revenue.py" not in frontmatter.values()
assert "references/skills/run-on-bq.md" not in frontmatter.values()
def test_shared_bundle_context_carries_no_realization_signal(self) -> None:
# The seed verdict's learning signal (realization rate 0.82, expected
# actual 24 600 NOK) must be absent from the rendered context — it may