# YSD-18107 — What becomes of the seven finished panels no route reaches

- **Status:** proposed
- **Priority:** EXT — blocking for YSD-18107 and, through it, for YSD-16005 and
  the Section 22.3 completion audit. Four of the twenty-three study surfaces are
  reached in part or not at all, six required (flow, surface) instrument pairs
  are open because of it, and no accessibility work of any kind can close them:
  no lane can open a page the application does not compose.
- **Decision owner:** @GreyChimp
- **Drafted:** 2026-08-19 by Claude Code (Opus 5)
- **Approval:** pending

## Question

`accessibility/surface-mounting.ts` measures, from the route entry points
inward, which files the application actually renders. Seven finished panels —
6,600 lines across four surfaces — are reached by nothing in the repository but
their own component tests:

| Panel                              | Surface               | Lines |
| ---------------------------------- | --------------------- | ----- |
| `EvidenceReviewQueue.tsx`          | lens-and-review       | 494   |
| `SourceSetInspectionPanel.tsx`     | lens-and-review       | 896   |
| `RetrievalSetInspectionPanel.tsx`  | lens-and-review       | 551   |
| `CitationValidationPanel.tsx`      | notebooks-and-reports | 676   |
| `GroundedReportPromotionPanel.tsx` | notebooks-and-reports | 1460  |
| `EvidenceExportBundlePanel.tsx`    | evidence-export       | 1542  |
| `AuthoringDraftWorkbenchPanel.tsx` | authoring-drafts      | 981   |

Each is declared in `accessibility-surface-map.json` with a reason, and the gate
refuses both directions — an undeclared unreachable path, and a declaration for
a path a route has since started reaching. That keeps the list honest. It does
not answer the question a reader is left with, which is: **mount them, or retire
them?**

The question is EXT because neither answer is the accessibility lane's to give.
Mounting means building producers, schemas or domains in another initiative's
code; retiring means moving files that another initiative's checklist marked
complete.

## What was measured

Three premises decide the cost, and all three are now re-derived on every run by
`accessibility/unmounted-backing.ts` rather than written down once — the same
correction this register has already made twice. The prose written on 2026-08-17
said the seven split into "a BFF projection over a store that exists" and "the
domain itself", citing `EvidencePack` as real "across contracts, the persistence
registry and a Veritas API client". Measured on 2026-08-19, that was wrong three
ways.

**There are two `EvidencePack` contracts.** The one under `contracts/common`
declares the object all three evidence panels cite — `slug`, `excerpts`,
`retrievalTrace`, `consumers`. The one under `contracts/veritas` declares a
different object of the same name — `storyId`, `claimIds`, `evidenceItems` — and
it is the Veritas one the API client serves at `/veritas/evidence-packs`. The
reach cited belonged to the other contract.

**A table with no writer is not a store.** The common contract does have a
table: the persistence registry derives `v1_cross_cutting_evidence_pack`, and it
is in `schema.prisma` and in the `20260527184417_v1_initial` migration. Nothing
outside the generated Prisma client has ever named its accessor
`crossCuttingEvidencePack`. Zero writers, zero readers.

**No panel imports the contract it cites.** Every one of the seven declares its
own view type — enums, statuses, field shapes — copied by hand from a contract
it never references, so a contract change cannot break a panel and typecheck
cannot report a divergence.

All thirty of their string-literal unions were held against every `z.enum` in
the cited contracts and in the modules those contracts import, on 2026-08-19:

- **23 are exact copies**, member for member and almost all in the same order.
  Two of the twenty-three are copied from a module the panel's header does not
  name at all — `SourceCredibilityTier` from `common/source.ts`,
  `GroundedReportReviewPriority` from `common/grounded-answer.ts` — and one is
  copied from an enum the contract does not export:
  `AuthoringDraftFactCheckStatus` reproduces
  `factCheckStatus: z.enum(['pending', 'partially_checked', 'checked', 'blocked'])`,
  declared inline inside an object in `authoring-job.ts`. A vocabulary that
  cannot be imported is a vocabulary that gets retyped.
- **1 has already diverged.** `GroundedReportGroundingLevel` declares four
  levels — `grounded`, `partially_grounded`, `synthesis_only`, `ungrounded`.
  `GroundedReport.groundingSummary.level` is
  `GroundedAnswerGroundingLevelSchema` and carries **six**: `weakly_grounded`
  and `unknown` are missing. The panel's readiness evaluator branches on that
  field three times, faithfully mirroring the three refinements the contract
  itself keys on the same four values — so the logic is right and the type
  cannot hold two of the six states a real report can be in. Nothing is wrong
  today, because nothing produces the view; it is wrong on the first day a
  producer meets a `weakly_grounded` report.
- **1 is a deliberate subset.** `AuthoringDraftHandoffTarget` offers six of the
  contract's eight checkpoint kinds plus an added `revise`; `outline_approval`
  and `draft_checkpoint` are not offerable as handoff targets.
- **5 have no counterpart anywhere in `libs/contracts`**:
  `RetrievalCandidateStatus`, `EvidenceExportFormat`, `EvidenceExportRedaction`,
  `EvidenceExportScopeFlag`, `EvidenceExportAudience`. These are vocabularies
  the panels invented — export formats, redaction levels, audit audiences, the
  ten `include_*` scope flags — and a producer would have to give each one a
  canonical home before it could assemble the projection at all. That is work
  the cost table below does not count, and all five sit on the same panel.

This half is a hand measurement made once, on the date above, and deliberately
not a rule: a gate over seven files that this decision may retire is a gate
written for a world that may not exist. If the ruling is to mount, it should
become one.

With those corrected, the seven do split — into three groups, not two:

| Panel                     | Contract               | Consumers outside `libs/contracts` | Table | Cost                         |
| ------------------------- | ---------------------- | ---------------------------------- | ----- | ---------------------------- |
| `EvidenceReviewQueue`     | common/evidence-pack   | persistence registry               | yes   | producer-over-an-empty-store |
| `RetrievalSetInspection`  | common/evidence-pack   | persistence registry               | yes   | producer-over-an-empty-store |
| `EvidenceExportBundle`    | common/evidence-pack   | persistence registry               | yes   | producer-over-an-empty-store |
| `SourceSetInspection`     | common/source-set      | `libs/oshun/evidence-sophia` (2)   | no    | store-first                  |
| `CitationValidation`      | common/citation        | none                               | no    | domain-first                 |
| `GroundedReportPromotion` | common/grounded-report | none                               | no    | domain-first                 |
| `AuthoringDraftWorkbench` | common/authoring-job   | none                               | no    | domain-first                 |

`SourceSet` is the one with a live consumer: `libs/oshun/evidence-sophia`
validates it, hashes it and computes readiness over it, and no table holds one.
That is a different job from the other six and a different team to ask.

Two facts about the citation row are worth stating because getting either wrong
inverts it. `common/citation.ts` declares both `CanonicalCitation`, whose
`verification` field the panel writes, and `CitationTrail`, which the registry
registers as `v1_cross_cutting_citation_trail`. A table for a sibling object in
the same file is not a table for this one, and asking the question at module
granularity made the registry a consumer of both. `AuthoringJob` is likewise
named in prose by four contract files that import nothing; outside its own
module and spec it is imported by exactly one non-test file in the repository —
`publication-release-gate.ts`, itself a contract.

## Where they came from

None of the seven is a surface the Yemaya study proposal asks for. Eight
vocabulary probes over `YEMAYA_STUDY_AND_DECONSTRUCTION_WORKSPACE_PROPOSAL.md` —
review queue, evidence pack, citation validation, grounded report, export
bundle, authoring draft, source set, retrieval set — return zero hits.

Every one was delivered under a V1-SOP item in
`docs/releases/v1/specs/todos.md`, and every one of those items is marked
complete:

```
[x] V1-SOP-015 Add evidence review queues backed by Sophia contracts.
[x] V1-SOP-016 Add source-set inspection views for research integrity review.
[x] V1-SOP-017 Add retrieval-set inspection tooling for grounded outputs.
[x] V1-SOP-018 Add citation validation tooling for operator review.
[x] V1-SOP-019 Add grounded report promotion workflows.
[x] V1-SOP-020 Add evidence export tooling for investigations and audits.
[x] V1-SOP-036 Build operator workbench surfaces for draft inspection, …
```

Their verification notes are careful and specific — V1-SOP-015's runs to
twenty-four lines, names every field of the view model, counts ten passing tests
and records a clean adversarial stub-grep. What no note claims is a route. **The
acceptance was that a component exists, is tested, and typechecks**, and under
that acceptance all seven are genuinely done.

Six of the seven sit beside a study surface the application does reach, doing an
operator-facing version of an act the study workspace already performs:
`DetectionReviewPanel` beside `EvidenceReviewQueue`, `StudyReportsWorkspace`
beside `GroundedReportPromotionPanel`, `ProjectHandoffPanel` beside
`EvidenceExportBundlePanel`. `authoring-drafts` is the exception and the reason
it is a whole unrendered surface: nothing in the study workspace does
authoring-draft promotion at all.

## Recommendation

**Retire all seven from the Yemaya study scope, and re-open the mounting debt
against V1-SOP, where the panels and the decision both belong.**

Retiring here means moving each file out of
`apps/oshun/web/src/components/ studio/` into an operator directory of V1-SOP's
choosing. The study surface map's scope is that directory; a file that leaves it
stops being a study surface and the map self-corrects. Nothing is deleted and no
test is lost.

Three things make this the answer rather than mounting:

1. **The study workspace was never asked for them.** Putting a Sophia operator
   queue on a learner's study route would be a product mistake made to satisfy a
   scope boundary that is a directory name.
2. **Mounting is V1-SOP work whichever panel you start with** — a producer for
   an empty table, a persistence design, or a domain — and V1-SOP is where the
   requirement, the contracts and the acceptance all live.
3. **Six of the seven would arrive beside a study surface that already performs
   their act**, which is a duplication a person has to rule on before any of it
   is worth building.

**Declining them is not available**, and it is the answer that would otherwise
look cheapest. The map's `declined` list carries twenty-six entries and the
established form of its reason is _"not a Yemaya study surface: it belongs to X,
which owns its own accessibility evidence"_ — Isis, Aja, the AI operations
workspace, the Phase-180 bridge. Writing that for these seven would be false:
`accessibility-surface-map.json` is the only accessibility surface map in the
repository, and there is no Sophia or V1-SOP register to pick them up. Declining
would file a hole in the product as a scoping decision and record, in writing,
that somebody else holds evidence nobody holds.

**The move must not go before the debt lands.** Moving a file out of the study
scope removes the only measurement in the repository that says these panels have
no route. The map already refuses to let that happen quietly — a declaration for
a path no surface claims is a finding, so the move forces an edit to
`accessibility-surface-map.json`, and that edit is exactly where the debt would
be dropped. The approval should therefore carry, as one act: the destination
directory, the re-opened V1-SOP entries, and the deletion of the seven
`unmounted` declarations.

## Options considered

- **Build the `crossCuttingEvidencePack` producer and mount the three evidence
  panels.** Not rejected on merit — it is the single highest-yield piece of work
  in the table, lighting three panels at once over a schema and a migration that
  already exist. Rejected as this checklist's call: the producer writes Sophia
  evidence packs, the panels are Sophia operator tooling, and neither the
  requirement nor the acceptance for either lives in the Yemaya study proposal.
  If V1-SOP wants them mounted, this is where to start — and two of the three
  are cheaper than the third: `EvidenceExportBundlePanel` carries all five of
  the invented vocabularies, so a producer for it has to canonicalize export
  formats, redaction levels, audit audiences and ten scope flags before it can
  assemble anything.
- **Mount `authoring-drafts` because it is the smallest hole.** Rejected. It is
  the smallest by file count and the largest by cost: one file, one surface,
  four flows, and `AuthoringJob` is inert — no table, no consumer, one importer
  and that a sibling contract. It is the only one of the seven where mounting
  means building the domain.
- **Re-file all seven as `declined`.** Rejected for the reason above: the reason
  string would be false, and the falsehood is the whole content of the entry.
- **Leave them declared unmounted indefinitely.** Rejected as an _answer_ and
  adopted as the _interim state_. It is what holds today, it is now measured
  every run rather than asserted once, and it is honest — but it leaves 6,600
  lines of finished panel that nobody can reach and four surfaces that cannot be
  evidenced, with no owner and no date.
- **Delete the seven panels.** Rejected. They are complete, tested work against
  real contracts; the fault is in where they were filed and what was accepted as
  done, not in the code.
- **Retire all seven to an operator directory, re-open the V1-SOP entries, and
  drop the declarations in the same change** — recommended.

## Consequences

- Four surfaces stop being partly- or wholly-unrendered, because three of them
  stop claiming paths the product does not present and `authoring-drafts` ceases
  to be a study surface at all. `lens-and-review`, `notebooks-and-reports` and
  `evidence-export` become fully mounted.
- Two of the six open instrument gaps close as a consequence rather than by any
  harness work: contrast and zoom on `authoring-drafts` were reported as
  instrument debt for a page that does not exist.
- The study scope drops from 137 files to 130, and 23 surfaces to 22.
- V1-SOP-015 through 020 and V1-SOP-036 re-open. Their acceptance form — a
  component that exists, is tested and typechecks — is the thing that produced
  seven route-less panels, and it will produce more until it asks for a route.
- The Yemaya register loses its measurement of these panels, which is the cost
  of the move and the reason the destination has to carry one before the files
  go.
- YSD-18107's remaining machine slice closes. What remains on the item is the
  manual assistive-technology pass it shares with YSD-16005, whose actor is
  participants nobody has recruited.

## Machine-enforced outcome

On approval, the seven paths move out of `apps/oshun/web/src/components/studio/`
and the seven `unmounted` declarations are deleted in the same change. The
surface map's scope resolver then accounts for 130 files rather than 137,
`assertSurfacesAreMounted` reports every claimed path reached from a route, and
`assertBackingStillHolds` has nothing left to measure. A move that deletes the
declarations without moving the files fails `assertScopeAccountedFor` — a scope
file that no surface claims and nobody declined; a move that takes the files
without deleting the declarations fails `mountingFindings` — a declaration aimed
at a path no surface claims. Neither half can ship alone.

If the decision instead goes to mounting, the same rule reports it: a route that
reaches one of the seven turns its declaration into a finding, and the panel's
surface starts answering for its flows again on the next run.

## Pending control

The seven panels are declared unmounted in
`docs/proposals/yemaya-study-workspace/accessibility-surface-map.json`, and
`libs/yemaya/study-workspace/src/accessibility/surface-mounting.ts` measures the
declaration against the import graph on every run of the accessibility gate, in
both directions. Beside it,
`libs/yemaya/study-workspace/src/accessibility/unmounted-backing.ts` measures
what each panel is waiting for — the projection, the contract's consumers, the
table's callers — and fails while any write-off has stopped being true or any
declared cost no longer matches the repository. Until this decision is made, the
gate reports four surfaces reached in part or not at all, six instrument pairs
with no possible instrument, and `0 of 10` accessibility flows evidenced; no
change to any harness can move those numbers, because the gap is in what the
product composes.
