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
This commit is contained in:
Kjell Tore Guttormsen 2026-07-16 10:58:08 +02:00
commit b0ad2aedfb
4 changed files with 450 additions and 0 deletions

View file

@ -0,0 +1,100 @@
# 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.