{"title":"Meta Museum Roadmap","path":"docs/roadmap.md","generatedAt":"2026-06-24T20:42:28.961Z","sourceUpdatedAt":"2018-10-20T01:46:40.000Z","milestones":[{"label":"Slice 0","title":"Staging","complete":true,"status":"complete"},{"label":"Slice 1","title":"Foundations (TDD infra first)","complete":true,"status":"complete"},{"label":"Slice 2","title":"Met vertical (canary)","complete":true,"status":"complete"},{"label":"Slice 3","title":"Getty vertical","complete":true,"status":"complete"},{"label":"Slice 4","title":"Records + Artworks + Entities","complete":true,"status":"complete"},{"label":"Slice 5","title":"Linked Art Inspector + Roadmap + Best-Practices","complete":true,"status":"complete"},{"label":"Slice 6","title":"Patterns + Graph","complete":true,"status":"complete"},{"label":"Slice 7","title":"Issues + SSE","complete":true,"status":"complete"},{"label":"Slice 8","title":"Agents + Jobs + Content Generation + Automation","complete":true,"status":"complete"},{"label":"Slice 9","title":"Workspace chrome + design-system pass (Custom CSS)","complete":true,"status":"complete"},{"label":"Slice 10","title":"Lift cleanup","complete":true,"status":"complete"}],"sections":[{"level":2,"heading":"Status (as of June 24, 2026)","body":"- [x] ✅ **Core stack and runtime**: Next.js 16.2.6 + React 19.2.4 + TypeScript strict + custom CSS; latest pilot outreach reply/evidence guard passed focused evidence/outreach/activation/support operator/service/offer/page/storage/docs/exporter checks (`70` tests / `13` suites), final `pnpm test` (`1103` tests / `310` suites), `pnpm lint`, and `pnpm build`; `/pilot` now renders a typed zero-complete activation evidence ledger with the six canonical seven-day pilot milestones, while `src/services/pilot-outreach-events.ts`, `src/services/pilot-activation-events.ts`, `src/services/pilot-support-issues.ts`, and `src/services/pilot-evidence-packet.ts` record sequence-guarded exact-account outreach, ordered exact-tenant activation events, chronology/status/identity/severity/resolution-evidence-guarded support load, explicit blocked/ready pilot packet artifacts, blocker-first Markdown packet summaries, open-blocking-support readiness blockers, and overdue open support-response blockers, `pnpm pilot:outreach`, `pnpm pilot:activation`, `pnpm pilot:support`, and `pnpm pilot:evidence --markdown` let operators append, package, and share real dated evidence without hand-editing JSON, and the runbook forbids marking outreach, activation, support, or packet readiness complete from demo, fixture, smoke evidence, reply claims without prior sent evidence, replies dated before `sentAt`, sent follow-up dates that predate `sentAt`, tenant-mismatched activation evidence, later activation milestones without prior tenant evidence, support response deadlines before `openedAt`, support issue `requester`, `summary`, `openedAt`, or `severity` rewrites, resolved support issue `resolvedAt` or `resolutionSummary` rewrites, support resolutions before `openedAt`, or open support issues carrying resolution evidence. Tenant RBAC evidence still proves signed-in members cannot use sibling active-org cookies to read sibling record lists/details, write records into sibling/default storage, read/review/publish/write sibling wiki draft state, read/review/triage/write sibling annotation queues, read/write sibling AgentTask history, or steer scoped AI/editorial record-read outputs away from their authenticated org; proving signed-in non-members cannot use a cookie-selected org to read or mutate records, annotation, wiki draft, or scoped AI/editorial record-read outputs; and proving signed-in non-admin roles cannot use org administration or support-adjacent routes to list orgs, add memberships, create/revoke invites, or export org audit packets. Gated pilot API routes return stable `429`/`Retry-After` denials before parsing or work execution without changing monthly quota denial semantics. The membership-validated `metamuseum.activeOrgId` cookie drives shared preview, request-storage, and pilot route-gate scope without weakening test override or explicit `?tenantId=...` compatibility precedence. The workspace shell renders sanitized active-org status, org id, accessible-org count, storage scope, membership role, selector, stale-selection warning, and an `/orgs` switch/manage affordance without exposing invite token material. Workspace records, explore, entities, entity detail, artwork detail, graph, and patterns pages derive preview storage from authenticated org session scope when no compatibility `?tenantId=...` is supplied, while explicit tenant preview links still win for controlled pilot URLs. Manual pilot entitlements can bind to authenticated org ids while route usage gates prefer the selected active org for entitlement lookup and post-success counter writes before compatibility tenant headers. `/orgs` can render the operator console while admin-only org routes create/list orgs, memberships, sanitized invites, invite revocation, invite acceptance, form posts, org-scoped audit rows, and org audit export packets while Auth.js org scope continues to resolve from managed org memberships/invites before compatibility env mappings or pilot tenant headers. Wiki draft create/list/detail/review/publish access, live-publish sync-map artifacts, reverse-ETL state, and tenant preview pages remain scoped while unscoped public requests cannot see scoped tenant records or artifacts. Provider facade/direct provider/explorer/Linked Art import writes, public browse reads, records, jobs, AgentTask, annotation, audit, activity route storage, wiki draft storage/access routes, wiki sync-map routes, and tenant-scoped export/DR managed documents remain isolated. The temporary `metagenauto/` AG2 reference repo has been distilled into `docs/agents/ag2-extraction-notes.md` before removal from the app tree.\n- [x] ✅ **Product surface**: `29` app pages and `125` API route handlers are live, including 13 provider integrations, ARK resolver, provider facade/readiness/capabilities, standards APIs, ActivityStreams, OpenAPI/docs, AI query/chat/evals, org admin/invite APIs, org audit export, `/api/orgs/active` active-org selection, the `/orgs` operator console for org provisioning, memberships, invites, and revocation, workspace-shell active-org status with selector, stale-selection warning, and switch/manage affordance, persisted AgentTask review history, wiki publish/sync, annotations, public trust, IIIF routes, a larger rotating midnight/navy rain-blue home hero with bright lemon-yellow Meta Museum highlights, top-padded full-color unfiltered contained artwork, a verified Met/CMA/AIC clean-deploy fallback when local image-backed imports are absent, a centered non-overlapping navy artwork info panel, a white hero label, three source-backed real-artwork pathway cards with provider/maker/date/rights/link metadata instead of AI placeholder images, a more readable artwork detail facts panel with metadata-forward desktop proportions and full-width long source fields, a full-width “Live source network” homepage band that frames the current project as a source-backed workbench with numbered source metrics, provenance, rights/attribution context, citation checks, and editor-gated review signals, and the `/pilot` Managed Linked Art Launch Pilot offer page with shared primary/footer navigation access, named outreach status ledger, zero-complete activation evidence ledger, and validated operator flows for outreach and activation evidence.\n- [x] ✅ **Era A + Era B**: legacy lift, parity, provider hardening, authority caching, B6.1 reconciliation, B7 gateway readiness/facade, B8 protocol conformance, B9 modeling guardrails, B10 ARK behavior, and pre-Era-C operational sign-off are complete.\n- [x] ✅ **Era C implementation surface**: C1-C5 core features are implemented, including multi-modal storage scaffolding, HAL/search/activity endpoints, C2 ETL/reconciliation/mapper, C3 IIIF + visualization surfaces, C4 AI query/chat/evals/mapping assist, specialized review agents, and C5 syndication/wiki/security/privacy hardening.\n- [x] ✅ **AI agent review layer**: five single-word field-aligned agents are live behind human approval: Clio (research signals), Mercator (Linked Art mapping review), Janus (reconciliation review), Themis (rights/provenance review), and Calliope (citation-backed curatorial drafts). Localhost smoke completed all five, avatar assets are verified, each returned AgentTask persists to editor-gated review history at `/api/agents/tasks`, and Mercator/Janus now have a disabled-by-default AG2 bridge boundary plus local review-only FastAPI worker with trace propagation, contract validation, timeout/refusal fallback, local fallback, safe enablement docs, and live-worker eval artifacts.\n- [x] ✅ **SEO/content publishing policy**: generated article, book, object-label, and collection-brief outputs now include source-derived SEO metadata (`seoTitle`, meta description, primary/secondary keywords, H1, subheadings, slug, rationale) on both accepted and refused content paths; WikiDrafts validate the same SEO envelope while preserving canonical source titles and citation/originality gates.\n- [x] ✅ **Security/privacy posture**: PII/sensitivity scan, review holds, audited human disposition, rights/reuse UI warnings, and public projection controls are active before Solr/GraphDB syndication.\n- [x] ✅ **Governance + docs contract**: markdown under `docs/` remains the canonical source of truth, surfaced by `/docs`, `/api/docs/manifest`, and `/api/docs/content`; Linked Art reference mapping, AIDD/TDD, and closeout evidence remain mandatory. The normal `pnpm session:closeout` path now refuses to append unless `README.md` and this roadmap were updated since the previous closeout.\n- [x] ✅ **Deployment (LIVE)**: the Next.js app is deployed to **production on Vercel against Neon Postgres** (`storageMode=postgres`), verified serving real records and public pages (2026-06-23). `vercel.json` pins `next build` so the close-out guard cannot break builds; the guard also self-skips when `VERCEL` is set. The `render.yaml` blueprint for the three FastAPI services + Redis cache is committed but **not yet deployed**; background workers (outbox/publish) remain deferred until projection/publishing is enabled. See [deployment.md](deployment.md).\n- [x] ✅ **Portfolio README + docs (2026-06-24)**: README trimmed from ~1,300 lines to a lean hero + highlights + run-it + honest \"what's real vs. in progress\" (detailed status stays here in the roadmap). New deep-dives added: [responsible-ai.md](responsible-ai.md) (key handling, denial-of-wallet auth-gate, citation/refusal gates, eval harness, cost control) and [linked-art/conformance-matrix.md](linked-art/conformance-matrix.md) (protocol MUSTs verified live + per-provider matrix + honest gaps).\n- [x] ✅ **Linked Art rights as `Right` entities — all providers (2026-06-24)**: started the [roadmap to 10/10](roadmap-to-10.md) with milestone **B1**. `src/utils/linked-art-rights.ts` synthesizes a conformant `subject_to` `Right` (classified by CC0 / rightsstatements.org URIs) for every object record that lacks one, wired into both `normalizeIncomingRecord` and the read-path `migrateToCurrentSchema`; Getty's are preserved. Closes the \"rights as labels outside Getty\" gap in the conformance matrix.\n- [x] ✅ **Reliable, badged CI — roadmap-to-10 A1 (2026-06-24)**: the session close-out guard no longer fails CI — `scripts/session-closeout.ts` skips the `--check` guard when `CI` is set (it stays enforced locally), so a stale local close-out log can't turn a green build red (the PR #16 failure mode). README header now carries CI / License / Linked Art / tests badges.\n- [x] ✅ **Supply-chain hygiene — roadmap-to-10 A2 (2026-06-24)**: resolved all 3 moderate `pnpm audit --prod` advisories via `pnpm.overrides` (postcss XSS → `>=8.5.10`; OpenTelemetry memory-DoS → core/resources/sdk-trace-base `^2.8.0`, which also fixes `@vercel/otel`'s mis-resolved 1.30.1 peers). Added `.github/dependabot.yml` (npm + github-actions + 4 pip services) and a `pnpm audit --prod --audit-level high` CI gate. Audit clean; tests 1,114; build green.\n- [x] ✅ **Per-provider conformance matrix generated — roadmap-to-10 B3 (2026-06-24)**: the 13×2 per-provider pass/fail fixtures (asserted in CI by `validation-architecture-depth.test.ts`) now drive a **generated** conformance matrix via `scripts/generate-conformance-matrix.ts` (`pnpm conformance:matrix`); `conformance-matrix-generated.test.ts` gates drift. The published `conformance-matrix.md` table is no longer hand-maintained. 13/13 providers pass both directions; suite 1,116.\n- [x] ✅ **SHACL conformance gate in CI — roadmap-to-10 B2 (2026-06-24)**: `services/validation-service/shacl_gate.py` + `.github/workflows/shacl-conformance.yml` validate every provider's pass fixture against the Linked Art SHACL shapes with pyshacl (JSON-LD → CIDOC-CRM RDF). Path-filtered job; `pnpm shacl:gate` locally. All 14 pass fixtures conform; a CRM-expansion regression now blocks the build.\n- [x] ✅ **Measured + gated test coverage — roadmap-to-10 A3 (2026-06-24)**: `pnpm test:coverage` runs the suite under c8 with a `--check-coverage` gate (lines 85 / funcs 85 / branches 70); CI's test step now enforces it. Current 89.4% lines / 92.1% funcs (core `src/services` 91.9%). README coverage badge added; also fixed CRLF-fragility in the B3 drift test so coverage runs clean cross-platform.\n- [x] ✅ **Published quality scores — roadmap-to-10 A4 (2026-06-24)**: [`docs/quality.md`](quality.md) publishes CI-measured numbers — Lighthouse a11y **100/100** on key pages, axe **0 severe** WCAG 2A/2AA violations across 18 routes, and the k6 p95 performance budget **met** (cached 73.5 ms, cold 56.1 ms, facet 55.1 ms, 0% errors). README a11y badge added.\n- [x] ✅ **Faceted / relevance search — roadmap-to-10 B4 (2026-06-24)**: `src/services/search.ts` ranks `/api/search` results by hit quality (exact label > prefix > substring > name) and returns `type`/`provider` facet counts + `q`/`type`/`provider`/`limit`/`offset` params in the `ld+json` `OrderedCollectionPage`. Tested at the service + API level; conformance-matrix \"basic, not faceted\" gap closed (Solr 9 documented as the env-gated scale backend).\n- [x] ✅ **Fixed flaky annotations test / CI reliability (2026-06-24)**: annotation ids were `annotation-${Date.now()}`, so two creates in the same millisecond shared an id — letting annotations in different org scopes collide and intermittently breaking the cross-org isolation assertion in CI. Centralized id minting in `mintAnnotationId()` (timestamp + `randomUUID`); added a deterministic 1,000-mint uniqueness test. Completes the A1 \"reliable CI\" goal.\n- [x] ✅ **HEAD + HTTP/2 conformance — roadmap-to-10 B6 (2026-06-24)**: the canonical Linked Art entity/collection routes now export `HEAD` (via a `bodilessResponse(await GET(...))` helper in `src/utils/protocol.ts`) — same headers as GET, no body, mirrors 200/404; `OPTIONS` advertises `GET,HEAD,OPTIONS`. HTTP/2 verified live (`HTTP/2.0 200` via Vercel). `tests/api/head-methods.test.ts`; suite 1,128.\n- [x] ✅ **Roadmap trimmed — roadmap-to-10 A5 (2026-06-24)**: this roadmap went from ~1,510 lines to ~420 by archiving the slice-by-slice Era A/B/C history to [`progress/era-history.md`](progress/era-history.md) (see \"Era delivery history\" below). `getStructuredRoadmap` aggregates both files so `/api/roadmap` still exposes full phases/milestones.\n- [x] ✅ **Activity Streams change feed — roadmap-to-10 B5 (2026-06-24)**: aligned `/api/activity` to Activity Streams 2.0 — AS2 `@context`, `next`/`prev` page links (kept `nextPage`/`prevPage` aliases), a fuller `partOf` `OrderedCollection` with `first`/`last`/`totalItems`, and `application/activity+json`. `tests/api/activity-as2.test.ts`; suite 1,129.\n- [x] 🟡 **Demo script — roadmap-to-10 A6 (2026-06-24)**: shot-by-shot 60–90s demo script + screenshot-gallery plan at [`demo-script.md`](demo-script.md). The video itself needs a human screen-record (can't be automated). This is the last open 10/10 item; all others are done and CI-confirmed.\n- [ ] ⚠️ **Era C exit gate is not green yet**: latest evidence run (`artifacts/exit-gate/era-c-exit-gate-latest.json`, generated `2026-06-10T11:36:30.690Z`) is `failed` across all four exit checks. The project is strong for controlled beta/demo use, but not yet ready to claim full public-production completion."},{"level":3,"heading":"Current Launch Readiness","body":"| Launch lane | Score | Current decision | Required next evidence |\n|---|---:|---|---|\n| Internal/dev demo | 9/10 | Safe to keep using and iterating locally. | Keep `pnpm test`, `pnpm lint`, `pnpm build`, and closeout guard green. |\n| Controlled public beta | 8/10 | App is now **live on Vercel + Neon** (2026-06-23), clearing the deployed-base-URL blocker. `pnpm launch:beta:readiness` is now `blocked` only on `AUTH_GITHUB_ID`, an uptime source, a k6 target URL, and disabling Vercel Deployment Protection; Postgres storage, DR proof, public-trust smoke, a11y, and explore smoke evidence are present. | Set `AUTH_GITHUB_ID`, point `BASE_URL` / `METAMUSEUM_PUBLIC_READ_BASE_URL` at the production URL, configure an uptime source and `IIIF_TILE_URL`, disable Vercel Deployment Protection, then rerun `pnpm launch:preflight`, `pnpm launch:review`, and `pnpm launch:beta:readiness` until status is `live-beta-ready` or only accepted warnings remain. |\n| General public production | 6.5/10 | Not yet; product is feature-rich but evidence gates are red. | Passing 30-day SLO/uptime evidence, real KPI telemetry, and external activity-feed adoption proof. |\n| Institution-grade / 10/10 | 5.5-6/10 | Blocked by time-based evidence and real-world adoption. | At least 30 days of green SLO + uptime samples, 3 declared external feed consumers, and SOTA §26 KPI targets. |"},{"level":3,"heading":"Current SaaS Readiness","body":"| SaaS lane | Score | Current decision | Required next evidence |\n|---|---:|---|---|\n| Technical SaaS foundation | 7/10 | Strong enough to begin SaaS packaging: auth, roles, Postgres storage, provider ingestion, validation, docs, launch review, and trust/syndication tooling are real. | Complete staging secrets, production-like deployment, usage limits, tenant-aware data boundaries, and supportable onboarding. |\n| Paid pilot readiness | 8/10 | Close for 1-3 concierge pilots where Sun & Rain Works can manually onboard collections and invoice outside the app; `/pilot` is shared-nav reachable and publishes the offer, named initial outreach queue, derived status ledger showing 10 researched accounts and 0 sent messages, and a zero-complete activation evidence ledger backed by managed outreach and activation events with the next required evidence. `pnpm pilot:outreach`, `pnpm pilot:activation`, `pnpm pilot:support`, and `pnpm pilot:evidence --markdown` now give operators validated no-JSON-edit commands for recording real first-outreach/milestone/support evidence and packaging explicit blocked/ready JSON plus Markdown evidence artifacts, with outreach replies requiring prior sent evidence, sent follow-up dates barred from predating `sentAt`, activation milestones requiring prior same-tenant evidence, tenant-mismatched activation evidence rejected, support response and resolution timestamps barred from predating `openedAt`, existing support issue updates barred from rewriting the original `requester`, `summary`, `openedAt`, or `severity`, already-resolved support issue updates barred from rewriting `resolvedAt` or `resolutionSummary`, open support issues barred from carrying resolution evidence, and open blocking support issues plus overdue open support responses preventing `ready` status. `docs/ops/managed-linked-art-pilot-runbook.md` defines the concierge setup, tenant/source namespace, activation events, support intake, monthly evidence packet, and counter-backed route usage gate; `src/services/pilot-entitlements.ts` makes the interim manual invoice-backed `pilot` entitlement durable in `storage/pilot-entitlements.json`, exact-tenant readable, optionally org-bound, validated, exportable to Postgres, and service-gated for import/AI/storage/export/API usage; `src/services/pilot-usage-counters.ts` persists tenant or org usage keys in managed `storage/pilot-usage-counters.json`; `src/services/pilot-support-issues.ts` persists support-load evidence in managed `storage/pilot-support-issues.json`; `src/services/pilot-route-gates.ts` now returns stable `429`/`Retry-After` denials for API-per-minute overages before request parsing or route work; `src/services/org-tenants.ts` stores first-class orgs, memberships, and hashed-token invites in managed `storage/org-tenants.json`; records, wiki draft, annotation, AgentTask, and scoped AI/editorial route tests prove signed-in members cannot use sibling active-org cookies to read sibling records or read/review/publish/write sibling editorial and agent-review state, while records, annotation, wiki draft, and scoped AI/editorial route tests now prove signed-in non-members cannot use cookie-selected org scope for those reads or writes, and org admin route tests prove signed-in non-admin roles cannot list orgs, add memberships, create/revoke invites, or export org audit packets; admin-only `/api/orgs*` routes now create/list orgs, memberships, sanitized invites, invite revocation, public invite acceptance, form posts, org-scoped audit rows, and org-specific audit export packets; `/api/orgs/active` persists a membership-validated active org in an HttpOnly cookie for signed-in members without exposing org admin writes; `/orgs` exposes the current operator console for org provisioning, membership adds, invite creation, sanitized invite review, and pending-invite revocation; `src/services/org-session-status.ts` and the workspace shell expose sanitized active-org status plus a selector/switch/manage affordance and stale-selection warning for signed-in org members; Auth.js resolves signed-in identities from active org memberships before falling back to `AUTH_ORG_MEMBERSHIPS` or pilot tenant headers; `src/services/active-org-selection.ts` now provides the shared membership-validated selected-org resolver used by `src/services/tenant-preview.ts`, `src/services/request-storage-scope.ts`, and `src/services/pilot-route-gates.ts`, so selected active orgs drive preview reads, API storage scope, entitlement lookup, and successful route counter writes while keeping test override headers and explicit tenant preview links as compatibility precedence; `tests/services/pilot-route-gates.test.ts` proves two pilot tenants stay isolated at the counter/gate layer and that org-scoped requests do not write against the compatibility tenant id; `proxy.ts` refuses tenant-tagged direct legacy provider routes before they can bypass facade counters; `src/auth/roles.ts` derives provider import role gates from the provider capability registry and keeps `/api/orgs/active` researcher-level before the admin-only org wildcard; `src/services/org-storage-scope.ts` and `tests/services/org-storage-isolation.test.ts` add service-level org-scoped isolation for records, jobs, AgentTask artifacts, and researcher annotations; tenant/org-scoped records, jobs, AgentTask, annotations, audit rows, ActivityStreams feed reads, activity readiness metrics, provider facade imports, explorer imports, Linked Art imports, wiki draft access routes, wiki sync-map/reverse-ETL routes, scoped public browse/derived plus AI/editorial read routes, and tenant preview pages now propagate exact scope into backing stores or read models; tenant-scoped managed JSON documents are included in Postgres export and DR restore rehearsal; and `docs/ops/procurement-readiness-packet.md` packages the first buyer-facing security overview, data flow, hosting/subprocessor note, backup/restore evidence path, incident summary, and checklist. | Send/track first outreach, onboard one real pilot dataset using the runbook, attach deployment-specific security/legal evidence to the buyer packet, add route-level support-access implementation tests only if a support-as-customer feature ships, and complete the activation ledger with real tenant evidence. |\n| Self-serve SaaS readiness | 3/10 | Not ready; the app lacks pricing pages, tenant signup, billing, plan gates, org invites, usage dashboards, and support workflows. | Add account/org onboarding, billing/manual-plan entitlements, quotas, usage analytics, and customer success runbooks. |\n| Profitable SaaS business | 4/10 | The product has a credible technical wedge, but revenue operations and repeatable sales motion are not proven yet. | Convert paid pilots into recurring subscriptions with gross-margin, retention, support-load, and acquisition-channel evidence. |"},{"level":3,"heading":"SaaS Commercialization Strategy","body":"**Primary wedge:** managed Linked Art API + data-quality cockpit for small/mid-size museums, archives, galleries, digital humanities labs, and artist estates that want standards-compliant publication without hiring a semantic-web team. This wedge matches the current product surface best: provider ingestion, Linked Art normalization, API/docs, validation, public trust, AI query, reconciliation, IIIF, ActivityStreams, and Neon-backed storage.\n\n**Secondary wedge, defer until the B2B pilot loop works:** creator-side provenance and authorship tools. This may scale further, but it needs simpler onboarding, consumer-grade billing, evidence storage, and marketplace/export integrations that are not yet core to the current app.\n\n**Initial paid offer:** \"Managed Linked Art Launch Pilot\" — fixed-scope onboarding of one collection export into a hosted workspace, including data-quality report, Linked Art API, public browse pages, provenance/rights review flags, and a monthly evidence packet. Manual invoicing is acceptable for the first pilots; in-app billing can follow validated demand."},{"level":3,"heading":"SaaS Roadmap Track","body":"| Phase | Goal | Build / decide | Exit criteria |\n|---|---|---|---|\n| SaaS-0: Positioning + offer | Turn the technical platform into a sellable pilot. | ✅ `/pilot` now publishes the ICP, pilot promise, pricing hypothesis, deliverables, data prerequisites, support boundaries, success metrics, ten qualified prospect profiles, and a ten-account outreach queue with structured stages, status evidence, and derived counts. | Offer page is published, success metrics are explicit, named accounts are listed, and status tracking is visible; full exit still needs at least one actually sent first outreach. |\n| SaaS-1: Concierge paid pilot | Earn first non-demo revenue without overbuilding self-serve. | ✅ `docs/ops/managed-linked-art-pilot-runbook.md` now defines the pilot workspace setup runbook, tenant namespace convention, manual plan entitlement config, activation events, support intake process, and customer evidence packet template. Next: execute it with first outreach, one real pilot dataset, and completed real-tenant activation milestones. | 1-3 paid pilots onboarded; each reaches first value within 7 days; pilot users can view/import/query/export without engineer intervention for routine tasks. |\n| SaaS-2: Multi-tenant product core | Make the app safe for multiple paying organizations. | ✅ Service-layer org-scoped storage isolation is in place for records, jobs, persisted AgentTask artifacts, researcher annotations, audit logs and org audit exports, ActivityStreams feed reads, activity readiness metrics, Postgres storage export, DR restore rehearsal, scoped public browse/derived reads, scoped AI/editorial read dependencies, wiki draft storage/access routes, wiki sync-map/reverse-ETL artifacts, authenticated org-session preview fallback for workspace pages, first-class managed `org-tenants.json` org/membership/invite backing, admin/team invite APIs, the `/orgs` operator UI, membership-validated `/api/orgs/active` cookie selection, workspace active-org status/selector/switch affordance plus stale-selection feedback, selected-org propagation through shared preview/storage/gate resolvers, records, wiki draft, annotation, AgentTask, and scoped AI/editorial route tenant RBAC read/write evidence, representative records/annotation/wiki draft/scoped AI-editorial non-member route RBAC evidence, non-admin org administration denial evidence, org-bound pilot entitlement/counter gates, and stable pilot API rate-limit denials, with Auth.js resolving active org memberships before compatibility env mappings. Support impersonation policy is now defined and test-locked in the procurement packet; next support-access evidence is route-level implementation proof only if the feature ships. | Tests prove tenant isolation at service and route boundaries; launch review includes tenant security checks; one hosted deployment supports multiple orgs without data bleed. |\n| SaaS-3: Billing + growth loop | Move from manual pilots to repeatable subscriptions. | ✅ Interim plan gates and durable manual pilot entitlement validation are executable in `src/services/pilot-entitlements.ts` and stored in managed `storage/pilot-entitlements.json`. Next: add pricing page, checkout or invoice-backed subscriptions, billing webhooks, usage enforcement, metered usage, trial/activation emails, onboarding checklist UI, churn/cancel reasons, and product analytics dashboard. | New org can sign up or be provisioned in under 15 minutes; MRR, activation, retention, support tickets, and usage are visible weekly. |\n| SaaS-4: Reliability + compliance for institutions | Make paid deployments procurement-friendly. | ✅ First procurement readiness packet is landed with security overview, data-flow diagram, hosting/subprocessor assumptions, backup/restore proof path, incident response summary, and checklist. Next: add customer-facing status page, SLA/SLO reporting, DPA/legal packet, access-review reports, deployment-specific backup evidence exports, incident drill evidence, and data-retention controls. | Controlled beta evidence is green enough for pilots; production launch review is green before broad public SaaS claims. |\n| SaaS-5: Profitability gate | Prove the business model, not just the software. | Track gross margin, cloud cost per tenant, support minutes per account, onboarding cost, conversion rate, retention, expansion, and CAC/payback by channel. | Positive gross margin per tenant, repeatable acquisition channel, retention evidence, and at least one pricing tier that remains profitable after support + infra cost. |"},{"level":3,"heading":"SaaS Product Backlog","body":"| Capability | Current state | SaaS-grade next step |\n|---|---|---|\n| Tenant/account model | Auth roles and Postgres storage exist; pilot entitlement/counter tests prove exact-tenant and org-bound gate isolation; `src/services/org-tenants.ts` stores first-class orgs, active/inactive memberships, and hashed-token invites in managed storage; `src/services/active-org-selection.ts` validates and persists selected active orgs for signed-in members and now shares that membership-validated resolver with request storage, preview, and route-gate scope; `src/services/org-session-status.ts` resolves sanitized active-org display state and generic stale-selection warnings; admin-only org APIs create/list orgs, memberships, sanitized invites, revocations, public invite acceptance, form posts, and org-scoped audit rows; `/orgs` provides the current operator UI for org creation, membership adds, invite creation, sanitized invite review, and pending-invite revocation; the workspace shell shows active org status, storage scope, membership role, accessible-org count, selector, stale-selection warning, and a switch/manage affordance without token material; Auth.js resolves active memberships into session org ids before compatibility env mappings; records, jobs, and persisted AgentTask artifacts support org-scoped service-layer storage under a shared root; tenant-tagged records, jobs, AgentTask, annotation, activity, audit, import, public browse/derived read routes, AI/editorial read routes, wiki draft access routes, wiki sync-map/reverse-ETL routes, and preview pages now propagate exact tenant or authenticated org scope into backing stores or read models. | Add route-level support-access implementation safeguards only if support-as-customer ships, plus broader tenant RBAC evidence before self-serve hosting. |\n| Plans and entitlements | `src/services/pilot-entitlements.ts` defines `free`, `pilot`, `institution`, and `enterprise` plan gates for imports, AI calls, storage, users, exports, API rate limits, and feature access; it also validates and persists interim manual pilot entitlement records by exact `tenantId` with optional `orgId` binding in managed `storage/pilot-entitlements.json`, evaluates requested usage against the active plan, and `src/services/pilot-route-gates.ts` reads tenant or selected authenticated-org usage from managed `storage/pilot-usage-counters.json` before provider facade import/search/profile, AI, content generation, records API, and records export work runs, returns stable `429`/`Retry-After` responses for API-per-minute overages, then records successful 2xx work back into the same counter ledger under the selected scope. `src/services/org-tenants.ts` now gives plan gates a first-class org/membership/invite backing store, `src/services/active-org-selection.ts` persists membership-validated org choice and feeds shared selected-org resolution into route gates, `src/services/org-session-status.ts` makes active-org scope visible in the workspace shell, while `proxy.ts` blocks tenant-tagged direct legacy provider routes, `src/auth/roles.ts` derives provider import write gates from the capability registry, scoped service storage covers records/jobs/AgentTask artifacts, records/jobs/AgentTask routes pass tenant identity into scoped stores, browse plus AI/editorial read APIs select scoped record stores when request context is scoped, wiki draft access routes select scoped draft stores, wiki sync-map/reverse-ETL routes select scoped stores, and preview pages select authenticated org records when query tenant scope is absent. | Add richer plan surfaces, production limiter metadata, and usage dashboards before supporting self-serve multi-org hosting. |\n| Billing | No in-app billing; first pilots now have a durable manual invoice-backed entitlement contract with required invoice reference, namespace, owner, publication boundary, monthly evidence cadence, and Postgres export coverage. | Use manual invoice entitlement records for pilots; graduate to Stripe or equivalent checkout/webhooks only after pilot pricing is validated. |\n| Onboarding | Developer-led setup works; the managed pilot runbook now defines concierge workspace setup, source-data requirements, namespace rules, and a seven-day activation checklist, but self-serve setup does not exist. | Execute the runbook on one real dataset, then add guided org setup, sample dataset path, first-value dashboard, and onboarding email flow. |\n| Usage analytics | Launch/exit evidence exists; `src/services/pilot-outreach-events.ts`, `src/services/pilot-activation-events.ts`, `src/services/pilot-support-issues.ts`, and `src/services/pilot-evidence-packet.ts` now record, summarize, and package required outreach/activation/support evidence, `pnpm pilot:outreach`, `pnpm pilot:activation`, `pnpm pilot:support`, and `pnpm pilot:evidence --markdown` give operators validated write/export paths for real JSON and customer-readable Markdown evidence, `/pilot` renders honest zero-sent and zero-complete ledgers until real records exist, and route-level entitlement gates read exact-tenant month/minute counters from managed storage and record successful gated route work, but broader customer activation analytics are still thin. | Implement tenant-aware tracking for activation milestones, validation improvements, customer views, weekly active users, usage cost, and monthly evidence exports. |\n| Support operations | Technical docs are strong; the managed pilot runbook now defines support intake fields, severity levels, response rules, and evidence cadence for concierge pilots. | Formalize intake tooling, known-issues page, escalation policy, and pilot feedback cadence once the first pilot is active. |\n| Sales/marketing surface | `/pilot` now publishes the buyer-facing Managed Linked Art Launch Pilot page with problem, buyer, offer, pricing hypothesis, success metrics, support boundaries, source-network CTA, contact CTA, shared primary/footer nav access, named outreach queue, an outreach status ledger with non-clipped status cells, and a zero-complete activation evidence ledger backed by managed outreach/activation events plus operator commands needed to record and package real evidence. | Send/record the first real outreach, then add proof screenshots, completed activation milestones, and a customer evidence packet once the first pilot is active. |\n| Procurement readiness | `docs/ops/procurement-readiness-packet.md` now packages a buyer-reviewable security overview, Mermaid data-flow diagram, hosting/subprocessor assumptions, backup/restore evidence path, incident response summary, checklist, and explicit non-SOC-2 / incomplete-tenant-isolation limits. | Attach actual deployment-specific evidence, legal/DPA artifacts, access-review exports, incident drill evidence, and customer-specific subprocessors once the first pilot is active. |"},{"level":3,"heading":"Current Evidence Blockers","body":"| Era C exit check | Current evidence | Required to clear |\n|---|---|---|\n| SOTA §20.4 p95 SLOs | `3/30` retained samples; retained k6 samples are legacy/partial and missing `sparqlWhitelisted` + `iiifTileServing` p95s. | Configure deployed-target nightly evidence vars and run complete five-scenario `pnpm k6:slo` samples for 30 days; keep all p95s under policy thresholds. |\n| Public-read uptime | `source: \"probe\"`, `availability30d: 1`, `sampleCount30d: 3`; source is now active but below the 30-sample evidence window. | Keep scheduled public probes running via `METAMUSEUM_PUBLIC_READ_BASE_URL` / uptime envs; retain at least 30 samples with >= 99.9% availability. |\n| Activity feed adoption | `0/3` declared external consumers in the latest artifact; single-consumer and three-consumer matrix proof tooling are now available. | Onboard three real external consumers that send `x-linked-art-consumer-id` to `/api/activity` within the 30-day window and capture `pnpm activity:adoption:matrix` artifacts. |\n| SOTA §26 KPIs | Failing `dataQualityEnrichedShare`, `reconciliationAutoApproveRate`, and `reconciliationPrecisionReviewed`; AI query cost telemetry is now sourced and within policy in the latest artifact. | Export production record-enrichment + reconciliation review counts to `monitoring/kpi-evidence.json`, then rerun telemetry sync. |"},{"level":3,"heading":"Next Operating Plan","body":"1. **Deployment foundation** — ✅ preflight automation and runbook are landed (`pnpm launch:preflight`, `pnpm launch:preflight:production`, `docs/ops/deployment-preflight.md`); Neon-backed `DATABASE_URL` is seeded with all present managed storage documents, `DATABASE_URL` now verifies with `sslmode=verify-full`, `pnpm dr:drill` verifies Postgres restore rehearsal for 6 documents, and `pnpm launch:smoke-token` now generates/rotates the staging researcher smoke token in `.env` without printing it. Latest local preflight against `http://localhost:3000` (`2026-06-10T15:55:43.932Z`) is down to `8 pass / 0 warn / 1 fail`; the exact hard failure left was `AUTH_GITHUB_ID` missing while `AUTH_GITHUB_SECRET` is already present. **Update 2026-06-23:** the Next.js app is now deployed to production on Vercel against Neon (`vercel.json`, `render.yaml`, `docs/deployment.md` landed; the close-out guard self-skips on Vercel), so the remaining hard inputs are `AUTH_GITHUB_ID`, pointing `BASE_URL`/`METAMUSEUM_PUBLIC_READ_BASE_URL` at the live URL, disabling Vercel Deployment Protection, and wiring the production URL into the nightly uptime/k6/KPI evidence vars.\n2. **Evidence pipeline** — ✅ nightly workflow now prefers deployed-target `pnpm k6:slo`, seeds `/api/ai/query` telemetry, probes declared activity adoption, preserves local `pnpm k6:slo:ci` fallback, and uploads performance/activity/monitoring artifacts; public-read uptime probe evidence is active but still needs 30 retained samples.\n3. **Telemetry completeness** — ✅ AI query runs emit per-query usage/cost logs, and `monitoring/kpi-evidence.json` can now supply aggregate record-enrichment + reconciliation review counts; still generate the real production export before the next exit-gate run.\n4. **External adoption proof** — ✅ partner/bot proof commands and runbook are landed (`pnpm activity:adoption:probe`, `pnpm activity:adoption:matrix`, `docs/ops/activity-adoption-proof.md`); still register three real partner/bot consumers for `/api/activity` and validate `class: \"declared\"`, `declaredId`, `isExternal`, and recent `lastSeenAt` in `storage/activity-consumers.json`.\n5. **Launch review** — ✅ `pnpm launch:review` / `pnpm launch:review:production` aggregate latest preflight, exit-gate, security, DR, public-trust, a11y, and explore-smoke evidence into a packet, and `pnpm launch:beta:readiness` now summarizes controlled-beta go/no-go status from launch-review plus deployment-preflight artifacts. Latest local beta readiness is `blocked` with `5` passed checks, `1` accepted beta warning, and exact preflight blockers for `AUTH_GITHUB_ID`, public base URL, uptime source, and k6 target URL; once those deploy inputs are set, rerun preflight/review/readiness for the live beta decision. **Update 2026-06-23:** production is live on Vercel + Neon, clearing the deployed-base-URL blocker; remaining inputs are `AUTH_GITHUB_ID`, the live uptime/k6 evidence vars, and disabling Deployment Protection.\n6. **SaaS packaging** — ⚠️ `/pilot` is now shared-nav reachable and publishes the Managed Linked Art Launch Pilot offer for concierge paid pilots, including pricing hypothesis, scope, prerequisites, support boundaries, success metrics, ten prospect profiles, a named outreach queue, and a status ledger that honestly shows 10 researched accounts and 0 sent messages; `src/services/site-navigation.ts` prevents header/footer/test navigation drift, `docs/ops/managed-linked-art-pilot-runbook.md` defines concierge setup, namespace, activation events, support intake, evidence packets, and counter-backed route usage gates, `pnpm pilot:outreach` records real dated first-outreach evidence through managed storage without hand-editing JSON while rejecting reply claims without prior sent evidence, replies before `sentAt`, and sent follow-ups before `sentAt`, `pnpm pilot:activation` records real dated activation events through managed storage without hand-editing JSON while rejecting tenant-mismatched evidence and later milestones without prior same-tenant evidence, `pnpm pilot:support` records real support-load evidence through managed storage without hand-editing JSON while rejecting response deadlines or resolution times before `openedAt` and rejecting open issues with resolution evidence, `pnpm pilot:evidence --markdown` writes blocked/ready monthly JSON and Markdown evidence artifacts from entitlement, usage, outreach, activation, and support ledgers, `src/services/pilot-entitlements.ts` defines executable interim commercial plan gates plus durable exact-tenant or org-bound manual pilot entitlement storage and service-level usage enforcement, `src/services/pilot-usage-counters.ts` adds managed tenant/org month/minute counters, `src/services/org-tenants.ts` adds managed org/membership/invite backing with hashed invite tokens, `src/services/active-org-selection.ts` persists selected active orgs after membership validation and now feeds the same selected-org resolver into preview, request-storage, and route-gate scope, `src/services/org-session-status.ts` exposes sanitized active-org status and stale-selection warnings for the workspace shell, admin-only `/api/orgs*` routes expose org, membership, invite, revoke, accept, form-post, audit workflows, and audit export packets, `/orgs` exposes the current shared-nav operator console for org provisioning and invite management without rendering token hashes, `src/services/tenant-preview.ts` lets preview pages fall back to selected authenticated org scope before compatibility tenant query links are needed, `src/services/request-storage-scope.ts` now has records and wiki draft route proof that sibling active-org cookies cannot steer signed-in member reads/writes into sibling org storage, `src/services/pilot-route-gates.ts` enforces provider facade, AI, content generation, records API, and records export usage from those counters before work runs, returns stable `429`/`Retry-After` responses for API-per-minute overages, and records successful 2xx work afterward while preferring selected authenticated org scope over compatibility tenant headers, focused tests prove exact-tenant and org-bound counter/gate isolation, `proxy.ts` refuses tenant-tagged direct legacy provider routes before they can bypass facade counters, `src/auth/roles.ts` derives provider import write gates from the provider capability registry, Auth.js carries first-class signed-in identity-to-org storage scope into sessions before env fallback, `src/services/org-storage-scope.ts` adds scoped service storage for records/jobs/AgentTask artifacts, tenant/org-scoped records/jobs/AgentTask/annotation/audit/activity/import/wiki-draft/wiki-sync routes now pass exact scope into backing stores and audit rows, scoped public browse/derived plus AI/editorial read APIs now select scoped record stores when request context is scoped, page previews can render scoped records/artwork/entity/explore/graph/pattern views from either explicit tenant query or authenticated org session scope, tenant-scoped managed documents participate in export/DR evidence, and `docs/ops/procurement-readiness-packet.md` provides the first buyer-facing security/data-flow/subprocessor/backup/incident-response packet, including the explicit no-production-support-impersonation policy and future control requirements. Next evidence is one sent first outreach, one real pilot dataset executed through the runbook, deployment-specific buyer evidence, route-level support-access evidence only if that feature ships, and the first completed real-tenant activation ledger; do not claim profitable SaaS readiness until recurring revenue, support load, retention, and gross-margin evidence are real.\n   Latest pilot evidence packet note: `/pilot` still honestly shows 10 researched accounts, 0 sent messages, and 0 of 6 activation milestones complete, but `pnpm pilot:outreach`, `pnpm pilot:activation`, `pnpm pilot:support`, and `pnpm pilot:evidence --markdown` now convert real manual outreach, activation, support, entitlement, and usage ledgers into explicit `blocked` or `ready` JSON plus Markdown packets, reject reply claims without prior sent evidence, reject replies dated before `sentAt`, reject sent follow-up dates before `sentAt`, reject later activation milestones without prior same-tenant evidence, reject tenant-mismatched activation evidence, reject support response and resolution times before `openedAt`, reject existing support issue updates that rewrite the original `requester`, `summary`, `openedAt`, or `severity`, reject already-resolved support issue updates that rewrite `resolvedAt` or `resolutionSummary`, reject open support issues that carry resolution evidence, and prevent `ready` status while any blocking support issue remains open or any open issue is past its next response time, so the next SaaS evidence target remains a real sent outreach plus a real tenant dataset with dated activation evidence.\n7. **Documentation currency** — ✅ every iteration must update `README.md` and `docs/roadmap.md` before `pnpm session:closeout`; `scripts/session-closeout.ts` enforces this on the normal closeout path by comparing both files to the previous closeout timestamp. Latest CI hardening keeps workflow JavaScript Actions on Node 24-native major versions while preserving the project’s Node 20 app execution path; latest UI polish keeps the home hero carousel in this current surface because clear full-color artwork imagery, attribution, reuse context, and a centered non-overlapping info panel are part of the public Linked Art trust contract.\n8. **Agent productionization** — ✅ persistent AgentTask review history is landed (`agent-tasks.json` managed storage + `/api/agents/tasks`), the internal AG2 bridge boundary is wired for Mercator/Janus behind `METAMUSEUM_AG2_BRIDGE_ENABLED`, and the local Python AG2 worker endpoint is available at `services/ag2-worker` with review-only contract tests, route-to-worker trace propagation, timeout/refusal fallback coverage, safe enablement docs, and live-worker eval artifacts via `pnpm ag2:worker:eval`. ⚠️ next value is collecting operator sign-off for any production bridge enablement; A2A/AG-UI remain deferred.\n\n---"},{"level":2,"heading":"Linked Art adherence uplift (current -> high)","body":"This section turns the current medium/medium-high areas into explicit completion criteria."},{"level":3,"heading":"A. Validation architecture depth (B2 follow-through)","body":"Current: validation architecture is in place and standards-linked.  \nTarget: high adherence through continuous standards-backed enforcement.\n\nAdherence upgrade target:\n- [x] ✅ Validation depth moved from \"in place\" to continuous fixture-backed drift enforcement.\n\nStatus:\n- [x] ✅ Complete.\n- [x] ✅ Evidence: `/explore` includes `vanda` source toggle and `/artwork/[id]` now exposes digital/IIIF manifest-image links when present in imported records.\n- [x] ✅ Provider/import transform policy is now executable via fixture manifest + tests:\n  - `tests/fixtures/validation/provider-fixture-manifest.json`\n  - `tests/quality/validation-architecture-depth.test.ts`\n- [x] ✅ Scheduled revalidation pass landed:\n  - `.github/workflows/validation-drift.yml` (weekly + manual dispatch)\n  - `scripts/validation-drift.ts`\n- [x] ✅ CI drift visibility + regression blocking landed:\n  - `.github/workflows/ci.yml` runs `pnpm validation:drift:check`\n  - net-new critical violations fail the job\n\nDefinition of done:\n- [x] ✅ Validation coverage includes object, digital, provenance, shared structures, and endpoint-shape fixtures from the reference rounds used in active slices.\n- [x] ✅ CI shows stable/no-regression validation trend for two consecutive release cycles.\n  - `config/validation-drift-cycles.json` tracks release-cycle snapshots.\n  - `pnpm validation:drift:trend` performs executable two-cycle no-regression gating."},{"level":3,"heading":"B. Provider rollout completeness (B5)","body":"Current: all planned expansion providers are landed.  \nTarget: high adherence with repeatable, standards-mapped provider slices.\n\nAdherence upgrade target:\n- [x] ✅ Provider rollout discipline is locked to keep all landed B5 providers green with standards-mapped tests (fixtures + protocol/profile checks + parity checklist).\n\n- [x] ✅ Execute remaining providers as independent slices (Louvre, Harvard, Smithsonian, V&A, Princeton, Europeana, AIC, CMA), each with:\n  - [x] ✅ adapter isolation conformance\n  - [x] ✅ fixture-anchored standards mapping\n  - [x] ✅ protocol/profile checks from B8\n- [x] ✅ Track per-provider readiness in this roadmap with explicit `not started / in progress / done` status and standards round coverage.\n\nProvider readiness matrix:\n\n| Provider | Status | Standards round coverage | B8 protocol/profile checks | Notes |\n|---|---|---|---|---|\n| Rijks | done | object + digital + provenance + shared structures + endpoint-shape fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests are landed and included in provider protocol conformance suite. |\n| NGA | done | object + digital + provenance + shared structures + endpoint-shape fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | CSV ingest/provider slice landed with adapter + profile/search/import routes + tests. |\n| Louvre | done | object + shared structures + references fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n| Harvard | done | object + shared structures + endpoint-shape fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n| Smithsonian | done | object + shared structures + data-discovery fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n| V&A | done | object + digital + data-discovery fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n| Princeton | done | object + digital + shared structures fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n| Europeana | done | object + shared structures + data-discovery fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n| AIC | done | object + digital + shared structures fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n| CMA | done | object + digital + shared structures fixture anchors mapped to `LinkedArtModel1.0-Reference.md` | complete | Routes + adapter + tests landed with provider facade wiring. |\n\nProvider parity checklist (all must be complete per provider before status can be set to `done`):\n- [x] ✅ identity mapping (stable URI + `equivalent` handling)\n- [x] ✅ activity/event modeling preserved (no object-person shortcut regressions)\n- [x] ✅ rights/reuse + attribution semantics preserved and surfaced\n- [x] ✅ IIIF/link-layer handling preserved when source provides it\n- [x] ✅ source provenance metadata preserved end-to-end (`_source.provider`, source URL, ingest time)\n\nDefinition of done:\n- [x] ✅ All planned B5 providers shipped with route + adapter + tests + standards mapping notes.\n- [x] ✅ Provider parity checklist complete for identity, activity modeling, rights, IIIF, and source provenance."},{"level":3,"heading":"C. HAL + Search relations conformance (rounds 71-79)","body":"Current: documented in the standards reference, partially deferred in platform slices.  \nTarget: high adherence with enforceable API behavior.\n\n- [x] ✅ Add conformance tests for OrderedCollection/OrderedCollectionPage search response shapes.\n- [x] ✅ Enforce stable relation naming and discoverability contracts via search relation fields (`nextPage` / `prevPage`) and HAL-aware protocol assertions.\n- [x] ✅ Prevent inverse-relationship duplication drift by asserting search-driven inverse discovery patterns.\n\nDefinition of done:\n- [x] ✅ Representative search endpoints pass relation + pagination + shape conformance tests.\n- [x] ✅ HAL link contract tests pass for versioning, related search links, and format/profile discoverability.\n\nStatus:\n- [x] ✅ `tests/quality/hal-search-relations-conformance.test.ts` enforces response-shape + relation-contract behavior on representative direct and provider-facade search routes.\n- [x] ✅ `tests/quality/protocol-conformance.test.ts` continues to enforce HAL separation + media-type/profile behavior on public API payloads."},{"level":3,"heading":"D. Era B exit-gate closure readiness","body":"Current: Era B gate closed for the current scope.  \nTarget: keep it closed as new providers land.\n\n- [x] ✅ B6 authority-cache request-path policy enforced.\n- [x] ✅ B8/B9 conformance suites in CI.\n- [x] ✅ Postgres mode is now the default storage-of-record when `DATABASE_URL` is present.\n- [x] ✅ `storage/*.json` removed from version control.\n- [x] ✅ Write audit-log verification in CI for all primary write routes.\n\nDefinition of done:\n- [x] ✅ Era B exit gate lines are green with evidence links to tests and commands (`tests/quality/era-b-exit-gate.test.ts`, `tests/quality/protocol-conformance.test.ts`, `tests/quality/provider-protocol-conformance.test.ts`, `tests/quality/linked-art-b9-guardrails.test.ts`).\n\nAdherence upgrade target:\n- [x] ✅ Era B sustainment is continuously enforced: provider-slice conformance, authority-cache policy, and write-audit checks remain green as new sources land.\n\nExecution policy:\n- [x] ✅ No provider/validation PR merges without round + fixture-anchor standards mapping.\n- [x] ✅ No protocol-affecting merges without conformance test coverage for headers/shape/negotiation touched.\n- [x] ✅ Enforcement evidence:\n  - `.github/pull_request_template.md`\n  - `tests/quality/execution-policy-gates.test.ts`\n\n---"},{"level":2,"heading":"Stack decisions — locked in","body":"| Layer | Decision | Locked because |\n|---|---|---|\n| Framework | Next.js 16 App Router + RSC | already scaffolded; matches SOTA §11 |\n| UI lang | TypeScript 5, `strict: true` | scaffolded |\n| Styling | **Custom CSS** — design tokens + BEM-lite component classes in `app/globals.css`; no utility framework | user decision (reversed earlier Tailwind choice); resolves SOTA §30 Q3 |\n| Forms | React Hook Form + Zod | SOTA §3.2 |\n| Data fetching | RSC `fetch` first; TanStack Query for interactive client state | SOTA §11.3 + Next 16 cache-components model |\n| Server state mutations | Server Actions over fetch-based POSTs where possible | Next 16 idiom |\n| Tests | `node:test` + `node:assert` via `tsx`, Playwright for e2e, axe-core for a11y | SOTA §23 + legacy convention |\n| Pkg manager | pnpm | scaffolded |\n| Persistence (this era) | Postgres JSONB is the storage-of-record behind `src/utils/storage.ts` (`postgres` default with compatibility `file`/`double-write` modes) | preserves stable call sites during/after B3 migration |\n| Triple store (SOTA era) | **GraphDB (Ontotext)** — Community Edition for OSS path; SE/EE if SPARQL p95 demands | user decision; resolves SOTA §30 Q1 |\n| Search index (SOTA era) | **Solr 9** (LUX-aligned) | architecture decision (May 30, 2026); replaces Solr/OpenSearch fork |\n| Persistence (SOTA era) | Postgres 16 + JSONB · Solr 9 · GraphDB · pgvector | SOTA §3.4 / §8 with finalized search choice |\n| Curator backend (SOTA era) | **In-house curator console** (custom, Linked Art-native) | architecture decision (May 30, 2026); draws lessons from Arches/Ogee without adopting platform lock-in |\n| Developer/ops backend | **In-house ops console** (pipeline/debug/automation focused) | architecture decision (May 30, 2026); complements curator console |\n| Canonical ID scheme | **`https://lod.metamuseum.org/{type}/{ulid}`** | architecture decision (May 30, 2026); opaque, sortable, federation-ready |\n| Publication bridge (SOTA era) | **MediaWiki + custom Wikibase** for Meta Wiki Art publishing | aligns Linked Art/SPARQL/citation goals; see `docs/meta-wiki-art-bridge.md` |\n\n**Previously deferred architecture decisions (now finalized, May 30, 2026):**\n- [x] ✅ Search engine: **Solr 9** (LUX-aligned).\n- [x] ✅ Curator backend: **in-house custom curator console**.\n- [x] ✅ Developer backend: **in-house ops console**.\n- [x] ✅ Canonical ID scheme: **`https://lod.metamuseum.org/{type}/{ulid}`**.\n\n---"},{"level":2,"heading":"Era delivery history","body":"All three delivery eras are complete (see Status above):\n\n- **Era A — The Lift** (10 PR-sized slices): TDD foundations, Met + Getty verticals, records/artworks/entities, Linked Art inspector, patterns/graph, issues/SSE, agents/jobs/content, workspace chrome.\n- **Era B — Hardening** (B1–B10): Zod contracts + schema versioning, formal validation, Postgres, auth + roles, 13-provider expansion, authority caching, exhibition/literature reconciliation, protocol + modeling guardrails, ARK conformance, gateway readiness.\n- **Era C — SOTA** (C1–C5): multi-modal storage + HAL, ETL + reconciliation + mapper, IIIF + visualizations, the AI layer, syndication + Meta Wiki Art + security/privacy hardening.\n\nThe full slice-by-slice and B-/C-series implementation detail is archived in **[progress/era-history.md](progress/era-history.md)**. The active forward plan is **[roadmap-to-10.md](roadmap-to-10.md)**.\n\n---"},{"level":2,"heading":"Cross-cutting standards (apply from Slice 1 onward)","body":"These are not phases — they are continuous quality gates. Borrowed from `_legacy/AGENTS.md` and SOTA §23.\n\n- [x] ✅ **AIDD + TDD is the default.** Define behavior in natural language and map standards rounds/fixture anchors first, then write the failing test (red), pass with minimum code (green), and refactor with the suite green. Tests are the spec; reviewers read tests before reading implementation. A failing test stops the line. See [CLAUDE.md](../CLAUDE.md) §\"We lead with AIDD + TDD\".\n- [x] ✅ **Adapters do not import each other.** Bridge via `src/utils/artwork-builder.ts`.\n- [x] ✅ **Contracts are leaf modules.** No upstream deps.\n- [x] ✅ **`_source.raw` is immutable.** Transform at read time.\n- [x] ✅ **Rights-aware by default.** Every UI surface showing an image carries reuse status + attribution.\n- [x] ✅ **Linked Art JSON-LD is the canonical data layer.** UI DTOs (`Artwork`) are separate; map at the boundary.\n- [x] ✅ **Loading / empty / error / success states** on every interactive UI.\n- [x] ✅ **Keyboard navigation + visible focus** on every interactive surface.\n- [x] ✅ **At least one test for any risky transform.**\n- [x] ✅ **Cite or refuse.** Generated content always carries citations + rights + review state.\n- [x] ✅ **Next 16 specifics**: `cookies()`, `headers()`, dynamic `params` are async — always `await` them.\n- [x] ✅ **No `unknown` swallowed silently.** A record with unknown rights gets an explicit \"Rights unknown — do not reuse\" badge.\n- [x] ✅ **Reference-driven conformance.** Any provider/API/schema/search/protocol PR must cite relevant [linked-art/LinkedArtModel1.0-Reference.md](linked-art/LinkedArtModel1.0-Reference.md) rounds and include failing-first tests mapped to the referenced fixture anchors.\n- [x] ✅ **Reference maintenance loop.** If a PR depends on newly published Linked Art guidance not yet captured in `LinkedArtModel1.0-Reference.md`, that round/addendum must be appended before (or in the same change as) the implementation PR.\n- [x] ✅ **Standards Mapping is required in provider/validation PRs.** Include: referenced round numbers, fixture anchors exercised, and failing-first test files proving red→green conformance.\n- [x] ✅ **PR template completion is required.** Every PR must complete [.github/pull_request_template.md](../.github/pull_request_template.md), including AIDD checklist gates, standards mapping, and protocol assertions touched.\n- [x] ✅ **AI-generated tests/refactors require human semantic verification.** AI can accelerate drafting, but authors/reviewers remain accountable for Linked Art correctness, provider semantics, and protocol behavior.\n- [x] ✅ **Provider/pipeline boundary drift is actively bounded.** `docs/risk-register.md` tracks risk posture and `tests/contracts/provider-boundary-contracts.test.ts` enforces adapter import boundaries.\n- [x] ✅ **Protocol conformance is mandatory.** Public API behavior must preserve JSON-LD context/profile correctness, support `GET` + `OPTIONS`, and provide baseline CORS + media-type negotiation.\n- [x] ✅ **AI-RSI compounding loop is mandatory.** Each merge requires: 72h review check, evidence capture in session log, and roadmap/README/CLAUDE updates before the next RSI expansion scope.\n- [x] ✅ **HAL/data separation is mandatory.** API navigation metadata lives in `_links` (non-semantic) and must not pollute semantic graph payloads.\n- [x] ✅ **URI opacity is mandatory.** Never infer semantics from URI path structure in router logic or client helpers.\n- [x] ✅ **Inverse discovery via Search API.** Prefer standardized search relations and OrderedCollection/OrderedCollectionPage responses rather than duplicating inverse relationship fields.\n- [x] ✅ **Carrier/content separation is non-negotiable.** `HumanMadeObject`/`DigitalObject` must remain distinct from `VisualItem`/`LinguisticObject`.\n- [x] ✅ **Authority-backed classification UX.** Curatorial/classification input paths must use controlled authority sources (AAT/ULAN/Wikidata equivalents), not free-text categories by default.\n- [x] ✅ **Data discovery signposting.** Public record HTML pages expose a single canonical `describedby` link to the Linked Art JSON-LD record.\n\nVerification note (May 31, 2026):\n- The first nine cross-cutting gates above are marked complete based on current enforcement in CI/tests and live implementation patterns (provider boundary checks, contract leaf structure, `_source.raw` invariants, rights surfaces, Linked Art boundary mapping, interactive state handling, and keyboard/focus coverage).\n- Additional governance/protocol gates are marked complete where enforced by executable tests (`protocol-conformance`, `provider-protocol-conformance`, `hal-search-relations-conformance`, `provider-digital-content-gates`) and PR governance checks (`execution-policy-gates`, PR template standards mapping requirements).\n- Remaining open gates in this section are intentionally left unchecked only where future era scope is intentionally deferred; cross-cutting gate set above is now fully enforced in current Era A/B surfaces.\n\n---"},{"level":2,"heading":"What this roadmap deliberately does NOT do (yet)","body":"To stay honest about scope:\n\n- [x] ✅ **No microservice split during Era A.** All routes lived in the single Next 16 app; Python services begin in Era B (validation) and Era C (reconciliation, AI).\n- [x] ✅ **No triple store or vector store in Era A or B.** Postgres + JSONB remains sufficient until Era C search/graph patterns are activated.\n- [x] ✅ **No module-federation for the Era A app.** The app remains a single deployable Next.js build.\n- [x] ✅ **No Arches / Ogee / Zelge adoption planned.** Lessons are reused, but curator and ops surfaces remain in-house.\n- [x] ✅ **No fancy IIIF in Era A.** Current Era A/B UI uses provider image URLs; OpenSeadragon remains planned for C3.\n- [x] ✅ **No NL→SPARQL until the SHACL gate exists** (B2 → C4).\n- [x] ✅ **No Meta Wiki Art write path** until contracts, validation, auth, audit log, and Postgres cutover are stable (C5).\n\nVerification note (May 31, 2026):\n- Constraints above are verified against current code/routes/dependencies and remain in force for pre-Era-C scope control.\n\n---"},{"level":2,"heading":"What I'd build next, concretely","body":"Era C1 prep while sustaining Era B quality gates:\n- [x] ✅ **RSI-5: AI evidence drift + citation freshness** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - Owner: Platform + AI Reliability\n  - **Action 1 complete (2026-06-09):** `/api/ai/query` now returns citation metadata, coverage, and explicit refusal state, locked by `tests/api/ai-query.test.ts` and `tests/quality/cite-or-refuse-conformance.test.ts`.\n  - **Action 2 complete (2026-06-09):** `/api/ai/query` and `/api/ai/chat` now emit `retrievedAt` + `citationFreshness` diagnostics and refuse stale evidence via policy-backed route tests.\n  - Scope:\n    - Enforce same cite-or-refuse behavior beyond `/api/ai/chat` so `/api/ai/query` emits stable evidence metadata and refuses under coverage/freshness thresholds.\n    - Keep `/api/ai/chat` grounded response semantics while adding the shared freshness guard.\n  - Acceptance:\n    - `/api/ai/query` returns `{ answer, citations, coverage, citationFreshness, refusalReason? }` with `entityId`, `propertyPath`, `sourceUrl`, and `retrievedAt` in cited outputs.\n    - Under-cited or stale-evidence answers return explicit refusal and reason.\n    - Regression test coverage proves chat/query parity and stale-evidence failure modes.\n  - Proof packet:\n    - `tests/api/ai-query.test.ts`, `tests/api/ai-chat.test.ts`, and `tests/quality/cite-or-refuse-conformance.test.ts` cover cited success, coverage refusal, and freshness refusal behavior.\n    - Close-out packet synchronizes `docs/risk-register.md`, `CLAUDE.md`, `README.md`, and this roadmap with evidence proofs.\n- [x] ✅ **RSI-6: AI eval drift baselines include citation freshness** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/services/ai-eval-harness.ts` now scores `citationFreshness` from actual citation/source timestamps instead of treating retrieval time as always fresh.\n  - `src/services/ai-eval-regression.ts`, `scripts/ai-eval-gate.ts`, and `config/ai-eval-regression-policy.json` now baseline, persist, print, and fail-fast on `citationFreshnessDrop`.\n  - `evals/golden-museum-questions.v1.json` and `docs/evals/golden-museum-questions.md` now declare `citationFreshnessThreshold = 0.95`.\n  - Proof packet: `tests/services/ai-eval-harness.test.ts`, `tests/services/ai-eval-regression.test.ts`, `tests/services/ai-eval-artifacts.test.ts`, `tests/quality/ai-eval-golden-dataset.test.ts`, plus direct AI eval gate output with `citationFreshness=1` and `citationFreshnessDrop=0`.\n- [x] ✅ **RSI-7: AI eval summary badges + aging-pressure alerting** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/services/ai-eval-artifacts.ts` now renders `artifacts/evals/summary.md` with status, faithfulness, relevance, citation accuracy, `citationFreshness`, pass-rate badges, artifact links, and alerts.\n  - `src/services/ai-eval-harness.ts` now persists citation freshness aging (`oldestAgeDays`, `oldestAgeRatio`, `maxAgeDays`) for trend-aware pressure detection.\n  - `.github/workflows/ai-eval-gate.yml` now appends the summary to `$GITHUB_STEP_SUMMARY` and uploads `artifacts/evals/` for CI inspection.\n  - Proof packet: `tests/services/ai-eval-artifacts.test.ts`, plus direct AI eval gate output with `summary=artifacts/evals/summary.md`, `citationFreshness=1`, and `oldestAgeRatio=0`.\n- [x] ✅ **RSI-8: AI eval artifact dashboard visibility** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/services/ai-eval-dashboard.ts` now loads ignored local eval artifacts, latest metrics, trend runs, artifact timestamps, and freshness-aging warnings with explicit empty/partial states.\n  - `app/(workspace)/ai-evals/page.tsx` now provides a read-only app dashboard for latest eval summary, trend index, freshness-aging state, and active warnings.\n  - Navigation now exposes the dashboard from the workspace sidebar, primary Workspace menu, and footer.\n  - Proof packet: `tests/services/ai-eval-dashboard.test.ts` and `tests/pages/ai-evals-page.test.ts`, plus focused system Node test output passing 3 tests.\n- [x] ✅ **RSI-9: latest-vs-previous AI eval artifact diff** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/services/ai-eval-dashboard.ts` now computes latest-vs-previous metric deltas, freshness-aging pressure deltas, status changes, prompt-count changes, identity changes, and compact “what changed” notes.\n  - `app/(workspace)/ai-evals/page.tsx` now renders a “Latest vs previous run” review section with metric arrows, freshness pressure movement, and fast-review notes.\n  - Proof packet: `tests/services/ai-eval-dashboard.test.ts` and `tests/pages/ai-evals-page.test.ts`, plus focused system Node test output passing 3 tests.\n- [x] ✅ **RSI-10: severity-labeled AI eval diff triage** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/services/ai-eval-dashboard.ts` now classifies metric deltas, freshness-aging pressure movement, and overall latest-vs-previous diff priority as `regression`, `watch`, `stable`, or `improved`.\n  - `app/(workspace)/ai-evals/page.tsx` now renders priority labels and visible metric/aging threshold pills so review starts with severity instead of raw deltas only.\n  - Proof packet: `tests/services/ai-eval-dashboard.test.ts` and `tests/pages/ai-evals-page.test.ts`, plus focused system Node output passing 3 tests, `pnpm test` passing 787/249, `pnpm lint` passing with one existing warning, and production Next build passing via system Node.\n- [x] ✅ **RSI-11: policy-driven AI eval priority visibility** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `config/ai-eval-regression-policy.json` now owns diff severity thresholds consumed by dashboard and CI summary generation.\n  - `app/(workspace)/ai-evals/page.tsx` now shows last-N severity distribution across `regression`, `watch`, `stable`, and `improved` comparisons.\n  - `artifacts/evals/summary.md` now includes CI-visible review priority when at least two retained eval runs exist.\n  - Proof packet: `tests/services/ai-eval-dashboard.test.ts`, `tests/services/ai-eval-artifacts.test.ts`, and `tests/pages/ai-evals-page.test.ts`, plus focused system Node output passing 8 tests, `pnpm test` passing 788/249, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build passing via system Node.\n- [x] ✅ **RSI-12: AI eval agent-summary reliability** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/services/ai-eval-severity.ts` now validates `diffSeverityPolicy` shape/order and fails malformed policy with explicit errors.\n  - `/api/ai-evals/summary` now exposes agent-ready JSON with latest run, review priority, severity distribution, severity history, alerts, and artifact links.\n  - `app/(workspace)/ai-evals/page.tsx` now renders compact severity sparkline and latest-vs-previous comparison history for fast trend review.\n  - Proof packet: `tests/services/ai-eval-severity.test.ts`, `tests/api/ai-evals-summary.test.ts`, `tests/services/ai-eval-dashboard.test.ts`, `tests/services/ai-eval-artifacts.test.ts`, and `tests/pages/ai-evals-page.test.ts`, plus focused system Node output passing 13 tests, `pnpm test` passing 793/251, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai-evals/summary`.\n- [x] ✅ **RSI-13: AI eval contract + CI annotation reliability** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/contracts/zod/ai-eval-summary.ts` now defines the reusable agent JSON contract and OpenAPI schema for `/api/ai-evals/summary`.\n  - `config/ai-eval-regression-policy.json` now owns `severityHistoryPolicy.maxComparisons`, which drives dashboard distribution/history and agent JSON window metadata.\n  - `scripts/ai-eval-gate.ts` now emits a GitHub PR warning annotation when review priority is `watch` or `regression`, with workflow path filters covering `/api/ai-evals`, policy, and contract changes.\n  - Proof packet: `tests/api/ai-evals-summary.test.ts`, `tests/api/openapi.test.ts`, `tests/services/ai-eval-severity.test.ts`, `tests/services/ai-eval-dashboard.test.ts`, `tests/services/ai-eval-artifacts.test.ts`, and `tests/pages/ai-evals-page.test.ts`, plus focused system Node output passing 18 tests, `pnpm test` passing 796/251, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai-evals/summary`.\n- [x] ✅ **RSI-14: AI eval version/pruning hygiene** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `/api/ai-evals/summary` now returns `schemaVersion: 1`, and `aiEvalSummaryResponseSchema` rejects unsupported versions before agents consume the payload.\n  - `docs/evals/golden-museum-questions.md` publishes exact GitHub CI annotation examples for `regression` and `watch`, with snapshot assertions keeping docs and formatter output aligned.\n  - `src/services/ai-eval-artifacts.ts` now prunes orphaned run JSON outside the retained trend window while preserving retained run files and non-JSON notes.\n  - Proof packet: `tests/api/ai-evals-summary.test.ts`, `tests/api/openapi.test.ts`, and `tests/services/ai-eval-artifacts.test.ts`, plus focused system Node output passing 22 tests, `pnpm test` passing 800/251, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai-evals/summary`.\n- [x] ✅ **RSI-15: AI eval migration/reporting visibility** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `src/contracts/zod/ai-eval-summary.ts` now includes future `schemaVersion: 2` migration notes, with v1 accepted and planned v2 rejected through fixture compatibility tests.\n  - `src/services/ai-eval-artifacts.ts` now returns a retention pruning report with `delete`/`dry-run` mode and retained/orphaned/deleted/preserved file counts.\n  - `artifacts/evals/summary.md` now surfaces latest CI annotation status and retention pruning status for PR/build reviewers.\n  - Proof packet: `tests/api/ai-evals-summary.test.ts`, `tests/services/ai-eval-artifacts.test.ts`, and `tests/fixtures/ai-eval-summary/*`, plus focused system Node output passing 24 tests, `pnpm test` passing 802/251, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai-evals/summary`.\n- [x] ✅ **RSI-16: AI eval summary artifact/schema visibility** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `tests/fixtures/ai-eval-summary/summary-snapshot.md` now locks the generated CI summary markdown, proving `summary.md` stays reviewable and stable.\n  - `/api/ai-evals/summary` now exposes the latest `retentionPruneReport` for agents, including delete/dry-run mode and retained/orphaned/deleted/preserved counts.\n  - `/api/openapi` now includes the AI eval summary schema migration compatibility table so future `schemaVersion` upgrades are discoverable from the contract surface.\n  - Proof packet: `tests/api/ai-evals-summary.test.ts`, `tests/api/openapi.test.ts`, `tests/services/ai-eval-artifacts.test.ts`, `tests/services/ai-eval-dashboard.test.ts`, and `tests/fixtures/ai-eval-summary/summary-snapshot.md`, plus focused system Node output passing 25 tests, `pnpm test` passing 803/251, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai-evals/summary`.\n- [x] ✅ **RSI-17: Visual ETL Mapper AI-assist safety** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `/api/ai/mapping-assist` now returns review-ready, contract-valid `MappingTemplate` drafts from source columns with confidence, rationale, standards anchors, and unmapped-column diagnostics.\n  - `/etl/mapper` now exposes a \"Suggest mapping with AI\" action while keeping generated mappings review-only before any ingestion activation.\n  - Unknown columns are surfaced as diagnostics instead of invented mappings.\n  - Proof packet: `tests/services/mapping-assist.test.ts`, `tests/api/ai-mapping-assist.test.ts`, `tests/components/etl-mapper-config.test.ts`, and `tests/api/openapi.test.ts`, plus focused system Node output passing 7 mapper-assist tests and 2 OpenAPI tests, `pnpm test` passing 809/253, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai/mapping-assist`.\n- [x] ✅ **RSI-18: mapper-assist fixture/schema/importability hardening** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `tests/fixtures/mapping-assist/tricky-columns.json` now locks tricky rights/credit/sensitive columns so mapper assist cannot invent unsafe Linked Art target paths.\n  - `src/utils/etl-mapper-assist.ts` and `/etl/mapper` now support importing returned suggestions as reviewable ReactFlow draft nodes/edges.\n  - `/api/openapi` now exposes `MappingAssistResponse` and references it from `/api/ai/mapping-assist`.\n  - Proof packet: `tests/services/mapping-assist.test.ts`, `tests/utils/etl-mapper-assist.test.ts`, `tests/components/etl-mapper-config.test.ts`, `tests/api/openapi.test.ts`, and `tests/api/ai-mapping-assist.test.ts`, plus focused system Node output passing 11 tests, `pnpm test` passing 811/254, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai/mapping-assist`.\n- [x] ✅ **RSI-19: provider-family/browser/request-schema mapper hardening** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `tests/fixtures/mapping-assist/provider-families.json` now covers Met, Getty, and Rijks-style mapper columns with allowed-path and must-stay-unmapped assertions.\n  - `scripts/smoke-etl-mapper-assist.ts` and `pnpm smoke:etl:mapper-assist` now provide a Playwright browser smoke for `/etl/mapper` assist generation plus draft import.\n  - `/api/openapi` now exposes `MappingAssistRequest` and attaches it to the `/api/ai/mapping-assist` POST request body.\n  - Proof packet: `tests/services/mapping-assist.test.ts`, `tests/scripts/etl-mapper-smoke-script.test.ts`, and `tests/api/openapi.test.ts`, plus focused system Node output passing 7 tests, `pnpm smoke:etl:mapper-assist` passing against `http://localhost:3001/en`, `pnpm test` passing 813/255, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build confirming `/api/ai/mapping-assist`.\n- [x] ✅ **RSI-20: negative mapper fixtures, visual screenshot, and API-doc examples** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `tests/fixtures/mapping-assist/negative-provider-families.json` now locks near-miss Met/Getty/Rijks columns so rights, credit, restriction, sensitivity, donor, and flag wording cannot trigger unsafe suggestions.\n  - `scripts/smoke-etl-mapper-assist.ts` now waits on the mapping-assist POST, imports the draft, and writes `artifacts/smoke/etl-mapper-assist-imported.png` with animations disabled.\n  - `/api/docs` now includes concrete mapping-assist request and response examples next to the Swagger UI entry point.\n  - Proof packet: `tests/services/mapping-assist.test.ts`, `tests/scripts/etl-mapper-smoke-script.test.ts`, and `tests/api/docs.test.ts`, plus focused system Node output passing 11 tests, `pnpm smoke:etl:mapper-assist` passing against `http://localhost:3001/en`, `pnpm test` passing 814/255, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build passing.\n- [x] ✅ **RSI-21: mapper layout, OpenAPI-sourced docs examples, and confidence policy** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `app/globals.css` now gives mapper actions a full-width wrapped row with no button overlap in the refreshed screenshot.\n  - `app/api/docs/route.ts` now renders mapping-assist request/response examples from `/api/openapi` instead of duplicating static JSON.\n  - `src/services/mapping-assist.ts` now applies a minimum confidence policy so lower-confidence accession/place/description patterns stay diagnostics-only.\n  - Proof packet: `tests/components/etl-mapper-config.test.ts`, `tests/api/docs.test.ts`, and `tests/services/mapping-assist.test.ts`, plus focused system Node output passing 15 tests, `pnpm smoke:etl:mapper-assist` refreshing `artifacts/smoke/etl-mapper-assist-imported.png`, `pnpm test` passing 817/255, `pnpm lint` passing with one existing warning, AI eval gate printing `reviewPriority: stable`, and production Next build passing.\n- [x] ✅ **RSI-22: public source narrative and trust uplift** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `app/datasets/page.tsx` now presents a public source-network narrative backed by `getProviderCapabilities`, not copied prototype constants.\n  - `app/about/page.tsx` and `app/projects/page.tsx` now migrate the sibling `meta-museum-art` mission/project surfaces into native Next pages while replacing coming-soon/static project cards with live source-network, Linked Art workbench, and Meta Wiki Art workflow links.\n  - `src/services/site-metadata.ts` centralizes OpenGraph/Twitter metadata with a reviewed local image, and footer navigation exposes Contact/Privacy/Terms plus asset provenance.\n  - `docs/asset-provenance.md` tracks all preserved sibling visual assets with SHA-256 hashes while marking placeholder thumbnails as excluded from public use and keeping copied legal text out.\n  - Proof packet: `tests/services/public-source-narrative.test.ts`, `tests/pages/public-source-pages.test.ts`, focused RSI-22 test output passing 6/2 plus follow-up `pnpm test -- tests/pages/public-source-pages.test.ts` passing 851/267 on 2026-06-09, `pnpm lint` passing with one existing warning, `pnpm build` passing, and screenshot proof at `artifacts/smoke/datasets-page.png`.\n- [x] ✅ **RSI-23: public-source agent API and trust smoke hardening** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `/api/public-sources/summary` now exposes schema-versioned agent JSON for source stats, provider capability flags, and imported asset provenance.\n  - `docs/asset-provenance.md` and `src/contracts/zod/public-sources-summary.ts` now enforce explicit license-review statuses so unknown asset rows fail.\n  - `pnpm smoke:public-trust` captures browser screenshots for `/datasets`, `/contact`, `/privacy`, and `/terms`; the nested `meta-museum-art` prototype copy was removed after all images were preserved and inventoried.\n  - Proof packet: `tests/api/public-sources-summary.test.ts`, `tests/services/public-source-narrative.test.ts`, `tests/scripts/public-trust-smoke-script.test.ts`, focused RSI-23 output passing 6/3, `pnpm smoke:public-trust` passing against `http://localhost:3001`, `pnpm test` passing 827/259, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-24: OpenAPI, checksum drift, and screenshot retention hardening** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `/api/openapi` now includes `PublicSourcesSummaryResponse` and references it from `/api/public-sources/summary`.\n  - Imported public-source assets now fail tests when their SHA-256 hashes drift from `docs/asset-provenance.md`.\n  - `pnpm smoke:public-trust` now writes timestamped screenshot runs, latest copies, previous-run links, a summary JSON, and prunes old runs outside the retention window.\n  - Proof packet: `tests/api/openapi.test.ts`, `tests/services/public-source-narrative.test.ts`, `tests/services/public-trust-smoke-artifacts.test.ts`, `tests/scripts/public-trust-smoke-script.test.ts`, focused RSI-24 output passing 6/5, `pnpm smoke:public-trust` passing against `http://localhost:3001`, `pnpm test` passing 828/260, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-25: public trust docs, diff metadata, and CI artifact visibility** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `/api/docs` now renders the `/api/public-sources/summary` response example from `/api/openapi`, keeping examples single-source for humans and agents.\n  - Public trust smoke artifacts now include latest-vs-previous checksum/byte diff metadata per screenshot plus CI-ready summary markdown.\n  - `.github/workflows/public-trust-smoke.yml` now builds, runs `pnpm smoke:public-trust`, appends public trust summary links to `$GITHUB_STEP_SUMMARY`, and uploads `public-trust-smoke-artifacts`.\n  - Proof packet: `tests/api/docs.test.ts`, `tests/api/openapi.test.ts`, `tests/services/public-trust-smoke-artifacts.test.ts`, `tests/services/public-trust-smoke-ci-summary.test.ts`, `tests/scripts/public-trust-smoke-script.test.ts`, `tests/scripts/public-trust-ci-workflow.test.ts`, focused RSI-25 output passing 12/10, `pnpm smoke:public-trust` passing against temporary `http://localhost:3001`, `pnpm test` passing 830/262, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-26: pixel-diff thresholds, public trust summary API, and main CI artifact links** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `pnpm smoke:public-trust` now decodes PNG screenshots, computes changed-pixel ratios, and fails when `PUBLIC_TRUST_SCREENSHOT_PIXEL_DIFF_THRESHOLD` is exceeded.\n  - `/api/public-trust/summary` now exposes schema-versioned latest public trust smoke artifacts and pixel-diff status for agents.\n  - `.github/workflows/ci.yml` now runs public trust smoke, appends summary links to `$GITHUB_STEP_SUMMARY`, and uploads `public-trust-smoke-artifacts`.\n  - Proof packet: `tests/services/public-trust-smoke-artifacts.test.ts`, `tests/api/public-trust-summary.test.ts`, `tests/api/openapi.test.ts`, `tests/scripts/public-trust-smoke-script.test.ts`, `tests/scripts/public-trust-ci-workflow.test.ts`, focused RSI-26 output passing 10/7, `pnpm smoke:public-trust` passing with `unchanged=4` and `pixel failures=0`, `pnpm test` passing 834/263, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-27: public trust per-page thresholds, OpenAPI docs example, and retention badge** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - Public trust smoke now applies stricter `/datasets` pixel drift policy (`0.005`) than Contact/Privacy/Terms legal pages (`0.02`).\n  - `/api/docs` now renders the `/api/public-trust/summary` response example from `/api/openapi`.\n  - Public trust CI summary now includes a retention badge snapshot-locked by `tests/fixtures/public-trust-summary/summary-snapshot.md`.\n  - Proof packet: `tests/services/public-trust-smoke-artifacts.test.ts`, `tests/scripts/public-trust-smoke-script.test.ts`, `tests/services/public-trust-smoke-ci-summary.test.ts`, `tests/fixtures/public-trust-summary/summary-snapshot.md`, `tests/api/docs.test.ts`, `tests/api/openapi.test.ts`, focused RSI-27 output passing 13/9, `pnpm smoke:public-trust` passing with per-page thresholds, `unchanged=4`, and `pixel failures=0`, `pnpm test` passing 835/263, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-28: JSON public trust threshold policy, agent-visible policy, and CI drift annotations** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - Public trust smoke policy is now persisted in `config/public-trust-smoke-policy.json` and consumed by `scripts/smoke-public-trust-pages.ts`.\n  - `/api/public-trust/summary` now exposes the applied threshold policy for agents alongside latest smoke artifacts.\n  - CI summary tooling now emits warning annotations when screenshots change but remain under their configured threshold.\n  - Proof packet: `config/public-trust-smoke-policy.json`, `src/services/public-trust-smoke-policy.ts`, `scripts/smoke-public-trust-pages.ts`, `src/services/public-trust-summary.ts`, `src/contracts/zod/public-trust-summary.ts`, `src/services/public-trust-smoke-ci-summary.ts`, `tests/services/public-trust-smoke-policy.test.ts`, `tests/scripts/public-trust-smoke-script.test.ts`, `tests/api/public-trust-summary.test.ts`, `tests/services/public-trust-smoke-ci-summary.test.ts`, `tests/scripts/public-trust-ci-workflow.test.ts`, focused RSI-28 output passing 20/12, `pnpm smoke:public-trust` passing with JSON policy thresholds, `unchanged=4`, and `pixel failures=0`, `pnpm test` passing 838/264, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-29: public trust reviewer rationale, schema rejection, and severity-grouped PR summaries** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - Public trust policy pages now carry `reviewSeverity`, `reviewerNote`, and `reasonCodes`.\n  - `/api/public-trust/summary` now exposes reviewer rationale metadata in the applied threshold policy for agents.\n  - Public trust CI summaries and warning annotations now group under-threshold visual drift by high/medium/low route severity.\n  - Proof packet: `config/public-trust-smoke-policy.json`, `src/services/public-trust-smoke-policy.ts`, `src/services/public-trust-summary.ts`, `src/contracts/zod/public-trust-summary.ts`, `scripts/smoke-public-trust-pages.ts`, `src/services/public-trust-smoke-ci-summary.ts`, `tests/services/public-trust-smoke-policy.test.ts`, `tests/api/public-trust-summary.test.ts`, `tests/services/public-trust-smoke-ci-summary.test.ts`, `tests/fixtures/public-trust-summary/summary-snapshot.md`, focused RSI-29 output passing 18/11 plus CI-summary focused retest 2/1, `pnpm exec start-server-and-test \"pnpm dev\" http://localhost:3000 \"pnpm smoke:public-trust\"` passing with JSON policy metadata and `pixel failures=0`, `pnpm test` passing 839/264, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-30: owner/reviewer initials, schema v2 fixture, and grouped annotation snapshot** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - Public trust policy pages now carry `ownerInitials` and `reviewerInitials` alongside severity, notes, and reason codes.\n  - `/api/public-trust/summary` and CI drift summary rows now expose owner/reviewer initials for fast review routing.\n  - Planned `schemaVersion: 2` policy fixture and grouped warning annotation snapshot are now committed as executable contract evidence.\n  - Proof packet: `config/public-trust-smoke-policy.json`, `src/services/public-trust-smoke-policy.ts`, `src/services/public-trust-summary.ts`, `src/contracts/zod/public-trust-summary.ts`, `src/services/public-trust-smoke-ci-summary.ts`, `tests/fixtures/public-trust-policy/schema-v2-planned.json`, `tests/fixtures/public-trust-summary/grouped-annotations-snapshot.txt`, `tests/services/public-trust-smoke-policy.test.ts`, `tests/api/public-trust-summary.test.ts`, `tests/services/public-trust-smoke-ci-summary.test.ts`, focused RSI-30 tests passing 15/10, `pnpm exec start-server-and-test \"pnpm dev\" http://localhost:3000 \"pnpm smoke:public-trust\"` passing with `unchanged=4` and `pixel failures=0`, isolated transient `tests/api/artworks/by-id.test.ts` retest passing, final `pnpm test` passing 839/264, `pnpm lint` passing with one existing warning, and `pnpm build` passing.\n- [x] ✅ **RSI-1: Provider/pipeline boundary drift hardening** (High-severity remediation) is closed and proven:\n  - boundary contract test passes with allowed shared imports only,\n  - full `pnpm test` + `pnpm lint` + `pnpm build` cycle passed in-cycle,\n  - close-out evidence synchronized in `CLAUDE.md`, `README.md`, and this roadmap.\n- [x] ✅ **RSI-2: UI journey automation scope** (Medium-severity remediation) is closed and proven:\n  - matrix automation now spans role/provider combinations (`pnpm smoke:explore:matrix`), and `/api/objects`, `/api/works`, `/api/agents`, `/api/places`, `/api/sets` route assertions verify positive + negative paths for imported records,\n  - smoke probe evidence and status updates are synchronized in `CLAUDE.md`, this roadmap, and `README.md`.\n- [x] ✅ **RSI-3: Single-file and process-complexity reduction** (Low-severity remediation) is complete (proven 2026-06-09):\n  - Owner map and top-complexity target inventory are now finalized in `docs/risk-register.md` (Action 1 complete).\n  - Action 2 is complete: `src/services/publish-queue-worker.ts` split into helper modules with existing tests preserved.\n  - Action 3 is complete: `src/services/issues.ts` split into focused modules under `src/services/issues/` with behavior preserved.\n  - Action 4 is complete: `src/services/outbox.ts` split into focused modules under `src/services/outbox/` with behavior preserved.\n  - Action 5 is complete: `src/services/reconciliation.ts` split into focused modules under `src/services/reconciliation/` with behavior preserved.\n  - Action 6 is complete: `src/services/wiki-publish.ts` split into focused modules under `src/services/wiki-publish/` with behavior preserved.\n  - Action 7 is complete: `src/services/monitoring-telemetry.ts` split into focused modules under `src/services/monitoring-telemetry/` with behavior preserved.\n  - Proof packet: `pnpm test`, `pnpm lint`, and `pnpm build` for the closeout cycle, plus synchronized updates in `CLAUDE.md`, `docs/roadmap.md`, and `README.md` with this evidence path.\n  - Action 8 is complete: `scripts/authority-cache-refresh.ts` split into focused modules under `scripts/authority-cache-refresh/` with behavior preserved.\n  - Proof packet: `pnpm test`, `pnpm lint`, and `pnpm build` with synchronized updates in `CLAUDE.md`, `docs/roadmap.md`, and `README.md`.\n  - Action 9 is complete: `src/services/ai-layer.ts` split into focused modules under `src/services/ai-layer/` with API preserved in the facade.\n  - Proof packet: `pnpm test`, `pnpm lint`, and `pnpm build` all passed, with close-out updates synchronized in `CLAUDE.md`, `README.md`, and `docs/risk-register.md`.\n- [x] ✅ **RSI-4: Chat grounding and citation enforcement** (Medium-severity remediation) is complete (proven 2026-06-09):\n  - `/api/ai/chat` now returns sentence-level grounded citations (`[entityId, propertyPath]`) and refuses output when citation coverage is incomplete.\n  - Evidence is implemented in `app/api/ai/chat/route.ts` and `src/services/ai-chat.ts`.\n  - Proof packet: `tests/api/ai-chat.test.ts`, `pnpm test` (full suite), `pnpm lint`, and `pnpm build`; close-out updates synchronized in `CLAUDE.md`, `README.md`, and `docs/risk-register.md`.\n- [x] ✅ Expand HAL `_links` discoverability coverage from representative routes to all public entity-role routes as they land (enforced through protocol + provider conformance suites and `hal-entity-discoverability-conformance` quality checks).\n- [x] ✅ Add search-relation conformance breadth tests across additional provider search endpoints (beyond representative NGA/RKD/facade checks) with pagination drift assertions.\n  - Evidence: [`tests/quality/hal-search-relations-conformance.test.ts`](/C:/Projects/metamuseum/tests/quality/hal-search-relations-conformance.test.ts) now validates relation and pagination behavior for Louvre, Harvard, Smithsonian, V&A, Princeton, Europeana, AIC, CMA, and Rijks routes in addition to existing representative checks.\n- [x] ✅ Keep execution-policy gates strict: no provider/validation merges without standards mapping + fixture anchors, and no protocol-affecting merges without conformance coverage.\n  - Evidence: [`tests/quality/execution-policy-gates.test.ts`](/C:/Projects/metamuseum/tests/quality/execution-policy-gates.test.ts), [`.github/pull_request_template.md`](/C:/Projects/metamuseum/.github/pull_request_template.md).\n\nSuggested branch name (used): `codex/era-c1-hal-search-conformance-breadth`.\n\n\n# Meta Museum — Era Delivery History\n\nArchived detailed record of the completed delivery eras (Lift / Hardening / SOTA), moved out of the active [roadmap](../roadmap.md) to keep it current. This is the slice-by-slice and B-/C-series implementation log; the roadmap holds the live status and the forward plan ([roadmap-to-10.md](../roadmap-to-10.md)).\n\n---"},{"level":2,"heading":"Three eras","body":"Scope honestly. The SOTA spec is a 20-week, multi-service, multi-store target. We get there in three eras with hard exit gates between them.\n\n| Era | Theme | Outcome | Tier |\n|---|---|---|---|\n| **A. Lift** | Move what exists into Next.js 16 + custom CSS | Public site + research workspace at parity with legacy prototype, on a modern stack | Slices 1-10 |\n| **B. Hardening** | Make the data layer trustworthy | Zod-mirrored contracts, SHACL validation, ValidationReport, Postgres swap, real auth | Quarters 2-3 post-lift |\n| **C. SOTA** | The full Yale-LUX-pattern platform | Multi-modal store, HAL hypermedia, Visual ETL Mapper, NL→SPARQL, Graph-RAG, IIIF deep-zoom, Meta Wiki Art bridge | Quarter 4+ |\n\n**Cardinal rule:** do not start a later era until the prior era's exit gate is green. Don't ship a SHACL validator on top of a JSON-file store, and don't ship Graph-RAG on top of unvalidated records.\n\n---"},{"level":2,"heading":"Era A — The Lift (10 slices, PR-sized each)","body":"Goal: end the era with the legacy prototype's full feature set running natively on Next.js 16, custom CSS, App Router + RSC, with the dependency rules from `_legacy/AGENTS.md` preserved (adapters don't cross-import; contracts are leaves; `_source.raw` is immutable)."},{"level":3,"heading":"Slice 0 — Staging (DONE)","body":"See §Status above."},{"level":3,"heading":"Slice 1 — Foundations (TDD infra first) (DONE)","body":"Port the dep-free leaves so everything else can be built on them. **Test infra lands before any port.**\n\nOrder within the slice:\n- [x] ✅ **Test infra**: `tsx` dev dependency + `\"test\": \"node --import tsx --test tests/**/*.test.ts\"` + smoke test (`tests/smoke.test.ts`) landed; `pnpm test` green.\n- [x] ✅ **Port test-first.** Legacy tests were ported and implementations landed for the specified dep-free leaves.\n   - [x] ✅ `src/constants.ts` (port of `_legacy/src/constants.js`)\n   - [x] ✅ `src/contracts/*.ts` — all 8 (`artwork`, `source-record`, `rights-report`, `import-job`, `agent-task`, `citation`, `shared-structures`, `wiki-draft`)\n   - [x] ✅ `src/utils/{text,rights,http,storage,linked-art}.ts` (leaf utils)\n- [x] ✅ No routes, no UI changes in this foundation slice scope.\n\n**Acceptance**: `pnpm test` green with full coverage of legacy `tests/contracts/*` and `tests/utils/{linked-art,validation-report}.test.ts`. `pnpm build` green. No new client-side bundle weight. Every implementation file has at least one corresponding test file."},{"level":3,"heading":"Slice 2 — Met vertical (canary) (DONE)","body":"Prove the route-handler pattern end-to-end with the simpler of the two adapters.\n\n- [x] ✅ `src/adapters/{adapter-utils,provider-interface,met}.ts` + tests\n- [x] ✅ Route handlers (all `app/api/.../route.ts`): `/health`, `/met/profile`, `/met/departments`, `/met/search` (POST), `/met/object` (POST), `/met/import` (POST)\n- [x] ✅ `app/explore/page.tsx` — minimal custom-CSS UI calling `/api/met/search`, showing image cards\n- [x] ✅ `app/layout.tsx` — `Create Next App` metadata replaced; shell landed and evolved in later slices\n\n**Acceptance**: User can search Met for \"flowers\", click a result, see object detail JSON. Loading/empty/error states present. No Linked Art shortcuts taken — Met objects normalize through the `Artwork` contract before display."},{"level":3,"heading":"Slice 3 — Getty vertical (DONE)","body":"Same shape as Slice 2 for Getty (more endpoints).\n\n- [x] ✅ `src/adapters/getty.ts` + tests\n- [x] ✅ Routes: `/getty/profile`, `/getty/entity` (POST), `/getty/import` (POST), `/getty/activity` (GET), `/getty/sparql` (POST)\n- [x] ✅ `app/explore/page.tsx` extended with provider toggle (Getty / Met / both; later expanded further)\n- [x] ✅ `app/getty/page.tsx` — SPARQL playground + ActivityStream peek\n\n**Acceptance**: Both providers reachable from `/explore`. Getty SPARQL playground returns rows. Rights and source attribution rendered on every card."},{"level":3,"heading":"Slice 4 — Records + Artworks + Entities (DONE)","body":"The persistence-touching slice. Storage stays JSON files in `storage/`.\n\n- [x] ✅ `src/utils/{artwork-builder,artwork-facets,entities,relationships}.ts` + tests\n- [x] ✅ Routes: `/records` (GET/POST), `/records/[id]` (GET), `/artworks/[id]` (GET), `/entities` (GET), `/entities/[id]` (GET), `/explorer/artworks` (GET), `/explorer/import` (POST)\n- [x] ✅ Pages: `app/records/page.tsx`, `app/artwork/[id]/page.tsx`, `app/entity/[id]/page.tsx`\n- [x] ✅ Async-`params` patterns applied per Next 16 requirements.\n\n**Acceptance**: Import a Met or Getty record from `/explore`; it appears on `/records`; clicking it opens `/artwork/[id]` with maker, date, materials, rights, citation, and a list of pivotable entities. Each entity link opens `/entity/[id]`."},{"level":3,"heading":"Slice 5 — Linked Art Inspector + Roadmap + Best-Practices (DONE)","body":"Port the JSON-LD inspect/import workflow plus the two reflective endpoints.\n\n- [x] ✅ `src/utils/best-practices-audit.ts` + tests\n- [x] ✅ Routes: `/linked-art/profile`, `/linked-art/inspect` (POST), `/linked-art/import` (POST), `/roadmap`, `/best-practices`\n- [x] ✅ Pages: `app/linked-art/page.tsx`, `app/roadmap/page.tsx`\n- [x] ✅ `/api/roadmap` returns this document as structured JSON; `/api/best-practices` preserves legacy audit semantics.\n\nStarted in this pass:\n- [x] ✅ `src/utils/best-practices-audit.ts` + tests.\n- [x] ✅ `/api/roadmap` route returning structured JSON.\n- [x] ✅ `/api/best-practices` route running the audit against stored records.\n- [x] ✅ `app/roadmap/page.tsx` initial UI shell.\n- [x] ✅ `/linked-art/profile`, `/linked-art/inspect`, `/linked-art/import` routes.\n- [x] ✅ `app/linked-art/page.tsx` full inspect/import workflow UI.\n\n**Acceptance**: Paste a Linked Art JSON-LD blob → see the inspection report. The roadmap page renders this file's phases and exit gates."},{"level":3,"heading":"Slice 6 — Patterns + Graph (DONE)","body":"Port pattern discovery and the graph view.\n\n- [x] ✅ `src/utils/patterns.ts` + tests\n- [x] ✅ Routes: `/patterns` (POST), `/graph` (GET)\n- [x] ✅ Pages: `app/patterns/page.tsx`, `app/graph/page.tsx` (Cytoscape force-directed — first external runtime dep; `cytoscape` + `cytoscape-cose-bilkent` per SOTA §3.2)\n\n**Acceptance**: Pattern scan produces buckets for unknown makers, missing dates, shared concepts. Graph page renders nodes (artworks + entities) with click-to-pivot.\n\nStarted in this pass:\n- [x] ✅ `src/utils/patterns.ts` + tests.\n- [x] ✅ `/api/patterns` route returning unknown + shared buckets.\n- [x] ✅ `/api/graph` route returning artwork/entity nodes + edges with pivot hrefs.\n- [x] ✅ `app/patterns/page.tsx` pattern scan UI.\n- [x] ✅ `app/graph/page.tsx` + `src/components/graph-viewer.tsx` Cytoscape force-directed graph UI.\n- [x] ✅ Runtime deps: `cytoscape`, `cytoscape-cose-bilkent`."},{"level":3,"heading":"Slice 7 — Issues + SSE (DONE)","body":"The streaming case.\n\n- [x] ✅ Route: `/issues` (GET), `/issues/webhook` (POST), `/issues/stream` (GET — `ReadableStream`, `export const dynamic = 'force-dynamic'`)\n- [x] ✅ Page: `app/issues/page.tsx` with the live-updating issue inventory (re-uses `_legacy/storage/linked-art-issues.json` as a fallback cache, refreshes from GitHub on a `revalidate` cadence)\n\n**Acceptance**: Issues view loads from cache instantly, hot-updates from SSE without a refresh, and respects the same `GITHUB_OWNER`/`GITHUB_REPO`/`ISSUE_POLL_MS` env vars as the legacy server.\n\nStarted in this pass:\n- [x] ✅ `src/services/issues.ts` service with legacy-compatible env vars, live GitHub fetch, local runtime cache, and `_legacy/storage/linked-art-issues.json` fallback.\n- [x] ✅ `/api/issues` route with optional `?refresh=1` force refresh.\n- [x] ✅ `/api/issues/webhook` route with optional signature verification and issue-event refresh hooks.\n- [x] ✅ `/api/issues/stream` route using `ReadableStream` SSE (`dynamic = \"force-dynamic\"`).\n- [x] ✅ `/issues/webhook` + `/issues/stream` direct route aliases for legacy-compatible pathing.\n- [x] ✅ `app/issues/page.tsx` + `src/components/issues-workbench.tsx` live issue inventory UI with SSE updates.\n- [x] ✅ New tests for service + routes: `tests/services/issues.test.ts`, `tests/api/issues.test.ts`, `tests/api/issues-webhook.test.ts`, `tests/api/issues-stream.test.ts`."},{"level":3,"heading":"Slice 8 — Agents + Jobs + Content Generation + Automation (DONE)","body":"Port the agent/job stubs as-is. No new agent logic this slice — just parity.\n\n- [x] ✅ Routes: `/agents/run` (POST), `/content/generate` (POST), `/jobs` (GET), `/jobs/run` (POST)\n- [x] ✅ Pages: `app/agents/page.tsx`, `app/automation/page.tsx`\n\n**Acceptance**: Manual pattern scan + collection brief jobs runnable from the UI; output appears in `app/automation/page.tsx`.\n\nStarted in this pass:\n- [x] ✅ `src/services/agents.ts` with legacy-parity agent/content stubs (`runAgent`, `generateContent`) and local fallback drafting.\n- [x] ✅ `src/services/jobs.ts` JSON-backed manual jobs service with seeded defaults and `lastRun` updates.\n- [x] ✅ `/api/agents/run`, `/api/content/generate`, `/api/jobs`, `/api/jobs/run` routes.\n- [x] ✅ Legacy-compatible direct route aliases: `/agents/run`, `/content/generate`, `/jobs`, `/jobs/run`.\n- [x] ✅ `app/agents/page.tsx` + `src/components/agents-workbench.tsx` for manual agent and content runs.\n- [x] ✅ `app/automation/page.tsx` + `src/components/automation-workbench.tsx` for job execution and output review.\n- [x] ✅ Route tests: `tests/api/agents-run.test.ts`, `tests/api/content-generate.test.ts`, `tests/api/jobs.test.ts`, `tests/api/jobs-run.test.ts`."},{"level":3,"heading":"Slice 9 — Workspace chrome + design-system pass (Custom CSS) (DONE)","body":"Now everything works route-by-route. Unify the visual layer.\n\n- [x] ✅ `app/(workspace)/layout.tsx` route group — sidebar nav + topbar, all custom CSS\n- [x] ✅ Expand `app/globals.css` design tokens + component classes to cover every recurring pattern; keep BEM-lite naming consistent\n- [x] ✅ Implement the design-system atomics from SOTA §12.1 in `src/components/` with a `data-la-entity-id` attribute on every entity-derived element: `<LinkedDate>`, `<LinkedDimensions>`, `<LinkedLabel>`, `<UriBadge>`, `<RightsBadge>` (already in Slice 2), `<SourceBadge>`, `<CitationBlock>`\n- [x] ✅ Entity cards (SOTA §12.2): `<ObjectCard>`, `<ActorCard>`, `<PlaceCard>`, `<ConceptCard>`\n- [x] ✅ a11y pass: axe-core in CI; keyboard nav on all interactive surfaces; WCAG 2.1 AA on public pages\n\n**Acceptance**: Lighthouse a11y ≥ 95 on `/`, `/explore`, `/artwork/[id]`. Storybook scaffold (vite-based, no Next coupling) for the atomic components.\n\nStarted in this pass:\n- [x] ✅ `app/(workspace)/layout.tsx` route-group shell with keyboard-first skip link, sidebar navigation, and topbar.\n- [x] ✅ Workspace pages moved under `app/(workspace)/...` so URLs stay unchanged while sharing common chrome.\n- [x] ✅ Expanded `app/globals.css` with workspace shell classes and reusable design-system component classes.\n- [x] ✅ Added atomics in `src/components/`: `LinkedDate`, `LinkedDimensions`, `LinkedLabel`, `UriBadge`, `SourceBadge`, `CitationBlock`; extended `RightsBadge` with `data-la-entity-id`.\n- [x] ✅ Added entity cards in `src/components/`: `ObjectCard`, `ActorCard`, `PlaceCard`, `ConceptCard`.\n- [x] ✅ Wired new components into `/explore`, `/artwork/[id]`, `/entity/[id]`, and `/records`.\n- [x] ✅ Added component tests: `tests/components/linked-atomics.test.ts`, `tests/components/entity-cards.test.ts`.\n- [x] ✅ Added CI workflow at `.github/workflows/ci.yml` running lint, tests, build, Playwright install, axe accessibility checks, and Lighthouse CI assertions.\n- [x] ✅ Added Vite-based Storybook scaffold (`.storybook/*`) and atomic stories (`src/components/atomics.stories.tsx`), validated with `pnpm storybook:build`."},{"level":3,"heading":"Slice 10 — Lift cleanup (DONE)","body":"- [x] ✅ Delete `_legacy/` (empty by now or only contains files we deliberately chose not to port)\n- [x] ✅ Rewrite `README.md` from the Next.js side; preserve the legacy product narrative\n- [x] ✅ Confirm `metamuseum-legacy/` can be archived/deleted (verified absent at `C:\\Projects\\metamuseum-legacy`).\n- [x] ✅ Security credential rotation moved to Era B operational preflight tracking (see `Pre-Era-C Operational Sign-Off` under Era B).\n- [x] ✅ Document the env-var surface (`PORT`, `GITHUB_OWNER`, `GITHUB_REPO`, `ISSUE_POLL_MS`, future `DATABASE_URL`) in `docs/env.md`\n\nStarted in this pass:\n- [x] ✅ Added automated parity gate test: `tests/quality/era-a-exit-gate.test.ts` (legacy route/view equivalents + route-test coverage checks).\n- [x] ✅ Lighthouse a11y gate now runs reliably in local Windows env via `scripts/lighthouse-a11y.mjs` (no `chrome-launcher` temp-dir cleanup failure path).\n- [x] ✅ `_legacy/` removed from workspace.\n- [x] ✅ Verified `C:\\Projects\\metamuseum-legacy` is absent as of **May 30, 2026** (already archived/deleted outside this repo).\n\n**Era A exit gate (must all be green):**\n- [x] ✅ Legacy API routes have Next 16 equivalents, tested (`tests/quality/era-a-exit-gate.test.ts`; current legacy snapshot evaluates to 33 route handlers).\n- [x] ✅ Legacy SPA views have Next 16 page equivalents (`tests/quality/era-a-exit-gate.test.ts`; current legacy snapshot evaluates to 13 named views).\n- [x] ✅ `pnpm build && pnpm test && pnpm lint` clean (validated in `metamuseum` conda env on May 30, 2026).\n- [x] ✅ Lighthouse a11y ≥ 95 on the public pages (`/`, `/explore`, `/artwork/[id]`) via `pnpm lighthouse:ci`.\n- [x] ✅ `_legacy/` deleted.\n- [x] ✅ `metamuseum-legacy/` archived/deleted (path not present at `C:\\Projects\\metamuseum-legacy` on May 30, 2026).\n\n---"},{"level":2,"heading":"Era B — Hardening (quarters, not weeks)","body":"Goal: make the data layer trustworthy enough that AI agents and external consumers can rely on it. The Era A app keeps shipping during this era; we add validation and durable storage *under* it.\n\nSlices in suggested order, but each is independently shippable:"},{"level":3,"heading":"B1 — Zod contracts + schema versioning","body":"- [x] ✅ Mirror every `src/contracts/*.ts` as a Zod schema in `src/contracts/zod/*.ts`\n- [x] ✅ Add `schemaVersion` field + a `src/utils/migrations/` registry\n- [x] ✅ Server Actions and Route Handlers validate at the boundary with the Zod schemas\n\nStatus:\n- [x] ✅ Completed."},{"level":3,"heading":"B2 — Formal validation","body":"- [x] ✅ Build a Python validation microservice (FastAPI + PySHACL + PyLD) — first non-Node service\n- [x] ✅ SHACL shapes in `shapes/linked-art/*.shacl.ttl`, fixtures in `fixtures/linked-art/{pass,fail}/`\n- [x] ✅ New route `app/api/validate/route.ts` proxies to it\n- [x] ✅ New contract `ValidationReport` (SOTA §5.1) wired into inspect/import flows\n- [x] ✅ Validation fixtures and route assertions are checked against [linked-art/LinkedArtModel1.0-Reference.md](linked-art/LinkedArtModel1.0-Reference.md) before merge.\n\nStatus:\n- [x] ✅ Completed."},{"level":3,"heading":"B3 — Postgres migration","body":"- [x] ✅ Postgres 16 via Docker Compose for dev (`ops/docker-compose.yml`)\n- [x] ✅ Migrate `storage/{records,jobs}.json` → Postgres JSONB tables via a one-time exporter that double-writes for one release, then cuts over\n- [x] ✅ Replace `src/utils/storage.ts` JSON-file impl with a Postgres impl behind the same interface; no call sites change\n- [x] ✅ Add `next-env.d.ts` env validation with Zod for `DATABASE_URL`\n\nStatus:\n- [x] ✅ Completed.\n- [x] ✅ Added `ops/docker-compose.yml` + `ops/postgres/init/01-storage.sql` (Postgres 16 dev runtime + table bootstrap).\n- [x] ✅ Added `scripts/export-storage-to-postgres.ts` one-time exporter and `pnpm storage:export:postgres`.\n- [x] ✅ `src/utils/storage.ts` now supports `file`, `double-write`, and `postgres` modes for the centralized managed-document contract behind unchanged `readJson/writeJson`.\n- [x] ✅ Added Zod-backed runtime env parsing for `DATABASE_URL` + storage mode in `src/utils/env.ts` and typed env keys in `next-env.d.ts`.\n- [x] ✅ Updated `records` and `jobs` services to avoid file-`stat` assumptions so Postgres cutover does not require call-site changes."},{"level":3,"heading":"B4 — Auth + roles","body":"- [x] ✅ Verify and document production credential rotation for `AUTH_SECRET` + `AUTH_GITHUB_SECRET` (operational sign-off completed; see [`docs/ops/auth-credential-rotation.md`](docs/ops/auth-credential-rotation.md) and `Pre-Era-C Operational Sign-Off`).\n- [x] ✅ Add Auth.js v5 with GitHub provider for write paths (`/api/records` POST, `/api/explorer/import`, `/api/getty/import`, `/api/met/import`, `/api/linked-art/import`, `/api/jobs/run`)\n- [x] ✅ Roles: `public` (read only), `researcher` (read + import), `editor` (import + agent jobs), `admin` (all)\n- [x] ✅ Middleware gates write routes; UI conditionally renders import/run buttons\n\nStatus:\n- [x] ✅ Completed.\n- [x] ✅ Added Auth.js v5 (`next-auth@5.0.0-beta.31`) with GitHub provider in root `auth.ts`.\n- [x] ✅ Added `/api/auth/[...nextauth]` handlers.\n- [x] ✅ Added centralized role mapping in `src/auth/roles.ts` (public/researcher/editor/admin with allowlist env vars).\n- [x] ✅ Added Next 16 `proxy.ts` route gates for write paths (`/api/records` POST, `/api/explorer/import`, `/api/getty/import`, `/api/met/import`, `/api/linked-art/import`, `/api/jobs/run`) plus editor-only agent endpoints.\n- [x] ✅ UI now conditionally enables import/run controls in linked-art, agents, and automation workbenches based on resolved role.\n- [x] ✅ Rotate production GitHub OAuth credentials if any legacy values are still active (operational follow-up reminder remains until verified)."},{"level":3,"heading":"B5 — Provider expansion","body":"- [x] ✅ New adapters for Harvard, Smithsonian Open Access, Rijks, RKD Knowledge Graph, National Gallery of Art Open Data, Louvre Collections JSON, V&A Collections API, Princeton University Art Museum API, Europeana, AIC, CMA (SOTA §27.1) — each shipped with route + adapter + tests.\n- [x] ✅ Each adapter implements the `provider-interface` contract; cross-adapter imports remain forbidden.\n- [x] ✅ `/explore` import flow now accepts all landed provider source IDs (`met`, `getty`, `rijks`, `nga`, `louvre`, `harvard`, `smithsonian`, `vanda`, `princeton`, `europeana`, `aic`, `cma`).\n- [x] ✅ Provider slices include executable conformance tests mapped to [linked-art/LinkedArtModel1.0-Reference.md](linked-art/LinkedArtModel1.0-Reference.md) fixture anchors.\n\n**Acceptance for each upcoming provider slice (object-specific AIDD + TDD gates):**\n- [x] ✅ Failing-first contract tests verify culturally valued physical objects normalize as `HumanMadeObject` unless explicit evidence requires another canonical class.\n- [x] ✅ Failing-first tests verify production/destruction remain structured activity/event nodes when present (not flattened to display-only strings).\n- [x] ✅ Failing-first tests verify physical characteristics (dimensions/materials/parts) are preserved as structured data when available.\n- [x] ✅ Failing-first tests verify ownership/location assertions remain distinct from non-ownership rights/reuse assertions.\n- [x] ✅ Failing-first tests verify physical object identity remains distinct from digital surrogates/representations while preserving linkage.\n- [x] ✅ Failing-first regression tests verify immovable/place-centric records are not coerced into moveable-object assumptions.\n- [x] ✅ Route-level tests verify inspect/import outputs preserve the above structures without mutating canonical source fields.\n\n**Acceptance for each upcoming provider slice (digital-content AIDD + TDD gates):**\n- [x] ✅ Failing-first contract tests verify `DigitalObject` records preserve `access_point`, `format`, and `conforms_to` when provided.\n- [x] ✅ Failing-first tests verify digital object creation events use `Creation` semantics where present, without coercion to physical-object production semantics.\n- [x] ✅ Failing-first tests verify content/carrier separation is preserved (`DigitalObject` versus `VisualItem`/`LinguisticObject`) without collapsing layers.\n- [x] ✅ Failing-first tests verify surrogate linkage can preserve shared visual content (`shows` and `digitally_shows`) when provider data supports it.\n- [x] ✅ Failing-first tests verify web-page and document references preserve the `subject_of` → `LinguisticObject` → `digitally_carried_by` pattern when present.\n- [x] ✅ Failing-first tests verify IIIF structures are preserved, including Presentation manifest `conforms_to`/`format` and Image API `DigitalService` via `digitally_available_via`.\n- [x] ✅ Route-level tests verify inspect/import outputs retain digital metadata structures (including IIIF fields) and do not mutate canonical `_source.raw`.\n- [x] ✅ Provider-slice test PRs must include or update fixture-backed tests mapped to `Round 3 Addendum — Digital Content` → `Fixture Anchors — Digital Content Examples` in [linked-art/LinkedArtModel1.0-Reference.md](linked-art/LinkedArtModel1.0-Reference.md) (web publication, surrogate parity, embedded representation image, subject_of web page, IIIF Presentation manifest, IIIF Image service).\n- [x] ✅ Provider-slice test PRs must also include a short \"Standards Mapping\" note listing the specific round addenda used (for example endpoint schema rounds, shared-structure rounds, and relevant search-relation rounds) and the fixture anchors exercised.\n\nStatus:\n- [x] ✅ Complete (Rijks, NGA, RKD, Louvre, Harvard, Smithsonian, V&A, Princeton, Europeana, AIC, CMA slices landed).\n- [x] ✅ Object-specific acceptance gates are executable and passing in `tests/quality/provider-object-specific-gates.test.ts`.\n- [x] ✅ Digital-content acceptance gates are executable and passing in `tests/quality/provider-digital-content-gates.test.ts`.\n- [x] ✅ Route-level digital inspect/import preservation is executable and passing in `tests/quality/provider-digital-content-gates.test.ts`.\n- [x] ✅ Provider manifest enforcement now requires active import providers to include `Round 3 Addendum - Digital Content` in `tests/fixtures/validation/provider-fixture-manifest.json` (`tests/quality/validation-architecture-depth.test.ts`).\n- [x] ✅ PR template now requires explicit provider digital-content fixture-anchor mapping + short Standards Mapping notes (`.github/pull_request_template.md`).\n- [x] ✅ Added `src/adapters/rijks.ts` with Search + Resolver + LDES + Change Discovery helpers and IIIF URL normalization.\n- [x] ✅ Added Rijks API routes: `/api/rijks/profile`, `/api/rijks/search`, `/api/rijks/resolve`, `/api/rijks/import`, `/api/rijks/ldes`, `/api/rijks/cd`.\n- [x] ✅ `/explore` source toggle now supports `both` / `met` / `getty` / `rijks`; `/api/explorer/import` supports Rijks URLs.\n- [x] ✅ Added test coverage for adapter + routes + provider inference (`tests/adapters/rijks.test.ts`, `tests/api/rijks/*`, updated explorer/provider tests).\n- [x] ✅ Added `src/adapters/nga.ts` plus routes `/api/nga/profile`, `/api/nga/search`, `/api/nga/import` with failing-first tests and explore/import wiring.\n- [x] ✅ Remaining provider slices are now landed: RKD Knowledge Graph, Louvre Collections JSON, Harvard, Smithsonian Open Access, V&A Collections API, Princeton University Art Museum API, Europeana, AIC, CMA.\n- [x] ✅ Rijks incremental-ingest hooks are executable: `extractRijksLdesHookData()` and `extractRijksChangeDiscoveryHookData()` with route coverage in `tests/api/rijks/ldes.test.ts` and `tests/api/rijks/cd.test.ts`.\n- [x] ✅ Rijks profile now exposes a bibliographic SRU extension-point base (`bibliographicSruBase`) and SRU URL builder coverage (`buildRijksSruSearchUrl()`), while UI flows remain unchanged.\n\nRijksmuseum integration scope now includes:\n- [x] ✅ Object metadata search and dereference pipeline (Search API + PID Resolver with content negotiation to Linked Art).\n- [x] ✅ Linked Data Event Streams ingest hooks for incremental refreshes.\n- [x] ✅ IIIF Change Discovery ingest hooks for change tracking.\n- [x] ✅ IIIF image/presentation compatibility via Micrio endpoints.\n- [x] ✅ Future bibliographic extension point via SRU (planned, not yet wired into UI flows)."},{"level":4,"heading":"B5.1 — RKD Knowledge Graph provider slice (done)","body":"Goal: integrate RKD Linked Data (CIDOC-CRM + Linked Art oriented) as a standards-first provider without bypassing current adapter boundaries.\n\nPlanned deliverables:\n- [x] ✅ Adapter: `src/adapters/rkd.ts` (provider-interface compliant, no cross-adapter imports).\n- [x] ✅ Routes:\n  - [x] ✅ `/api/rkd/profile` (GET)\n  - [x] ✅ `/api/rkd/search` (POST, paged candidate retrieval)\n  - [x] ✅ `/api/rkd/entity` (POST, URI-based fetch/enrichment)\n  - [x] ✅ `/api/rkd/import` (POST, normalize + persist)\n  - [x] ✅ optional `/api/rkd/sparql` (POST, read-only, allowlisted query templates only)\n- [x] ✅ UI:\n  - [x] ✅ added `rkd` source toggle support in `/explore`\n  - [x] ✅ provider attribution + ODC-By 1.0 license/reuse guidance rendered in RKD card/detail data.\n\nData-source constraints:\n- [x] ✅ Dataset scale is 600M+ statements and is consumed via bounded queries/pagination (`limit`/`offset` clamps and bounded search defaults).\n- [x] ✅ SPARQL endpoint details are deploy-time config (env vars) via `getRkdProfile()`/`buildRkdSparqlEndpoint()`.\n- [x] ✅ Graph scoping is supported via optional `graph` input on search/entity/template flows.\n- [x] ✅ Raw SPARQL usage is constrained to controlled, allowlisted query templates (`entitySummary`/`labelSearch`) for route-level access.\n- [x] ✅ Triply protocol-compatible SPARQL request behavior is implemented with explicit `Accept` negotiation and read-only request handling.\n\nLicense and attribution:\n- [x] ✅ RKD dataset license is Open Data Commons Attribution License 1.0; provider output carries source URL + provider attribution + license metadata.\n- [x] ✅ Rights/reuse output remains conservative when image-level rights are not explicit in source payload.\n\nAcceptance:\n- [x] ✅ Failing-first tests for adapter + routes are landed (`tests/adapters/rkd.test.ts`, `tests/api/rkd/*.test.ts`).\n- [x] ✅ Standards mapping coverage includes object/digital/shared-structure/data-discovery anchors in `tests/fixtures/validation/provider-fixture-manifest.json` (`rkd` entry).\n- [x] ✅ B8 protocol conformance coverage includes RKD routes in `tests/quality/provider-protocol-conformance.test.ts`.\n- [x] ✅ Token security checks included (`Authorization: Bearer` from env-only `RKD_TRIPLY_TOKEN`, no token persistence/logging in adapter/route flows)."},{"level":4,"heading":"B5.2 — Smithsonian Open Access provider slice (done)","body":"Goal: integrate Smithsonian Open Access search/content APIs with secure API-key handling and standards-first normalization.\n\nPlanned deliverables:\n- [x] ✅ Adapter: `src/adapters/smithsonian.ts` (provider-interface compliant, no cross-adapter imports).\n- [x] ✅ Routes:\n  - [x] ✅ `/api/smithsonian/profile` (GET)\n  - [x] ✅ `/api/smithsonian/search` (POST)\n  - [x] ✅ `/api/smithsonian/content` (POST)\n  - [x] ✅ `/api/smithsonian/import` (POST)\n- [x] ✅ UI:\n  - [x] ✅ added `smithsonian` source toggle in `/explore`\n  - [x] ✅ source attribution and conservative rights/reuse indicators are preserved in Smithsonian discovery/import card flows.\n\nOfficial API constraints:\n- [x] ✅ API key required (`api_key`) via data.gov registration (`SMITHSONIAN_API_KEY` enforced for Smithsonian search/content and URL-import retrieval).\n- [x] ✅ Search pagination uses `start` + `rows`; route schema enforces integer bounds (`start >= 0`, `rows` in `1..1000`) with compatibility aliases for legacy callers.\n- [x] ✅ Core category filters and row-group inputs are explicit enum validation at route boundary (`smithsonianSearchInputSchema` for category + `rowGroup: objects|archives`).\n\nAcceptance:\n- [x] ✅ Failing-first adapter + route tests with mocked Smithsonian responses.\n- [x] ✅ API-key security checks included (env-only secrets, no key in logs/errors/client, API key stripped from exposed source URLs).\n- [x] ✅ Standards Mapping note coverage includes fixture anchors for Smithsonian in `tests/fixtures/validation/provider-fixture-manifest.json`.\n- [x] ✅ B8 protocol checks include Smithsonian routes (`profile`, `search`, `content`, `import`) in `tests/quality/provider-protocol-conformance.test.ts`."},{"level":4,"heading":"B5.3 — Harvard Art Museums provider slice","body":"Goal: integrate Harvard Art Museums API with strong conformance to official usage constraints and Linked Art normalization boundaries.\n\nStatus:\n- [x] ✅ Planned deliverables in this slice scope (adapter + routes) are complete.\n\nPlanned deliverables:\n- [x] ✅ Adapter: `src/adapters/harvard.ts` (provider-interface compliant, no cross-adapter imports).\n- [x] ✅ Routes:\n  - [x] ✅ `/api/harvard/profile` (GET)\n  - [x] ✅ `/api/harvard/search` (POST)\n  - [x] ✅ `/api/harvard/object` (POST)\n  - [x] ✅ `/api/harvard/import` (POST)\n- [x] ✅ UI:\n  - [x] ✅ add `harvard` source toggle in `/explore`\n  - [x] ✅ preserve attribution/link-back + rights/reuse indicators on all surfaces.\n\nOfficial API constraints:\n- [x] ✅ API key required on all calls (`apikey` parameter).\n- [x] ✅ Paging uses `size` (max 100) + `page`; adapter honors `info.next/info.prev` flows.\n- [x] ✅ Respect call budget guidance (2500/day) and non-commercial + attribution terms.\n- [x] ✅ Cache/storage policy enforces a two-week max retention guidance (`<=14 days`) without explicit permission.\n- [x] ✅ Use provider image URLs directly (no local copies).\n\nAcceptance:\n- [x] ✅ Failing-first adapter + route tests with mocked Harvard responses.\n- [x] ✅ API key handling checks included (env-only key, no key in logs/errors/client surfaces).\n- [x] ✅ Rate-budget and cache-TTL policy checks included (`<=14 days` cache window).\n- [x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.\n- [x] ✅ B8 protocol checks included for all Harvard routes."},{"level":4,"heading":"B5.4 — V&A Collections API provider slice","body":"Goal: integrate V&A Collections API v2 with strong support for identifier/keyword filters and IIIF image/presentation link preservation.\n\nStatus:\n- [x] ✅ Complete.\n\nPlanned deliverables:\n- [x] ✅ Adapter: `src/adapters/vanda.ts` (provider-interface compliant, no cross-adapter imports).\n- [x] ✅ Routes:\n  - [x] ✅ `/api/vanda/profile` (GET)\n  - [x] ✅ `/api/vanda/search` (POST)\n  - [x] ✅ `/api/vanda/object` (POST)\n  - [x] ✅ `/api/vanda/import` (POST)\n- [x] ✅ UI:\n  - [x] ✅ add `vanda` source toggle in `/explore`\n  - [x] ✅ expose IIIF manifest/image links in artwork/detail surfaces where available.\n\nOfficial API constraints:\n- [x] ✅ API base is `https://api.vam.ac.uk/v2`.\n- [x] ✅ Search result pages should honor official paging constraints (`size` cap 100).\n- [x] ✅ API is suitable for dynamic subsets; bulk export flows should avoid naive high-volume API crawling.\n- [x] ✅ Terms/licensing constraints and citation requirements must be preserved in downstream usage.\n\nAcceptance:\n- [x] ✅ Failing-first adapter + route tests with mocked V&A responses.\n- [x] ✅ Identifier/keyword filter behavior tests included.\n- [x] ✅ IIIF image/presentation field extraction tests included.\n- [x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.\n  - [x] ✅ Standards Mapping note: Linked Art Model 1.0 references include Digital Content + Shared Structures + Data Discovery/API endpoint-shape rounds; fixture anchors include `tests/fixtures/validation/providers/vanda/pass.json` and `tests/fixtures/validation/providers/vanda/fail.json` plus provider manifest mapping in `tests/fixtures/validation/provider-fixture-manifest.json`.\n- [x] ✅ B8 protocol checks included for all V&A routes."},{"level":4,"heading":"B5.5 — Princeton University Art Museum provider slice","body":"Goal: integrate Princeton API object/search resources with strong preservation of nested research context and IIIF media references.\n\nPlanned deliverables:\n- [x] ✅ Adapter: `src/adapters/princeton.ts` (provider-interface compliant, no cross-adapter imports).\n- [x] ✅ Routes:\n  - [x] ✅ `/api/princeton/profile` (GET)\n  - [x] ✅ `/api/princeton/search` (POST)\n  - [x] ✅ `/api/princeton/object` (POST)\n  - [x] ✅ `/api/princeton/import` (POST)\n- [x] ✅ UI:\n  - [x] ✅ add `princeton` source toggle in `/explore`\n  - [x] ✅ preserve source attribution and media link visibility.\n\nOfficial API constraints:\n- [x] ✅ Base endpoint `https://data.artmuseum.princeton.edu`.\n- [x] ✅ No auth currently required, and adapter/profile surface explicit `authMode: none` + future-auth compatibility without interface breakage.\n- [x] ✅ Static weekly full datasets are reflected in import guidance with anti-crawl guardrails on large interactive API imports.\n\nAcceptance:\n- [x] ✅ Failing-first adapter + route tests with mocked Princeton responses.\n- [x] ✅ Nested-field preservation tests included (`texts`, `media`, `exhibitions`, `geography`, `terms`, `classifications`).\n- [x] ✅ IIIF URI extraction tests included.\n- [x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.\n  - [x] ✅ Standards Mapping note: Linked Art Model 1.0 references include Object + Digital Content + Shared Structures + API endpoint-shape rounds; fixture anchors include `tests/fixtures/validation/providers/princeton/pass.json` and `tests/fixtures/validation/providers/princeton/fail.json` plus provider manifest mapping in `tests/fixtures/validation/provider-fixture-manifest.json`.\n- [x] ✅ B8 protocol checks included for all Princeton routes."},{"level":4,"heading":"B5.6 — National Gallery of Art Open Data provider slice","body":"Goal: integrate NGA public open data as a CSV-first provider while preserving Linked Art boundary contracts and provenance-safe source lineage.\n\nPlanned deliverables:\n- [x] ✅ Adapter: `src/adapters/nga.ts` (provider-interface compliant, no cross-adapter imports).\n- [x] ✅ Routes:\n  - [x] ✅ `/api/nga/profile` (GET)\n  - [x] ✅ `/api/nga/search` (POST)\n  - [x] ✅ `/api/nga/import` (POST)\n  - [x] ✅ optional `/api/nga/refresh` (POST) deferred by design; manual refresh is supported through `/api/nga/import` with `url` + bounded `limit`.\n- [x] ✅ UI:\n  - [x] ✅ add `nga` source toggle in `/explore`\n  - [x] ✅ preserve source attribution, citation guidance, and conservative rights/reuse indicators.\n    - [x] ✅ `/explore` includes an NGA-specific citation/reuse advisory callout (CC0 metadata + verify image/media rights per object).\n\nOfficial data constraints:\n- [x] ✅ Primary distribution is CSV (UTF-8), refreshed frequently (typically daily), and should be ingested in bounded batches.\n- [x] ✅ Images/media files are not distributed in the dataset package; only links/references are included where available.\n- [x] ✅ Dataset is CC0; attribution/citation is still recommended for research usage.\n- [x] ✅ Wikidata IDs are present when known but non-exhaustive; treat as reconciliation hints, not complete authority truth.\n  - [x] ✅ Enforcement evidence: adapter/profile/import tests in `tests/adapters/nga.test.ts` and `tests/api/nga/import.test.ts` assert UTF-8 CSV parsing, bounded ingest behavior, link-only media handling, CC0 metadata/citation guidance surfaces, and optional Wikidata-hint mapping.\n\nAcceptance:\n- [x] ✅ Failing-first adapter + route tests with fixture-backed CSV parsing and UTF-8 safety.\n- [x] ✅ Idempotent upsert tests for repeated daily ingest runs.\n- [x] ✅ Tests verifying preservation of source media-link references without assuming media binary availability.\n- [x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.\n  - [x] ✅ Standards Mapping note: Linked Art Model 1.0 Object + Digital Content + Shared Structures + API endpoint-shape rounds; fixture anchors include `tests/fixtures/validation/providers/nga/pass.json` and `tests/fixtures/validation/providers/nga/fail.json` plus manifest mapping in `tests/fixtures/validation/provider-fixture-manifest.json`.\n- [x] ✅ B8 protocol checks included for all NGA routes."},{"level":4,"heading":"B5.7 — Louvre Collections JSON provider slice","body":"Goal: integrate Louvre ARK-linked JSON records as a standards-first provider while preserving attribution/provenance nuance and image-rights constraints.\n\nPlanned deliverables:\n- [x] ✅ Adapter: `src/adapters/louvre.ts` (provider-interface compliant, no cross-adapter imports).\n- [x] ✅ Routes:\n  - [x] ✅ `/api/louvre/profile` (GET)\n  - [x] ✅ `/api/louvre/object` (POST)\n  - [x] ✅ `/api/louvre/import` (POST)\n  - [x] ✅ optional `/api/louvre/search` (POST) is landed (bounded, protocol-safe endpoint)\n- [x] ✅ UI:\n  - [x] ✅ add `louvre` source toggle in `/explore`\n  - [x] ✅ preserve source attribution and image-rights disclosures on all surfaces.\n\nOfficial data constraints:\n- [x] ✅ Access is object-entry URL plus `.json` suffix (ARK-based records).\n- [x] ✅ Record content is French-first; normalization must not destructively strip source language signals.\n- [x] ✅ Image usage and text reuse must follow Louvre Terms of Use.\n- [x] ✅ Image payloads include per-image rights/copyright text and must be preserved.\n  - [x] ✅ Enforcement evidence: `tests/adapters/provider-expansion.test.ts` and `tests/api/louvre/import.test.ts` assert `.json` URL normalization, French-field preservation in `_source.raw`, and rights/copyright retention from source image payloads.\n\nAcceptance:\n- [x] ✅ Failing-first adapter + route tests with mocked Louvre JSON responses.\n- [x] ✅ URL normalization + ARK extraction safety tests included.\n- [x] ✅ Creator attribution nuance tests included (`attributionLevel`, `doubt`, `creatorRole`, attribution metadata where present).\n- [x] ✅ Rights/reuse mapping tests included with conservative defaults when rights are unclear.\n- [x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.\n- [x] ✅ B8 protocol checks included for all Louvre routes.\n  - Evidence:\n    - `tests/adapters/provider-expansion.test.ts`\n    - `tests/api/louvre/object.test.ts`\n    - `tests/api/louvre/import.test.ts`\n    - `tests/quality/provider-protocol-conformance.test.ts`\n    - `docs/providers/louvre-collections-json.md`"},{"level":3,"heading":"B6 — Authority caching","body":"- [x] ✅ Replace inline authority lookups on the request path with local cache-only access.\n- [x] ✅ Schedule a daily/weekly job that downloads Getty AAT/ULAN/TGN N-Triples + Wikidata `linked-art`-related QIDs + GeoNames + LoC NAF into Postgres (SOTA §6.2).\n- [x] ✅ Surface in the entity profile pages: \"From AAT / ULAN / Wikidata\".\n\nStatus:\n- [x] ✅ `src/services/authority-cache.ts` landed as the local authority cache service.\n- [x] ✅ `tests/quality/era-b-exit-gate.test.ts` enforces zero runtime external authority fetches on request-path route code.\n- [x] ✅ Scheduled authority refresh pipeline landed:\n  - [x] ✅ `scripts/authority-cache-refresh.ts`\n  - [x] ✅ `pnpm authority:refresh`\n  - [x] ✅ `.github/workflows/authority-cache-refresh.yml` (weekly + manual dispatch)\n- [x] ✅ Entity authority-source UX landed:\n  - [x] ✅ `src/utils/entities.ts` emits `authoritySources`\n  - [x] ✅ `app/(workspace)/entity/[id]/page.tsx` renders `From AAT / ULAN / Wikidata` style provenance labels when present\n  - [x] ✅ coverage in `tests/utils/entities.test.ts` and `tests/api/entities/by-id.test.ts`"},{"level":3,"heading":"B6.1 — Exhibition + literature reconciliation hardening","body":"Goal: prevent duplicate or fragmented cross-provider historical narratives by reconciling shared exhibitions and literature records without collapsing source provenance.\n\n- [x] ✅ Add explicit reconciliation scope beyond people/concepts:\n  - [x] ✅ Exhibition concepts/plans (`PropositionalObject`) and exhibition activities (`Activity` classified as exhibition) are candidate-matched across providers.\n  - [x] ✅ Literature records (`LinguisticObject`) including catalogs/publications about exhibitions or objects are candidate-matched across providers.\n- [x] ✅ Add deterministic candidate blocking + scoring pipeline:\n  - [x] ✅ title/label normalization + language-aware comparison\n  - [x] ✅ timespan overlap logic\n  - [x] ✅ place/venue equivalence checks using local authority identifiers/labels\n  - [x] ✅ identifier evidence (ISBN/ISSN/OCLC/DOI/local accession refs when present)\n  - [x] ✅ participant/organizer/publisher overlap evidence\n- [x] ✅ Preserve Linked Art identity/provenance invariants:\n  - [x] ✅ never rewrite source URIs in `_source.raw`\n  - [x] ✅ never infer semantics from URI path shape\n  - [x] ✅ link via explicit reconciliation decisions rather than destructive record collapse\n  - [x] ✅ keep event-centric modeling (no direct object-person shortcut introduced by reconciliation)\n- [x] ✅ Add human-review queue gates for ambiguous matches:\n  - [x] ✅ thresholds for auto-link vs review-required vs no-link (`>=0.90`, `0.65-0.89`, `<0.65`)\n  - [x] ✅ audit metadata per reconciliation decision (`actor`, `recordedAt`)\n  - [x] ✅ reversible decision model shape (`auto-link` / `needs-review` / `no-link`)\n- [x] ✅ Add failing-first fixture suite:\n  - [x] ✅ pass cases for true exhibition/literature same-as candidates from different providers\n  - [x] ✅ fail cases for near-title collisions, edition conflicts, and time/place mismatches\n  - [x] ✅ regression coverage for threshold behavior and invariants\n\nDefinition of done:\n- [x] ✅ `tests/quality/reconciliation-exhibitions-literature.test.ts` passes with fixture-backed pass/fail coverage.\n- [x] ✅ Reconciliation outputs are provenance-safe and standards-mapped (round + fixture anchor references in PR).\n- [x] ✅ Entity pages expose linked \"same exhibition\"/\"same publication\" context without mutating source records.\n\nImplementation note:\n- [x] ✅ Use [reconciliation/exhibition-literature-reconciliation.md](reconciliation/exhibition-literature-reconciliation.md) as the required execution checklist for this slice.\n\nStatus:\n- [x] ✅ `src/services/reconciliation.ts` landed with explicit exhibition/publication candidate extraction, deterministic scoring, and human-review thresholds.\n- [x] ✅ `tests/fixtures/reconciliation/exhibitions-literature-pass.json` + `tests/fixtures/reconciliation/exhibitions-literature-fail.json` landed as fixture anchors.\n- [x] ✅ `app/(workspace)/entity/[id]/page.tsx` now surfaces cross-provider alignment context where reconciliation candidates exist."},{"level":3,"heading":"B8 — API protocol + profile conformance hardening","body":"- [x] ✅ Enforce JSON-LD 1.1 output with canonical Linked Art context on all public entity payloads.\n- [x] ✅ Add explicit content negotiation behavior:\n  - [x] ✅ `Accept: application/ld+json;profile=\"https://linked.art/ns/v1/linked-art.json\"`\n  - [x] ✅ graceful fallback when generic JSON/LD headers are used\n- [x] ✅ Add `GET` + `OPTIONS` support and baseline CORS behavior on public API endpoints.\n- [x] ✅ Add protocol tests asserting URI opacity: no handler or client helper may derive semantics by parsing URI path shapes.\n- [x] ✅ Add serialization tests ensuring multi-valued Linked Art fields remain arrays even when cardinality is one.\n\nStatus:\n- [x] ✅ Complete for current Era B route inventory.\n- [x] ✅ Representative executable conformance tests now run for `/api/linked-art/profile`, `/api/artworks/[id]`, and `/api/entities/[id]`:\n  - [x] ✅ `OPTIONS` + baseline CORS headers\n  - [x] ✅ Linked Art media type negotiation for `Accept: application/ld+json;profile=...`\n  - [x] ✅ URI opacity, array cardinality safety, and HAL separation assertions\n  - [x] ✅ representative entity-role coverage across object/work/agent/place/set\n- [x] ✅ Expanded executable protocol checks to currently landed provider/search endpoints (`/api/met/*`, `/api/getty/*`, `/api/rijks/*`, `/api/nga/*`, `/api/rkd/*`, plus `/api/providers/*`) via `tests/quality/provider-protocol-conformance.test.ts`.\n- [x] ✅ Generic JSON-LD fallback behavior is covered (`Accept: application/ld+json` negotiates to canonical Linked Art profile media type).\n- [x] ✅ Ongoing policy: B8 executable checks are applied to all currently landed provider slices; continue applying the same checks for additional future sources.\n\nAcceptance:\n- [x] ✅ Protocol conformance tests pass for representative routes across object/work/agent/place/set.\n- [x] ✅ No regressions in existing inspect/import flows."},{"level":3,"heading":"B9 — Linked Art modeling guardrails (provenance + lifecycle)","body":"- [x] ✅ Add conformance tests for provenance partitioning patterns:\n  - [x] ✅ wrapper provenance `Activity`\n  - [x] ✅ `Acquisition` + `Payment` as parts when both are asserted\n- [x] ✅ Add explicit ownership vs custody invariants:\n  - [x] ✅ `TransferOfCustody` must not be rewritten into `Acquisition` unless title transfer evidence is present\n- [x] ✅ Add explicit unknown-transfer handling:\n  - [x] ✅ use `Transfer` for ambiguous exchange events rather than fabricating legal outcomes.\n- [x] ✅ Extend inspect/import audits to flag carrier/content conflation and direct object-person shortcuts that bypass event nodes.\n\nStatus:\n- [x] ✅ Complete for current Era B guardrail scope.\n- [x] ✅ Conformance guardrails added in `src/utils/linked-art.ts` for:\n  - [x] ✅ wrapper provenance Activity partitioning checks\n  - [x] ✅ `Acquisition` + `Payment` split-into-parts checks when both are asserted\n  - [x] ✅ custody-vs-title invariants (`TransferOfCustody` vs `Acquisition`)\n  - [x] ✅ unknown-transfer guardrails (`Transfer` for ambiguous exchanges)\n  - [x] ✅ carrier/content conflation and direct object-person shortcut detection\n- [x] ✅ Failing-first pass/fail fixtures added:\n  - [x] ✅ `tests/fixtures/b9/provenance-guardrails-pass.json`\n  - [x] ✅ `tests/fixtures/b9/provenance-guardrails-fail.json`\n- [x] ✅ Executable guardrail tests added in `tests/quality/linked-art-b9-guardrails.test.ts`.\n- [x] ✅ Best-practices audit includes actionable B9 category output: `Provenance & Lifecycle Guardrails (B9)`.\n\nAcceptance:\n- [x] ✅ Failing-first fixtures prove the above patterns and invariants across pass/fail cases.\n- [x] ✅ Best-practices audit reports actionable violations for these categories."},{"level":3,"heading":"B10 — ARK conformance slice","body":"- [x] ✅ ULID-based ARK minting for normalized records.\n- [x] ✅ Add `/api/ark/resolve` resolver endpoint with suffix pass-through behavior.\n- [x] ✅ Add `?info` inflection response for metadata + persistence statement retrieval.\n- [x] ✅ Define and return a persistence statement structure for ARK `?info` responses.\n- [x] ✅ Add failing-first tests for ARK utility behavior and resolver route behavior.\n- [x] ✅ Update roadmap/README/CLAUDE standards guidance for ARK conformance expectations.\n\nStatus:\n- [x] ✅ Complete for current Era B scope.\n- [x] ✅ Implemented `src/utils/ark.ts` with opaque ULID minting, ARK normalization, suffix pass-through resolution, and `?info` payload construction.\n- [x] ✅ Implemented `app/api/ark/resolve/route.ts` with:\n  - [x] ✅ `GET` resolution (`303` redirect style)\n  - [x] ✅ suffix pass-through for variant/service paths\n  - [x] ✅ `?info` inflection JSON-LD payload\n  - [x] ✅ `OPTIONS` + baseline CORS behavior\n- [x] ✅ `normalizeIncomingRecord` now mints ARKs via ULID-based helper (`mintArkIdentifier`) instead of non-deterministic short random tokens.\n- [x] ✅ Added executable tests:\n  - [x] ✅ `tests/utils/ark.test.ts`\n  - [x] ✅ `tests/api/ark/resolve.test.ts`\n\nAcceptance:\n- [x] ✅ Resolver pass-through and inflection tests are green.\n- [x] ✅ ARK minting tests assert opaque ULID-form ARK output.\n- [x] ✅ ARK conformance behavior is now documented in project guidance."},{"level":3,"heading":"B7 — API gateway readiness for multi-source scale","body":"- [x] ✅ Keep direct provider adapters as default while source count and traffic stay moderate (current implementation remains direct adapters).\n- [x] ✅ Gateway activation policy is implemented and threshold-gated. Activate only when one or more conditions are true:\n  - [x] ✅ 6+ external providers in production.\n  - [x] ✅ 2+ upstream credential/security models to centralize.\n  - [x] ✅ cross-provider rate limiting/circuit breaking becomes operationally necessary.\n- [x] ✅ Candidate gateway responsibilities are defined and readiness-backed:\n  - [x] ✅ Centralized auth/secrets policy, rate limiting, retries/circuit breakers, request/response telemetry, and provider health dashboards.\n  - [x] ✅ Stable internal route facade (`/api/providers/:provider/...`) so UI and jobs remain unchanged as provider backends evolve.\n  - [x] ✅ Response envelope standardization plus provider capability registry for dynamic UI feature flags.\n- [x] ✅ B7 non-goals are explicitly enforced:\n  - [x] ✅ No business logic migration out of adapters.\n  - [x] ✅ No forced microservice split of the Next app.\n  - [x] ✅ No gateway requirement for local development.\n\nStatus:\n- [x] ✅ Complete for Era B readiness scope.\n- [x] ✅ Added gateway-readiness diagnostics endpoint: `/api/providers/readiness` (threshold evaluation without forcing architecture changes).\n- [x] ✅ Added provider capability registry endpoint: `/api/providers/capabilities`.\n- [x] ✅ Added stable internal facade routes: `/api/providers/:provider/profile|search|import` with standardized response envelopes.\n- [x] ✅ Added conformance coverage for facade + capability routes in `tests/quality/provider-protocol-conformance.test.ts`.\n- [x] ✅ Re-evaluated activation threshold with current production providers (Met, Getty, Rijks, NGA, RKD, Louvre, Harvard, Smithsonian, V&A, Princeton, Europeana, AIC, CMA): threshold is now hit (6+ providers), so `/api/providers/readiness` reports `gatewayRecommended: true` while direct adapters remain the active mode.\n\n**Era B exit gate:**\n- [x] ✅ 100% of public-facing sample records pass validation checks (SHACL when validator service is configured; local standards fallback otherwise).\n- [x] ✅ All writes auth-gated; audit log row per primary write route.\n- [x] ✅ Postgres is the storage of record when `DATABASE_URL` is set; `storage/*.json` removed from version control.\n- [x] ✅ All authority lookups served from local cache policy (zero runtime authority calls on the request path).\n- [x] ✅ Protocol/profile conformance suite green (context, media type profile, CORS/OPTIONS, URI opacity, array cardinality safety).\n\n**Era B completion verdict:**\n- [x] ✅ **Engineering-complete.** Core B1-B10 deliverables and Era B exit-gate checks are green.\n- [x] ✅ **Operational sign-off complete** for current pre-Era-C checklist items."},{"level":3,"heading":"Pre-Era-C Operational Sign-Off","body":"- [x] ✅ Verify and record rotation of production `AUTH_SECRET` and `AUTH_GITHUB_SECRET` (with date and owner in release notes/runbook).\n  - [x] Evidence anchor: `docs/ops/auth-credential-rotation.md`; operation signed as complete in this roadmap section and `docs/progress/2026-05-31/era-c-readiness-snapshot.md`.\n- [x] ✅ Record explicit gateway activation decision now that `gatewayRecommended: true` is reported (`activate now` vs `keep direct-adapter mode`) with owner + review date.\n  - [x] ✅ Decision: **keep direct-adapter mode active** for now; do not force gateway activation yet.\n  - [x] ✅ Owner: `@rsung`\n  - [x] ✅ Review date: **August 31, 2026**\n  - [x] ✅ Decision record reference: `docs/progress/2026-05-31/era-c-readiness-snapshot.md`\n- [x] ✅ Add a one-page Era C readiness snapshot to `docs/progress/` linking latest green evidence: `era-b-exit-gate`, `protocol-conformance`, `provider-protocol-conformance`, `linked-art-b9-guardrails`, `validation-drift:trend`.\n\n---"},{"level":2,"heading":"Era C — SOTA platform (quarters 4+)","body":"Goal: implement the Yale-LUX-pattern hybrid platform described in [LinkedArtSOTAWebApp.md](linked-art/LinkedArtSOTAWebApp.md) §§2–22. By this point, the Next.js app is stable enough to host a curator workbench and an AI agent surface on top of an honest data layer.\n\nRoughly the same numbering as the SOTA spec's phases — but starting *here*, after Era A and B have shipped:"},{"level":3,"heading":"C1 — Multi-modal storage + HAL hypermedia (SOTA Phase 1)","body":"- [x] ✅ Solr 9 + **GraphDB** provisioned via Helm (dev: Compose, GraphDB CE image)\n- [x] ✅ GraphDB SPARQL 1.1 endpoint + Lucene plugin for hybrid text+graph queries; named graphs per source institution for provenance partitioning (SOTA §8.2)\n- [x] ✅ RDFS + SHACL reasoning only at runtime — no full OWL DL (SOTA §8.2)\n- [x] ✅ `src/utils/record-materializer.ts` builds Yale-LUX-style denormalized `Record` documents + shortcut triples (SOTA §20.1)\n- [x] ✅ HAL `_links` on every entity response (SOTA §9.2)\n- [x] ✅ Entity HAL discoverability now includes stable `la:activityFeed` link (`/api/activity`) via shared link builder + conformance tests.\n- [x] ✅ Canonical role endpoint scaffolds landed for `/api/objects/[id]`, `/api/works/[id]`, `/api/agents/[id]`, `/api/places/[id]`, `/api/sets/[id]` with executable B8 protocol conformance coverage.\n- [x] ✅ Add `/api/concepts/[id]` and `/api/events/[id]` canonical endpoints to complete the canonical C1 role endpoint surface.\n- [x] ✅ `/api/search` landed with OrderedCollectionPage pagination contract and executable HAL/search conformance coverage.\n- [x] ✅ `/api/activity` syndication endpoint\n\nStatus:\n- [x] ✅ Dev Compose provisioning added in `ops/docker-compose.yml` (`sota` profile: `solr:9.6`, `ontotext/graphdb:10.8.14`).\n- [x] ✅ Helm provisioning added in `ops/helm/metamuseum-search-graph/` (StatefulSets + Services + PVC defaults for Solr and GraphDB).\n- [x] ✅ GraphDB bootstrap automation added:\n  - `scripts/graphdb-bootstrap.ts` creates repository config + verifies SPARQL query/update endpoints + provisions Lucene connector via `luc:createConnector`.\n  - Runtime reasoning policy enforced in bootstrap: `GRAPHDB_RULESET` accepts only `rdfsplus` / `rdfsplus-optimized` (OWL-family rulesets rejected).\n  - `scripts/graphdb-load-named-graph.ts` loads provider RDF into source-specific named graphs for provenance partitioning.\n  - `src/utils/provenance-graphs.ts` defines stable institution graph URIs.\n- [x] ✅ Record materialization + index flattening foundations landed:\n  - `src/services/records.ts` now materializes on write for all import/persist paths.\n  - `src/utils/record-materializer.ts` emits denormalized shortcut fields/triples.\n  - `src/utils/search-index.ts` flattens materialized records into Solr/OpenSearch-ready documents using `_shortcuts`."},{"level":3,"heading":"C2 — ETL pipeline + reconciliation (SOTA Phase 2)","body":"- [x] ✅ `pipeline/` Dagster project — ELT, idempotent at every stage, SHA-256 dedupe keys\n- [x] ✅ FastAPI reconciliation service hitting Getty SPARQL / VIAF / Wikidata / GeoNames behind Redis URI cache\n- [x] ✅ Promote B6.1 exhibition/literature reconciliation heuristics into the C2 service as first-class pipelines (not optional post-processing)\n- [x] ✅ Confidence thresholds per SOTA §7.3 with a human-review queue in `app/curator/reconciliation/page.tsx`\n- [x] ✅ Visual ETL Mapper (ReactFlow) + `MappingTemplate` contract\n\nStatus:\n- [x] ✅ `pipeline/` scaffold landed with Dagster project files (`pipeline/pyproject.toml`, `pipeline/requirements.txt`) and runnable entry points (`pipeline/run_materialize.py`, `metamuseum_pipeline.definitions`).\n- [x] ✅ ELT asset chain implemented in `pipeline/metamuseum_pipeline/assets.py`: `extract_source_records` → `load_records` → `transform_records` → `dedupe_records` → `upsert_materialized_records`.\n- [x] ✅ SHA-256 dedupe key policy implemented in `pipeline/metamuseum_pipeline/dedupe.py` (`canonical_json` + `record_sha256`) and consumed at load/dedupe/materialize stages.\n- [x] ✅ Idempotence coverage added in `pipeline/tests/test_c2_pipeline.py` (repeat materialization does not duplicate state rows; unchanged rows are no-op upserts).\n- [x] ✅ Reconciliation service scaffold landed in `services/reconciliation-service/`:\n  - FastAPI app with `POST /reconcile/lookup` and `GET /health`.\n  - Provider adapters for Getty SPARQL, VIAF AutoSuggest, Wikidata, and GeoNames.\n  - Redis URI cache layer with deterministic SHA-256 cache keys and TTL controls.\n  - Unit coverage in `services/reconciliation-service/tests/test_reconciliation_service.py`.\n- [x] ✅ B6.1 heuristics promoted to first-class C2 pipeline endpoints in `services/reconciliation-service/main.py`:\n  - `GET /reconcile/pipelines`\n  - `POST /reconcile/pipelines/exhibitions-literature`\n  - `GET /reconcile/pipelines/exhibitions-literature/bands`\n  - Heuristic parity implementation in `services/reconciliation-service/pipelines.py` with fixture-backed tests in `services/reconciliation-service/tests/test_b61_pipeline.py`.\n- [x] ✅ SOTA §7.3 threshold model and queue UI landed:\n  - Threshold bands implemented in `src/services/reconciliation.ts` (`>=0.95` auto-approve, `0.85-0.95` weekly digest flag, `0.70-0.85` human-review queue, `<0.70` drop candidate).\n  - Curator queue page added at `app/curator/reconciliation/page.tsx` with explicit human-review and weekly-digest sections.\n  - Regression assertions updated in `tests/quality/reconciliation-exhibitions-literature.test.ts`.\n- [x] ✅ Visual ETL Mapper + MappingTemplate contract landed:\n  - `src/contracts/mapping-template.ts` defines `createMappingTemplate`/`validateMappingTemplate` for JSON-serializable mapper nodes, edges, and executable rules.\n  - `src/contracts/zod/mapping-template.ts` mirrors the contract boundary and is exported through `src/contracts/zod/index.ts`.\n  - ReactFlow mapper UI shipped at `app/(workspace)/etl/mapper/page.tsx` via `src/components/etl-mapper-workbench.tsx` with dry-run projection preview.\n  - Contract and schema coverage added in `tests/contracts/mapping-template.test.ts` and `tests/contracts/zod-mirror.test.ts`.\n- [x] ✅ **C2 complete.** ETL pipeline, reconciliation service, B6.1 pipeline promotion, SOTA §7.3 thresholds + human-review queue, and Visual ETL Mapper + `MappingTemplate` contract are all landed and test-verified."},{"level":3,"heading":"C3 — IIIF + visualizations (SOTA Phase 3)","body":"- [x] ✅ `<IIIFCanvasViewer>` (OpenSeadragon wrapper) with deep-zoom, side-by-side compare, annotations\n- [x] ✅ `<ProvenanceTimeline>`, `<GeoMapViewer>` (Leaflet), `<NetworkGraph>` (Cytoscape — partially landed in Slice 6)\n- [x] ✅ `<ConcertinaList>` + `<FacetHistogram>` for dense entity browses (SOTA §12.3)\n- [x] ✅ `<EntityKnowledgePanel>` merging internal + DBpedia/Wikidata/ULAN/AAT context\n- [x] ✅ Overlapping exhibition timelines with zoom + direct navigation to exhibition/activity records\n\nStatus:\n- [x] ✅ IIIF workspace route shipped at `/iiif` via `app/(workspace)/iiif/page.tsx` with imported-record-backed source selection.\n- [x] ✅ OpenSeadragon wrapper shipped in `src/components/iiif-canvas-viewer.tsx` with deep-zoom, side-by-side compare mode, annotation overlays, and optional viewport lock.\n- [x] ✅ Canvas source normalization + fallback behavior covered in `tests/utils/iiif.test.ts` (`deriveIiifInfoJsonUrl`, OpenSeadragon image tile fallback, deduplicated source extraction).\n- [x] ✅ Insights workspace now composes first-class C3 visualization components:\n  - `src/components/provenance-timeline.tsx`\n  - `src/components/geo-map-viewer.tsx` (Leaflet)\n  - `src/components/network-graph.tsx` (Cytoscape)\n- [x] ✅ `app/(workspace)/insights/page.tsx` now includes timeline + Leaflet geospatial view + Cytoscape relationship network in one drillable research workflow.\n- [x] ✅ Deterministic timeline-window + histogram binning logic is test-covered in `tests/utils/provenance-visualization.test.ts`.\n- [x] ✅ Dense entity browse route shipped at `/entities` via `app/(workspace)/entities/page.tsx`, with URL-driven `q`/`type`/`authority` filters.\n- [x] ✅ `src/components/concertina-list.tsx` and `src/components/facet-histogram.tsx` are now first-class C3 components for high-density entity review.\n- [x] ✅ Facet/filter/group model is test-covered in `tests/utils/entity-browse.test.ts` and component rendering is covered in `tests/components/entity-browse-components.test.ts`.\n- [x] ✅ `app/(workspace)/entity/[id]/page.tsx` now includes `EntityKnowledgePanel` with internal profile metrics plus cache-backed external authority context sections for DBpedia, Wikidata, ULAN, and AAT.\n- [x] ✅ Knowledge-model merge behavior is covered in `tests/utils/entity-knowledge.test.ts` and panel rendering is covered in `tests/components/entity-knowledge-panel.test.ts`.\n- [x] ✅ Exhibition overlap analysis is now first-class in Insights via `src/components/exhibition-timeline.tsx` + `src/utils/exhibition-timeline.ts`, with independent zoom/window controls and direct links into `/entity/:id` exhibition/activity records."},{"level":3,"heading":"C4 — AI layer (SOTA Phase 4)","body":"- [x] ✅ pgvector + voyage-3 embeddings for entity summaries and statement texts; SigLIP for IIIF visual similarity\n- [x] ✅ `/api/ai/query` — NL → SPARQL/HAL with mandatory SHACL pre-execution validation\n- [x] ✅ `/api/ai/chat` — Graph-RAG with mandatory citations (`[entityId, propertyPath]` per sentence); \"cite or refuse\" rule (RSI-4 complete, proven 2026-06-09)\n- [x] ✅ LLM-assisted reconciliation tiebreaker (SOTA §10.4)\n- [x] ✅ LLM-assisted mapping for the Visual ETL Mapper\n\nStatus:\n- [x] ✅ AI embedding service landed in `src/services/ai-layer.ts`, including entity-summary + statement-text document extraction from `buildEntityIndex(...)`, Voyage API integration (`voyage-3`), and deterministic fallback embeddings for non-keyed/local runs.\n- [x] ✅ pgvector persistence landed with bootstrap SQL + upsert path:\n  - `ops/postgres/init/02-ai-layer.sql`\n  - `persistEmbeddingsPgvector(...)` in `src/services/ai-layer.ts`\n- [x] ✅ AI API routes landed:\n  - `app/api/ai/embeddings/route.ts` (document build + embeddings + optional pgvector persist)\n  - `app/api/ai/visual-similarity/route.ts` (SigLIP service call path + heuristic fallback)\n- [x] ✅ NL query API landed: `app/api/ai/query/route.ts` with NL → HAL/SPARQL planning, mandatory SHACL-catalog pre-execution validation, and explicit `412` blocking on disallowed queries.\n- [x] ✅ Query execution logs now capture NL prompt + generated query for retraining/audit (`storage/ai-query-log.json` via `src/services/ai-query.ts`).\n- [x] ✅ SigLIP visual-similarity fallback + IIIF candidate extraction landed (`extractVisualSimilarityCandidates`, `rankVisualSimilarity`) with representation/access-point awareness.\n- [x] ✅ Dev infra for pgvector readiness updated: `ops/docker-compose.yml` now uses `pgvector/pgvector:pg16` for local Postgres bootstrap compatibility.\n- [x] ✅ Coverage landed for C4 behavior:\n  - `tests/services/ai-layer.test.ts`\n  - `tests/api/ai-embeddings.test.ts`\n  - `tests/api/ai-visual-similarity.test.ts`\n  - `tests/services/ai-query.test.ts`\n  - `tests/api/ai-query.test.ts`\n- [x] ✅ RSI-4 complete: `/api/ai/chat` implemented with graph-driven claim extraction, per-sentence citation enforcement, and refusal path when coverage is incomplete.\n  - Proof: `tests/api/ai-chat.test.ts`, `pnpm test`, `pnpm lint`, and `pnpm build`.\n  - Route contracts and behavior are reflected in `app/api/ai/chat/route.ts` and `src/services/ai-chat.ts`.\n- [x] ✅ C4 Visual ETL Mapper AI assist is complete:\n  - `src/services/mapping-assist.ts` suggests review-ready `MappingTemplate` drafts from source columns using local model-compatible heuristics with confidence, rationale, standards anchors, and unmapped-column diagnostics.\n  - `app/api/ai/mapping-assist/route.ts` exposes a POST assist endpoint with explicit JSON validation and CORS preflight.\n  - `src/components/etl-mapper-workbench.tsx` adds a curator-visible \"Suggest mapping with AI\" action that calls the assist endpoint and keeps outputs review-only.\n  - Proof packet: `tests/services/mapping-assist.test.ts`, `tests/api/ai-mapping-assist.test.ts`, `tests/components/etl-mapper-config.test.ts`, and `tests/api/openapi.test.ts`."},{"level":3,"heading":"C5 — Syndication + Meta Wiki Art + hardening (SOTA Phase 5)","body":"- [x] ✅ ActivityStreams subscriptions endpoint open to external aggregators\n- [x] ✅ `/api/activity` external-consumer readiness metric capture landed (`/api/activity/readiness` + per-request consumer telemetry with explicit `x-linked-art-consumer-id` support).\n- [x] ✅ `/api/activity/subscriptions` landed for external aggregator registration/discovery:\n  - `GET /api/activity/subscriptions` (ActivityStreams `OrderedCollectionPage` shape + metrics)\n  - `POST /api/activity/subscriptions` (consumer-aware callback registration)\n  - `DELETE /api/activity/subscriptions?id=...` (unsubscribe)\n  - Public CORS preflight via `OPTIONS`\n- [x] ✅ Meta Wiki Art publish flow: `WikiDraft` → review → publish to **MediaWiki + custom Wikibase** with citation + rights templates\n- [x] ✅ C5 publish-flow implementation evidence:\n  - `app/api/wiki-drafts/route.ts`\n  - `app/api/wiki-drafts/[id]/review/route.ts`\n  - `app/api/wiki-drafts/[id]/publish/route.ts`\n  - `src/services/wiki-publish.ts`\n  - Verification (2026-05-31): `tests/api/wiki-drafts/flow.test.ts` and `tests/services/wiki-publish.test.ts` both passing, including dry-run and live-publish adapter paths with citation + rights templates.\n- [x] ✅ Wikibase statement-level references required for every publishable claim (provenance-safe writes)\n  - `evaluateWikiPublishPreflight(...)` now validates every claim carries at least one statement-level reference with valid `sourceUrl`, `retrievedAt`, and `citationText` before publish can proceed.\n  - Evidence: `src/services/wiki-publish.ts`, `tests/services/wiki-publish.test.ts`, `tests/api/wiki-drafts/flow.test.ts`.\n- [x] ✅ Bidirectional mapping maintained between internal entity IDs and wiki item/property IDs for traceable sync\n  - Live publish now upserts durable sync mappings in `wiki-sync-map.json` (Postgres-managed in non-file modes) for:\n    - internal entity ID ↔ wikibase item ID\n    - internal property ID ↔ wikibase property ID\n  - API lookup route landed for traceable sync diagnostics:\n    - `GET /api/wiki-sync-map?internalEntityId=...`\n    - `GET /api/wiki-sync-map?wikiItemId=...`\n    - `GET /api/wiki-sync-map?internalPropertyId=...`\n    - `GET /api/wiki-sync-map?wikiPropertyId=...`\n  - Evidence: `src/services/wiki-sync-map.ts`, `app/api/wiki-drafts/[id]/publish/route.ts`, `app/api/wiki-sync-map/route.ts`, `tests/services/wiki-sync-map.test.ts`, `tests/api/wiki-sync-map.test.ts`, `tests/api/wiki-drafts/flow.test.ts`.\n- [x] ✅ WCAG 2.1 AA full audit on every public route\n  - Evidence (2026-05-31): `pnpm a11y:check` passes with zero serious/moderate/critical axe violations across all public UI routes:\n    - `/`, `/explore`, `/records`, `/linked-art`, `/patterns`, `/insights`, `/graph`, `/entities`, `/iiif`, `/issues`, `/agents`, `/automation`, `/etl/mapper`, `/roadmap`, `/getty`, `/curator/reconciliation`, `/artwork/[id]`, `/entity/[id]`.\n  - Remediations landed for detected violations:\n    - `src/components/geo-map-viewer.tsx` (removed nested-interactive conflict by replacing `role=\"img\"` map container semantics with described interactive container text)\n    - `src/components/etl-mapper-workbench.tsx` (added keyboard focus to scrollable dry-run `<pre>` region).\n- [x] ✅ k6 load test against SOTA §20.4 SLOs (API p95 < 200ms cached, < 500ms cold; search p95 < 300ms)\n  - Harness landed:\n    - `scripts/k6-slo.js` (scenario definitions + per-scenario thresholds)\n    - `scripts/k6-slo-runner.mjs` (local binary / PATH / Docker fallback runner)\n    - `pnpm k6:slo` and `pnpm k6:slo:ci`\n    - runbook: `docs/ops/k6-slo.md`\n  - Update (2026-06-10): evidence contract now covers the full SOTA §20.4 p95 set, including whitelisted SPARQL p95 `< 2s` and IIIF tile serving p95 `< 100ms`.\n    - `src/services/era-c-exit-gate.ts`\n    - `config/era-c-exit-gate-policy.json`\n    - `tests/services/era-c-exit-gate.test.ts`\n  - Verification (2026-05-31, `pnpm k6:slo`, summary export `artifacts/performance/k6-slo-summary.json`):\n    - `cached_record_hit` p95: **73.5495ms** (target `< 200ms`) ✅\n    - `cold_record_read` p95: **56.134ms** (target `< 500ms`) ✅\n    - `keyword_facet_search` p95: **55.0616ms** (target `< 300ms`) ✅\n    - `http_req_failed` rate by scenario: **0.00** ✅\n- [x] ✅ OpenAPI 3.1 at `/api/docs`\n  - Implementation landed:\n    - `GET /api/openapi` returns generated OpenAPI 3.1 JSON from live route-handler discovery (`src/services/openapi.ts`).\n    - `GET /api/docs` serves interactive Swagger UI wired to `/api/openapi`.\n  - Evidence:\n    - `app/api/openapi/route.ts`\n    - `app/api/docs/route.ts`\n    - `src/services/openapi.ts`\n    - `tests/api/openapi.test.ts`\n    - `tests/api/docs.test.ts`\n  - Verification (2026-05-31): `pnpm test -- tests/api/openapi.test.ts tests/api/docs.test.ts` passing.\n- [x] ✅ Pen test + DR drill\n  - Executable hardening gate landed:\n    - `pnpm pentest:baseline` (dependency advisory regression check against committed baseline)\n    - `pnpm dr:drill` (non-destructive restore rehearsal with SHA-256 parity checks)\n    - `pnpm hardening:pen-dr` (combined gate)\n  - Baselines + runbooks:\n    - `config/security-audit-baseline.json`\n    - `docs/ops/security-dr-drill.md`\n  - Artifacts:\n    - `artifacts/security/pnpm-audit-summary.json`\n    - `artifacts/dr-drill/latest.json`"},{"level":3,"heading":"Era C Principal Hardening Addenda (Staff/Principal Review)","body":"These items are now integrated as explicit execution backlog for distributed systems, lifecycle integrity, AI safety, UX globalization, and privacy controls."},{"level":4,"heading":"1) Infrastructure + distributed systems","body":"- [x] ✅ Transactional outbox for Postgres → Solr/GraphDB consistency on write paths (`outbox_events` table + reliable projector worker + replay-safe idempotency keys).\n  - Landed transactional write-path integration in `src/services/records.ts`:\n    - Postgres mode now atomically upserts `storage_documents.records` and enqueues `outbox_events` in one transaction.\n    - Idempotency key: `sha256(\"record.upsert|recordId|sourceHash\")`.\n  - Outbox persistence + retry lifecycle:\n    - `src/services/outbox.ts` (`claim`/`process`/`retry`/`dead_letter` flow, SKIP LOCKED claims, backoff).\n    - `ops/postgres/init/02-outbox.sql` bootstraps `outbox_events` + indexes.\n  - Reliable projector worker:\n    - `src/services/outbox-projector.ts` (Solr + GraphDB projection with per-event ack/fail handling).\n    - `scripts/outbox-projector.ts`, `pnpm outbox:projector`, `pnpm outbox:projector:once`.\n  - Ops docs:\n    - `docs/ops/outbox-projector.md`\n- [x] ✅ Outbox failure handling policy (retry budget, dead-letter queue, operator replay tooling, and alerting).\n  - Policy + queue health primitives:\n    - `src/services/outbox.ts`\n      - env-driven policy (`OUTBOX_MAX_ATTEMPTS`, queue/age thresholds)\n      - queue health summary counters/aging\n      - dead-letter listing, replay helpers, stale-processing requeue\n  - Operator tooling:\n    - `scripts/outbox-ops.ts`\n    - `pnpm outbox:status`\n    - `pnpm outbox:dlq:list`\n    - `pnpm outbox:replay:dlq`\n    - `pnpm outbox:requeue:stale`\n  - Alerting:\n    - `src/services/outbox-alerts.ts`\n    - `scripts/outbox-alert-check.ts`\n    - `pnpm outbox:alert:check` with optional webhook dispatch via `OUTBOX_ALERT_WEBHOOK_URL`\n  - Ops docs:\n    - `docs/ops/outbox-projector.md`\n- [x] ✅ OpenTelemetry end-to-end trace propagation across Next.js, validation service, reconciliation service, Dagster pipeline runs, and GraphDB/Solr calls.\n- [x] ✅ Correlated request/run identifiers enforced in logs + traces (`x-request-id` / traceparent continuity).\n  - Evidence: `instrumentation.ts` (`@vercel/otel` registration), Python OTel bootstrap in `services/validation-service/main.py`, `services/reconciliation-service/main.py`, and pipeline run tracing in `pipeline/run_materialize.py`.\n  - Evidence: request/response trace header continuity via `src/utils/observability.ts`, `src/utils/protocol.ts`, and `proxy.ts`; write-audit correlation fields persisted from async trace context in `src/services/write-audit.ts`.\n  - Evidence: local OTLP wiring templates + runbook (`.env.otlp.tempo.example`, `.env.otlp.jaeger.example`, `docs/ops/otel-local.md`) and explicit DB span attributes at finalized GraphDB/Solr call sites (`src/utils/otel-db-spans.ts`, `src/services/ai-query.ts`, `src/services/solr-client.ts`)."},{"level":4,"heading":"2) Data lifecycle + upstream sync","body":"- [x] ✅ Provider tombstone handling in C2 pipeline (HTTP `404/410` upstream signals mark local tombstone, deindex in Solr, and emit deletion activity).\n  - C2 pipeline lifecycle handling landed in `pipeline/metamuseum_pipeline/assets.py`:\n    - upstream tombstone detection from `_source.upstreamStatus` / `_source.httpStatus` / `_source.statusCode`\n    - local tombstone registry persisted in `pipeline/state/materialized-records.json` (`tombstones` block)\n    - Solr deindex call on tombstone transition (`/solr/<core>/update` delete-by-id)\n    - deletion activity emission to `pipeline/state/deletion-activities.json` with `Delete` + `Tombstone` object semantics\n  - Test coverage:\n    - `pipeline/tests/test_c2_pipeline.py` (`test_tombstone_404_marks_local_tombstone_and_emits_delete_activity`)\n  - Live drill (2026-05-31):\n    - projected record `https://example.org/object/outbox-deindex-final-1780254290729` into Solr via outbox projector,\n    - ran C2 tombstone materialization with `_source.upstreamStatus=410`,\n    - Solr exact-id count transitioned `before=1` -> `after=0`,\n    - summary included `deindexed=1` + `deletion_activities_emitted=1`.\n- [x] ✅ ActivityStreams deletion semantics for tombstoned records (`Delete`/`Tombstone` event policy documented and implemented).\n  - Feed policy + implementation landed in `app/api/activity/route.ts`:\n    - `/api/activity` now merges C2 tombstone lifecycle activities from `pipeline/state/deletion-activities.json`.\n    - Tombstoned records are emitted as ActivityStreams `Delete` events with `object.type = \"Tombstone\"` and `object.formerType = \"HumanMadeObject\"`.\n    - Response includes explicit `policy.deletionSemantics` metadata for aggregator consumers.\n  - Coverage:\n    - `tests/api/activity.test.ts` (`includes Delete/Tombstone activities from tombstone lifecycle state`)\n- [x] ✅ Meta Wiki Art source-of-truth contract finalized (publication-target-only vs community-editable model).\n  - Default mode: `publication-target-only` (safe default).\n  - Optional mode: `community-editable` (explicit opt-in).\n  - Executable policy gate landed:\n    - `src/services/wiki-source-of-truth.ts`\n    - `app/api/wiki-drafts/[id]/publish/route.ts`\n  - Publish preflight now enforces source anchoring:\n    - draft `sourceRecordId` must resolve to an internal record before publish proceeds.\n  - Coverage:\n    - `tests/services/wiki-source-of-truth.test.ts`\n    - `tests/api/wiki-drafts/flow.test.ts` (`blocks publish when source record is missing under publication-target-only contract`)\n- [x] ✅ If community-editable: reverse-ETL conflict resolution pipeline from Wikibase back to Postgres with deterministic merge policy.\n  - Reverse-ETL apply endpoint landed:\n    - `POST /api/wiki-sync/reverse-etl` (`app/api/wiki-sync/reverse-etl/route.ts`)\n  - Deterministic merge + idempotency engine landed:\n    - `src/services/wiki-reverse-etl.ts`\n    - precedence policy: newer `modifiedAt` wins; equal timestamp tie-break by lexicographic `changeId`; replayed `changeId` is skipped.\n  - Back-sync state persistence landed:\n    - `storage/wiki-reverse-etl-state.json` (`wiki_reverse_etl_state` managed in Postgres modes)\n  - Coverage:\n    - `tests/services/wiki-reverse-etl.test.ts`\n    - `tests/api/wiki-reverse-etl.test.ts`"},{"level":4,"heading":"3) AI/LLM reliability (EvalOps)","body":"- [x] ✅ Golden evaluation dataset for complex museum questions (minimum 100 prompts with expected grounding/citation behavior).\n  - Dataset landed:\n    - `evals/golden-museum-questions.v1.json` (`120` prompts, rubric + per-prompt grounding/citation/refusal expectations)\n  - EvalOps documentation landed:\n    - `docs/evals/golden-museum-questions.md`\n  - Executable conformance gate landed:\n    - `tests/quality/ai-eval-golden-dataset.test.ts`\n- [x] ✅ CI eval gate for AI-layer PRs (faithfulness, relevance, citation accuracy, citation freshness) using an evaluation harness (Ragas/DeepEval-equivalent workflow).\n  - Eval harness landed:\n    - `src/services/ai-eval-harness.ts`\n    - `scripts/ai-eval-gate.ts`\n  - Commands landed:\n    - `pnpm ai:eval:report`\n    - `pnpm ai:eval:gate`\n  - CI workflow landed (AI-layer path-gated):\n    - `.github/workflows/ai-eval-gate.yml`\n  - Coverage:\n    - `tests/services/ai-eval-harness.test.ts`\n- [x] ✅ Regression thresholds for model/prompt/version changes with fail-fast policy on citation and citation-freshness drift.\n  - Versioned regression policy landed:\n    - `config/ai-eval-regression-policy.json`\n    - baseline identity keys: `datasetId + datasetVersion + modelVersion + promptVersion`\n  - Drift evaluator landed:\n    - `src/services/ai-eval-regression.ts`\n    - citation drift is configured as fail-fast (`failFastOnCitationDrift`)\n    - citation freshness drift is configured as fail-fast (`failFastOnCitationFreshnessDrift`)\n  - Gate runner wiring landed:\n    - `scripts/ai-eval-gate.ts` (`--check`, `--record-baseline`)\n    - `pnpm ai:eval:gate`\n    - `pnpm ai:eval:baseline:record`\n  - CI enforcement landed:\n    - `.github/workflows/ai-eval-gate.yml`\n  - Coverage:\n    - `tests/services/ai-eval-regression.test.ts`\n- [x] ✅ Structured evaluation artifact retention for trend analysis (per-run metrics + prompt/model version metadata).\n  - Eval artifact retention service landed:\n    - `src/services/ai-eval-artifacts.ts`\n  - Gate runner now writes:\n    - `artifacts/evals/ai-eval-gate-latest.json`\n    - `artifacts/evals/runs/ai-eval-gate-<timestamp>.json`\n    - `artifacts/evals/trend-index.json`\n    - `artifacts/evals/summary.md` with CI badges, artifact links, and freshness-aging alerts\n  - CI visibility:\n    - `.github/workflows/ai-eval-gate.yml` appends `artifacts/evals/summary.md` to `$GITHUB_STEP_SUMMARY`\n    - `.github/workflows/ai-eval-gate.yml` uploads `artifacts/evals/` for post-run inspection\n  - Retention control:\n    - `METAMUSEUM_EVAL_RETENTION_MAX_RUNS` (default `200`)\n  - Coverage:\n    - `tests/services/ai-eval-artifacts.test.ts`"},{"level":4,"heading":"4) Frontend UX + research quality","body":"- [x] ✅ Next.js i18n routing + locale negotiation from `Accept-Language` with graceful fallback.\n  - Locale-aware proxy routing landed:\n    - `proxy.ts`\n  - i18n routing policy + negotiation utilities landed:\n    - `src/utils/i18n-routing.ts`\n    - `src/utils/locale-preferences.ts`\n  - Behavior:\n    - Non-localized `GET/HEAD` page requests redirect to `/{locale}/...` using negotiated locale.\n    - Locale-prefixed requests rewrite to canonical internal routes while preserving locale context via request header/cookie.\n    - Unsupported locale negotiation gracefully falls back to default locale (`en`).\n    - Write-route role checks remain enforced against locale-normalized paths.\n  - Coverage:\n    - `tests/utils/i18n-routing.test.ts`\n- [x] ✅ Linked Art language-tag selection policy in UI rendering (prefer user locale, then fallback chain, preserving source labels).\n  - Locale and language-tag policy utilities landed:\n    - `src/utils/locale-preferences.ts`\n    - `src/utils/linked-art-language.ts`\n  - UI renderers now pass request locale preferences from `Accept-Language`:\n    - `app/(workspace)/artwork/[id]/page.tsx`\n    - `app/(workspace)/entity/[id]/page.tsx`\n    - `app/(workspace)/records/page.tsx`\n    - `app/(workspace)/entities/page.tsx`\n    - `app/(workspace)/iiif/page.tsx`\n  - Linked Art projection layers now apply locale-aware label selection while preserving source-label fallbacks:\n    - `src/utils/artwork-builder.ts`\n    - `src/utils/entities.ts`\n  - Coverage:\n    - `tests/utils/linked-art-language.test.ts`\n    - `tests/utils/artwork-builder.test.ts`\n    - `tests/utils/entities.test.ts`\n- [x] ✅ Researcher feedback/annotation loop using W3C Web Annotation model (claim-targeted annotations without mutating canonical `_source.raw`).\n  - W3C annotation contracts + validation landed:\n    - `src/contracts/zod/web-annotation.ts`\n    - `src/contracts/web-annotation.ts`\n    - `src/contracts/zod/requests.ts`\n  - Annotation persistence is isolated from canonical records and stored as a separate managed document (`annotations.json`):\n    - `src/services/annotations.ts`\n    - `src/utils/storage.ts`\n  - API endpoints landed for create/list/get with public CORS and audit logging:\n    - `app/api/annotations/route.ts`\n    - `app/api/annotations/[id]/route.ts`\n  - Research UI loop landed on artwork detail pages:\n    - `src/components/research-annotation-loop.tsx`\n    - `app/(workspace)/artwork/[id]/page.tsx`\n  - Coverage:\n    - `tests/api/annotations.test.ts`\n    - `tests/auth/roles.test.ts`\n- [x] ✅ Curator triage queue for annotation-driven correction proposals with provenance-safe review flow.\n  - Curator triage queue API landed with state-aware queue metrics and claim-target proposal payloads:\n    - `app/api/annotations/triage/route.ts`\n  - Provenance-safe review action API landed:\n    - `app/api/annotations/[id]/review/route.ts`\n    - `src/services/annotations.ts` (`review()` workflow transitions)\n  - Role policy enforces editor/admin review access while preserving researcher submit access:\n    - `src/auth/roles.ts`\n  - Curator workspace queue UI landed:\n    - `app/curator/annotations/page.tsx`\n    - `src/components/annotation-triage-workbench.tsx`\n    - `app/layout.tsx` (workspace navigation link)\n  - Coverage:\n    - `tests/api/annotations.test.ts`\n    - `tests/auth/roles.test.ts`"},{"level":4,"heading":"5) Security + privacy posture","body":"- [x] ✅ PII/sensitivity scan stage in C2 ETL before public indexing/syndication.\n  - `src/utils/sensitivity.ts` scans materialized records for PII, cultural-sensitivity, and restricted-publication signals while excluding raw provider payload blobs.\n  - `src/utils/record-materializer.ts` attaches `_sensitivity` review state during import/persist materialization.\n  - `src/services/outbox-projector.ts` skips Solr + GraphDB public projections for records held by sensitivity review.\n  - Coverage:\n    - `tests/utils/sensitivity.test.ts`\n    - `tests/utils/record-materializer.test.ts`\n    - `tests/utils/search-index.test.ts`\n    - `tests/services/outbox-projector.test.ts`\n- [x] ✅ Human-review hold policy for flagged records (restricted publication until disposition).\n  - Flagged records carry `_sensitivity.status = \"review_required\"` and `_sensitivity.holdPublication = true`.\n  - Held records are excluded from Solr/GraphDB syndication and flattened with `publication_status = \"held_for_review\"` plus no public `text_all`.\n- [x] ✅ Culturally sensitive knowledge handling rules integrated with rights/reuse UI and syndication controls.\n  - Cultural-sensitivity scan rules and syndication holds now flow into explorer DTOs as `held_for_review` records with explicit rights/reuse review labels.\n  - `RightsBadge` renders sensitivity labels as review-required publication holds, keeping cultural-sensitivity warnings visible anywhere rights/reuse chips appear.\n  - Coverage:\n    - `tests/components/linked-atomics.test.ts`\n    - `tests/utils/artwork-builder.test.ts`\n- [x] ✅ Security telemetry for sensitivity decisions (who approved, why, and when).\n  - Scanner telemetry records version, scan time, signal count, highest severity, and hold policy.\n  - Human disposition telemetry now requires reviewer identity, rationale, and timestamp before approval can clear a publication hold.\n  - Materialization preserves existing disposition telemetry during record rewrites, and reviewed records only return to public indexing after an approved disposition.\n  - Coverage:\n    - `tests/utils/sensitivity.test.ts`\n    - `tests/utils/record-materializer.test.ts`\n    - `tests/utils/search-index.test.ts`"},{"level":4,"heading":"6) Content credibility engine (trust/originality/distribution/consistency)","body":"- [x] ✅ Baseline credibility-engine policy document landed:\n  - `docs/content-credibility-engine.md`\n- [x] ✅ Trust-layer storage templates landed:\n  - `provenance/ledger.json`\n  - `provenance/source-map.yaml`\n- [x] ✅ Originality-layer storage template landed:\n  - `semantic-core/originality-index.json`\n  - baseline novelty threshold documented (`cosine_distance > 0.18`)\n- [x] ✅ Distribution/consistency scaffolding landed:\n  - `distribution/schedule.yaml`\n  - runtime queue path reserved at `distribution/queue.db` (gitignored)\n  - `generation/style-profile.md`\n- [x] ✅ Monitoring scaffold landed:\n  - `monitoring/metrics.json`\n- [x] ✅ Enforce citation-coverage gates in code for generated/publishable artifacts.\n  - `POST /api/content/generate` now returns `422` when computed citation coverage falls below threshold (`METAMUSEUM_CITATION_COVERAGE_THRESHOLD`, default `0.95`), including coverage diagnostics in response.\n  - `POST /api/wiki-drafts/[id]/publish` now runs explicit publish preflight and returns `422` with preflight diagnostics when citation coverage or other publishability checks fail.\n  - Evidence: `src/utils/citation-coverage.ts`, `app/api/content/generate/route.ts`, `src/services/wiki-publish.ts`, `app/api/wiki-drafts/[id]/publish/route.ts`, `tests/api/content-generate.test.ts`, `tests/quality/cite-or-refuse-conformance.test.ts`, `tests/services/wiki-publish.test.ts`.\n- [x] ✅ Enforce originality-score gates in code for generated/publishable artifacts.\n  - Originality scoring utility landed with policy-driven threshold + minimum unique-insight checks:\n    - `src/utils/originality-score.ts`\n  - `POST /api/content/generate` now returns `422` when originality score gates fail, including originality diagnostics in output payload.\n    - `src/services/agents.ts`\n    - `app/api/content/generate/route.ts`\n  - Wiki publish preflight now enforces originality gates before publishable status:\n    - `src/services/wiki-publish.ts`\n    - `app/api/wiki-drafts/[id]/publish/route.ts`\n  - Coverage:\n    - `tests/utils/originality-score.test.ts`\n    - `tests/api/content-generate.test.ts`\n    - `tests/quality/cite-or-refuse-conformance.test.ts`\n    - `tests/services/wiki-publish.test.ts`\n    - `tests/api/wiki-drafts/flow.test.ts`\n- [x] ✅ Add weekly credibility audit automation (drift + relevance + broken-link checks).\n  - Weekly audit orchestration script landed:\n    - `scripts/credibility-audit.ts`\n    - `src/services/credibility-audit.ts`\n  - Package commands:\n    - `pnpm credibility:audit`\n    - `pnpm credibility:audit:check`\n  - Weekly GitHub Action landed:\n    - `.github/workflows/credibility-audit.yml`\n    - uploads `artifacts/credibility-audit/latest.json`\n  - Coverage:\n    - `tests/services/credibility-audit.test.ts`\n- [x] ✅ Add queue worker implementation for multi-channel publish orchestration (web/linkedin/medium/email/api).\n  - Publish queue worker service landed with:\n    - schedule parsing from `distribution/schedule.yaml`\n    - durable queue state in `distribution/queue.db`\n    - per-channel delivery states, retries/backoff, dead-letter handling\n    - per-day channel cap deferral logic from schedule policy\n    - channel adapters for `web`, `linkedin`, `medium`, `email`, `api`\n  - Files:\n    - `src/services/publish-queue-worker.ts`\n    - `scripts/publish-queue-worker.ts`\n    - `distribution/README.md`\n  - Commands:\n    - `pnpm publish:queue:worker`\n    - `pnpm publish:queue:worker:once`\n    - `pnpm publish:queue:worker:drain`\n  - Coverage:\n    - `tests/services/publish-queue-worker.test.ts`\n- [x] ✅ Add OpenTelemetry metric/span conventions for trust/originality/distribution events.\n  - Shared conventions module landed with stable span/metric names plus common layer/event/kind/outcome attributes:\n    - `src/utils/otel-credibility.ts`\n  - Trust/originality gates now emit standardized spans/metrics:\n    - `src/utils/citation-coverage.ts`\n    - `src/utils/originality-score.ts`\n  - Wiki publish preflight/execute and distribution queue orchestration emit the same conventions:\n    - `src/services/wiki-publish.ts`\n    - `src/services/publish-queue-worker.ts`\n  - Coverage:\n    - `tests/utils/otel-credibility.test.ts`\n- [x] ✅ Add eval thresholds for engagement velocity and trust/originality regression alerts.\n  - Threshold evaluator landed for engagement velocity minimum plus trust/originality minimum and baseline-drop budgets:\n    - `src/services/credibility-eval-thresholds.ts`\n    - `config/credibility-eval-thresholds.json`\n  - Weekly credibility audit now evaluates and reports these alerts:\n    - `src/services/credibility-audit.ts`\n    - `scripts/credibility-audit.ts`\n  - Coverage:\n    - `tests/services/credibility-eval-thresholds.test.ts`\n    - `tests/services/credibility-audit.test.ts`"},{"level":4,"heading":"Highest ROI priority","body":"- [x] ✅ Implement OpenTelemetry before broader C4/C5 expansion to prevent distributed-debugging bottlenecks.\n\nMeta Wiki Art bridge implementation notes:\n- [x] ✅ See [meta-wiki-art-bridge.md](meta-wiki-art-bridge.md) for sequencing constraints, boundaries, and the staged publish flow.\n  - Sequencing constraints are documented under `## Sequencing constraints`.\n  - Bridge boundaries are documented under `## Boundaries (Out Of Scope For Era A/B)`.\n  - Staged publish flow is documented under `## Planned C5 flow`.\n\n**Era C exit gate:**\n- [x] ✅ Automated evidence pack landed for all four checks (artifact schema + nightly job + dated run history).\n  - Schema: `docs/schemas/era-c-exit-gate-evidence.schema.json`\n  - Policy: `config/era-c-exit-gate-policy.json`\n  - Script + artifacts: `scripts/era-c-exit-gate.ts`, `artifacts/exit-gate/`\n  - Trend index now carries compact failed-check reasons so agents can prioritize the next blocker without opening every historical artifact.\n  - Telemetry snapshot automation: `scripts/monitoring-telemetry-sync.ts` via `pnpm monitoring:telemetry:sync` (wired into `pnpm era-c:exit-gate:evidence` / `pnpm era-c:exit-gate:check`)\n  - Nightly workflow: `.github/workflows/era-c-exit-gate-evidence.yml`\n  - Nightly workflow now supports deployed-target evidence via `METAMUSEUM_EVIDENCE_BASE_URL`, `METAMUSEUM_EVIDENCE_IIIF_TILE_URL`, optional SPARQL/query vars, and matrix-first `METAMUSEUM_ACTIVITY_CONSUMER_IDS` (`METAMUSEUM_ACTIVITY_CONSUMER_ID` fallback); when target vars are missing it keeps the local `pnpm k6:slo:ci` fallback so automation still produces artifacts.\n- [x] ✅ Deployment-foundation preflight landed for controlled beta / production launch review.\n  - Commands: `pnpm launch:preflight`, `pnpm launch:preflight:production`\n  - Script + service: `scripts/deployment-preflight.ts`, `src/services/deployment-preflight.ts`\n  - Runbook: `docs/ops/deployment-preflight.md`\n  - Scope: verifies env/secrets, Postgres mode, uptime source, SLO target URL, fresh DR restore rehearsal, and staging-vs-production smoke-token posture before collecting exit-gate evidence.\n- [x] ✅ Launch review packet landed for controlled beta / production launch decision evidence.\n  - Commands: `pnpm launch:review`, `pnpm launch:review:check`, `pnpm launch:review:production`\n  - Script + service: `scripts/launch-review.ts`, `src/services/launch-review.ts`\n  - Runbook: `docs/ops/launch-review.md`\n  - Scope: aggregates latest preflight, Era C exit-gate, security audit baseline, DR drill, public-trust smoke, a11y evidence, and explore smoke evidence; production fails on missing/stale/red evidence while staging can warn for beta-only evidence collection.\n  - Evidence producers: `pnpm a11y:check` writes `artifacts/launch/a11y-latest.json`, and `pnpm smoke:explore:matrix` writes `artifacts/launch/explore-smoke-latest.json`.\n- [ ] ⚠️ Latest exit-gate status is **failed** (`2026-06-10T11:36:30.690Z`), but the artifact is now agent-actionable:\n  - SLO failures distinguish incomplete k6 summaries from actual p95 threshold breaches via `missingMetricsInWindow` and per-sample `metricDetails`.\n  - Uptime failures include evidence `source` and `notes`.\n  - Activity adoption credits only declared external consumers with `class: \"declared\"` and `declaredId`.\n  - KPI failures include source metadata, snapshot notes, and per-failed-metric source/reason details.\n- [ ] All SLOs in SOTA §20.4 met at p95 over a 30-day window.\n  - Evidence contract now requires all five p95 SLO metrics: cached Record, cold Record, keyword+facet search, whitelisted SPARQL, and IIIF tile serving.\n  - SLO evidence artifacts now include missing-metric summaries and per-sample metric details so incomplete k6 runs are actionable separately from threshold breaches.\n  - Nightly workflow hardening can now collect complete deployed-target samples once GitHub vars provide the app base URL, IIIF tile URL, and whitelisted SPARQL inputs.\n  - Current blocker: retained k6 history has only `3/30` samples and those samples are legacy three-metric summaries missing whitelisted SPARQL and IIIF tile p95 values.\n- [ ] 99.9% uptime on public read.\n  - Uptime gate now rejects stale or undated availability snapshots; `uptime.maxSnapshotAgeHours` defaults to `48` so the 30-day proof must be continuously refreshed.\n  - Uptime evidence artifacts now surface source (`prometheus`, `probe`, `unavailable`) plus notes, making missing public-read proof actionable without opening telemetry snapshots.\n  - Current blocker: uptime source is `probe`, availability is `1`, and `sampleCount30d` is `3`; continue scheduled probes until the 30-sample window is met.\n- [ ] ≥ 3 external Linked Art systems consume the `/api/activity` feed.\n  - Exit-gate adoption evidence now credits only declared external consumers via `x-linked-art-consumer-id`; derived fingerprints remain diagnostic and cannot satisfy the gate.\n  - Evidence ingestion preserves `class` + `declaredId` from `storage/activity-consumers.json`, so declared external consumers can satisfy the gate when real adoption arrives.\n  - Activity adoption proof tooling now rejects placeholder/local IDs by default, probes `/api/activity` + `/api/activity/readiness`, and writes dated single-consumer + matrix artifacts under `artifacts/activity-adoption/`.\n  - Current blocker in the latest published artifact: declared external consumers are `0/3`; run `pnpm activity:adoption:matrix` with three partner-owned consumer IDs against the deployed target after those consumers are onboarded.\n- [ ] KPIs in SOTA §26 hit.\n  - KPI gate now rejects stale or undated KPI snapshots and requires real AI query cost telemetry when `kpis26.requireAiQueryCostTelemetry` is enabled; fallback default cost values remain diagnostic only.\n  - KPI evidence artifacts now include source metadata, snapshot notes, and per-failed-metric source/reason details so SOTA §26 blockers are actionable without opening telemetry inputs.\n  - KPI telemetry sync now accepts `monitoring/kpi-evidence.json` (or `METAMUSEUM_KPI_EVIDENCE_PATH`) for aggregate production record-enrichment and reconciliation-review counts; invalid sections are ignored instead of creating false-green metrics.\n  - AI query telemetry now logs `costUsd`, `costCurrency`, `costSource`, and usage counts per query; the deterministic local planner records `costUsd: 0` with `costSource: \"deterministic-local-planner\"` instead of relying on fallback KPI defaults.\n  - Nightly workflow now seeds one deployed `/api/ai/query` request before telemetry sync when `METAMUSEUM_EVIDENCE_BASE_URL` is configured.\n  - Current blockers in the latest published artifact: `dataQualityEnrichedShare`, `reconciliationAutoApproveRate`, and `reconciliationPrecisionReviewed`; AI query cost telemetry is now sourced and within policy, so the remaining KPI work is production enrichment/reconciliation evidence.\n\n---"}]}