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:
parent
30ba68a703
commit
d691510163
2 changed files with 77 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue