llm-ingestion-okf/docs/plan/phase-3-configurable-contract.md
Kjell Tore Guttormsen b0ad2aedfb docs(plan): add detailed phase 1-4 implementation plans
Phase 1 (Door A): ingest-spec v1 implementation + §11 golden fixtures,
pinned to the current spec revision, with TDD order and byte-exact
verification criteria. Phase 2 (Doors B/C): guard-gated inbox and
external bundle import. Phase 3: configurable bundle contract via a
profile object, default profile locked to the golden suite. Phase 4:
zero-dep Node half, coordination-first with owner sign-offs.

Each plan carries explicit key assumptions with tests and a
verification section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QeqhJpYQyghASjiJo5EhGg
2026-07-16 10:58:08 +02:00

5.5 KiB
Raw Blame History

Phase 3 plan — Configurable bundle contract

Status: approved roadmap phase (see CLAUDE.md); details settled here before code. Depends on: Phases 12 (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

  1. 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 reserved verdict exclusion stays unconditional (it is a spec invariant, not profile config).
    • frontmatter_schema: ordered required keys + optional keys, per concept type where needed (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 required / optional / required-absent policy (e.g. a profile may demand that log.md is absent).
    • index_policy: managed-link format and per-level index expectations.
  2. Built-in profiles:
    • DEFAULT (implicit): exactly the ingest-spec v1 + Phase 2 contract.
    • STRICT_V1: the claude-code-llm-wiki contract — closed type enum (Concept/Guide/Reference/Release), extended required frontmatter (type, title, description, timestamp, layer, source_tier, source_url, source_sha), layer path prefixes, log.md required-absent, root-index metadata.
  3. Plumbing change: materialization, inbox, import, and index generation take an optional profile argument defaulting to DEFAULT. 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:

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

  1. BundleProfile type + DEFAULT profile; refactor Phase 1/2 constants onto it with the full existing test suite as the safety net.
  2. Golden suite re-run — byte-for-byte identical under DEFAULT (the phase's load-bearing regression test).
  3. STRICT_V1 profile + fixtures: a wiki-shaped bundle that validates under STRICT_V1 and is rejected under DEFAULT (extra frontmatter keys are fine under preserve-unknown; the rejection comes from the missing ingest-spec layer), and an ingest-spec bundle rejected under STRICT_V1 (missing required keys, log.md policy).
  4. Reserved-file policy tests (required-absent enforcement).
  5. 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
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 at phase start; config-file loading is 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.

Verification

  1. pytest green; mypy --strict src/; ruff check . and ruff format --check . clean.
  2. Golden suite byte-for-byte under DEFAULT — zero fixture changes in the diff (git diff --stat examples/ empty for this phase).
  3. STRICT_V1 fixture bundle validates; cross-profile rejection tests pass in both directions (step 3 above).
  4. C3 named test: constructing a profile that admits verdict raises at construction time.
  5. 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).
  6. Public API check: Phase 1/2 call sites compile unchanged (profile argument is optional) — verified by leaving existing tests untouched.