ID-114 {114.3} TECH - relocate product functionality out of the private docs-site
ID-114 {114.3} TECH - relocate product functionality out of the private docs-site (de-ID + re-home)
Section titled “ID-114 {114.3} TECH - relocate product functionality out of the private docs-site (de-ID + re-home)”Date: 15/06/2026
Author: Task Planner (FRESH dispatch - Q-PLANNER-2; read {114.1} RESEARCH + {114.2} PRODUCT fresh, NOT the PRODUCT author).
Tier: TECH (of a PRODUCT+TECH+PLAN chain). Maps 1:1 to PRODUCT’s 13 PI-N invariants.
Predecessors (read in full this dispatch): {114.1} RESEARCH, {114.2} PRODUCT (specs/id-114-docs-site-relocation/).
Builds on (do NOT re-implement): ID-95.5 - lib/ai/skills/classification.md skeleton + tenant_config.classificationDisambiguation.
Coordinates with (in-flight, do NOT collide): ID-104 / ID-71.14 on the id71-id104 branch.
Governing principle: “the private docs-site must host docs ONLY - no product functionality.”
Context
Section titled “Context”This Task re-homes product/coupled content currently mis-homed in the private
knowledge-hub-docs-site repo. PRODUCT defines the 13 PI-N behaviour invariants; this
TECH grounds each in current file:line reality and supplies the migration plan. The
single most consequential finding of this dispatch re-frames the harness relocate-set:
the canonical eval infrastructure already exists in MAIN - the docs-site harness/ eval
slice is a stale duplicate/fork, so PI-3/PI-4 are predominantly a delete-the-duplicate
operation, not a lift-and-move.
Code-intelligence orientation (cited verbatim, this dispatch)
Section titled “Code-intelligence orientation (cited verbatim, this dispatch)”GitNexus index is STALE (last indexed 69b7bd0); per the dispatch brief I preferred
ast-dataflow + grep for exact file:line and grep-swept the .md/.py/.sh surfaces
GitNexus/ast-dataflow do not cover.
gitnexus query(auto-surfaced forresolveEvalFixture): “resolveEvalFixture (lib/eval/fixtures.ts)Called by: main, main, main; Calls:resolvePrivateDocsDir” - confirms the gold-standard fixture bridge is the only graphed coupling and its three callers are thescripts/eval-*.tsrunners’main. No graphed runtime flow for the classification-prompt load path (build-time codegen only - matches PRODUCT).grep(TS/MD/SH/PY sweep) established the duplicate-fork reality below; cited inline.
Grounded current state (MAIN = knowledge-hub, DS = knowledge-hub-docs-site)
Section titled “Grounded current state (MAIN = knowledge-hub, DS = knowledge-hub-docs-site)”The eval infra already lives in MAIN (canonical), duplicated in DS harness/ (stale fork):
| Asset | MAIN (canonical, present) | DS harness/ (stale fork) |
|---|---|---|
| Runners | scripts/eval-{classification,entity-classification,procurement-drafting,summarisation,search}.ts | harness/scripts/eval-*.ts |
| Vitest eval suites | __tests__/eval/{classification,entity-classification,procurement-drafting,summarisation,search}-eval.test.ts | harness/__tests__/eval/*-eval.test.ts |
| Public de-ID’d fixtures | __tests__/fixtures/eval-gold/{classification,entity}-*.json (+ entity-*.README.md) | harness/__tests__/fixtures/{classification,entity}-*.json |
| Metric baselines | __tests__/fixtures/eval-baselines/{classification,entity-classification,search,summarisation}.baseline.json | harness/__tests__/fixtures/eval-baselines/*.baseline.json |
| Shared eval lib | lib/eval/{metrics,baseline,reporter,types}.ts; lib/ai/pricing.ts; lib/anthropic.ts | harness/lib/eval/*; harness/lib/ai/pricing.ts; harness/lib/anthropic.ts (the fork) |
diff -q confirms MAIN scripts/eval-classification.ts and DS harness/scripts/eval-classification.ts
differ by exactly one structural line: MAIN line 31 import { resolveEvalFixture } from '../lib/eval/fixtures'
(plus canonical ../lib/ai/pricing, ../lib/anthropic) vs the harness fork resolving
fixtures locally against ../lib/*. MAIN imports resolve to the real modules; the harness
fork’s ../lib/ is the workaround copy.
The latent --live break (PI-4) lives in the DS fork, not MAIN. harness/lib/ contains
ONLY: anthropic.ts, ai/pricing.ts, eval/{baseline,metrics,reporter,types}.ts. It does
NOT contain ai/classify.ts, ai/skills/loader.ts, client-config.ts, or ai-parse.ts.
So harness/scripts/eval-classification.ts:383 and harness/scripts/eval-entity-classification.ts:309
(await import('../lib/ai/classify')) resolve into a directory that lacks the module -> a
--live module-not-found. MAIN’s scripts/eval-classification.ts:383 / eval-entity-classification.ts
resolve to the real lib/ai/classify.ts and are not broken.
PI-4 5-runner scope CORRECTED (carried nit 1). Of the five runners, only TWO import
ai/classify: eval-classification.ts and eval-entity-classification.ts (grep -c "ai/classify":
classification=5 refs/1 import, entity=1 import; procurement-drafting=0, summarisation=0,
search=0). The other three import only ../lib/eval/* + ../lib/anthropic - all present in
the fork, so they are NOT subject to the --live classify break. The “all 5 harness eval
runners” framing in the carried nit is wrong; the import-break surface is exactly 2 runners
(both in the fork being deleted).
KEEP slice inside harness/ (PI-12). harness/__tests__/{workflows,skills,actions,scripts,dependencies,decommission}/*
and harness/scripts/{docubot,skills}/* test docs-site operators (docubot/run-skill/SEO-audit)
and are wired into DS CI (harness/.github/.../*.yml use working-directory: harness;
harness/.github/workflows/ci.yml runs bunx vitest run). These are NOT the eval duplicate
and stay.
The classification-prompt build bridge (PI-8/9). ops/classification-prompt.md (1877 L; 24
“Phew” occurrences, worked-examples L194-809; TAXONOMY markers at L33/L255). Two build-time
call sites resolve it via the KH_PRIVATE_DOCS_DIR bridge (lib/private-docs.ts:resolvePrivateDocsDir,
fail-loud Inv 29):
scripts/generate-classification-prompt-taxonomy.ts:26-30-fetchTaxonomy()(L93) selectstaxonomy_domains{id,name,description,key_signal}+taxonomy_subtopics{name,domain_id,description}(bothis_active, ordereddisplay_order),generateTaxonomySection()(L143) renders markdown,inject()(L190) writes it back between the markers. The taxonomy is already 100% DB-derived; the markdown is only an injection target.scripts/bundle-plugin.ts:172-186-validate()reads the markdown viaparseCanonicalTaxonomy(CANONICAL_PATH)(scripts/lib/taxonomy-parser.ts:7, regex over theTAXONOMY_START..ENDblock ->Map<domain, {slug,desc}[]>) and compares it against the pluginskills/classification/SKILL.mdTAXONOMY_INJECTblock.
The denylists (PI-6). Two DS files + four MAIN consumers (worktree copies excluded):
ops/identity-denylist.json(3466 B, PC-31 canonical) and.config/ip-denylist.txt(381 B, “Phew”)..github/workflows/identity-guard.yml:78reads the existingKH_CLIENT_NAME_DENYLISTGitHub secret (L19-25: “holds the FULL canonical denylist JSON … synced manually fromops/identity-denylist.json”). ZERO docs-site dependency at the public required check.scripts/sweep-identity-relocation.ts:184-207resolvesKH_PRIVATE_DOCS_DIR/ops/identity-denylist.json(then a sibling-checkout default). Bridge-coupled..claude/hooks/ip-leak-filename-guard.sh:23reads${KH_PRIVATE_DOCS_DIR}/.config/ip-denylist.txt; silent no-op if absent (L24). Bridge-coupled.scripts/generate-purge-path-inventory.ts,__tests__/setup.tsalso reference the term set.
The eval-fixtures (PI-5). DS root eval-fixtures/{procurement-drafting,summarisation}-eval-gold-standard.json
(72 KB / 75 KB; Phew-saturated). lib/eval/fixtures.ts:47-50,76-78 (resolveEvalFixture)
routes the private names (summarisation, procurement-drafting) to
resolvePrivateDocsDir()/eval-fixtures/<file>; public names (classification, entity) already
resolve in-repo to __tests__/fixtures/eval-gold/. Consumers: scripts/eval-{procurement-drafting,summarisation}.ts,
__tests__/eval/*-eval.test.ts, __tests__/validation/eval-fixture-sync.test.ts
(describe.skipIf(!KH_PRIVATE_DOCS_DIR) for the private pair - the parity guard).
check-token-parity (PI-7). scripts/check-token-parity.ts:172 resolve(here, '../../app/globals.css')
- the parent-dir escape - vs
:173../src/styles/warm-meridian.css(the DS mirror).:180-184skip-cleans when MAIN CSS is absent (so it is a no-op in the standalone DS build and is not currently wired into any DS workflow). MAINapp/globals.cssis the source of truth; the DS file is the mirror.
In-flight collision surface (OQ-B). git log origin/id71-id104 + git diff --name-only main...origin/id71-id104:
the eval work there is the NEW ID-104 engine (lib/eval/{contract,regression,severity}.ts,
supabase/migrations/20260615170612_id104_eval_engine.sql, scripts/mcp-eval/fixtures.ts,
__tests__/lib/eval/{contract,regression,severity}.test.ts) and ID-71 MCP-tool collapses. It does
NOT touch lib/eval/fixtures.ts, eval-fixtures/*.json, resolveEvalFixture, or the
gold-standard runners. scripts/mcp-eval/fixtures.ts is a DISTINCT module from lib/eval/fixtures.ts.
The shared file the brief named, scripts/mcp-eval/functional-correctness.ts (83 KB), is the
bid->form rename surface - orthogonal to the gold-standard fixture path. So the PI-5 rewire and
the id71-id104 eval-engine work occupy different files; they coordinate on sequencing, not on a
shared edit.
Proposed changes (per invariant)
Section titled “Proposed changes (per invariant)”Each PI-N maps to a proposed change; verification lives in Testing and validation.
PI-1 - de-ID precondition for MAIN entry
Section titled “PI-1 - de-ID precondition for MAIN entry”For every item entering MAIN (PI-5 fixtures, PI-8 worked-examples), de-ID in the moving
commit, before it lands tracked in MAIN: swap first-client organisation/product strings for
Example Client Limited + the {CLIENT_*} placeholders the ID-95.5 skeleton already uses (the
public __tests__/fixtures/eval-gold/classification-*.json is the de-ID precedent - 21 neutral
refs, zero “Phew”). Run scripts/sweep-identity-relocation.ts (PC-31 term set) over the relocated
MAIN paths as the gate. De-ID is a relocation precondition, NOT a flip precondition.
PI-2 - NOT flip-blocking
Section titled “PI-2 - NOT flip-blocking”No code change. ID-114 adds no dependency to/from the flip Task; identity-guard.yml stays scoped
to the public repo. The de-ID gate (PI-1) fires on “about to enter MAIN”, because at that point
identity-guard scans it. Asserted by review of the workflow config + the Task dependency edges.
PI-3 - harness/ eval slice -> MAIN (already canonical) -> DELETE the DS duplicate
Section titled “PI-3 - harness/ eval slice -> MAIN (already canonical) -> DELETE the DS duplicate”Because MAIN already holds the canonical runners, suites, public fixtures, baselines, and
lib/eval/*, the action is delete the duplicate eval slice from DS harness/, not move it:
harness/scripts/eval-*.ts, harness/__tests__/eval/*, harness/__tests__/fixtures/{classification,entity}-*.json,
harness/__tests__/fixtures/eval-baselines/*, and the eval-only parts of harness/lib/ (see PI-4).
Before deleting, diff each DS harness eval file against its MAIN twin and forward-port any
genuine divergence into MAIN (the only known divergence is the resolveEvalFixture import line,
already canonical in MAIN). The KEEP slice of harness/ (operators/docubot/skills - PI-12) stays;
the DS harness/.github/workflows/ci.yml bunx vitest run keeps passing once the eval *.test.ts
- fixtures are removed (its remaining suites are the operator tests). Decision: ID-114 owns the DS deletion + the divergence forward-port; ID-68 remains de-ID policy owner.
PI-4 - eliminate the harness/lib fork; fix the latent --live break
Section titled “PI-4 - eliminate the harness/lib fork; fix the latent --live break”Resolved BY PI-3’s deletion. Deleting harness/lib/{anthropic.ts,ai/pricing.ts,eval/*} and the two
classify-importing runners removes the fork and the broken import('../lib/ai/classify') sites
(harness/scripts/eval-classification.ts:383, harness/scripts/eval-entity-classification.ts:309)
in one move - MAIN’s twin already imports the real lib/ai/classify.ts. Scope correction baked
in: only those 2 runners carry the classify import; the other 3 do not. No new MAIN code; this is
a duplicate-removal that incidentally retires the break.
PI-5 - root eval-fixtures/*.json -> MAIN private-gated, de-ID’d; move-then-rewire-lockstep
Section titled “PI-5 - root eval-fixtures/*.json -> MAIN private-gated, de-ID’d; move-then-rewire-lockstep”Three separate commits, strict order:
- (MAIN, ID-114) Add de-ID’d copies of
procurement-drafting/summarisationgold standards to the MAIN private-gated home__tests__/fixtures/eval-gold/(alongside the existing public pair), de-ID’d toExample Client Limitedper PI-1. Do NOT yet flipresolveEvalFixture. DS originals untouched - the bridge route still resolves them. - (MAIN, lockstep-coordinated with ID-104 / ID-71.14) Flip
lib/eval/fixtures.ts: movesummarisation+procurement-draftingfromPRIVATE_FIXTURES(bridge route) toPUBLIC_FIXTURES(in-repoPUBLIC_FIXTURE_DIR), and update__tests__/validation/eval-fixture-sync.test.tsso the pair is no longerskipIf(!KH_PRIVATE_DOCS_DIR)-gated. This is the rewire commit; it lands in the Task that next touches these fixtures (per OQ-B resolution below), not ahead of it. - (DS, after the rewire commit lands) Delete
eval-fixtures/{procurement-drafting,summarisation}-eval-gold-standard.jsonfrom DS. The bridge keeps resolving the unmoved copy until step 2 is live, so there is never a broken-path window. The UUID re-key ofexpected_kb_items_used(RESEARCH OQ-4) is flagged to ID-104, not owned here.
PI-6 - denylists -> secret/config store; evaluate the EXISTING secret FIRST (carried nit 2)
Section titled “PI-6 - denylists -> secret/config store; evaluate the EXISTING secret FIRST (carried nit 2)”The KH_CLIENT_NAME_DENYLIST GitHub-encrypted secret already exists and is already the canonical
store for the public required check (identity-guard.yml:78). So the net-new question is only for
the LOCAL consumers (sweep-identity-relocation.ts, ip-leak-filename-guard.sh,
generate-purge-path-inventory.ts) that still bridge to a DS path. Proposed end-state: a single
private secret/config home (the existing KH_CLIENT_NAME_DENYLIST JSON as the canonical source;
the .config/ip-denylist.txt term-set derivable from it), with local consumers resolving from a
MAIN-checkout-relative config path (or the secret-materialised file in CI) rather than
KH_PRIVATE_DOCS_DIR. The sweep already has a sibling-checkout default fallback (:191-207); the
move is to make a MAIN-resident path the primary, not the bridge. Final home + whether to consolidate
the two denylists into one source is OQ-C (Liam) - do not stand up a NEW secret; the existing one
is the store.
PI-7 - check-token-parity: keep the function, re-home the coupling
Section titled “PI-7 - check-token-parity: keep the function, re-home the coupling”Remove the ../../app/globals.css parent-dir escape (check-token-parity.ts:172). Recommended
shape: re-home the guard to MAIN, where it reads MAIN app/globals.css directly (no escape) and
fetches the DS warm-meridian.css mirror via the existing GitHub-App-token bridge (the docubot /
sync-source-docs pattern) - flipping the coupling so it points docs-site-ward, not MAIN-ward. The
check still runs in CI and still fails on a real token drift; the DS skip-clean branch
(:180-184) becomes unnecessary once MAIN owns the source side. MAIN-homed guard vs shared composite
action is OQ-D (Liam) (RESEARCH OQ-5).
PI-8 - de-ID the residual worked-examples
Section titled “PI-8 - de-ID the residual worked-examples”In ops/classification-prompt.md L194-809 (24 “Phew” occurrences), replace first-client
organisation/product prose with the {CLIENT_*} placeholders the ID-95.5 skeleton already uses, OR
delete the region as a duplicate of tenant_config.classificationDisambiguation. Gate with
sweep-identity-relocation.ts -> zero hits. This is independent of the OQ-A mechanism choice and can
land first.
PI-9 - TAXONOMY/classification codegen regenerates from DB direct; bridge retired at both sites
Section titled “PI-9 - TAXONOMY/classification codegen regenerates from DB direct; bridge retired at both sites”OQ-A RESOLVED -> option (a): build-step codegen that materialises the DB-derived taxonomy into a
MAIN-homed artefact (not the no-intermediary runtime fetch). Justification: bundle-plugin.ts:validate()
needs a canonical taxonomy to diff the plugin SKILL.md against, and its reader is parseCanonicalTaxonomy()
(markdown-block regex). A materialised MAIN artefact (a) keeps validate()’s contract intact with a
one-line path change, (b) preserves a reviewable diff of the canonical taxonomy in git, (c) avoids
giving bundle-plugin a DB/network dependency at plugin-build time (it currently needs none). The
no-intermediary fetch would force a live DB round-trip into every bundle-plugin run and rewrite
parseCanonicalTaxonomy - more surface, less reviewability, for no gain since the taxonomy is small
and already materialised today. Mechanism:
- Source of truth:
taxonomy_domains+taxonomy_subtopics(the DB columnsfetchTaxonomy()already reads) - the canonical taxonomy. No DS markdown is load-bearing. - Codegen target: rewire
generate-classification-prompt-taxonomy.tsto write thegenerateTaxonomySection()output to a MAIN-homed artefact (e.g.lib/ai/taxonomy/canonical-taxonomy.generated.mdor.json- exact path is an impl detail for PLAN), droppingresolvePrivateDocsDir()(L20,26-30). The script keepsfetchTaxonomy+generateTaxonomySection; only the write target + path import change. - Validation reader: point
bundle-plugin.ts:validate()CANONICAL_PATH(L175-179) at the same MAIN artefact;parseCanonicalTaxonomy()is reused unchanged if the artefact keeps theTAXONOMY_START..END#### N. DOMAINshape, or gets a trivial JSON-reader variant if.jsonis chosen (PLAN decides).
- Retire the bridge at both sites: remove
resolvePrivateDocsDir()import/use fromgenerate-classification-prompt-taxonomy.tsandbundle-plugin.ts. After this, withKH_PRIVATE_DOCS_DIRunset, both codegen and plugin-bundle validation succeed. No migration/DDL: the taxonomy tables already exist and are read-only here. No newREVOKE/search_path(no new function).--env=prodopt-in (assertEnvFlag, L83) is preserved for the codegen’s DB fetch.
PI-10 - public MAIN carries only the generic skeleton
Section titled “PI-10 - public MAIN carries only the generic skeleton”Ensured by PI-8 (worked-examples de-ID’d) + PI-9 (the materialised artefact is the generic
DB-regenerated taxonomy only). No per-client disambiguation values enter tracked MAIN; those stay in
tenant_config.classificationDisambiguation (ID-95.5). No code beyond PI-8/9.
PI-11 - ID-95.5 runtime split NOT rebuilt
Section titled “PI-11 - ID-95.5 runtime split NOT rebuilt”No change to lib/ai/classify.ts runtime prompt-assembly, buildDisambiguationBlock(), the skeleton,
or tenant_config. ID-114 touches only build-time codegen + the docs-site-resident artefacts. Asserted
by git diff showing zero edits to the runtime classifier path.
PI-12 - KEEP set; wire DS root __tests__/ into DS CI
Section titled “PI-12 - KEEP set; wire DS root __tests__/ into DS CI”No relocation of workflow-evaluation/sessions/**, the docubot lane, the workflow-evaluator operators,
the KEEP slice of harness/, or the DS root __tests__/ + vitest.config.ts. Additionally, wire the
DS root __tests__/ into DS CI so it executes (currently harness/.github/workflows/ci.yml runs only
the harness/ vitest from working-directory: harness; the DS root tests are unwired). Add a CI job /
extend the existing one to run the root suite from the repo root. No MAIN dependency added to any KEEP item.
PI-13 - no new external-library API surface
Section titled “PI-13 - no new external-library API surface”No new dependency in package.json/requirements.txt. The only external-library calls in touched code
are pre-existing: @supabase/supabase-js createClient/.from().select() in the codegen (carried
unchanged) and the Anthropic SDK in the (being-deleted) harness fork. The Q-EX2 import-and-call gate is
not triggered (PRODUCT PI-13 scope). No empirical-verification block required for this TECH.
Testing and validation
Section titled “Testing and validation”Maps each invariant to a concrete check. KH bars throughout: bun run test (NEVER bun test);
behaviour-first tests (reference/test-philosophy.md); UK English; no barrel re-exports; semantic
tokens; sb()/tryQuery() where Supabase is touched (codegen uses the existing service-role
createClient pattern, unchanged).
- PI-1 / PI-8 / PI-10:
bun run scripts/sweep-identity-relocation.tsover the relocated MAIN paths- the de-ID’d
ops/classification-prompt.md-> zero hits;grep -ci phewover each relocated MAIN file -> 0. No first-client name in any tracked MAIN file introduced by this Task.
- the de-ID’d
- PI-2: review
identity-guard.ymlstill scopes to the public repo; ID-114 has no flip-Task edge; DS-resident identity (KEEP items) trips no required check. - PI-3 / PI-4: post-deletion, no
harness/scripts/eval-*.ts,harness/__tests__/eval/*, orharness/lib/{anthropic,ai/pricing,eval/*}remain in DS;grep "import('../lib/ai/classify')"over DSharness/-> 0; MAINbun run testgreen (the canonical__tests__/eval/*already exist); MAIN--liveclassify path (scripts/eval-classification.ts) resolveslib/ai/classify.ts(smoke: dry import). DSharness/.github/workflows/ci.ymlbunx vitest runstill green on the remaining operator suites. - PI-5: after step 1, MAIN holds de-ID’d
procurement-drafting/summarisationfixtures; after step 2,resolveEvalFixture('procurement-drafting'|'summarisation')returns the in-repo path and__tests__/validation/eval-fixture-sync.test.tsruns them un-skipped; at no commit between move and rewire does any consumer resolve a missing path (the bridge resolves the unmoved DS copy until step 2); DS delete (step 3) only after step 2 lands.bun run testgreen across each commit. - PI-6: no MAIN consumer reads either denylist from a DS path after the move;
KH_CLIENT_NAME_DENYLISTsync + the identity sweep produce the SAME term set as before (diff the resolved term set pre/post).ip-leak-filename-guard.shstill blocks a denylisted term. - PI-7: no
../../app/globals.cssescape remains in the re-homed guard; introduce a deliberate token mismatch -> the check fails; revert -> passes; the check runs in CI. - PI-9: with
KH_PRIVATE_DOCS_DIRUNSET,bun run scripts/generate-classification-prompt-taxonomy.tsandbun run build:plugin(->bundle-plugin.ts:validate()) both succeed; no build path resolvesops/classification-prompt.mdvia the bridge (grep "resolvePrivateDocsDir" scripts/{generate-classification-prompt-taxonomy,bundle-plugin}.ts-> 0); the generated artefact’s domains/subtopics match the active DBtaxonomy_domains/taxonomy_subtopicsrows. - PI-11:
git diffshows zero edits tolib/ai/classify.ts/buildDisambiguationBlock/ the skeleton /tenant_config; no new per-client store; classification eval accuracy unchanged on the gold standard (scripts/eval-classification.tsbaseline parity). - PI-12: KEEP items still in DS post-Task; DS root
__tests__/executes in DS CI (was unexecuted); docubot/evaluator lanes operate on the DS corpus unchanged. - PI-13:
git diff package.json requirements.txtshows no new dependency.
Sequencing and parallelisation
Section titled “Sequencing and parallelisation”Sibling-only Subtask deps within ID-114; cross-Task coordination (ID-104 / ID-71.14 for the PI-5 step-2 rewire) is a Task-level handshake, not a Subtask dep (no cross-Task Subtask dependency is created). Proposed wave order:
- PI-6 denylists + PI-8 de-ID worked-examples - low-coupling, unblock nothing, can run parallel.
- PI-3/PI-4 harness duplicate deletion (forward-port divergence first) - atomic; retires the fork+break.
- PI-9 codegen regenerate-from-DB + bridge retirement - depends on PI-8 landing in the same file is NOT required (different region), but sequence PI-8 first to keep the de-ID gate clean.
- PI-5 step 1 (MAIN add de-ID’d fixtures) - independent; step 2 (rewire) lands lockstep in the OQ-B-resolved Task; step 3 (DS delete) strictly after step 2.
- PI-7 check-token-parity re-home - independent; resolve OQ-D shape first.
- PI-12 wire DS root
__tests__/into CI - DS-only, independent.
OQ-B resolution (eval-fixture rewire sequencing handshake)
Section titled “OQ-B resolution (eval-fixture rewire sequencing handshake)”The id71-id104 branch does NOT touch lib/eval/fixtures.ts / eval-fixtures / resolveEvalFixture
(grounded above), so there is no file collision - only a sequencing courtesy. Recommended handshake:
ID-114 owns PI-5 step 1 (add de-ID’d MAIN fixtures) and step 3 (DS delete). PI-5 step 2 (the
resolveEvalFixture flip) is a ~10-line change ID-114 also authors, but it MERGES only after the
id71-id104 eval work has landed on the shared trunk, to avoid two eval-touching branches racing the same
bun run test gate - i.e. ID-114 does not merge step 2 ahead of id71-id104, it rebases onto it. Because
the files are disjoint, no rebase conflict is expected; the handshake is purely “land after, not before”.
The UUID re-key (OQ-4) is deferred to ID-104. If Liam prefers ID-71.14 to own the flip entirely
(fold step 2 into the fixture-rename lane), that is the cleaner ownership boundary - flagged as OQ-E.
Risks and mitigations
Section titled “Risks and mitigations”- Deleting the harness eval duplicate loses unique history/divergence. Mitigate:
diffevery DS harness eval file against its MAIN twin and forward-port real divergence into MAIN BEFORE deleting; the only known divergence is theresolveEvalFixtureimport (already canonical in MAIN). - PI-5 broken-path window if the 3-commit order is violated. Mitigate: enforce move(1) -> rewire(2) ->
delete(3); the
skipIf(!KH_PRIVATE_DOCS_DIR)guard ineval-fixture-sync.test.tskeeps the private pair green during transition. Nevergit mvthe fixtures and delete the DS copy in one commit. - PI-9 bridge retirement could break
bundle-pluginvalidation if the MAIN artefact shape drifts fromparseCanonicalTaxonomy’s regex. Mitigate: keep theTAXONOMY_START..END+#### N. DOMAIN/- \slug`: desc` shape in the materialised artefact, OR add a JSON-reader variant in the same PR; the PI-9 validation test (KH_PRIVATE_DOCS_DIR unset) catches drift. - Denylist regression (PI-6) if the term set differs post-move. Mitigate: diff the resolved term set
pre/post; the
KH_CLIENT_NAME_DENYLISTsecret stays the canonical source. - De-ID miss lands client identity in MAIN (PI-1). Mitigate: the sweep gate is the relocation precondition; identity-guard will scan MAIN once landed, so a miss is caught - but the gate must run pre-commit, not post.
Open questions (for Liam)
Section titled “Open questions (for Liam)”- OQ-C - denylist canonical home (PI-6). The existing
KH_CLIENT_NAME_DENYLISTsecret is confirmed as the store for the public check; do the LOCAL consumers (sweep,ip-leak-filename-guard.sh,generate-purge-path-inventory) resolve from a MAIN-checkout config path, or a secret-materialised file in CI? And: consolidate.config/ip-denylist.txt+ops/identity-denylist.jsoninto one source? (Do NOT stand up a new secret.) - OQ-D - check-token-parity re-home shape (PI-7). MAIN-homed guard (reads MAIN CSS, fetches DS mirror via GitHub-App bridge) vs shared composite action invoked from both repos?
- OQ-E - PI-5 step-2 ownership. ID-114 authors the
resolveEvalFixtureflip and merges-after-id71-id104, OR fold the flip into ID-71.14’s fixture-rename lane entirely (cleaner ownership)? (OQ-A/OQ-B are resolved in this TECH; these three home/ownership choices remain.)
Sources (read this dispatch)
Section titled “Sources (read this dispatch)”- {114.1} RESEARCH + {114.2} PRODUCT (
specs/id-114-docs-site-relocation/), read in full. - MAIN:
scripts/generate-classification-prompt-taxonomy.ts(full),scripts/bundle-plugin.ts:140-280,scripts/lib/taxonomy-parser.ts(full),lib/eval/fixtures.ts(full),lib/private-docs.ts(full),scripts/eval-{classification,entity-classification,procurement-drafting,summarisation,search}.ts(import scope),__tests__/eval/*,__tests__/fixtures/eval-gold/*,__tests__/fixtures/eval-baselines/*,scripts/sweep-identity-relocation.ts:180-219,.claude/hooks/ip-leak-filename-guard.sh,.github/workflows/identity-guard.yml,__tests__/validation/eval-fixture-sync.test.ts,vitest.config.ts. - DS:
harness/{scripts,lib,__tests__,package.json,tsconfig.json,vitest.config.ts},harness/.github/workflows/ci.yml,eval-fixtures/*.json,ops/{classification-prompt.md,identity-denylist.json},.config/ip-denylist.txt,scripts/check-token-parity.ts. git log/diff origin/id71-id104(OQ-B collision check) - eval work is disjoint from the gold-standard fixture path.