feat(profiles): SEGMENTED_V1 profile and SegmentationPolicy capability
This commit is contained in:
parent
499253e53b
commit
cd2c7517b0
3 changed files with 190 additions and 0 deletions
|
|
@ -758,6 +758,37 @@ class IndexPolicy:
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SegmentationPolicy:
|
||||||
|
"""Whether a bundle admits one document expanding into many concepts.
|
||||||
|
|
||||||
|
OKF v0.2 §2 calls a concept "a single unit of knowledge within a bundle"
|
||||||
|
and a concept ID "the path of the concept's file within the bundle" —
|
||||||
|
neither ties a concept to a source file. A door emitting exactly one flat
|
||||||
|
concept per dropped file therefore implements the shape Appendix A
|
||||||
|
presents v0.2 as migrating AWAY from, and §11 cannot notice: it checks
|
||||||
|
that every non-reserved `.md` parses with a non-empty `type`, so one giant
|
||||||
|
concept is fully conformant. Conformance is the floor, not the proof.
|
||||||
|
|
||||||
|
The presence of this object IS the capability. Every downstream branch
|
||||||
|
reads `profile.segmentation is not None` and never
|
||||||
|
`IndexPolicy.per_directory`: `STRICT_V1` already sets that field True
|
||||||
|
while Door B ignores it, so keying the 1-to-N path there would silently
|
||||||
|
change a shipped profile's output and break its byte-stability pin.
|
||||||
|
|
||||||
|
Every field NAMES a key and none supplies a value. The value of
|
||||||
|
`bundle_id` tracks the caller's own identity scheme, so it arrives through
|
||||||
|
`root_frontmatter_values` (decision D5): a constant here would claim a
|
||||||
|
decision this library does not own.
|
||||||
|
"""
|
||||||
|
|
||||||
|
hierarchical_paths: bool = True
|
||||||
|
bundle_id_key: str = "bundle_id"
|
||||||
|
segment_id_key: str = "segment_id"
|
||||||
|
offset_key: str = "source_offset"
|
||||||
|
nav_label: str = "index"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class BundleProfile:
|
class BundleProfile:
|
||||||
"""One bundle contract: types, frontmatter, filenames, index."""
|
"""One bundle contract: types, frontmatter, filenames, index."""
|
||||||
|
|
@ -767,6 +798,11 @@ class BundleProfile:
|
||||||
paths: PathPolicy
|
paths: PathPolicy
|
||||||
index: IndexPolicy
|
index: IndexPolicy
|
||||||
ownership: OwnershipPolicy = field(default_factory=OwnershipPolicy)
|
ownership: OwnershipPolicy = field(default_factory=OwnershipPolicy)
|
||||||
|
# Defaulted, so all four existing constants construct unchanged and every
|
||||||
|
# positional call site stays source-compatible. `None` is not "segmentation
|
||||||
|
# off" as a setting — it is the profile not having the capability at all,
|
||||||
|
# which is what the downstream `is not None` checks read.
|
||||||
|
segmentation: SegmentationPolicy | None = None
|
||||||
|
|
||||||
|
|
||||||
# The ingest-spec + Phase 2 contract. Every value here was a constant in
|
# The ingest-spec + Phase 2 contract. Every value here was a constant in
|
||||||
|
|
@ -1039,6 +1075,41 @@ OKF_V0_2 = BundleProfile(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# STRUCTURED_V1 plus the capability to split ONE document into MANY concepts.
|
||||||
|
#
|
||||||
|
# A new profile rather than a flag on an existing one, for the same reason
|
||||||
|
# STRUCTURED_V1 was: `DEFAULT` states commons' ingest-spec §6 layer and
|
||||||
|
# `STRICT_V1` mirrors a consumer's ratified contract, so moving either one's
|
||||||
|
# bytes from here would be this repo editing another repo's contract (O2).
|
||||||
|
# `OKF_V0_2` states upstream's. What stays byte-stable is therefore all four of
|
||||||
|
# them, proven by `tests/test_segmented_profile.py` at the profile level and by
|
||||||
|
# the golden suite at the byte level.
|
||||||
|
#
|
||||||
|
# What this profile adds on top of STRUCTURED_V1's facets and structure keys:
|
||||||
|
#
|
||||||
|
# - `segmentation` — the capability itself. Its presence is what every 1-to-N
|
||||||
|
# branch keys on.
|
||||||
|
# - `per_directory` — a segmented bundle has directories, and a bundle whose
|
||||||
|
# nested concepts are reachable only by guessing a path is a filing cabinet
|
||||||
|
# again. Set HERE rather than inherited, because STRUCTURED_V1 leaves it off.
|
||||||
|
# - `root_frontmatter=("bundle_id",)` — the identity carrier settled by order
|
||||||
|
# `…2527032751`: form (c), a root identifier consumers join on. Naming the
|
||||||
|
# key PERMITS it and fixes its position; the caller supplies the value (D5),
|
||||||
|
# because a bundle is a collection the caller delimits and only the caller
|
||||||
|
# knows what it is called. It is deliberately absent from
|
||||||
|
# `root_frontmatter_required`: Door B refuses a segmentation plan without an
|
||||||
|
# id, which is a rule about the door, not about every index this profile
|
||||||
|
# might ever write.
|
||||||
|
SEGMENTED_V1 = BundleProfile(
|
||||||
|
types=STRUCTURED_V1.types,
|
||||||
|
frontmatter=STRUCTURED_V1.frontmatter,
|
||||||
|
paths=STRUCTURED_V1.paths,
|
||||||
|
index=replace(STRUCTURED_V1.index, per_directory=True, root_frontmatter=("bundle_id",)),
|
||||||
|
ownership=STRUCTURED_V1.ownership,
|
||||||
|
segmentation=SegmentationPolicy(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# "The latest version supported as STABLE", not the latest present in this
|
# "The latest version supported as STABLE", not the latest present in this
|
||||||
# module. It therefore keeps v0.1 UPSTREAM semantics for as long as v0.2 is
|
# module. It therefore keeps v0.1 UPSTREAM semantics for as long as v0.2 is
|
||||||
# provisional, and flipping it is the GA event — one auditable action rather
|
# provisional, and flipping it is the GA event — one auditable action rather
|
||||||
|
|
|
||||||
|
|
@ -221,4 +221,10 @@ def test_a_profile_is_assembled_from_its_policies() -> None:
|
||||||
"paths",
|
"paths",
|
||||||
"index",
|
"index",
|
||||||
"ownership",
|
"ownership",
|
||||||
|
# SEGMENTED_V1's capability, registered here for the same reason
|
||||||
|
# `ownership` was at D2: the alternative to a policy on the object is a
|
||||||
|
# profile branch somewhere else, which is exactly what this assertion
|
||||||
|
# exists to forbid. Defaulted to None, so the four shipped profiles
|
||||||
|
# construct unchanged and their bytes do not move.
|
||||||
|
"segmentation",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
113
tests/test_segmented_profile.py
Normal file
113
tests/test_segmented_profile.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
"""SEGMENTED_V1: the capability that lets one document become many concepts.
|
||||||
|
|
||||||
|
Additive in the strict sense the upstream-version policy demands. `DEFAULT`,
|
||||||
|
`STRICT_V1`, `STRUCTURED_V1` and `OKF_V0_2` keep the bytes they had, because
|
||||||
|
the new field is defaulted and every branch downstream is keyed on
|
||||||
|
`profile.segmentation is not None`.
|
||||||
|
|
||||||
|
That toggle is deliberately NOT `IndexPolicy.per_directory`. `STRICT_V1`
|
||||||
|
already sets that field True while Door B ignores it, so keying the capability
|
||||||
|
there would change `STRICT_V1`'s Door B output and break a byte-stability pin
|
||||||
|
that four consumers depend on. The measured trap is the reason the assertion
|
||||||
|
below is written as an identity check on all four profiles rather than on one.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import replace
|
||||||
|
|
||||||
|
from llm_ingestion_okf.profiles import (
|
||||||
|
DEFAULT,
|
||||||
|
OKF_LATEST,
|
||||||
|
OKF_V0_2,
|
||||||
|
SEGMENTED_V1,
|
||||||
|
STRICT_V1,
|
||||||
|
STRUCTURED_V1,
|
||||||
|
BundleProfile,
|
||||||
|
SegmentationPolicy,
|
||||||
|
)
|
||||||
|
|
||||||
|
EXISTING = (DEFAULT, STRICT_V1, STRUCTURED_V1, OKF_V0_2)
|
||||||
|
|
||||||
|
|
||||||
|
# --- the capability -------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_the_new_profile_declares_the_segmentation_capability() -> None:
|
||||||
|
assert SEGMENTED_V1.segmentation is not None
|
||||||
|
for profile in EXISTING:
|
||||||
|
assert profile.segmentation is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_capability_is_not_keyed_on_per_directory() -> None:
|
||||||
|
# STRICT_V1 already sets per_directory True and Door B ignores it. Keying
|
||||||
|
# the 1-to-N branch there would silently change STRICT_V1's output.
|
||||||
|
assert STRICT_V1.index.per_directory is True
|
||||||
|
assert STRICT_V1.segmentation is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_new_profile_writes_an_index_per_directory_and_names_bundle_id() -> None:
|
||||||
|
assert SEGMENTED_V1.index.per_directory is True
|
||||||
|
assert SEGMENTED_V1.index.root_frontmatter == ("bundle_id",)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bundle_id_is_permitted_at_the_root_rather_than_demanded_by_the_policy() -> None:
|
||||||
|
# The policy NAMES the key and fixes its position; the caller owns the
|
||||||
|
# value (D5). Demanding it here would claim a decision this library does
|
||||||
|
# not own -- Door B enforces the plan-requires-an-id rule instead.
|
||||||
|
assert "bundle_id" not in SEGMENTED_V1.index.root_frontmatter_required
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_new_profile_keeps_the_structured_facets_and_keys() -> None:
|
||||||
|
assert SEGMENTED_V1.index.facets == STRUCTURED_V1.index.facets
|
||||||
|
assert SEGMENTED_V1.frontmatter.order == STRUCTURED_V1.frontmatter.order
|
||||||
|
assert SEGMENTED_V1.types is STRUCTURED_V1.types
|
||||||
|
assert SEGMENTED_V1.paths is STRUCTURED_V1.paths
|
||||||
|
|
||||||
|
|
||||||
|
# --- the additivity proof -------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_four_existing_profiles_equal_freshly_constructed_copies() -> None:
|
||||||
|
# A defaulted field must not move any of them. `replace()` with no changes
|
||||||
|
# reconstructs each profile through the dataclass constructor, so a field
|
||||||
|
# that had become required -- or had picked up a non-default value -- shows
|
||||||
|
# up here as inequality rather than as golden churn six steps later.
|
||||||
|
for profile in EXISTING:
|
||||||
|
assert replace(profile) == profile
|
||||||
|
|
||||||
|
|
||||||
|
def test_okf_latest_is_still_default() -> None:
|
||||||
|
assert OKF_LATEST is DEFAULT
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_profile_constructs_without_naming_the_new_field() -> None:
|
||||||
|
minimal = BundleProfile(
|
||||||
|
types=DEFAULT.types,
|
||||||
|
frontmatter=DEFAULT.frontmatter,
|
||||||
|
paths=DEFAULT.paths,
|
||||||
|
index=DEFAULT.index,
|
||||||
|
)
|
||||||
|
assert minimal.segmentation is None
|
||||||
|
|
||||||
|
|
||||||
|
# --- the policy's own defaults -------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_policy_names_the_keys_it_governs() -> None:
|
||||||
|
policy = SEGMENTED_V1.segmentation
|
||||||
|
assert policy is not None
|
||||||
|
assert policy.hierarchical_paths is True
|
||||||
|
assert policy.bundle_id_key == "bundle_id"
|
||||||
|
assert policy.segment_id_key == "segment_id"
|
||||||
|
assert policy.offset_key == "source_offset"
|
||||||
|
assert policy.nav_label == "index"
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_policy_is_frozen() -> None:
|
||||||
|
policy = SegmentationPolicy()
|
||||||
|
try:
|
||||||
|
policy.bundle_id_key = "other" # type: ignore[misc]
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
raise AssertionError("SegmentationPolicy must be frozen")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue