feat(phase-3): the index policy becomes configurable, with the reader that judges it
`IndexPolicy` grew five judging fields and `IndexPolicy.violations`, closing the
gap a test has been pinning since `848e395`. `STRICT_V1.index` is now the proving
consumer's shape rather than DEFAULT's.
The design was settled by a conflict rather than by a preference. The convention
owner reported that an index is an authored count of a directory's children and
that a validator enumerating the directory has implemented the wrong contract.
Checked against the proving consumer before adopting it, the two turn out to be
directly opposed: gate BUNDLE_INDEX_COMPLETE (validate.py:1081-1120, ERROR) builds
its expected set by enumerating the directory and demands an exact bidirectional
match, and bundle.py:498-567 writes every index from a tree walk. Neither is
incoherent inside its own spec, so authored-versus-derived is a policy field in
both directions and a library invariant in neither.
Consequences encoded rather than documented: nothing here enumerates a directory
— the caller supplies the listing, `violations` refuses one when the profile's
index is authored and refuses to run without one when it is derived, so code
written to the wrong contract fails at the call instead of passing every test one
would think to write. Root and nested indexes are asymmetric (confirmed in both
consumers, different key sets). A per-entry description is template-level, so
`render_link` refuses both a missing description and an unwanted one.
DEFAULT keeps upstream's root-only index and judges nothing, for the same measured
reason it carries no required frontmatter key set: upstream binds `index.md` to
the bundle root alone, so a judging default would condemn conforming bundles.
25 new tests, 493 green. C1 re-proven: `git diff --stat examples/` empty.
This commit is contained in:
parent
848e3953fc
commit
9436427520
6 changed files with 626 additions and 29 deletions
|
|
@ -307,19 +307,60 @@ def test_strict_v1_path_namespaces_are_the_spec_namespaces() -> None:
|
|||
assert STRICT_V1.paths == DEFAULT.paths
|
||||
|
||||
|
||||
def test_strict_v1_index_policy_is_still_the_default_shape() -> None:
|
||||
"""A known gap, pinned so it cannot drift silently.
|
||||
def test_strict_v1_index_policy_is_the_consumers_shape() -> None:
|
||||
"""The gap this test used to pin is closed; it now states the shape.
|
||||
|
||||
The consumer's index shape — one index per directory, entries
|
||||
`* [Title](link) - description`, exactly one `# ` heading, and a root index
|
||||
carrying okf_version/bundle_profile/okf_spec_commit — is not expressible by
|
||||
`IndexPolicy` as it stands: neither per-directory indexes nor a description
|
||||
on an entry has a field. It lands with the reader that judges it. Until
|
||||
then, nothing consumes a profile's index policy, so this assertion is the
|
||||
honest statement of where the port has reached.
|
||||
Every value is read out of their code rather than recalled — the entry
|
||||
template and the root/nested frontmatter split from `bundle.py:498-567`,
|
||||
the bidirectional match from gate `BUNDLE_INDEX_COMPLETE`
|
||||
(`validate.py:1081-1120`, severity ERROR). The reader that judges all of
|
||||
it lives on `IndexPolicy.violations`, so no field here is written without
|
||||
something that reads it.
|
||||
"""
|
||||
assert STRICT_V1.index == DEFAULT.index
|
||||
assert "description" not in STRICT_V1.index.link_template
|
||||
index = STRICT_V1.index
|
||||
assert index != DEFAULT.index
|
||||
assert index.name == DEFAULT.index.name
|
||||
assert index.render_link("Hooks", "hooks.md", "How hooks fire.") == (
|
||||
"* [Hooks](hooks.md) - How hooks fire."
|
||||
)
|
||||
assert index.per_directory is True
|
||||
assert index.heading_required is True
|
||||
assert index.allows_prose is False
|
||||
assert index.entries_match_directory is True
|
||||
assert index.root_frontmatter == ("okf_version", "bundle_profile", "okf_spec_commit")
|
||||
|
||||
|
||||
def test_a_conforming_wiki_root_index_passes_and_a_default_one_does_not() -> None:
|
||||
"""The plan's second cross-profile test: a bundle valid under one profile
|
||||
is refused by the other. It could not come from frontmatter — DEFAULT has
|
||||
no required key set, by measurement — so it comes from index shape.
|
||||
"""
|
||||
conforming = (
|
||||
"---\nokf_version: 0.1\nbundle_profile: strict-v1\n"
|
||||
"okf_spec_commit: d44368c\n---\n\n# Wiki\n\n"
|
||||
"* [Concepts](concepts/index.md) - Machine-generated index of concepts.\n"
|
||||
)
|
||||
assert (
|
||||
STRICT_V1.index.violations(conforming, is_root=True, expected_targets={"concepts/index.md"})
|
||||
== ()
|
||||
)
|
||||
|
||||
default_shaped = "# Wiki\n\nCurated prose.\n\n- [Concepts](concepts/index.md)\n"
|
||||
codes = {
|
||||
violation.code
|
||||
for violation in STRICT_V1.index.violations(
|
||||
default_shaped, is_root=True, expected_targets={"concepts/index.md"}
|
||||
)
|
||||
}
|
||||
assert codes == {
|
||||
"index_root_key_missing",
|
||||
"index_prose_not_allowed",
|
||||
"index_entry_missing",
|
||||
}
|
||||
# ...and the reverse direction: DEFAULT judges neither of them, because a
|
||||
# judging default would condemn the bundles this library itself writes.
|
||||
assert DEFAULT.index.violations(conforming, is_root=True) == ()
|
||||
assert DEFAULT.index.violations(default_shaped, is_root=True) == ()
|
||||
|
||||
|
||||
def test_strict_v1_emission_is_the_ordered_prefix_then_the_sorted_tail() -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue