Everything built so far targets OKF v0.1. Upstream published v0.2 on 2026-07-25, so the plan records how the library relates to it and, more importantly, who owns each decision. Read from the spec itself rather than secondhand, which corrected two readings that a summary had gotten wrong: - STRICT_V1's `timestamp` is NOT a defect. Section 13.1 grants consumers a documented fallback to legacy `timestamp` precisely when `generated` is absent, and STRICT_V1 emits no `generated`. Nothing is asked of the wiki. - The one measured shape problem is DEFAULT's `generated: "true"`, because v0.2 requires `generated.by` within `generated`. That key was not reserved in v0.1, so it was legal when written; v0.2 claimed the name. DEFAULT states commons' ingest-spec section 5 layer, so the fix is commons' call and is raised there as open question V1 rather than patched locally. Two findings shrink the work. The canonical form for `generated` and a single `verified` is an inline flow mapping, which the existing scalar parser already round-trips as an opaque string, so block-list support is only needed for `sources` and multi-verifier `verified` -- and only if a named consumer asks. And the collision degrades safely: `_is_ingest_owned` returns False for a v0.2 mapping, so a foreign concept is refused rather than overwritten. The track sits between Phases 3 and 4 because Phase 4 freezes the cross-runtime contract. Freezing a v0.1 shape into two runtimes would let the shared fixture suite certify the drift instead of catching it. Self-imposed rule, since the spec does not require it: conform first, claim after. Declaring `okf_version: "0.2"` is a MAY with no conformance checkpoint, so claiming it early would be permitted -- and would be the same class of true-sounding misleading claim as reporting a 0.2.0 measurement under a 0.3.1 heading. Also moves the guard 0.3.1 measurement procedure out of session state and into execution-order.md, where it belongs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
7.7 KiB
Phase 3 plan — Configurable bundle contract
Status: approved roadmap phase (see CLAUDE.md); details settled here before code.
Depends on: Phases 1–2 (their behavior becomes the default profile and must not change).
Proving consumer: claude-code-llm-wiki with a strict-v1 profile.
Goal
Everything the first two phases hard-code about what a valid bundle looks like — concept types, layers/path prefixes, frontmatter sets and ordering, reserved-file policy — becomes configuration on a profile object instead of constants. The observable behavior under the default profile is byte-identical to Phase 1/2; the golden suite is the regression harness for that claim.
Design
BundleProfile— a frozen dataclass (stdlib, no config-file DSL in v1; profiles are constructed in code by the consumer):type_policy: closed enum vs. open set; the reservedverdictexclusion stays unconditional (it is a spec invariant, not profile config).frontmatter_schema: three distinct fields, not one — the required key set, the allowlisted key set, and the canonical emission order. STRICT_V1 requires eight keys inside an eleven-key allowlist (seedocs/phase-3-split-table.md), so a single ordered required/optional pair cannot express it. Also carries nullable keys and the scalar encoding subset. Phase 1's seven-key layer and Phase 2's inbox layer are expressions of this schema.path_policy: layer/path prefixes, filename namespaces, reserved filename patterns (index.md,promoted-verdict-*, …).reserved_files: per-file policy, THREE values not two — required, required-absent, and present-but-exempt-from-document-gates (the proving consumer'sREADME.md, which carries no frontmatter and is never indexed yet is still content-scanned).index_policy: managed-link format and per-level index expectations. This is the widest gap between the two profiles: one index per bundle versus one per directory,- [label](target)versus* [Title](link) - description, append-if-absent versus exact match in both directions, and a root index with or without frontmatter.
- Built-in profiles:
-
DEFAULT(implicit): exactly the ingest-spec v1 + Phase 2 contract. -
STRICT_V1: theclaude-code-llm-wikicontract — closed type enum (Concept/Guide/Reference/Release), eight required frontmatter keys (type,title,description,timestamp,layer,source_tier,source_url,source_sha) inside an eleven-key allowlist that doubles as emission order, layer/path/type/source_tier agreement rules,log.mdrequired-absent,README.mdreserved-exempt, per-directory indexes, and root-index metadata (okf_version,bundle_profile,okf_spec_commit).This line was drafted as eight, corrected to four against the consumer's
REQUIRED_KEYSconstant on 2026-07-25, and settled at eight by their operator the same day: the four-key constant is their emit-path minimum, not their contract, and all eight are present on 522/522 of their documents. STRICT_V1 is therefore deliberately stricter than the consumer's own validator; the split table records the agreed consequence.
-
- Plumbing change: materialization, inbox, import, and index generation take
an optional
profileargument defaulting toDEFAULT. No behavior branches outside what the profile object expresses.
The split table (settled before code)
The wiki's existing validator mixes three kinds of gates. First implementation
step is a written mapping of every gate to exactly one column — the mapping is
reviewed with the operator before any porting. Drafted 2026-07-25 in
docs/phase-3-split-table.md, over all 25 gates, and sent to the proving
consumer for review; two gates turned out to bundle checks from different
columns and are decomposed there rather than forced into one.
| Column | Meaning | Goes where |
|---|---|---|
| Profile | Structural bundle contract (types, keys, paths, reserved files) | This phase, BundleProfile |
| Guard | Content safety (sanitize, quarantine, fencing, secrets, budgets) | Guard calls — never reimplemented here |
| Consumer | Domain logic (source pipelines, promotion, staging) | Stays in the consumer repo |
TDD order
BundleProfiletype +DEFAULTprofile; refactor Phase 1/2 constants onto it with the full existing test suite as the safety net.- Golden suite re-run — byte-for-byte identical under
DEFAULT(the phase's load-bearing regression test). STRICT_V1profile + fixtures: a wiki-shaped bundle that validates underSTRICT_V1and is rejected underDEFAULT(extra frontmatter keys are fine under preserve-unknown; the rejection comes from the missing ingest-spec layer), and an ingest-spec bundle rejected underSTRICT_V1(missing required keys,log.mdpolicy).- Reserved-file policy tests (required-absent enforcement).
- Split-table review artifact committed under
docs/and cross-checked against the implemented profile fields.
Key assumptions (each with its test)
| # | Assumption | Test |
|---|---|---|
| C1 | Default-profile refactor is behavior-neutral | Phase 1 golden suite byte-identical; full Phase 1/2 test suite green, unmodified |
| C2 | strict-v1 requirements are fully enumerable from the wiki's validator |
The split table is exhaustive: every wiki gate appears in exactly one column; reviewed with the operator. Drafted 2026-07-25 against validate.py/bundle.py at their 9ee5a8e, which is the single gate entrypoint — the enumeration holds only against that commit, so re-check before porting. Two gates required decomposition rather than a single column |
| C3 | Verdict reservation must survive every profile | Named test: no profile construction can permit type: verdict |
| C4 | Profiles-in-code (no config files) suffice for the proving consumer | CONFIRMED with the operator 2026-07-25 at phase start: profiles are constructed in code, config-file loading stays an extension point |
Non-goals
- Porting the wiki's security stack — those gates map to the Guard column.
- Config-file/DSL profile loading, profile inheritance chains — extension points.
- Adopting the second-brain (Node-world) conventions — that contract enters in Phase 4; this phase only ensures the profile object is expressive enough not to block it (checked against the catalog's spec during the split-table step).
- Any change to commons' ingest-spec; if profile work reveals spec friction, it is raised in commons, not patched locally.
- Aligning the contract with upstream OKF v0.2. This phase targets v0.1, and the
profile object is what makes the upstream version a policy rather than a
constant: no profile may hard-code an
okf_versionvalue, and the frontmatter-set fields must be able to express either the legacytimestampor v0.2'sgenerated. The decisions themselves live indocs/plan/okf-v0.2-alignment.mdand are sequenced after this phase.
Verification
pytestgreen;mypy --strict src/;ruff check .andruff format --check .clean.- Golden suite byte-for-byte under
DEFAULT— zero fixture changes in the diff (git diff --stat examples/empty for this phase). STRICT_V1fixture bundle validates; cross-profile rejection tests pass in both directions (step 3 above).- C3 named test: constructing a profile that admits
verdictraises at construction time. - The split table exists under
docs/, every wiki gate row has exactly one column, and the Guard column contains no rows implemented in this repo (grep-gate from Phase 2 still clean). - Public API check: Phase 1/2 call sites compile unchanged (profile argument is optional) — verified by leaving existing tests untouched.