feat(profiles): SEGMENTED_V1 profile and SegmentationPolicy capability

This commit is contained in:
Kjell Tore Guttormsen 2026-09-01 00:02:14 +02:00
commit cd2c7517b0
3 changed files with 190 additions and 0 deletions

View file

@ -758,6 +758,37 @@ class IndexPolicy:
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)
class BundleProfile:
"""One bundle contract: types, frontmatter, filenames, index."""
@ -767,6 +798,11 @@ class BundleProfile:
paths: PathPolicy
index: IndexPolicy
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
@ -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
# 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