ID-114 {114.4} PLAN - relocate product functionality out of the private docs-site
ID-114 {114.4} PLAN - relocate product functionality out of the private docs-site (de-ID + re-home)
Section titled “ID-114 {114.4} PLAN - relocate product functionality out of the private docs-site (de-ID + re-home)”Date: 16/06/2026
Author: Task Planner (FRESH dispatch - Q-PLANNER-2; read {114.1} RESEARCH + {114.2}
PRODUCT + {114.3} TECH in full, NOT a prior-leg author).
Tier: PLAN (decomposition leg of a PRODUCT+TECH+PLAN chain). Decomposes the ratified
13-PI-N spec pair into implementation Subtasks {114.5}-{114.13}.
Predecessors (read in full this dispatch): {114.1} RESEARCH, {114.2} PRODUCT, {114.3}
TECH (specs/id-114-docs-site-relocation/), all ratified.
Baseline: HEAD 7fe481f74 on canonical-pipeline-setup.
Governing principle: “the private docs-site must host docs ONLY - no product functionality.”
Ratified revisions folded into this PLAN (these CORRECT the TECH PI prose)
Section titled “Ratified revisions folded into this PLAN (these CORRECT the TECH PI prose)”The TECH was ratified WITH the following five orchestrator-supplied corrections. They
override the TECH’s Proposed changes prose where they conflict; every Subtask below
encodes them:
-
PI-3 forward-ports NOTHING (delete-not-port). The TECH’s “diff each DS harness eval file against its MAIN twin and forward-port any genuine divergence” instruction is WRONG for
eval-procurement-drafting.ts. Grounded this dispatch: the DS copy (harness/scripts/eval-procurement-drafting.ts, 486 L) queries the droppedbid_responses.cited_itemscolumn (L154-155) with a silent-swallowcatch(L176) and readsdb.cited_items ?? [](L243). MAIN’sscripts/eval-procurement-drafting.ts(478 L) already carries the bl-215 fix: it queriesform_responsesselectingsource_content_ids(L156-157) and fails loud (throw new Error(...), L161). The DS copy is a stale regression of an already-fixed bug. Forward-porting it would RE-INTRODUCE the bug. MAIN is canonical for EVERY runner; the action is DELETE the DS eval slice, port nothing. (The only divergence the TECH expected - theresolveEvalFixtureimport line - is moot, because every DS eval runner is deleted, not migrated.) -
PI-9 reconcile - “regenerate-from-DB” = a MAIN-homed materialised DB-derived taxonomy artefact, NOT a zero-intermediary runtime fetch. The codegen materialises the DB taxonomy into a MAIN-resident artefact wrapped in
TAXONOMY_START/TAXONOMY_ENDmarkers soparseCanonicalTaxonomy()’s regex contract (matches#### N. DOMAIN+- `slug`: desc) is preserved with a one-line path change. The marker-wrapper is mandatory. -
OQ-E - the eval-fixture rewire (PI-5 step-2) is OWNED BY {71.14}, EXCLUDED from ID-114. The parallel id71-id104 (S364) track owns the
resolveEvalFixtureflip (movingsummarisation+procurement-draftingfromPRIVATE_FIXTUREStoPUBLIC_FIXTURES) and theeval-fixture-sync.test.tsun-gating. ID-114 keeps ONLY PI-5 step-1 (add de-ID’d copies to MAIN, bridge route untouched) and step-3 (delete the DS originals, AFTER {71.14}‘s rewire lands). This PLAN does NOT include thelib/eval/fixtures.tsedit or theeval-fixture-sync.test.tsedit. -
OQ-C - use the EXISTING
KH_CLIENT_NAME_DENYLISTsecret. No net-new secret. The local consumers re-home to a MAIN-checkout-relative config path (or the secret-materialised file in CI); the existing secret remains the canonical store. -
Sequencing precondition (already satisfied). ID-114 impl runs AFTER the id71-id104
classify.tsrewrite is on the shared base. It already is at7fe481f74, so PI-11 baseline-parity measures cleanly. No Subtask waits on it.
Context (code-intelligence orientation, cited verbatim this dispatch)
Section titled “Context (code-intelligence orientation, cited verbatim this dispatch)”GitNexus is TS-only; flow.py/SQL surfaces were grep-swept (came back empty - confirming
scripts/kb_pipeline/ is gone and no SQL references the bridge). Per the brief I did NOT
run npx gitnexus analyze.
gitnexus_query({query: 'taxonomy generation eval runner fixture', repo: 'knowledge-hub'})->"processes": [](no graphed runtime flow - matches PRODUCT’s build-time-only finding). Definitions returned the canonical eval surface:scripts/eval-runner.ts:runEvalTouchpoint(228-331) - ID-104’s central runner, already on the shared base;scripts/eval-classification.ts:classifyFixtureItem(411-564) +GoldItem(100-116);scripts/mcp-eval/seed-fixtures.ts(a DISTINCT module fromlib/eval/fixtures.ts, confirming OQ-B’s no-collision finding). No taxonomy-codegen process is graphed (build-time codegen, not a runtime flow).gitnexus_context({name: 'resolveEvalFixture', file_path: 'lib/eval/fixtures.ts', repo: 'knowledge-hub'})->incoming.calls: exactly FOUR -scripts/eval-{classification,entity-classification,procurement-drafting,summarisation}.ts:main;outgoing.calls: ONE -lib/private-docs.ts:resolvePrivateDocsDir. This is the precise blast radius of the PI-5 fixture path: four runnermains + the one bridge call. It confirms (a) the rewire surface is exactly thePUBLIC_FIXTURES/PRIVATE_FIXTURESmaps inlib/eval/fixtures.ts(owned by {71.14} per OQ-E, NOT touched here), and (b) ID-114’s PI-5 step-1 only ADDS files under__tests__/fixtures/eval-gold/- it does not touchresolveEvalFixtureat all, so no caller is perturbed.
Grounded MAIN-side contracts this PLAN relies on (verified this dispatch):
lib/eval/fixtures.ts:70-85-resolveEvalFixture;PUBLIC_FIXTURES= {classification, entity} (in-repo__tests__/fixtures/eval-gold/),PRIVATE_FIXTURES= {summarisation, procurement-drafting} (bridge route). ID-114 step-1 adds the two private gold standards to__tests__/fixtures/eval-gold/WITHOUT moving them in the map (the map flip = {71.14}).scripts/generate-classification-prompt-taxonomy.ts-PROMPT_PATH(L26-30) is the bridge to retire;fetchTaxonomy(L93, DB select),generateTaxonomySection(L143, emits#### N. DOMAIN+- `slug`: desc+---),inject(L190, writes between markers). Keep fetch+render; change only the write target + dropresolvePrivateDocsDir.scripts/lib/taxonomy-parser.ts:7-61-parseCanonicalTaxonomy(filePath): regex overTAXONOMY_START..END, matches#### N. DOMAIN(L28) and- `slug`: desc(L46). This is the contract the materialised artefact MUST satisfy (revision 2).scripts/bundle-plugin.ts:validate()-CANONICAL_PATH(the bridge) ->parseCanonicalTaxonomy(CANONICAL_PATH). Re-pointCANONICAL_PATHat the MAIN artefact; drop the bridge.- Denylist consumers:
scripts/sweep-identity-relocation.ts:180-219(candidate-chain:--denylistflag ->KH_PRIVATE_DOCS_DIR/ops/identity-denylist.json-> sibling-checkout default),scripts/generate-purge-path-inventory.ts:349-380(same chain),.claude/hooks/ip-leak-filename-guard.sh:23(${KH_PRIVATE_DOCS_DIR}/.config/ip-denylist.txt, silent no-op if absent, L24). scripts/check-token-parity.tslives ONLY in DS (docs-site/scripts/, 8545 B; NOT in MAIN). Escape:resolve(here, '../../app/globals.css')(L172); DS mirrorresolve(here, '../src/styles/warm-meridian.css')(L173).- DS harness eval slice present + confirmed stale:
harness/scripts/eval-*.ts(5 runners),harness/__tests__/eval/*-eval.test.ts(5 suites),harness/__tests__/fixtures/*(goldeval-baselines/),harness/lib/{anthropic.ts,ai/pricing.ts,eval/{types,reporter,baseline,metrics}.ts}(the fork - lacksai/classify.ts, hence the latent--livebreak in the 2 classify-importing runners). DS rooteval-fixtures/{procurement-drafting,summarisation}-eval-gold-standard.jsonpresent. MAIN already holds the canonical twins (__tests__/fixtures/eval-gold/,__tests__/fixtures/eval-baselines/,scripts/eval-*.ts,__tests__/eval/*).
Decomposition - 9 Subtasks {114.5}-{114.13}
Section titled “Decomposition - 9 Subtasks {114.5}-{114.13}”Sibling-only deps. Cross-Task coordination (the {71.14} fixture flip) is a Task-level handshake, not a Subtask dep - no cross-Task Subtask dependency is created (the would-be dep is the reason PI-5 step-2 is excluded entirely). 9 Subtasks is well under the 25 soft ceiling. The two repos (MAIN-write vs DS-write) are kept in separate Subtasks so each lands as a coherent commit in its own repo.
| Subtask | Title | Repo | PI | Deps |
|---|---|---|---|---|
| {114.5} | de-ID worked-examples in classification-prompt.md | DS | PI-8 | - |
| {114.6} | codegen regenerate-from-DB to MAIN artefact + retire bridge | MAIN | PI-9/10 | - |
| {114.7} | delete DS harness eval slice (delete-not-port) | DS | PI-3/4 | - |
| {114.8} | add de-ID’d procurement/summarisation fixtures to MAIN | MAIN | PI-5 step-1 | - |
| {114.9} | re-home denylist local consumers off the bridge | MAIN | PI-6 | - |
| {114.10} | re-home check-token-parity coupling (drop parent-dir escape) | DS | PI-7 | - |
| {114.11} | wire DS root __tests__/ into DS CI | DS | PI-12 | {114.7} |
| {114.12} | delete DS root eval-fixtures originals (after {71.14} rewire) | DS | PI-5 step-3 | {114.8} |
| {114.13} | PI-1/2/11/13 verification sweep (no-regression gate) | MAIN+DS | PI-1/2/11/13 | {114.5},{114.6},{114.7},{114.8},{114.9},{114.10} |
Why these boundaries
Section titled “Why these boundaries”- {114.5} (de-ID) is independent of {114.6} (codegen). They touch different regions of
different files (DS
ops/classification-prompt.mdworked-examples L194-809 vs MAIN codegen scripts). Both feed the PI-10 “MAIN carries only the generic skeleton” end-state. - {114.6} bundles PI-9 + PI-10 because PI-10 is fully ensured by PI-8 ({114.5}) + the
materialised-generic-artefact of PI-9 - no separate code. {114.6} is the single MAIN
codegen edit: rewire
generate-classification-prompt-taxonomy.tswrite target, add the marker-wrapped MAIN artefact, re-pointbundle-plugin.ts:validate(), drop the bridge at both sites. - {114.7} (harness delete) and {114.8} (MAIN fixture add) are split by repo. {114.7} is
a pure DS deletion (retires the fork + the latent
--livebreak in one move - PI-4 is resolved BY the deletion). {114.8} is a pure MAIN add (de-ID’d copies into__tests__/fixtures/eval-gold/, map untouched). - {114.11} depends on {114.7} because wiring the DS root
__tests__/into CI must happen against the post-deletion DS tree (theharness/CI job’s eval suites are gone; the root-test job is the new wiring). Sibling dep, both DS. - {114.12} depends on {114.8} (the MAIN copies must exist before the DS originals are
deleted) AND is gated at the Task level on the {71.14} rewire landing (a
status_notehandshake, NOT a Subtask dep - that cross-Task dep is exactly what OQ-E removes by giving {71.14} the flip). The bridge keeps resolving the unmoved DS copy until {71.14} flips the map, so there is never a broken-path window. - {114.13} is the final verification gate - it depends on every code-touching Subtask
and asserts the cross-cutting invariants (PI-1 de-ID sweep zero-hits, PI-2 not
flip-blocking, PI-11 classifier runtime byte-unchanged, PI-13 no new dependency). It
writes no product code; it runs sweeps +
git diffassertions +bun run test.
Sequencing and parallelisation
Section titled “Sequencing and parallelisation”Wave order (sibling deps only; cross-Task handshake noted):
- Wave 1 (parallel, no deps): {114.5} de-ID, {114.6} codegen, {114.7} harness delete, {114.8} MAIN fixture add, {114.9} denylist re-home, {114.10} token-parity re-home.
- Wave 2: {114.11} (after {114.7}), {114.12} (after {114.8}; Task-level gated on {71.14}).
- Wave 3: {114.13} verification gate (after all code-touching Subtasks).
Task-level handshake (NOT a Subtask dep): {114.12} (DS delete of the eval-fixtures
originals) must merge AFTER {71.14}‘s resolveEvalFixture flip lands on the shared trunk.
Record this as a status_note marker on ID-114 when {114.8} completes; do not encode it as
a Subtask dependency (it is cross-Task; per OQ-E the flip itself is {71.14}‘s, so the only
ID-114 obligation is “delete the DS originals after the flip is live”).
Risks and mitigations (decomposition-specific)
Section titled “Risks and mitigations (decomposition-specific)”- {114.7} could remove unique DS history. Mitigated by revision 1: MAIN is canonical
for every runner; the one suspected divergence (
eval-procurement-drafting.ts) is a stale regression, not unique value. The Executor verifies viagrepthat no DS eval runner holds a fix absent from MAIN before deleting (it does not - bl-215 is already in MAIN). No forward-port. - {114.12} broken-path window if it runs before {71.14}‘s flip. Mitigated by the
Task-level
status_notegate + the bridge resolving the unmoved DS copy until the flip. Never delete the DS originals in the same commit as anything else. - {114.6} bundle-plugin validation breaks if the MAIN artefact shape drifts from
parseCanonicalTaxonomy’s regex. Mitigated by revision 2: the artefact keeps theTAXONOMY_START..END+#### N. DOMAIN+- `slug`: descshape; theKH_PRIVATE_DOCS_DIR-unset validation test ({114.6} testStrategy) catches drift. - {114.9} denylist regression if the term set changes post-move. Mitigated by diffing
the resolved term set pre/post; the
KH_CLIENT_NAME_DENYLISTsecret stays canonical (OQ-C).
Carried open questions (home-selection - do NOT block decomposition)
Section titled “Carried open questions (home-selection - do NOT block decomposition)”These were left OPEN in TECH for Liam and are NOT resolved by this PLAN; each Subtask
encodes the recommended shape but flags the choice in details:
- OQ-C (PI-6 local-consumer config path): MAIN-checkout config path vs
secret-materialised file in CI; and whether to consolidate
.config/ip-denylist.txt+ops/identity-denylist.jsoninto one source. {114.9} encodes the MAIN-checkout-relative recommendation (no new secret per the ratified revision). - OQ-D (PI-7 re-home shape): MAIN-homed guard (reads MAIN CSS, fetches DS mirror via GitHub-App bridge) vs shared composite action. {114.10} encodes the drop-the-parent-dir-escape requirement either way; the exact host is flagged.
Sources (read this dispatch)
Section titled “Sources (read this dispatch)”- {114.1} RESEARCH + {114.2} PRODUCT + {114.3} TECH (
specs/id-114-docs-site-relocation/), read in full. bun scripts/ledger-cli.ts show task 114(slice read); ID-71 title (slice read, {71.14} ownership confirmation).- gitnexus (repo
knowledge-hub):query('taxonomy generation eval runner fixture')-> no runtime flow, eval-surface definitions;context('resolveEvalFixture', lib/eval/fixtures.ts)-> 4 runner-maincallers +resolvePrivateDocsDir. - MAIN (verified this dispatch):
lib/eval/fixtures.ts(full),scripts/generate-classification-prompt-taxonomy.ts(full),scripts/lib/taxonomy-parser.ts(full),scripts/bundle-plugin.ts:150-230,scripts/eval-procurement-drafting.ts(bl-215 fix region),scripts/sweep-identity-relocation.ts:180-219,scripts/generate-purge-path-inventory.ts:349-380,.claude/hooks/ip-leak-filename-guard.sh:18-30,__tests__/validation/eval-fixture-sync.test.ts(skip-gate region),__tests__/fixtures/eval-gold/+eval-baselines/(existing twins). - DS (verified this dispatch):
harness/{scripts,lib,__tests__}(stale eval slice + fork),harness/scripts/eval-procurement-drafting.ts(thebid_responses.cited_itemsstale regression),eval-fixtures/*.json,scripts/check-token-parity.ts(the../../app/globals.cssescape). - grep sweep (Python/SQL, TS-only-tool gap):
scripts/cocoindex_pipeline/,scripts/kb_pipeline/(empty),supabase/migrations/- no taxonomy-marker / bridge references.