fix(profiles): P1-F1 — a permitted root key is not a required one
`IndexPolicy.root_frontmatter` carried two claims at once: which keys the root index may carry and in what order, and which it must carry. The judge read it as the second, so `OKF_V0_2` — which names `okf_version` only to fix its position, upstream granting it as a MAY (§8:509-510, §12:773-775) — reported every bundle exercising that MAY as violating. Measured in P1 over 17 bundles: 14 failed with exactly this one violation, upstream's own four reference bundles among them, while D5's emitter treated the same key as optional. We emitted a MAY correctly and graded it a MUST. `root_frontmatter` now permits and orders; `root_frontmatter_required` demands. A required key outside the ordered set fails at construction, the same contradiction `FrontmatterSchema` already refuses when `required` strays outside `allowed`. - `OKF_V0_2` requires none — upstream's MAY, stated on the judge side too. - `STRICT_V1` requires all three: the proving consumer's root index carries exactly those keys in that order on every bundle measured (`c5141f8`), so separating the meanings costs them nothing. - `DEFAULT` names no root keys and is untouched. Re-measured over the eight bundle-root indexes reachable locally (our four goldens, upstream's four): 7 of 8 failing under the old semantics, 0 of 8 after. Emit path byte-identical — the golden suite fails otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqCmfJ2ukpFXeFjfab8wvy
This commit is contained in:
parent
a8ceea9667
commit
2c01e58259
2 changed files with 85 additions and 5 deletions
|
|
@ -375,9 +375,18 @@ class IndexPolicy:
|
|||
caller supplies the listing, and a policy that does not judge it refuses
|
||||
to be handed one at all — so code written to the wrong contract fails at
|
||||
the call rather than passing every test one would think to write.
|
||||
- `root_frontmatter` — the ordered keys the ROOT index carries, where
|
||||
- `root_frontmatter` — the ordered keys the ROOT index may carry, where
|
||||
nested indexes carry none. Confirmed independently in both consumers, so
|
||||
the asymmetry is shape rather than one repo's preference.
|
||||
the asymmetry is shape rather than one repo's preference. Naming a key
|
||||
here PERMITS it and fixes its position; it does not demand it.
|
||||
- `root_frontmatter_required` — the subset that must actually be present.
|
||||
Separate from the tuple above because the two are different claims, and
|
||||
collapsing them was P1-F1: `OKF_V0_2` names `okf_version` to fix its
|
||||
position, but upstream §8/§12 grant it as a MAY, and judging the pair as
|
||||
one field failed 14 of 17 real bundles — upstream's own four included —
|
||||
each with exactly this one violation, while our emitter treated the same
|
||||
key as optional. A profile whose consumer really does demand the keys
|
||||
says so here, which is what `STRICT_V1` does.
|
||||
"""
|
||||
|
||||
name: str
|
||||
|
|
@ -388,8 +397,16 @@ class IndexPolicy:
|
|||
allows_prose: bool = True
|
||||
entries_match_directory: bool = False
|
||||
root_frontmatter: tuple[str, ...] = ()
|
||||
root_frontmatter_required: frozenset[str] = field(default_factory=frozenset)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
stray = sorted(self.root_frontmatter_required - set(self.root_frontmatter))
|
||||
if stray:
|
||||
raise ValueError(
|
||||
f"required root keys must be named in the ordered set "
|
||||
f"(got {', '.join(repr(key) for key in stray)}) — a key demanded "
|
||||
"but never named could not be judged for position"
|
||||
)
|
||||
if self.requires_description and "description" not in self.link_pattern.groupindex:
|
||||
raise ValueError(
|
||||
"link_template names {description} but link_pattern has no "
|
||||
|
|
@ -526,7 +543,7 @@ class IndexPolicy:
|
|||
found = [
|
||||
IndexViolation(key, "is pinned on the root index and absent", "index_root_key_missing")
|
||||
for key in self.root_frontmatter
|
||||
if key not in head
|
||||
if key in self.root_frontmatter_required and key not in head
|
||||
]
|
||||
present = [key for key in head if key in self.root_frontmatter]
|
||||
declared = [key for key in self.root_frontmatter if key in head]
|
||||
|
|
@ -646,6 +663,11 @@ STRICT_V1 = BundleProfile(
|
|||
allows_prose=False,
|
||||
entries_match_directory=True,
|
||||
root_frontmatter=("okf_version", "bundle_profile", "okf_spec_commit"),
|
||||
# The proving consumer demands all three, not merely permits them: their
|
||||
# root index carries exactly these keys in exactly this order on every
|
||||
# bundle measured (`c5141f8`). Stated explicitly so that separating
|
||||
# "permitted" from "required" costs them nothing.
|
||||
root_frontmatter_required=frozenset({"okf_version", "bundle_profile", "okf_spec_commit"}),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -700,7 +722,11 @@ _OKF_V0_2_KEY_ORDER = (
|
|||
#
|
||||
# Declaring it stays a MAY: none of upstream's four reference bundles carries
|
||||
# the key at all (catalog grepped `okf/bundles` and `okf/samples`: zero hits),
|
||||
# so omitting `root_frontmatter_values` emits no block.
|
||||
# so omitting `root_frontmatter_values` emits no block. The JUDGE says the same
|
||||
# thing since P1-F1 — the key is named here and left out of
|
||||
# `root_frontmatter_required` — because for one release it did not, and a
|
||||
# bundle exercising the MAY was reported as violating by the very profile that
|
||||
# had emitted it correctly.
|
||||
#
|
||||
# **Measured limitation (guard 0.2.0, 2026-07-26):** a bundle emitted under this
|
||||
# profile cannot be read back through a guard-gated import. The guard's T2
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import re
|
|||
|
||||
import pytest
|
||||
|
||||
from llm_ingestion_okf.profiles import DEFAULT, STRICT_V1, IndexPolicy
|
||||
from llm_ingestion_okf.profiles import DEFAULT, OKF_V0_2, STRICT_V1, IndexPolicy
|
||||
|
||||
# --- DEFAULT keeps the Phase 1/2 shape (assumption C1) --------------------
|
||||
|
||||
|
|
@ -238,6 +238,60 @@ def test_the_pinned_keys_are_ordered_not_merely_present() -> None:
|
|||
assert [v.code for v in found] == ["index_root_key_order"]
|
||||
|
||||
|
||||
# --- P1-F1: permitted-and-ordered is not the same claim as required -------
|
||||
|
||||
|
||||
def test_a_permitted_root_key_may_be_omitted_without_a_finding() -> None:
|
||||
"""P1-F1, measured 2026-07-31: `OKF_V0_2` names `okf_version` because
|
||||
upstream §8/§12 say a bundle-root index MAY carry it — a permission, not a
|
||||
demand. Judging the key as required made 14 of the 17 bundles swept fail
|
||||
with exactly this one violation, upstream's own four reference bundles
|
||||
among them, while D5's emitter treats the same key as optional (omitting
|
||||
`root_frontmatter_values` writes no block). We emitted a MAY correctly and
|
||||
graded it a MUST."""
|
||||
text = "# Bundle\n\n- [Sales](ingest-sales.md)\n"
|
||||
assert OKF_V0_2.index.violations(text, is_root=True) == ()
|
||||
|
||||
|
||||
def test_the_permitted_key_is_still_ordered_and_still_root_only() -> None:
|
||||
"""Separating the two meanings must not cost the checks that were right:
|
||||
a nested index carrying the key is still a finding, and a root index that
|
||||
carries it is still judged for order."""
|
||||
nested = OKF_V0_2.index.violations("---\nokf_version: 0.2\n---\n\n# Concepts\n")
|
||||
assert [(v.subject, v.code) for v in nested] == [
|
||||
("okf_version", "index_root_frontmatter_unexpected")
|
||||
]
|
||||
root = OKF_V0_2.index.violations("---\nokf_version: 0.2\n---\n\n# Bundle\n", is_root=True)
|
||||
assert root == ()
|
||||
|
||||
|
||||
def test_a_required_root_key_is_still_reported_when_absent() -> None:
|
||||
"""`STRICT_V1` mirrors the proving consumer's ratified contract, which
|
||||
demands all three (they emit all three — measured across their bundle at
|
||||
`c5141f8`). The fix separates the meanings; it does not relax them."""
|
||||
found = STRICT_V1.index.violations(
|
||||
"---\nokf_version: 0.1\n---\n\n# Wiki\n", is_root=True, expected_targets=set()
|
||||
)
|
||||
assert [(v.subject, v.code) for v in found] == [
|
||||
("bundle_profile", "index_root_key_missing"),
|
||||
("okf_spec_commit", "index_root_key_missing"),
|
||||
]
|
||||
|
||||
|
||||
def test_a_required_key_outside_the_ordered_set_fails_at_construction() -> None:
|
||||
"""A key demanded but never named in the ordered tuple could never be
|
||||
judged for position — the same contradiction `FrontmatterSchema` refuses
|
||||
when `required` strays outside `allowed`."""
|
||||
with pytest.raises(ValueError, match="ordered"):
|
||||
IndexPolicy(
|
||||
name="index.md",
|
||||
link_template="- [{label}]({target})",
|
||||
link_pattern=DEFAULT.index.link_pattern,
|
||||
root_frontmatter=("okf_version",),
|
||||
root_frontmatter_required=frozenset({"bundle_profile"}),
|
||||
)
|
||||
|
||||
|
||||
def test_a_nested_index_carries_no_frontmatter_at_all() -> None:
|
||||
"""Confirmed independently in two consumers: the wiki writes frontmatter
|
||||
only at the bundle root (`bundle.py:544-557`), and the catalog reports
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue