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
100 lines
5.5 KiB
Markdown
100 lines
5.5 KiB
Markdown
# 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
|
||
|
||
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.
|