Skip to content

ID-135 {135.3} TECH — OKF human search/browse + source_document provenance UI

ID-135 {135.3} TECH — OKF human search/browse + source_document provenance UI

Section titled “ID-135 {135.3} TECH — OKF human search/browse + source_document provenance UI”

Status: TECH artefact — not self-ratified. Authored 28/06/2026 by a fresh Planner instance (Q-PLANNER-2 — NOT the {135.1} RESEARCH author, NOT the {135.2} PRODUCT author). British English throughout; dates DD/MM/YYYY.

Predecessors (read in full): {135.2} PRODUCT.md (gated PASS_WITH_NOTES) — 32 numbered behaviour invariants BI-1 … BI-32 plus the RD-1…4 / AAT-1…4 / BND-1…2 / OOS-1 decision flags; {135.1} RESEARCH.md (gated PASS) — the per-surface reuse-or-rebuild verdict matrix (§2) and read-contract dependency split (§3). This TECH maps one proposed change (or an explicit “no change”) per invariant and is the implementation spine the Checker verifies against.

Invariant-tag convention (inherited): a bare BI-N is THIS spec’s invariant; any other Task’s invariant is Task-qualified (e.g. id-111 B-28, id-117 INV-19, id-131 BI-23, {131.11} AC4).

1.1 Code-intelligence orientation (cited verbatim, not paraphrased)

Section titled “1.1 Code-intelligence orientation (cited verbatim, not paraphrased)”

GitNexus runs against the indexed repo at absolute path /Users/liamj/Documents/development/canonical (the bare name canonical is ambiguous — two registered repos resolve to it: …/canonical and …/procurement — so the absolute path is passed to disambiguate). On-disk reads re-verified in the id-135 worktree at HEAD 3a222eac.

S438 {135.19} reconcile (DR-013): the “EXTEND verdict” / “REUSE-AS-IS” / “HARD RULE” framing in this section is superseded. Per DR-013 the id-135 surfaces are net-new-by-design: Surface A reuses id-111 only at the leaf/pattern level (its single-grain, non-paginated useReferenceData forces the polymorphic abstraction), and Surface B reuses id-117’s version-chain data leg only (RPC + /versions route + diff link-out) — NOT UnifiedDiffContainer. Read the code-intel below as reuse evidence, not an extend-in-place mandate.

Surface-A reuse base (id-111 — EXTEND verdict):

  • gitnexus_context({name: 'ReferenceContent'})Function:app/reference/reference-content.tsx:ReferenceContent (44–165). incoming.calls: exactly ONE — ReferencePage (app/reference/page.tsx). outgoing.calls: ReferenceSearchBox, ReferenceFilterControls (both file-local, not exported, same file), ReferenceCard (components/reference/reference-card.tsx), ReferenceLoadingSkeleton / ReferenceEmptyState / ReferenceErrorState (components/reference/reference-states.tsx, exported), Button, useReferenceData. processes: []. The complete Surface-A reuse base; the search box + filter controls are file-private, so Surface A copy-and-generalises them, while the states trio is import-reusable.
  • gitnexus_context({name: 'useReferenceData'})Function:hooks/reference/use-reference-data.ts:useReferenceData (146–369). incoming.calls: exactly ONE — ReferenceContent. processes: []. Single-caller, so copy-and-generalise to a polymorphic multi-grain hook without mutating /reference. On-disk: list mode = useInfiniteQuery over the reference_list RPC (offset cursor, PAGE_SIZE = 48); search mode = useQuery POSTing /api/search/reference (NOT /api/search — reference search is reference-scoped, B-23). URL-driven state via useSearchParams/router.push (?q, ?domain, ?subtopic, ?source, ?from, ?to).

Surface-B reuse base (id-117 REUSE-AS-IS + id-111 EXTEND):

  • gitnexus_context({name: 'UnifiedDiffContainer'})Function:components/diff/unified-diff-container.tsx:UnifiedDiffContainer (74–105). incoming.calls: exactly ONE — DocumentDiffPage (app/documents/[id]/diff/page.tsx). outgoing.calls: BinaryDiffPane, toRevisionBlob, RevisionDiffView, deriveRenderMode. The diff/version-compare ENGINE Surface B links to is shipped; never rebuilt (HARD RULE).
  • gitnexus_context({name: 'sourceDocumentRevisionToUnified'})Function:lib/diff/adapters/source-document-revision.ts:sourceDocumentRevisionToUnified (53–83). incoming.calls: exactly ONE — DocumentDiffPage. The SD→UnifiedDiff adapter is shipped; reused via the link-out, not re-implemented.
  • VersionHistory / RevisionHistoryPanel reuse check (HARD-RULE due diligence): read components/item-detail/version-history.tsx — it is coupled to content_history via fetchItemHistoryList/ItemHistoryEntry and takes itemId/currentContent props. The SD version chain is the get_document_version_chain RPC shape (version/parent_id/ created_at/filename), structurally distinct. So the BI-25 inline version list is a thin presentational map of the shipped RPC rows — not a net-new diff/compare/version engine (that engine, UnifiedDiffContainer, IS reused for BI-26). This is the deliberate “state why it cannot be reused” the HARD RULE demands.

On-disk reads (load-bearing):

  • app/api/source-documents/[id]/route.ts (GET) — auths via getAuthenticatedClient()authFailureResponse, UUID-gates, then reads with createServiceClient(): .from('source_documents').select('*').eq('id', id).single(), then joins content_items (.from('content_items').select('id, title, content_type, primary_domain, primary_subtopic, freshness, created_at').eq('source_document_id', id).is('archived_at', null)) and returns { ...doc, content_items: items ?? [] }. content_items is eliminated by id-131 BI-9 — this read breaks (BND-1 / BI-29).

  • app/api/source-documents/[id]/versions/route.ts — auths, UUID-gates, calls get_document_version_chain RPC, returns { document_id, total_versions, versions }. Shipped, reused as-is (BI-25).

  • app/documents/[id]/diff/page.tsx (DocumentDiffPage) — sibling of the net-new detail page; resolves the parent_id version pair under createClient() (RLS, id-117 INV-19), projects via sourceDocumentRevisionToUnified, renders UnifiedDiffContainer. Confirms app/documents/[id]/ holds only diff/ (BND-2: page.tsx is net-new).

  • app/api/search/route.ts (POST) — auths, rate-limits, embeds, calls hybrid_search RPC; response schema is { results: z.array(z.unknown()), count }results are opaque today (content_items-shaped via hooks/use-search.ts’s SearchResult = Omit<ContentListItem,…>, types/content.ts). {131.11} AC4 must hand this path a typed polymorphic shape — Surface A consumes that, never the legacy SearchResult (AAT-2).

  • app/reference/[id]/page.tsx + reference-detail-client.tsx — the id-111 read-only detail shell pattern Surface B reuses: server component, UUID gate → notFound(), primary read, secondary tryQuery enrichment that degrades (never 404s) on failure, client presenter. The B-28 provenance field set is verbatim here: original_filename, filename, mime_type, file_size, extraction_method, source_url, created_at, with extractionMethodLabel() plain-language rendering (docling*→“Extracted via Docling”, never the raw enum).

  • ccc search fallback disclosure: the cocoindex-code daemon errors in this environment (DaemonStartError — the same failure {135.1}/{135.2} recorded), so the ccc orientation lane is unavailable. The findings above rest on the definitive GitNexus + on-disk evidence, conclusive for these two surfaces. Not a greenfield surface — the reuse base is fully identified.

1.2 Per-surface spec tier (RESEARCH §6.2)

Section titled “1.2 Per-surface spec tier (RESEARCH §6.2)”
  • Surface A — heavier. Polymorphic multi-grain result model, in-flight {131.11} dependencies (AAT-1/2/3), the kind-narrow + filter UX, URL-driven state, incremental load. New domain subdirs + a polymorphic result card + a generalised data hook + a discriminated-union type.
  • Surface B — lighter (composition). Largely composition of shipped id-111 detail-shell pattern + id-117 diff/version leg + id-111 B-28 provenance field set, plus one net-new page, the BND-1 route re-point branch, and the citations / derived-pairs / version-list sections.

S438 {135.19} reconcile (DR-013): “copy-and-generalisation of the id-111 reference stack (RESEARCH §2 EXTEND)” reads as net-new-by-design — the parallel app/search/ + components/corpus-search/ tree is a fresh build that reuses id-111 only at leaf/pattern level; Surface B composes id-117’s version-chain data leg + diff link-out only, never UnifiedDiffContainer.

Surface A lives under net-new app/search/, components/corpus-search/, hooks/corpus-search/, types/corpus-search.ts — a parallel copy-and-generalisation of the id-111 reference stack (RESEARCH §2 EXTEND), leaving /reference untouched (useReferenceData is single-caller; mutating it in place would disturb the shipped reference surface).

Surface B adds app/documents/[id]/page.tsx (sibling to the shipped diff/) plus components/source-document-detail/ and hooks/source-document-detail/, composing the id-111 detail-shell pattern and id-117 diff/version leg.

Both surfaces are authenticated, read-only (BI-1): server reads use getAuthenticatedClient() / createClient() (RLS) → check auth.successauthFailureResponse(auth); neither is added to proxy.ts publicRoutes (the publicRoutes allowlist is for NEW public routes — these are not public; omitting them is what makes an unauthenticated visitor redirect to /login).

3. Proposed changes — 1:1 mapping against PRODUCT BI-1 … BI-32

Section titled “3. Proposed changes — 1:1 mapping against PRODUCT BI-1 … BI-32”

Each invariant maps to one or more concrete proposed changes (route / component / hook / query-key / type), or an explicit “no change”. Artefact names are catalogued in §4.

BIProposed change
BI-1 (authenticated, read-only)Surface A app/search/page.tsx + Surface B app/documents/[id]/page.tsx are server components reading via createClient() (RLS) / the auth’d API routes; all API routes keep getAuthenticatedClient()authFailureResponse(auth). Neither route added to proxy.ts publicRoutes (explicit non-change — these are authenticated, not public). No create/update/delete affordance rendered on either surface.
BI-2 (role-uniform)No role gating in any new component; no requiresEdit on the Surface-A nav entry (§ BI-21). Inherits id-111 B-21. No proposed change introduces a role branch.
BI-3 (AI-invisible)CorpusResultCard and useCorpusSearch never render/return similarity/score/model/profile fields; the polymorphic result type (§5) deliberately omits score fields from the display shape. SourceDocumentProvenance renders the {131.9} classification family as plain metadata with no “AI classified”/confidence chrome (drops classification_confidence/classification_reasoning from display). Per ai-visibility-policy.md.
BI-4 (Warm Meridian tokens, AA)All new components use semantic tokens only (text-foreground, bg-muted, border-border, text-primary, Badge variants) — no raw Tailwind colours; new tokens (if any) defined per components/CLAUDE.md. Every kind label / status / state carries text+icon, never colour-only.
BI-5 (UK English, DD/MM/YYYY)All copy UK English; dates via the shipped formatDateUK() (lib/format) reused by both surfaces.
BI-6 (corpus visibility scope)Surface A inherits the hybrid_search scoping (application_type from workspace_id, §9) — no widening/narrowing here; Surface B inherits the source_documents RLS row read (createClient()). Exact Surface-A scope reconciles with the shipped {131.11} contract (AAT-1). No new scoping code.

Surface A — corpus search/browse (BI-7 … BI-21)

Section titled “Surface A — corpus search/browse (BI-7 … BI-21)”
BIProposed change
BI-7 (distinct corpus-search entry)New route /search (verified free; recommended path — semantically search-first; /browse is freed by id-131 BI-12 as an alternative, but route naming is co-decided with id-118 nav, BI-21). Server shell app/search/page.tsx renders client CorpusSearchContent (app/search/search-content.tsx). Distinct surface, links INTO /library + /documents/[id] + /reference/[id] (RD-1) — not a /library mode.
BI-8 (no-query default state)CorpusSearchContent renders a guidance/empty state when no ?q and no filters — reuse ReferenceEmptyState (its hasActiveQueryOrFilters boolean already distinguishes guidance vs no-results) or a corpus-worded CorpusSearchStates.NoQuery if copy must diverge. Distinct from BI-18/BI-19.
BI-9 (query submission + URL state)useCorpusSearch mirrors useReferenceData’s URL-driven pattern: ?q + filter params via useSearchParams/router.push (shareable, restorable, back/forward-safe). Search box keyed on ?q (key={query}) — clean remount, no setState-in-effect (components/CLAUDE.md). Re-submitting an unchanged set hits the TanStack cache (no duplicate fetch).
BI-10 (default scope = ALL grains)useCorpusSearch POSTs /api/search with no type/scope narrow by default → the §9 search-ALL contract (q_a_pair + content_chunk→SD collapse + reference_item, per-grain normalise+merge). Consumes {131.11}‘s polymorphic shape (AAT-1).
BI-11 (answer-first implicit boost)No client-side ranking — ordering comes from the one shipped application_type profile in hybrid_search (~×1.1 answer-first boost, server-side). The card renders results in returned order; never exposes the boost (BI-3). (AAT-3workspace_idapplication_type threading is {131.11} AC4.)
BI-12 (three result kinds, labelled)CorpusResultCard switches on the result’s discriminated kind (answer/document/reference) and renders a text+icon kind label. content_chunk hits never appear — they arrive already collapsed to source_document by the §9 contract (de-dup by provenance, server-side); the type union has no content_chunk member.
BI-13 (per-kind card content + link target)CorpusResultCard branches: answer → question title + answer snippet + scope/domain badges → links /library; document → classified title/filename + summary + primary_domain/primary_subtopic ({131.9} cols) → links /documents/[id] (Surface B); reference → reference title + source → links /reference/[id] (RD-3), reusing the id-111 reference card visual pattern.
BI-14 (polymorphic id resolution)The link target derives from the discriminated-union kind field (carried by the id-131 BI-27 content_type = owner_kind, AAT-2). The type makes mis-routing unrepresentable (each variant carries its own id + destination builder).
BI-15 (kind-narrow control narrows only)CorpusKindNarrow control (answers/documents/references) sets a ?kind= param that useCorpusSearch passes as the §9 type/scope NARROW (narrows only, never widens, RD-2). Updates URL (BI-9); clearing returns to ALL grains.
BI-16 (metadata filters)CorpusFilterControls (copy-and-generalise of id-111 ReferenceFilterControls) — domain / subtopic / date, pushed into the search params. q_a_pair results restricted to publication_status = published (id-131 BI-20, shipped) — enforced server-side by hybrid_search; Surface A asserts it, adds no client filter.
BI-17 (loading state)Reuse ReferenceLoadingSkeleton (direct import @/components/reference/reference-states). In-flight supersession via the TanStack query key changing on new ?q (AbortController signal like the reference search path) — no stale render over newer.
BI-18 (empty/no-results)ReferenceEmptyState with hasActiveQueryOrFilters = true (or CorpusSearchStates.NoResults) — guidance to broaden/clear, no zero-count or technical wording. Distinct from BI-8/BI-19.
BI-19 (error state)ReferenceErrorState (or CorpusSearchStates.Error) — non-technical message + retry re-issuing the same query+filter set. No raw error/stack/model/score.
BI-20 (pagination / incremental load)useCorpusSearch uses useInfiniteQuery with an offset cursor (mirroring useReferenceData’s PAGE_SIZE list path) once {131.11}‘s /api/search exposes offset/limit paging; stable ordering + no cross-page dupes + explicit end-of-results indicator. AAT-1 — if {131.11} ships ranked top-N only, fall back to a limit-raising “load more”; the BI-20 contract (stable order, no dupes, end indicator) holds either way.
BI-21 (nav placement, co-decided w/ id-118)Extend components/shell/site-header.tsx NAV_LINKS with the corpus search/browse entry ({ href: '/search', label, icon, requiresEdit: false }), reusing the existing active-state machinery. Placed under the id-118-ratified grouping (pending) — do NOT presume option-B “Knowledge Base”; ship consistently with whatever id-118 ratifies, never a divergent slot. PLAN flags this as id-118-coupled.

Surface B — source_document detail/provenance (BI-22 … BI-31)

Section titled “Surface B — source_document detail/provenance (BI-22 … BI-31)”
BIProposed change
BI-22 (net-new detail page, not in nav)New app/documents/[id]/page.tsx (server), sibling of diff/ (BND-2). Reuses the id-111 detail-shell pattern (server page → notFound() gate → primary read → tryQuery sections → client presenter SourceDocumentDetailClient). No top-nav slot.
BI-23 (not-found / invalid-id)UUID-format gate (the UUID_RE pattern reused from app/reference/[id]/page.tsx) → notFound() for non-UUID; a well-formed id with no source_documents row → notFound(). Never a 500/blank/partial shell.
BI-24 (provenance panel)SourceDocumentProvenance renders the id-111 B-28 field set verbatim (original_filename/filename, mime_type, extraction_method via reused extractionMethodLabel() plain language, source_url, landed created_at via formatDateUK) plus the {131.9} classification family (primary_domain, primary_subtopic, secondary_domain, secondary_subtopic, summary, ai_keywords) as plain metadata — no AI/confidence chrome (BI-3). Null fields omitted or “not recorded”, never an error.
BI-25 (version chain, inline list)DocumentVersionList — thin presentational map of get_document_version_chain rows from the shipped /api/source-documents/[id]/versions route, fetched via useDocumentVersions (TanStack). Ordered along version/parent_id; current version marked; single-version → single-entry list, not empty/error (RD-4).
BI-26 (diff link — reuse, never rebuild)Each non-current version row links to /documents/[id]/diff (shipped id-117 surface reusing UnifiedDiffContainer + sourceDocumentRevisionToUnified). No net-new version/diff component (HARD RULE). Per DR-013, link-out is the only form — Surface B never composes UnifiedDiffContainer inline (RD-4).
BI-27 (citations panel)DocumentCitationsPanel reads the id-131 BI-23 CITE-EXT cited_target_kind ({q_a_pair, reference_item, source_document, concept}) via a citations read (new app/api/source-documents/[id]/citations/route.ts or a server read in page.tsx). citations is 0 rows today → clear empty state, not error. Design against the extended contract (AAT-4).
BI-28 (derived q_a_pairs list)DerivedPairsList lists q_a_pairs where source_document_id = id AND publication_status = 'published'; each links to /library. No published answers → clear empty state. Data from the re-pointed [id] route (BI-29 / BND-1).
BI-29 (derived-records data requirement)app/api/source-documents/[id]/route.ts GET STOPS joining content_items and reads q_a_pairs by source_document_id (published only), returning a derived_pairs field (replacing content_items). See the BND-1 dual-ownership branch (§6) — id-135 designs both paths; the page reads derived_pairs regardless of who lands the route change.
BI-30 (loading + partial-failure)page.tsx renders a skeleton while loading; the three sections (version chain, citations, derived pairs) are independent TanStack queries — each renders its own localised non-technical error + retry on failure without failing the page; a core SD-read failure (not not-found) → single SourceDocumentDetailError (modelled on ReferenceDetailError) with retry.
BI-31 (read-only)No edit/delete/version-mutation affordance in any Surface-B component; re-ingest/send-to-review (id-117) not surfaced. Explicit non-change.
BIProposed change
BI-32 (no related-records, no keyword facet — OOS-1)No change — explicitly out of scope. No find_related_items/filter_by_keywords affordance in either surface; recorded as the §9-dropped co-design backlog seam. No artefact proposed.

Routes / pages (net-new):

  • app/search/page.tsx — Surface A server shell (BI-7). NOT in proxy.ts publicRoutes.
  • app/search/search-content.tsxCorpusSearchContent client (BI-7…20).
  • app/documents/[id]/page.tsx — Surface B server detail page (BI-22), sibling of diff/.

Routes / API:

  • Consumed (id-135 does NOT own): app/api/search POST — {131.11} AC4 delivers the typed polymorphic shape + threads application_type (AAT-1/2/3).
  • Reused as-is: app/api/source-documents/[id]/versions (BI-25, shipped).
  • Re-pointed (DR-012 Path β — id-131 owns): app/api/source-documents/[id]/route.tscontent_itemsq_a_pairs published, returns derived_pairs (BI-29). id-135 consumes derived_pairs only (Task-level dep on id-131; {135.11} dropped).
  • New or server-read (BI-27): app/api/source-documents/[id]/citations/route.ts (citations read) — or an in-page.tsx server read; design against id-131 BI-23 CITE-EXT.

Components (net-new — all in domain subdirs, never repo root):

  • components/corpus-search/corpus-result-card.tsxCorpusResultCard (the one genuinely net-new component — polymorphic per-owner_kind, EXTEND of the id-111 ReferenceCard pattern).
  • components/corpus-search/corpus-search-controls.tsxCorpusSearchBox, CorpusFilterControls, CorpusKindNarrow (copy-and-generalise of the file-private id-111 ReferenceSearchBox / ReferenceFilterControls).
  • components/corpus-search/corpus-search-states.tsx (optional) → corpus-worded NoQuery/NoResults/Error if id-111 reference-states copy must diverge; otherwise reuse ReferenceLoadingSkeleton/ReferenceEmptyState/ReferenceErrorState directly.
  • components/source-document-detail/source-document-detail-client.tsxSourceDocumentDetailClient
    • SourceDocumentDetailError (modelled on ReferenceDetailClient/ReferenceDetailError).
  • components/source-document-detail/source-document-provenance.tsxSourceDocumentProvenance (BI-24).
  • components/source-document-detail/document-version-list.tsxDocumentVersionList (BI-25).
  • components/source-document-detail/document-citations-panel.tsxDocumentCitationsPanel (BI-27).
  • components/source-document-detail/derived-pairs-list.tsxDerivedPairsList (BI-28).

Components reused as-is (direct file imports, no barrels): UnifiedDiffContainer + sourceDocumentRevisionToUnified (via /documents/[id]/diff link-out, BI-26); ReferenceCard visual pattern; ReferenceLoadingSkeleton/ReferenceEmptyState/ReferenceErrorState; Badge, Button, formatDateUK/formatFileSize, ContentRenderer.

Hooks (net-new):

  • hooks/corpus-search/use-corpus-search.tsuseCorpusSearch (generalised useReferenceData; polymorphic multi-grain; URL-driven; useInfiniteQuery cursor).
  • hooks/source-document-detail/use-source-document-detail.tsuseDocumentVersions, useDocumentCitations, useDerivedPairs (per-section independent TanStack queries for BI-30).

TanStack query keys (lib/query/query-keys.ts):

  • New namespace corpusSearch: all: ['corpus-search'], search(query, kind, filters).
  • EXTEND existing sourceDocuments (add members only, never rewrite): detail(id), versions(id), citations(id), derivedPairs(id).

Types (net-new): types/corpus-search.tsCorpusSearchResult (discriminated union — answer | document | reference), CorpusSearchFilters, CorpusKind. See §5.

5. The polymorphic search contract Surface A expects (AAT-1/2/3)

Section titled “5. The polymorphic search contract Surface A expects (AAT-1/2/3)”

S438 {135.19} reconcile: the reconcile target is no longer unknown. {131.11} G-SEARCH is implemented-to-commit on cmux-worker-subo-search-13111 (migration 20260702120000 authored, not yet applied): the shipped emit is PolymorphicSearchResult over record_embeddings (4-arm UNION), preserving the 8-field value-path (id, title, suggested_title, content_type, primary_domain, primary_subtopic, summary, similarity) in a 21-col return. Reconcile CorpusSearchResult (below) against PolymorphicSearchResult at the read boundary; the discriminated union stays the stable internal contract.

Surface A’s whole result model is designed against the §9 / {131.11} target, not shipped. The TS interface Surface A expects (and which MUST reconcile with {131.11}‘s actual /api/search emit before implementation — AAT-2):

// types/corpus-search.ts — the shape Surface A consumes; reconcile vs {131.11} AC4 emit.
export type CorpusKind = 'answer' | 'document' | 'reference';
interface CorpusResultBase { id: string; kind: CorpusKind; title: string; }
export type CorpusSearchResult =
| (CorpusResultBase & { kind: 'answer'; answerSnippet: string; scopeTags: string[];
primaryDomain: string | null; primarySubtopic: string | null }) // → /library
| (CorpusResultBase & { kind: 'document'; summary: string | null;
primaryDomain: string | null; primarySubtopic: string | null }) // → /documents/[id]
| (CorpusResultBase & { kind: 'reference'; sourceUrl: string | null }); // → /reference/[id]

kind is derived from the id-131 BI-27 content_type = owner_kind field (§9 §7.3 owner-approved). No score/similarity/model field is included in the display shape (BI-3). If {131.11} emits field names differing from the above, the mapping layer in useCorpusSearch reconciles them at the read boundary (mirroring toReferenceListItem) — the discriminated union is the stable internal contract.

6. BND branches (carry forward — parent decides, do not resolve unilaterally)

Section titled “6. BND branches (carry forward — parent decides, do not resolve unilaterally)”
  • BND-1 (OQ-5) — WHO re-points app/api/source-documents/[id]/route.ts. Confirmed on disk: the GET joins eliminated content_items (route.ts:47–54).

    S438 {135.19} reconcile (DR-012): this branch is resolved — Path β decided. id-131 owns the route re-point (its content_items-elimination sweep); id-135’s Surface-B subtask is consume-only with a Task-level dependency on id-131 ({135.11} dropped). Path α is superseded; the α/β wording below is retained only as historical record.

    • Path α (id-135 owns it) — SUPERSEDED (DR-012): a Surface-B subtask re-points the route — drop the content_items select, add q_a_pairs by source_document_id (publication_status = 'published'), return derived_pairs. Self-contained in id-135.
    • Path β (id-131 ships it): id-131’s content_items-elimination sweep re-points the route; id-135’s subtask becomes consume-only and gains a Task-level dependency on id-131 (not a sibling Subtask dep — sibling-only constraint).
    • Either way the page reads a derived_pairs field of published q_a_pairs. The Orchestrator rules ownership before the Surface-B wave; PLAN carries both as labelled branches.
  • BND-2 (OQ-10) — shared route tree. id-135 adds app/documents/[id]/page.tsx into id-117’s existing /documents/[id] tree (recommended — same surface family). Note the shared-route-tree seam with id-117; no behavioural coupling (sibling pages), but PLAN flags the co-located tree.
  • Semantic Warm Meridian tokens only — no raw Tailwind colours in any new component; text+icon for every kind/state (WCAG AA, BI-4). New tokens (if any) per components/CLAUDE.md.
  • TanStack Query exclusivelyuseCorpusSearch + the three Surface-B section hooks; keys in lib/query/query-keys.ts, fetchers via the established fetchJson/tryQuery paths. No SWR / raw fetch in hooks.
  • No barrel re-exports — direct file imports (@/components/reference/reference-states, @/lib/diff/adapters/source-document-revision, etc.).
  • AuthgetAuthenticatedClient()/createClient() → check auth.successauthFailureResponse(auth) on every API route; RLS client for page reads.
  • proxy.ts/search and /documents/[id] are AUTHENTICATED → MUST NOT be added to publicRoutes (publicRoutes is for new public routes; these are not public — omission is what enforces the /login redirect for unauthenticated visitors, BI-1).
  • Supabase safetysb() / tryQuery() for all reads (matches the id-111 detail shell + id-117 diff page); never bare .from().select() without the safe wrapper.
  • Testsbun run test (never bun test); behaviour-first per test-philosophy.md (assert rendered states/link targets/empty-vs-error distinctions, not implementation).

No DDL in id-135. Every column read is shipped: source_documents classification family ({131.9}, on this HEAD), q_a_pairs.publication_status/source_document_id (id-131 BI-20, schema shipped), get_document_version_chain RPC (id-117). The in-flight reads (§9 polymorphic hybrid_search, the typed /api/search shape, the CITE-EXT citations contract) are owned by {131.11}/id-131 — id-135 consumes them and does not migrate them. The only data-layer change id-135 may own is the BND-1 route re-point (a .from() swap, no DDL).

9. Empirical verification (OQ-3 / Q-EX2 scope)

Section titled “9. Empirical verification (OQ-3 / Q-EX2 scope)”

This TECH cites no external-library symbols. Every symbol is internal Canonical (ReferenceContent, useReferenceData, UnifiedDiffContainer, sourceDocumentRevisionToUnified, formatDateUK, the reference/version-chain RPCs, hybrid_search), Postgres/Supabase RPC surface (get_document_version_chain, .from().select(), .rpc()), or Next.js/TanStack framework (notFound, useQuery, useInfiniteQuery, useSearchParams) — all out of the import-and-call verification scope (external-library symbols only, per shared-discipline §Empirical verification). No verification block required. The two runtime contracts that will drift — the §9 polymorphic search shape (AAT-1/2/3) and the BND-1 content_itemsq_a_pairs route join — are carried as explicit assumptions-against-target / boundary branches gated on {131.11} and the Orchestrator, never as unverified present facts.


End of {135.3} TECH. One proposed change (or explicit non-change) per BI-1 … BI-32; concrete named routes/components/hooks/query-keys/types catalogued (§4); reference_item rendered as a first-class third result kind; the diff/version-compare engine reused never rebuilt (HARD RULE honoured with the VersionHistory due-diligence note); BND-1/BND-2 carried as labelled branches for PLAN; quality bars (semantic tokens, TanStack-only, no barrels, auth helper, proxy.ts non-addition, sb()/tryQuery(), bun run test) baked in. No DDL, no ledger writes, no commits.