Measure first, widen after. The 19-fixture guard-surface suite was re-run against v0.3.4 in a scratch venv before the range moved, and reproduced the three deltas measured against v0.3.3 exactly, with none added. v0.3.4 is the tag pinned rather than v0.3.3 because it shipped first and repairs a quadratic regex (okf._MD_LINK_RE) that sits on Door C's own call path. Door C now passes allow_reserved=False explicitly. The guard added the keyword in the 0.3 line and defaults it True for received bundles, which would merge a sender's index.md / log.md instead of rejecting them. The override keeps the unconditional reserved-name refusal committed to before the keyword existed, and the reason is structural rather than a second opinion on the guard's scan: Door C generates the merged bundle's index.md from what it merged and writes every merged concept verbatim, so a sender's index.md would be a second and irreconcilable claim on one path. This is not a behaviour change for anyone on the previous pin: under v0.2.0 the keyword did not exist and reserved names were refused by construction. The floor is >=0.3 and not >=0.2 for a measured reason. allow_reserved is absent in v0.2.0 and present from v0.3.0 onward, checked across all five tags: a >=0.2 floor would admit a version that raises TypeError on every Door C import. That measurement also corrects a recorded premise -- the plan said the keyword "shipped in v0.3.3", which read the first version we ran the suite against as the version it was introduced in. The conclusion held; the reason did not, and the reason is what a future bump would have relied on. test_door_c_pins_allow_reserved_false_against_the_guards_default locks both halves: that the guard still defaults True, without which the override is a no-op that would pass forever over nothing, and that Door C overrides it. 586 tests, mypy --strict clean, goldens byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2v1hrDhrff2H3y2TNJHkF
204 lines
12 KiB
Markdown
204 lines
12 KiB
Markdown
# Execution order — the full roadmap, sequenced
|
||
|
||
Status: approved sequencing across all remaining phases. This doc is the
|
||
connective tissue between the per-phase plans; it owns the *order and the
|
||
reasons*, not the phase detail. Each phase's detail and verification live in its
|
||
own doc:
|
||
|
||
- `docs/plan/phase-1-door-a.md` — DONE, shipped at v0.3.2.
|
||
- `docs/plan/phase-2-doors-b-c.md`
|
||
- `docs/plan/phase-3-configurable-contract.md`
|
||
- `docs/plan/okf-v0.2-alignment.md` — a policy track, not a phase; slots between
|
||
Phases 3 and 4 for the reason given below.
|
||
- `docs/plan/phase-4-node-half.md`
|
||
|
||
Headline order: **Stage 0 (guard gate) → Phase 2 → Phase 3 → OKF v0.2 alignment
|
||
decisions → Phase 4 code**, with Phase 2 split guard-independent-first and
|
||
Phase 4's *coordination* started in parallel from the beginning. The quality
|
||
argument for this order is below — it is not merely the roadmap numbering.
|
||
|
||
## Stage 0 — Guard readiness (gate; do first, cheap)
|
||
|
||
Before any Phase 2 code, confirm `llm-ingestion-guard` 0.2 exists with the
|
||
pinned surface (`prepare_input`, `screen_output`, `okf.import_bundle`,
|
||
disposition enum) and is installable in CI. This is Phase 2's assumptions
|
||
B1+B2; B2 is explicitly "not yet decided", and the guard is a separate repo
|
||
(`open/llm-ingestion-pipeline-security`), so its readiness is partly outside
|
||
this repo's control.
|
||
|
||
Why first: it is the single biggest unknown, it is cheap to check (read the
|
||
guard repo), and its answer decides the *shape* of Phase 2 — run straight
|
||
through, or front-load the guard-independent half while readiness is confirmed
|
||
in parallel. Do not build a bearing dependency into the plan on an unverified
|
||
foundation. If the surface has drifted from what the plan assumes, the whole
|
||
persist-gate design must know now, not at the integration step.
|
||
|
||
Outcome recorded in STATE before Phase 2 code starts.
|
||
|
||
### Stage 0 revisited — measuring guard 0.3.1 before widening the pin
|
||
|
||
The pin held at the time was `>=0.2,<0.3`, which could only ever resolve
|
||
`v0.2.0` (it has since moved to `>=0.3,<0.4`, resolving `v0.3.4`). The guard repo
|
||
gates its own 1.0 on our fixture suite being measured against `v0.3.1`, so this
|
||
measurement is owed outward and is that repo's only named blocker. Order is
|
||
*measure first, widen after*: widening the range before measuring would ship a
|
||
window admitting a version the fixtures never ran against.
|
||
|
||
Procedure — do not vary it, each step exists because of a specific failure mode:
|
||
|
||
1. Scratch venv **outside** `.venv`: install the package `--no-deps`, then the
|
||
guard from the `v0.3.1` tag. Do not touch `pyproject.toml`.
|
||
2. **Assert the resolved version** via `importlib.metadata` *before* running
|
||
anything, and carry it in the reported result. An unmodified tree resolves
|
||
`0.2.0` and runs green — a true claim about 0.2.0 published under a heading
|
||
that says 0.3.1. This step is what makes the result honest.
|
||
3. Run `tests/test_guard_adapter.py` (19 tests, real scanner) and the full suite.
|
||
Watch percent-escapes specifically: the slugger builds filenames from titles,
|
||
and the guard has recorded that as its most likely false positive. Report raw
|
||
URLs, not counts.
|
||
4. **Send the result whichever way it goes**, and state explicitly that 19
|
||
fixture tests are not a corpus — their gate is "does 0.3.1 change our
|
||
verdicts", not "how often does the false positive fire".
|
||
5. Green → one release: `>=0.2,<0.4`, uv tag `v0.3.1`, CLAUDE.md and README
|
||
updated together. Red → no bump, and the finding goes back to the guard.
|
||
|
||
**Done, and step 5's predicted range was wrong — deliberately superseded.** The
|
||
bump landed as `>=0.3,<0.4` against tag `v0.3.4`, not `>=0.2,<0.4` against
|
||
`v0.3.1`. Two corrections, both forced rather than chosen:
|
||
|
||
- **The floor had to rise to `0.3`.** Door C now passes `allow_reserved=False`,
|
||
a keyword measured absent in `0.2.0` and present from `v0.3.0` onward. A
|
||
`>=0.2` floor would let a consumer resolve `0.2.0` and take a `TypeError` on
|
||
every Door C import — the range would have admitted a version the code cannot
|
||
call. This is the one case where widening past the measured version is not
|
||
conservative but broken. The signature was measured across all five tags
|
||
rather than inferred from the one the fixtures happened to run against.
|
||
- **The tag is the newest measured one, not the first one asked for.** `v0.3.2`,
|
||
`v0.3.3` and `v0.3.4` all shipped while this was open. The suite was re-run
|
||
against `v0.3.4` before the bump — pinning the tag that was measured is the
|
||
whole point of the order, and an older tag would have been measured against a
|
||
guard nobody would install.
|
||
|
||
That also closes the under-defend below: consumers no longer resolve `0.2.0`.
|
||
|
||
## Phase 2 — Doors B/C (split: guard-independent first)
|
||
|
||
Internal order follows the phase-2 doc's TDD steps, deliberately:
|
||
|
||
1. **Guard-independent half (steps 1–2):** extraction registry (stdlib core
|
||
types, fail-fast on unknown extension and on `[extract]` types without the
|
||
extra) + provenance rendering and filename slugging (pure functions). Zero
|
||
external dependency, maximal test-determinism, reuses the Phase 1 renderer.
|
||
Highest-quality-yield work to start with; validates B3 (`html.parser`
|
||
adequacy) early while it is cheap to change.
|
||
2. **Guard seam (steps 3–5):** Door B against a *stub* guard first (nail the
|
||
disposition control flow deterministically), then integration with the *real*
|
||
guard, then Door C. Stub-first means that when the real-guard integration
|
||
test is the only thing that can newly fail, a failure isolates cleanly to the
|
||
integration boundary. Door B before Door C: C reuses B's guard-gate pattern
|
||
and both reuse Phase 1 primitives.
|
||
|
||
This is where `pyproject` runtime deps become exactly the guard pin — a
|
||
semver-worthy event (propose 0.4.0), its own CHANGELOG entry.
|
||
|
||
## Phase 3 — Configurable bundle contract (only after 2)
|
||
|
||
Extract configurability only once Door A **and** Door B/C both concretely use
|
||
the type/layer/frontmatter/reserved-file constants. Phase 3 abstracts a pattern;
|
||
you need ≥2 real consumers before the right seams are visible. Building the
|
||
config object first would be speculative abstraction — an explicit anti-pattern.
|
||
The proving consumer (`claude-code-llm-wiki`, `strict-v1`) is the concrete
|
||
second profile that proves the abstraction is not single-use.
|
||
|
||
The 2→3 boundary has no painful retrofit: Phase 2 already leaves the seam open —
|
||
Door C v1 deliberately does NOT persist the guard log "because reserved-file
|
||
policy differs per consumer and becomes configurable in Phase 3." This is where
|
||
the contract becomes explicit and frozen — the precondition for a clean Node
|
||
port.
|
||
|
||
## OKF v0.2 support — between 3 and 4, and it is real code
|
||
|
||
Everything through Phase 3 targets OKF v0.1. Upstream published v0.2 on
|
||
2026-07-25, and the standing operator policy is that the library always supports
|
||
the current latest OKF version (see `CLAUDE.md`). So this is a committed
|
||
implementation track, not a decision round:
|
||
`docs/plan/okf-v0.2-alignment.md`.
|
||
|
||
**Why it must land before Phase 4 code.** Phase 4 ports a *frozen, explicit*
|
||
contract and uses the shared fixture suite as the cross-runtime conformance
|
||
oracle. Freezing that contract before v0.2 is supported means freezing a v0.1
|
||
shape into two runtimes instead of one, and the fixture suite would then certify
|
||
the drift rather than catch it. Worse, the Node half would need the same v0.2
|
||
work done twice, in two languages, against a contract that had already been
|
||
declared final. The expensive version of this mistake is discovered after the
|
||
Node half exists.
|
||
|
||
**Why it is not a phase of its own.** It adds no new door and no new stage — it
|
||
adds a *profile*, which is precisely what Phase 3 built the seam for. Support is
|
||
additive: `DEFAULT` (commons') and `STRICT_V1` (the wiki's) keep their contracts
|
||
untouched, and v0.2 arrives as a third profile plus an `OKF_LATEST` alias. This
|
||
is the first time the Phase 3 abstraction is forced by something outside this
|
||
repo rather than by a second consumer, which is the better test of it.
|
||
|
||
**Order inside the track.** Characterization and audit first — they add tests and
|
||
change no behavior. Then the frontmatter model that can carry block lists
|
||
(`sources`, multi-verifier `verified`), then the profile, then the golden
|
||
fixtures that make the support claim testable. The `okf_version: 0.2`
|
||
declaration comes last — **unquoted**, per A-E6 — once the shape has earned it. Commons' answer on
|
||
`DEFAULT`'s `generated` runs in parallel and no longer blocks: the additive
|
||
design took it off the critical path.
|
||
|
||
## Phase 4 — Node half (two clocks)
|
||
|
||
**Coordination clock — start early, parallel to Stages 0–3, but only the
|
||
shape-independent agreements.** Phase 4's hard preconditions are agreements, not
|
||
code: okr's reference-impl lift *in principle*, catalog as re-pin owner,
|
||
guard-as-contract at the Node persist seam, and the linkedin-studio
|
||
non-normalization carve-out can all be initiated early — via coord — so they are
|
||
settled when code starts. The one agreement that must WAIT is the exact
|
||
second-brain *contract shape*: the phase-4 doc ties the catalog-spec
|
||
expressiveness check to Phase 3's split-table step, so do not freeze the
|
||
cross-runtime contract before Phase 3 has proven the profile can express it.
|
||
|
||
**Code clock — last.** The Node/ESM implementation ports a *frozen, explicit*
|
||
contract (Phase 3's output) and uses the shared fixture suite as the
|
||
cross-runtime conformance oracle. Porting before the contract is frozen means
|
||
chasing a moving target and guaranteed drift between halves. Halves share
|
||
contract and fixtures, never code.
|
||
|
||
## Quality spine (holds across every stage)
|
||
|
||
- TDD iron law throughout: no production code without a failing test first.
|
||
- The golden/fixture suite is the backbone — every phase extends it; it is the
|
||
regression oracle and, in Phase 4, the cross-runtime conformance oracle. It
|
||
never goes non-byte-exact.
|
||
- Determinism invariant every phase (explicit `ingested_at`, LF-only,
|
||
byte-exact). Phase 2 adds: the guard *version* is part of the input surface —
|
||
pin it.
|
||
- Boundary grep-gate green every phase: `grep -rn "sanitize\|quarantine\|lexicon"
|
||
src/` empty (guard imports only).
|
||
- One phase = one release with a CHANGELOG entry; the guard dependency landing in
|
||
Phase 2 is the architectural milestone.
|
||
|
||
## Verification — the gate between each handoff
|
||
|
||
These are the objective checks that a stage is done and the next may begin:
|
||
|
||
1. **Enter Phase 2:** guard readiness recorded in STATE — either "0.2 API
|
||
matches + installable in CI" or an explicit "front-load guard-independent
|
||
half; guard integration blocked on <named item>".
|
||
2. **Phase 2 → Phase 3:** Phase 1 golden suite still byte-for-byte; `pyproject`
|
||
runtime deps == exactly one range on `llm-ingestion-guard`; persist-gate proof
|
||
test green (a fail-secure fixture yields zero new files).
|
||
3. **Phase 3 → Phase 4:** golden suite byte-identical under `DEFAULT`
|
||
(`git diff --stat examples/` empty for the phase); `STRICT_V1` cross-profile
|
||
rejection tests pass both directions; split-table artifact under `docs/`
|
||
reviewed with the operator (contract is frozen and expressive enough for the
|
||
second-brain spec).
|
||
4. **Phase 4 code start:** each shape-independent coordination step has a
|
||
recorded sign-off; the contract-shape agreement is signed off *after* the
|
||
Phase 3 split-table, not before; and the OKF v0.2 open questions V1–V5 are
|
||
each answered or explicitly deferred *in writing*, so the frozen contract
|
||
states its relation to upstream on purpose rather than by omission.
|
||
5. **Throughout:** `mypy --strict src/`, `ruff check .`, `ruff format --check .`
|
||
clean; boundary grep-gate empty.
|