fix(okf): one unquoting rule for frontmatter scalars (kø-(a)/(i))
Two rules existed. verdicts._unquote took quotes off correctly; the
bundle_context title renderer stripped only `"`. Measured before the fix:
`title: " Spaced "` rendered as `## concept: Spaced ` (the whitespace
half kø-(a) named), and `title: 'Single'` rendered its quotes verbatim —
both ordinary YAML a hand-authoring curator writes, and both reach the
agent's read-context.
The (p) defect class: a duplicated conversion drifts, and the drifted copy
decides something. Same fix shape — ONE source, owned by the module that
owns parse_frontmatter. okf.unquote_scalar is now the rule; verdicts
delegates by identity, so the structural key that _mint_id hashes is
unchanged.
The commons-owned nav-goldens could never have caught this: every golden
title is double-quoted with no inner whitespace, so both rules render them
byte-identically. That is asserted as a control, and it goes RED if a
future golden gains a discriminating title.
Load-bearing MEASURED against the whole suite, three mutations:
- weaken the renderer back to .strip('"') -> ONLY the 2 new rows red,
643 others green (incl. nav-goldens) = it covers ground nothing did
- reintroduce a private _unquote copy in verdicts -> only the identity
test red, 644 green (the copy is behaviourally identical TODAY, which
is exactly why identity is the only thing that catches the class)
- (control, kø-(n)) add `import numpy` to a second src module -> the
existing test_semretrieval_is_the_sole_numpy_importer goes red alone,
confirming that gate is live rather than green-but-dead
638 -> 645 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DVMsih6tXx39VMyq6wJ5H7
This commit is contained in:
parent
1515a474d6
commit
fbc7a6120a
3 changed files with 130 additions and 5 deletions
|
|
@ -41,6 +41,20 @@ _COST_BASELINE = "cost-baseline.json"
|
|||
_LINK_RE = re.compile(r"\]\(([^)]+\.md)\)")
|
||||
|
||||
|
||||
def unquote_scalar(raw: str) -> str:
|
||||
"""The ONE unquoting rule for a frontmatter scalar — ``parse_frontmatter`` preserves quotes
|
||||
(OKF SPEC §4), so every consumer of a scalar has to take them off, and they must all take them
|
||||
off the SAME way.
|
||||
|
||||
It lives here because this module owns ``parse_frontmatter``. It was a private copy in
|
||||
``verdicts`` (structural key) while ``bundle_context`` stripped only ``"`` (rendered title) —
|
||||
two rules, and the weaker one in the path that becomes the agent's read-context. Both quote
|
||||
styles, and whitespace on either side of them, are ordinary YAML a hand-authoring curator
|
||||
writes. Mirrors the (p) precedent: a duplicated conversion drifts, and the drifted copy decides
|
||||
something. Gated by ``tests/test_frontmatter_unquote_loadbearing.py``."""
|
||||
return raw.strip().strip('"').strip("'").strip()
|
||||
|
||||
|
||||
def parse_frontmatter(path: str | Path) -> dict[str, str]:
|
||||
"""Read the leading ``---``-delimited YAML frontmatter block as key:value strings.
|
||||
|
||||
|
|
@ -213,7 +227,7 @@ def bundle_context(bundle: Bundle, *, dimension: str | None = None) -> str:
|
|||
file_dim = f.frontmatter.get("dimension")
|
||||
if file_dim is not None and file_dim != dimension:
|
||||
continue
|
||||
title = f.frontmatter.get("title", f.name).strip('"')
|
||||
title = unquote_scalar(f.frontmatter.get("title", f.name))
|
||||
body = f.body.strip("\n")
|
||||
sections.append(f"## {f.type or 'document'}: {title}\n\n{body}")
|
||||
return "\n\n".join(s for s in sections if s.strip())
|
||||
|
|
|
|||
|
|
@ -488,10 +488,12 @@ class VerdictFrontmatterError(ValueError):
|
|||
malformed files because anyone may drop anything there."""
|
||||
|
||||
|
||||
def _unquote(raw: str) -> str:
|
||||
"""``parse_frontmatter`` preserves quotes (OKF SPEC §4); the structural fields are compared and
|
||||
hashed against the IR projection's RAW JSON values, so the quotes have to come off here."""
|
||||
return raw.strip().strip('"').strip("'").strip()
|
||||
# ``parse_frontmatter`` preserves quotes (OKF SPEC §4); the structural fields are compared and
|
||||
# hashed against the IR projection's RAW JSON values, so the quotes have to come off. DELEGATED,
|
||||
# not copied: this was a private implementation while ``okf.bundle_context`` stripped only ``"``,
|
||||
# and a duplicated conversion drifts (the (p) precedent). ``okf`` owns ``parse_frontmatter``, so it
|
||||
# owns the unquoting rule. Gated by ``tests/test_frontmatter_unquote_loadbearing.py``.
|
||||
_unquote = okf.unquote_scalar
|
||||
|
||||
|
||||
def _parse_affected_codes(raw: str) -> frozenset[str]:
|
||||
|
|
|
|||
109
tests/test_frontmatter_unquote_loadbearing.py
Normal file
109
tests/test_frontmatter_unquote_loadbearing.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
"""Kø-(a)/(i) — ONE unquoting rule for frontmatter scalars, and it renders the read-context.
|
||||
|
||||
Two rules existed. ``verdicts._unquote`` (``.strip().strip('"').strip("'").strip()``) unquoted the
|
||||
structural key correctly; ``okf.bundle_context`` unquoted a title with a bare ``.strip('"')``. The
|
||||
weaker one sat in the path that becomes the agent's read-context and that the commons-owned
|
||||
nav-goldens byte-compare. Measured before the fix:
|
||||
|
||||
title: " Spaced " -> ``## concept: Spaced `` (the whitespace half — what (a) named)
|
||||
title: 'Single' -> ``## concept: 'Single'`` (single quotes rendered verbatim)
|
||||
|
||||
Both are frontmatter YAML writes any hand-authoring curator may make, and both reach the model.
|
||||
This is the (p) defect class — two copies of one conversion, the drifted copy where it matters —
|
||||
so the fix is the same shape: ONE source, owned by the module that owns ``parse_frontmatter``.
|
||||
|
||||
Detach points, each its own test: weaken the renderer back to ``.strip('"')`` -> RED; reintroduce
|
||||
a private copy in ``verdicts`` -> RED. The control proves the gate covers ground the nav-goldens
|
||||
do not: every golden title is double-quoted with no inner whitespace, so the goldens stay byte-
|
||||
identical under BOTH rules and can never have caught this.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from portfolio_optimiser import okf
|
||||
from portfolio_optimiser import verdicts as verdicts_mod
|
||||
|
||||
_GOLDEN_ROOT = Path(__file__).resolve().parents[1] / "shared" / "examples"
|
||||
|
||||
|
||||
def _bundle(tmp_path: Path, title_line: str) -> str:
|
||||
"""A minimal two-file bundle whose single concept file carries ``title_line`` verbatim."""
|
||||
(tmp_path / "index.md").write_text(
|
||||
'---\ntype: index\ntitle: "Root"\n---\n\nSummary.\n\n- [A](a.md)\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
(tmp_path / "a.md").write_text(
|
||||
f"---\ntype: concept\n{title_line}\n---\n\nbody a\n", encoding="utf-8"
|
||||
)
|
||||
return str(tmp_path)
|
||||
|
||||
|
||||
def _heading(bundle_dir: str) -> str:
|
||||
ctx = okf.bundle_context(okf.navigate_bundle(bundle_dir))
|
||||
return next(line for line in ctx.splitlines() if line.startswith("## "))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("title_line", "expected"),
|
||||
[
|
||||
('title: " Spaced "', "## concept: Spaced"),
|
||||
("title: 'Single'", "## concept: Single"),
|
||||
('title: "Plain"', "## concept: Plain"),
|
||||
("title: Bare", "## concept: Bare"),
|
||||
],
|
||||
)
|
||||
def test_rendered_title_is_unquoted_and_trimmed(
|
||||
tmp_path: Path, title_line: str, expected: str
|
||||
) -> None:
|
||||
"""The rendered heading carries the TITLE, not the frontmatter's quoting artifacts.
|
||||
|
||||
Detach point: revert ``bundle_context`` to ``.strip('"')`` -> the first two rows go RED
|
||||
(``## concept: Spaced `` and ``## concept: 'Single'``). The last two rows are the control
|
||||
that the stronger rule did not change the cases that already worked."""
|
||||
assert _heading(_bundle(tmp_path, title_line)) == expected
|
||||
|
||||
|
||||
def test_frontmatter_unquoting_has_exactly_one_source() -> None:
|
||||
"""``verdicts`` must DELEGATE, not carry its own copy — the (p) rule applied to unquoting.
|
||||
|
||||
Identity, not equal behaviour: two implementations that agree today are exactly the state
|
||||
this defect class starts from. Reintroduce a private copy in ``verdicts`` -> RED."""
|
||||
assert verdicts_mod._unquote is okf.unquote_scalar, (
|
||||
"verdicts must delegate to okf.unquote_scalar; a private copy is the defect class"
|
||||
)
|
||||
|
||||
|
||||
def test_verdict_structural_key_still_unquotes_after_delegation() -> None:
|
||||
"""The delegation must be behaviour-identical on the structural key, because ``_mint_id``
|
||||
hashes these values — a normalising change would re-key every promoted verdict.
|
||||
|
||||
Not a tautology against the source: the expected values are written out longhand here."""
|
||||
assert okf.unquote_scalar('" Spaced "') == "Spaced"
|
||||
assert okf.unquote_scalar("'Single'") == "Single"
|
||||
assert okf.unquote_scalar(" bare ") == "bare"
|
||||
assert okf.unquote_scalar('"18000"') == "18000"
|
||||
|
||||
|
||||
def test_nav_goldens_cannot_have_caught_this() -> None:
|
||||
"""CONTROL — the commons-owned byte-level fasit is blind to this defect by construction.
|
||||
|
||||
Every golden title is double-quoted with no inner whitespace, so ``.strip('"')`` and
|
||||
``unquote_scalar`` render byte-identically there. If a future golden gains a single-quoted or
|
||||
space-padded title this test goes RED and says so: the fasit would then be load-bearing for
|
||||
the rule, and this file's claim to cover uncovered ground would no longer hold."""
|
||||
titles = [
|
||||
line.split(":", 1)[1].strip()
|
||||
for path in sorted(_GOLDEN_ROOT.glob("nav-golden-*/**/*.md"))
|
||||
for line in path.read_text(encoding="utf-8").splitlines()
|
||||
if line.startswith("title:")
|
||||
]
|
||||
assert titles, "no golden titles found — the control has lost its subject"
|
||||
for raw in titles:
|
||||
assert raw.strip('"') == okf.unquote_scalar(raw), (
|
||||
f"golden title {raw!r} now discriminates between the two rules — the nav-goldens "
|
||||
"have become load-bearing for unquoting, so this control must be re-stated"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue