{"id":"progress/era-history","relativePath":"progress/era-history.md","title":"Meta Museum — Era Delivery History","markdown":"# 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---\n## Three eras\n\nScope 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---\n\n## Era A — The Lift (10 slices, PR-sized each)\n\nGoal: 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).\n\n### Slice 0 — Staging (DONE)\n\nSee §Status above.\n\n### Slice 1 — Foundations (TDD infra first) (DONE)\n\nPort 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.\n\n### Slice 2 — Met vertical (canary) (DONE)\n\nProve 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.\n\n### Slice 3 — Getty vertical (DONE)\n\nSame 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.\n\n### Slice 4 — Records + Artworks + Entities (DONE)\n\nThe 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]`.\n\n### Slice 5 — Linked Art Inspector + Roadmap + Best-Practices (DONE)\n\nPort 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.\n\n### Slice 6 — Patterns + Graph (DONE)\n\nPort 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`.\n\n### Slice 7 — Issues + SSE (DONE)\n\nThe 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`.\n\n### Slice 8 — Agents + Jobs + Content Generation + Automation (DONE)\n\nPort 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`.\n\n### Slice 9 — Workspace chrome + design-system pass (Custom CSS) (DONE)\n\nNow 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`.\n\n### Slice 10 — Lift cleanup (DONE)\n\n- [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---\n\n## Era B — Hardening (quarters, not weeks)\n\nGoal: 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:\n\n### B1 — Zod contracts + schema versioning\n\n- [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.\n\n### B2 — Formal validation\n\n- [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.\n\n### B3 — Postgres migration\n\n- [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.\n\n### B4 — Auth + roles\n\n- [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).\n\n### B5 — Provider expansion\n\n- [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).\n\n#### B5.1 — RKD Knowledge Graph provider slice (done)\n\nGoal: 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).\n\n#### B5.2 — Smithsonian Open Access provider slice (done)\n\nGoal: 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`.\n\n#### B5.3 — Harvard Art Museums provider slice\n\nGoal: 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.\n\n#### B5.4 — V&A Collections API provider slice\n\nGoal: 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.\n\n#### B5.5 — Princeton University Art Museum provider slice\n\nGoal: 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.\n\n#### B5.6 — National Gallery of Art Open Data provider slice\n\nGoal: 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.\n\n#### B5.7 — Louvre Collections JSON provider slice\n\nGoal: 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`\n\n### B6 — Authority caching\n\n- [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`\n\n### B6.1 — Exhibition + literature reconciliation hardening\n\nGoal: 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.\n\n### B8 — API protocol + profile conformance hardening\n\n- [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.\n\n### B9 — Linked Art modeling guardrails (provenance + lifecycle)\n\n- [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.\n\n### B10 — ARK conformance slice\n\n- [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.\n\n### B7 — API gateway readiness for multi-source scale\n\n- [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.\n\n### Pre-Era-C Operational Sign-Off\n\n- [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---\n\n## Era C — SOTA platform (quarters 4+)\n\nGoal: 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:\n\n### C1 — Multi-modal storage + HAL hypermedia (SOTA Phase 1)\n\n- [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`.\n\n### C2 — ETL pipeline + reconciliation (SOTA Phase 2)\n\n- [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.\n\n### C3 — IIIF + visualizations (SOTA Phase 3)\n\n- [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.\n\n### C4 — AI layer (SOTA Phase 4)\n\n- [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`.\n\n### C5 — Syndication + Meta Wiki Art + hardening (SOTA Phase 5)\n\n- [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`\n\n### Era C Principal Hardening Addenda (Staff/Principal Review)\n\nThese items are now integrated as explicit execution backlog for distributed systems, lifecycle integrity, AI safety, UX globalization, and privacy controls.\n\n#### 1) Infrastructure + distributed systems\n\n- [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`).\n\n#### 2) Data lifecycle + upstream sync\n\n- [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`\n\n#### 3) AI/LLM reliability (EvalOps)\n\n- [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`\n\n#### 4) Frontend UX + research quality\n\n- [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`\n\n#### 5) Security + privacy posture\n\n- [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`\n\n#### 6) Content credibility engine (trust/originality/distribution/consistency)\n\n- [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`\n\n#### Highest ROI priority\n\n- [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---\n\n","sections":[{"level":2,"heading":"Three eras","anchor":"three-eras"},{"level":2,"heading":"Era A — The Lift (10 slices, PR-sized each)","anchor":"era-a-the-lift-10-slices-pr-sized-each"},{"level":3,"heading":"Slice 0 — Staging (DONE)","anchor":"slice-0-staging-done"},{"level":3,"heading":"Slice 1 — Foundations (TDD infra first) (DONE)","anchor":"slice-1-foundations-tdd-infra-first-done"},{"level":3,"heading":"Slice 2 — Met vertical (canary) (DONE)","anchor":"slice-2-met-vertical-canary-done"},{"level":3,"heading":"Slice 3 — Getty vertical (DONE)","anchor":"slice-3-getty-vertical-done"},{"level":3,"heading":"Slice 4 — Records + Artworks + Entities (DONE)","anchor":"slice-4-records-artworks-entities-done"},{"level":3,"heading":"Slice 5 — Linked Art Inspector + Roadmap + Best-Practices (DONE)","anchor":"slice-5-linked-art-inspector-roadmap-best-practices-done"},{"level":3,"heading":"Slice 6 — Patterns + Graph (DONE)","anchor":"slice-6-patterns-graph-done"},{"level":3,"heading":"Slice 7 — Issues + SSE (DONE)","anchor":"slice-7-issues-sse-done"},{"level":3,"heading":"Slice 8 — Agents + Jobs + Content Generation + Automation (DONE)","anchor":"slice-8-agents-jobs-content-generation-automation-done"},{"level":3,"heading":"Slice 9 — Workspace chrome + design-system pass (Custom CSS) (DONE)","anchor":"slice-9-workspace-chrome-design-system-pass-custom-css-done"},{"level":3,"heading":"Slice 10 — Lift cleanup (DONE)","anchor":"slice-10-lift-cleanup-done"},{"level":2,"heading":"Era B — Hardening (quarters, not weeks)","anchor":"era-b-hardening-quarters-not-weeks"},{"level":3,"heading":"B1 — Zod contracts + schema versioning","anchor":"b1-zod-contracts-schema-versioning"},{"level":3,"heading":"B2 — Formal validation","anchor":"b2-formal-validation"},{"level":3,"heading":"B3 — Postgres migration","anchor":"b3-postgres-migration"},{"level":3,"heading":"B4 — Auth + roles","anchor":"b4-auth-roles"},{"level":3,"heading":"B5 — Provider expansion","anchor":"b5-provider-expansion"},{"level":4,"heading":"B5.1 — RKD Knowledge Graph provider slice (done)","anchor":"b5-1-rkd-knowledge-graph-provider-slice-done"},{"level":4,"heading":"B5.2 — Smithsonian Open Access provider slice (done)","anchor":"b5-2-smithsonian-open-access-provider-slice-done"},{"level":4,"heading":"B5.3 — Harvard Art Museums provider slice","anchor":"b5-3-harvard-art-museums-provider-slice"},{"level":4,"heading":"B5.4 — V&A Collections API provider slice","anchor":"b5-4-v-a-collections-api-provider-slice"},{"level":4,"heading":"B5.5 — Princeton University Art Museum provider slice","anchor":"b5-5-princeton-university-art-museum-provider-slice"},{"level":4,"heading":"B5.6 — National Gallery of Art Open Data provider slice","anchor":"b5-6-national-gallery-of-art-open-data-provider-slice"},{"level":4,"heading":"B5.7 — Louvre Collections JSON provider slice","anchor":"b5-7-louvre-collections-json-provider-slice"},{"level":3,"heading":"B6 — Authority caching","anchor":"b6-authority-caching"},{"level":3,"heading":"B6.1 — Exhibition + literature reconciliation hardening","anchor":"b6-1-exhibition-literature-reconciliation-hardening"},{"level":3,"heading":"B8 — API protocol + profile conformance hardening","anchor":"b8-api-protocol-profile-conformance-hardening"},{"level":3,"heading":"B9 — Linked Art modeling guardrails (provenance + lifecycle)","anchor":"b9-linked-art-modeling-guardrails-provenance-lifecycle"},{"level":3,"heading":"B10 — ARK conformance slice","anchor":"b10-ark-conformance-slice"},{"level":3,"heading":"B7 — API gateway readiness for multi-source scale","anchor":"b7-api-gateway-readiness-for-multi-source-scale"},{"level":3,"heading":"Pre-Era-C Operational Sign-Off","anchor":"pre-era-c-operational-sign-off"},{"level":2,"heading":"Era C — SOTA platform (quarters 4+)","anchor":"era-c-sota-platform-quarters-4"},{"level":3,"heading":"C1 — Multi-modal storage + HAL hypermedia (SOTA Phase 1)","anchor":"c1-multi-modal-storage-hal-hypermedia-sota-phase-1"},{"level":3,"heading":"C2 — ETL pipeline + reconciliation (SOTA Phase 2)","anchor":"c2-etl-pipeline-reconciliation-sota-phase-2"},{"level":3,"heading":"C3 — IIIF + visualizations (SOTA Phase 3)","anchor":"c3-iiif-visualizations-sota-phase-3"},{"level":3,"heading":"C4 — AI layer (SOTA Phase 4)","anchor":"c4-ai-layer-sota-phase-4"},{"level":3,"heading":"C5 — Syndication + Meta Wiki Art + hardening (SOTA Phase 5)","anchor":"c5-syndication-meta-wiki-art-hardening-sota-phase-5"},{"level":3,"heading":"Era C Principal Hardening Addenda (Staff/Principal Review)","anchor":"era-c-principal-hardening-addenda-staff-principal-review"},{"level":4,"heading":"1) Infrastructure + distributed systems","anchor":"1-infrastructure-distributed-systems"},{"level":4,"heading":"2) Data lifecycle + upstream sync","anchor":"2-data-lifecycle-upstream-sync"},{"level":4,"heading":"3) AI/LLM reliability (EvalOps)","anchor":"3-ai-llm-reliability-evalops"},{"level":4,"heading":"4) Frontend UX + research quality","anchor":"4-frontend-ux-research-quality"},{"level":4,"heading":"5) Security + privacy posture","anchor":"5-security-privacy-posture"},{"level":4,"heading":"6) Content credibility engine (trust/originality/distribution/consistency)","anchor":"6-content-credibility-engine-trust-originality-distribution-consistency"},{"level":4,"heading":"Highest ROI priority","anchor":"highest-roi-priority"}],"html":"<h1 id=\"meta-museum-era-delivery-history\">Meta Museum — Era Delivery History</h1>\n<p>Archived 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)).</p>\n<p>---</p>\n<h2 id=\"three-eras\">Three eras</h2>\n<p>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.</p>\n<p>| Era | Theme | Outcome | Tier |</p>\n<p>|---|---|---|---|</p>\n<p>| <strong>A. Lift</strong> | 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 |</p>\n<p>| <strong>B. Hardening</strong> | Make the data layer trustworthy | Zod-mirrored contracts, SHACL validation, ValidationReport, Postgres swap, real auth | Quarters 2-3 post-lift |</p>\n<p>| <strong>C. SOTA</strong> | 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+ |</p>\n<p><strong>Cardinal rule:</strong> do not start a later era until the prior era&#39;s exit gate is green. Don&#39;t ship a SHACL validator on top of a JSON-file store, and don&#39;t ship Graph-RAG on top of unvalidated records.</p>\n<p>---</p>\n<h2 id=\"era-a-the-lift-10-slices-pr-sized-each\">Era A — The Lift (10 slices, PR-sized each)</h2>\n<p>Goal: end the era with the legacy prototype&#39;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&#39;t cross-import; contracts are leaves; `_source.raw` is immutable).</p>\n<h3 id=\"slice-0-staging-done\">Slice 0 — Staging (DONE)</h3>\n<p>See §Status above.</p>\n<h3 id=\"slice-1-foundations-tdd-infra-first-done\">Slice 1 — Foundations (TDD infra first) (DONE)</h3>\n<p>Port the dep-free leaves so everything else can be built on them. <strong>Test infra lands before any port.</strong></p>\n<p>Order within the slice:</p>\n<ul><li>[x] ✅ <strong>Test infra</strong>: `tsx` dev dependency + `&quot;test&quot;: &quot;node --import tsx --test tests/<em>*/</em>.test.ts&quot;` + smoke test (`tests/smoke.test.ts`) landed; `pnpm test` green.</li><li>[x] ✅ <strong>Port test-first.</strong> Legacy tests were ported and implementations landed for the specified dep-free leaves.</li><li>[x] ✅ `src/constants.ts` (port of `_legacy/src/constants.js`)</li><li>[x] ✅ `src/contracts/*.ts` — all 8 (`artwork`, `source-record`, `rights-report`, `import-job`, `agent-task`, `citation`, `shared-structures`, `wiki-draft`)</li><li>[x] ✅ `src/utils/{text,rights,http,storage,linked-art}.ts` (leaf utils)</li><li>[x] ✅ No routes, no UI changes in this foundation slice scope.</li></ul>\n<p><strong>Acceptance</strong>: `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.</p>\n<h3 id=\"slice-2-met-vertical-canary-done\">Slice 2 — Met vertical (canary) (DONE)</h3>\n<p>Prove the route-handler pattern end-to-end with the simpler of the two adapters.</p>\n<ul><li>[x] ✅ `src/adapters/{adapter-utils,provider-interface,met}.ts` + tests</li><li>[x] ✅ Route handlers (all `app/api/.../route.ts`): `/health`, `/met/profile`, `/met/departments`, `/met/search` (POST), `/met/object` (POST), `/met/import` (POST)</li><li>[x] ✅ `app/explore/page.tsx` — minimal custom-CSS UI calling `/api/met/search`, showing image cards</li><li>[x] ✅ `app/layout.tsx` — `Create Next App` metadata replaced; shell landed and evolved in later slices</li></ul>\n<p><strong>Acceptance</strong>: User can search Met for &quot;flowers&quot;, 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.</p>\n<h3 id=\"slice-3-getty-vertical-done\">Slice 3 — Getty vertical (DONE)</h3>\n<p>Same shape as Slice 2 for Getty (more endpoints).</p>\n<ul><li>[x] ✅ `src/adapters/getty.ts` + tests</li><li>[x] ✅ Routes: `/getty/profile`, `/getty/entity` (POST), `/getty/import` (POST), `/getty/activity` (GET), `/getty/sparql` (POST)</li><li>[x] ✅ `app/explore/page.tsx` extended with provider toggle (Getty / Met / both; later expanded further)</li><li>[x] ✅ `app/getty/page.tsx` — SPARQL playground + ActivityStream peek</li></ul>\n<p><strong>Acceptance</strong>: Both providers reachable from `/explore`. Getty SPARQL playground returns rows. Rights and source attribution rendered on every card.</p>\n<h3 id=\"slice-4-records-artworks-entities-done\">Slice 4 — Records + Artworks + Entities (DONE)</h3>\n<p>The persistence-touching slice. Storage stays JSON files in `storage/`.</p>\n<ul><li>[x] ✅ `src/utils/{artwork-builder,artwork-facets,entities,relationships}.ts` + tests</li><li>[x] ✅ Routes: `/records` (GET/POST), `/records/[id]` (GET), `/artworks/[id]` (GET), `/entities` (GET), `/entities/[id]` (GET), `/explorer/artworks` (GET), `/explorer/import` (POST)</li><li>[x] ✅ Pages: `app/records/page.tsx`, `app/artwork/[id]/page.tsx`, `app/entity/[id]/page.tsx`</li><li>[x] ✅ Async-`params` patterns applied per Next 16 requirements.</li></ul>\n<p><strong>Acceptance</strong>: 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]`.</p>\n<h3 id=\"slice-5-linked-art-inspector-roadmap-best-practices-done\">Slice 5 — Linked Art Inspector + Roadmap + Best-Practices (DONE)</h3>\n<p>Port the JSON-LD inspect/import workflow plus the two reflective endpoints.</p>\n<ul><li>[x] ✅ `src/utils/best-practices-audit.ts` + tests</li><li>[x] ✅ Routes: `/linked-art/profile`, `/linked-art/inspect` (POST), `/linked-art/import` (POST), `/roadmap`, `/best-practices`</li><li>[x] ✅ Pages: `app/linked-art/page.tsx`, `app/roadmap/page.tsx`</li><li>[x] ✅ `/api/roadmap` returns this document as structured JSON; `/api/best-practices` preserves legacy audit semantics.</li></ul>\n<p>Started in this pass:</p>\n<ul><li>[x] ✅ `src/utils/best-practices-audit.ts` + tests.</li><li>[x] ✅ `/api/roadmap` route returning structured JSON.</li><li>[x] ✅ `/api/best-practices` route running the audit against stored records.</li><li>[x] ✅ `app/roadmap/page.tsx` initial UI shell.</li><li>[x] ✅ `/linked-art/profile`, `/linked-art/inspect`, `/linked-art/import` routes.</li><li>[x] ✅ `app/linked-art/page.tsx` full inspect/import workflow UI.</li></ul>\n<p><strong>Acceptance</strong>: Paste a Linked Art JSON-LD blob → see the inspection report. The roadmap page renders this file&#39;s phases and exit gates.</p>\n<h3 id=\"slice-6-patterns-graph-done\">Slice 6 — Patterns + Graph (DONE)</h3>\n<p>Port pattern discovery and the graph view.</p>\n<ul><li>[x] ✅ `src/utils/patterns.ts` + tests</li><li>[x] ✅ Routes: `/patterns` (POST), `/graph` (GET)</li><li>[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)</li></ul>\n<p><strong>Acceptance</strong>: Pattern scan produces buckets for unknown makers, missing dates, shared concepts. Graph page renders nodes (artworks + entities) with click-to-pivot.</p>\n<p>Started in this pass:</p>\n<ul><li>[x] ✅ `src/utils/patterns.ts` + tests.</li><li>[x] ✅ `/api/patterns` route returning unknown + shared buckets.</li><li>[x] ✅ `/api/graph` route returning artwork/entity nodes + edges with pivot hrefs.</li><li>[x] ✅ `app/patterns/page.tsx` pattern scan UI.</li><li>[x] ✅ `app/graph/page.tsx` + `src/components/graph-viewer.tsx` Cytoscape force-directed graph UI.</li><li>[x] ✅ Runtime deps: `cytoscape`, `cytoscape-cose-bilkent`.</li></ul>\n<h3 id=\"slice-7-issues-sse-done\">Slice 7 — Issues + SSE (DONE)</h3>\n<p>The streaming case.</p>\n<ul><li>[x] ✅ Route: `/issues` (GET), `/issues/webhook` (POST), `/issues/stream` (GET — `ReadableStream`, `export const dynamic = &#39;force-dynamic&#39;`)</li><li>[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)</li></ul>\n<p><strong>Acceptance</strong>: 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.</p>\n<p>Started in this pass:</p>\n<ul><li>[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.</li><li>[x] ✅ `/api/issues` route with optional `?refresh=1` force refresh.</li><li>[x] ✅ `/api/issues/webhook` route with optional signature verification and issue-event refresh hooks.</li><li>[x] ✅ `/api/issues/stream` route using `ReadableStream` SSE (`dynamic = &quot;force-dynamic&quot;`).</li><li>[x] ✅ `/issues/webhook` + `/issues/stream` direct route aliases for legacy-compatible pathing.</li><li>[x] ✅ `app/issues/page.tsx` + `src/components/issues-workbench.tsx` live issue inventory UI with SSE updates.</li><li>[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`.</li></ul>\n<h3 id=\"slice-8-agents-jobs-content-generation-automation-done\">Slice 8 — Agents + Jobs + Content Generation + Automation (DONE)</h3>\n<p>Port the agent/job stubs as-is. No new agent logic this slice — just parity.</p>\n<ul><li>[x] ✅ Routes: `/agents/run` (POST), `/content/generate` (POST), `/jobs` (GET), `/jobs/run` (POST)</li><li>[x] ✅ Pages: `app/agents/page.tsx`, `app/automation/page.tsx`</li></ul>\n<p><strong>Acceptance</strong>: Manual pattern scan + collection brief jobs runnable from the UI; output appears in `app/automation/page.tsx`.</p>\n<p>Started in this pass:</p>\n<ul><li>[x] ✅ `src/services/agents.ts` with legacy-parity agent/content stubs (`runAgent`, `generateContent`) and local fallback drafting.</li><li>[x] ✅ `src/services/jobs.ts` JSON-backed manual jobs service with seeded defaults and `lastRun` updates.</li><li>[x] ✅ `/api/agents/run`, `/api/content/generate`, `/api/jobs`, `/api/jobs/run` routes.</li><li>[x] ✅ Legacy-compatible direct route aliases: `/agents/run`, `/content/generate`, `/jobs`, `/jobs/run`.</li><li>[x] ✅ `app/agents/page.tsx` + `src/components/agents-workbench.tsx` for manual agent and content runs.</li><li>[x] ✅ `app/automation/page.tsx` + `src/components/automation-workbench.tsx` for job execution and output review.</li><li>[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`.</li></ul>\n<h3 id=\"slice-9-workspace-chrome-design-system-pass-custom-css-done\">Slice 9 — Workspace chrome + design-system pass (Custom CSS) (DONE)</h3>\n<p>Now everything works route-by-route. Unify the visual layer.</p>\n<ul><li>[x] ✅ `app/(workspace)/layout.tsx` route group — sidebar nav + topbar, all custom CSS</li><li>[x] ✅ Expand `app/globals.css` design tokens + component classes to cover every recurring pattern; keep BEM-lite naming consistent</li><li>[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: `&lt;LinkedDate&gt;`, `&lt;LinkedDimensions&gt;`, `&lt;LinkedLabel&gt;`, `&lt;UriBadge&gt;`, `&lt;RightsBadge&gt;` (already in Slice 2), `&lt;SourceBadge&gt;`, `&lt;CitationBlock&gt;`</li><li>[x] ✅ Entity cards (SOTA §12.2): `&lt;ObjectCard&gt;`, `&lt;ActorCard&gt;`, `&lt;PlaceCard&gt;`, `&lt;ConceptCard&gt;`</li><li>[x] ✅ a11y pass: axe-core in CI; keyboard nav on all interactive surfaces; WCAG 2.1 AA on public pages</li></ul>\n<p><strong>Acceptance</strong>: Lighthouse a11y ≥ 95 on `/`, `/explore`, `/artwork/[id]`. Storybook scaffold (vite-based, no Next coupling) for the atomic components.</p>\n<p>Started in this pass:</p>\n<ul><li>[x] ✅ `app/(workspace)/layout.tsx` route-group shell with keyboard-first skip link, sidebar navigation, and topbar.</li><li>[x] ✅ Workspace pages moved under `app/(workspace)/...` so URLs stay unchanged while sharing common chrome.</li><li>[x] ✅ Expanded `app/globals.css` with workspace shell classes and reusable design-system component classes.</li><li>[x] ✅ Added atomics in `src/components/`: `LinkedDate`, `LinkedDimensions`, `LinkedLabel`, `UriBadge`, `SourceBadge`, `CitationBlock`; extended `RightsBadge` with `data-la-entity-id`.</li><li>[x] ✅ Added entity cards in `src/components/`: `ObjectCard`, `ActorCard`, `PlaceCard`, `ConceptCard`.</li><li>[x] ✅ Wired new components into `/explore`, `/artwork/[id]`, `/entity/[id]`, and `/records`.</li><li>[x] ✅ Added component tests: `tests/components/linked-atomics.test.ts`, `tests/components/entity-cards.test.ts`.</li><li>[x] ✅ Added CI workflow at `.github/workflows/ci.yml` running lint, tests, build, Playwright install, axe accessibility checks, and Lighthouse CI assertions.</li><li>[x] ✅ Added Vite-based Storybook scaffold (`.storybook/*`) and atomic stories (`src/components/atomics.stories.tsx`), validated with `pnpm storybook:build`.</li></ul>\n<h3 id=\"slice-10-lift-cleanup-done\">Slice 10 — Lift cleanup (DONE)</h3>\n<ul><li>[x] ✅ Delete `_legacy/` (empty by now or only contains files we deliberately chose not to port)</li><li>[x] ✅ Rewrite `README.md` from the Next.js side; preserve the legacy product narrative</li><li>[x] ✅ Confirm `metamuseum-legacy/` can be archived/deleted (verified absent at `C:\\Projects\\metamuseum-legacy`).</li><li>[x] ✅ Security credential rotation moved to Era B operational preflight tracking (see `Pre-Era-C Operational Sign-Off` under Era B).</li><li>[x] ✅ Document the env-var surface (`PORT`, `GITHUB_OWNER`, `GITHUB_REPO`, `ISSUE_POLL_MS`, future `DATABASE_URL`) in `docs/env.md`</li></ul>\n<p>Started in this pass:</p>\n<ul><li>[x] ✅ Added automated parity gate test: `tests/quality/era-a-exit-gate.test.ts` (legacy route/view equivalents + route-test coverage checks).</li><li>[x] ✅ Lighthouse a11y gate now runs reliably in local Windows env via `scripts/lighthouse-a11y.mjs` (no `chrome-launcher` temp-dir cleanup failure path).</li><li>[x] ✅ `_legacy/` removed from workspace.</li><li>[x] ✅ Verified `C:\\Projects\\metamuseum-legacy` is absent as of <strong>May 30, 2026</strong> (already archived/deleted outside this repo).</li></ul>\n<p><strong>Era A exit gate (must all be green):</strong></p>\n<ul><li>[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).</li><li>[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).</li><li>[x] ✅ `pnpm build &amp;&amp; pnpm test &amp;&amp; pnpm lint` clean (validated in `metamuseum` conda env on May 30, 2026).</li><li>[x] ✅ Lighthouse a11y ≥ 95 on the public pages (`/`, `/explore`, `/artwork/[id]`) via `pnpm lighthouse:ci`.</li><li>[x] ✅ `_legacy/` deleted.</li><li>[x] ✅ `metamuseum-legacy/` archived/deleted (path not present at `C:\\Projects\\metamuseum-legacy` on May 30, 2026).</li></ul>\n<p>---</p>\n<h2 id=\"era-b-hardening-quarters-not-weeks\">Era B — Hardening (quarters, not weeks)</h2>\n<p>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 <em>under</em> it.</p>\n<p>Slices in suggested order, but each is independently shippable:</p>\n<h3 id=\"b1-zod-contracts-schema-versioning\">B1 — Zod contracts + schema versioning</h3>\n<ul><li>[x] ✅ Mirror every `src/contracts/<em>.ts` as a Zod schema in `src/contracts/zod/</em>.ts`</li><li>[x] ✅ Add `schemaVersion` field + a `src/utils/migrations/` registry</li><li>[x] ✅ Server Actions and Route Handlers validate at the boundary with the Zod schemas</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Completed.</li></ul>\n<h3 id=\"b2-formal-validation\">B2 — Formal validation</h3>\n<ul><li>[x] ✅ Build a Python validation microservice (FastAPI + PySHACL + PyLD) — first non-Node service</li><li>[x] ✅ SHACL shapes in `shapes/linked-art/*.shacl.ttl`, fixtures in `fixtures/linked-art/{pass,fail}/`</li><li>[x] ✅ New route `app/api/validate/route.ts` proxies to it</li><li>[x] ✅ New contract `ValidationReport` (SOTA §5.1) wired into inspect/import flows</li><li>[x] ✅ Validation fixtures and route assertions are checked against linked-art/LinkedArtModel1.0-Reference.md(linked-art/LinkedArtModel1.0-Reference.md) before merge.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Completed.</li></ul>\n<h3 id=\"b3-postgres-migration\">B3 — Postgres migration</h3>\n<ul><li>[x] ✅ Postgres 16 via Docker Compose for dev (`ops/docker-compose.yml`)</li><li>[x] ✅ Migrate `storage/{records,jobs}.json` → Postgres JSONB tables via a one-time exporter that double-writes for one release, then cuts over</li><li>[x] ✅ Replace `src/utils/storage.ts` JSON-file impl with a Postgres impl behind the same interface; no call sites change</li><li>[x] ✅ Add `next-env.d.ts` env validation with Zod for `DATABASE_URL`</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Completed.</li><li>[x] ✅ Added `ops/docker-compose.yml` + `ops/postgres/init/01-storage.sql` (Postgres 16 dev runtime + table bootstrap).</li><li>[x] ✅ Added `scripts/export-storage-to-postgres.ts` one-time exporter and `pnpm storage:export:postgres`.</li><li>[x] ✅ `src/utils/storage.ts` now supports `file`, `double-write`, and `postgres` modes for the centralized managed-document contract behind unchanged `readJson/writeJson`.</li><li>[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`.</li><li>[x] ✅ Updated `records` and `jobs` services to avoid file-`stat` assumptions so Postgres cutover does not require call-site changes.</li></ul>\n<h3 id=\"b4-auth-roles\">B4 — Auth + roles</h3>\n<ul><li>[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`).</li><li>[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`)</li><li>[x] ✅ Roles: `public` (read only), `researcher` (read + import), `editor` (import + agent jobs), `admin` (all)</li><li>[x] ✅ Middleware gates write routes; UI conditionally renders import/run buttons</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Completed.</li><li>[x] ✅ Added Auth.js v5 (`next-auth@5.0.0-beta.31`) with GitHub provider in root `auth.ts`.</li><li>[x] ✅ Added `/api/auth/[...nextauth]` handlers.</li><li>[x] ✅ Added centralized role mapping in `src/auth/roles.ts` (public/researcher/editor/admin with allowlist env vars).</li><li>[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.</li><li>[x] ✅ UI now conditionally enables import/run controls in linked-art, agents, and automation workbenches based on resolved role.</li><li>[x] ✅ Rotate production GitHub OAuth credentials if any legacy values are still active (operational follow-up reminder remains until verified).</li></ul>\n<h3 id=\"b5-provider-expansion\">B5 — Provider expansion</h3>\n<ul><li>[x] ✅ New adapters for Harvard, Smithsonian Open Access, Rijks, RKD Knowledge Graph, National Gallery of Art Open Data, Louvre Collections JSON, V&amp;A Collections API, Princeton University Art Museum API, Europeana, AIC, CMA (SOTA §27.1) — each shipped with route + adapter + tests.</li><li>[x] ✅ Each adapter implements the `provider-interface` contract; cross-adapter imports remain forbidden.</li><li>[x] ✅ `/explore` import flow now accepts all landed provider source IDs (`met`, `getty`, `rijks`, `nga`, `louvre`, `harvard`, `smithsonian`, `vanda`, `princeton`, `europeana`, `aic`, `cma`).</li><li>[x] ✅ Provider slices include executable conformance tests mapped to linked-art/LinkedArtModel1.0-Reference.md(linked-art/LinkedArtModel1.0-Reference.md) fixture anchors.</li></ul>\n<p><strong>Acceptance for each upcoming provider slice (object-specific AIDD + TDD gates):</strong></p>\n<ul><li>[x] ✅ Failing-first contract tests verify culturally valued physical objects normalize as `HumanMadeObject` unless explicit evidence requires another canonical class.</li><li>[x] ✅ Failing-first tests verify production/destruction remain structured activity/event nodes when present (not flattened to display-only strings).</li><li>[x] ✅ Failing-first tests verify physical characteristics (dimensions/materials/parts) are preserved as structured data when available.</li><li>[x] ✅ Failing-first tests verify ownership/location assertions remain distinct from non-ownership rights/reuse assertions.</li><li>[x] ✅ Failing-first tests verify physical object identity remains distinct from digital surrogates/representations while preserving linkage.</li><li>[x] ✅ Failing-first regression tests verify immovable/place-centric records are not coerced into moveable-object assumptions.</li><li>[x] ✅ Route-level tests verify inspect/import outputs preserve the above structures without mutating canonical source fields.</li></ul>\n<p><strong>Acceptance for each upcoming provider slice (digital-content AIDD + TDD gates):</strong></p>\n<ul><li>[x] ✅ Failing-first contract tests verify `DigitalObject` records preserve `access_point`, `format`, and `conforms_to` when provided.</li><li>[x] ✅ Failing-first tests verify digital object creation events use `Creation` semantics where present, without coercion to physical-object production semantics.</li><li>[x] ✅ Failing-first tests verify content/carrier separation is preserved (`DigitalObject` versus `VisualItem`/`LinguisticObject`) without collapsing layers.</li><li>[x] ✅ Failing-first tests verify surrogate linkage can preserve shared visual content (`shows` and `digitally_shows`) when provider data supports it.</li><li>[x] ✅ Failing-first tests verify web-page and document references preserve the `subject_of` → `LinguisticObject` → `digitally_carried_by` pattern when present.</li><li>[x] ✅ Failing-first tests verify IIIF structures are preserved, including Presentation manifest `conforms_to`/`format` and Image API `DigitalService` via `digitally_available_via`.</li><li>[x] ✅ Route-level tests verify inspect/import outputs retain digital metadata structures (including IIIF fields) and do not mutate canonical `_source.raw`.</li><li>[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).</li><li>[x] ✅ Provider-slice test PRs must also include a short &quot;Standards Mapping&quot; 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.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Complete (Rijks, NGA, RKD, Louvre, Harvard, Smithsonian, V&amp;A, Princeton, Europeana, AIC, CMA slices landed).</li><li>[x] ✅ Object-specific acceptance gates are executable and passing in `tests/quality/provider-object-specific-gates.test.ts`.</li><li>[x] ✅ Digital-content acceptance gates are executable and passing in `tests/quality/provider-digital-content-gates.test.ts`.</li><li>[x] ✅ Route-level digital inspect/import preservation is executable and passing in `tests/quality/provider-digital-content-gates.test.ts`.</li><li>[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`).</li><li>[x] ✅ PR template now requires explicit provider digital-content fixture-anchor mapping + short Standards Mapping notes (`.github/pull_request_template.md`).</li><li>[x] ✅ Added `src/adapters/rijks.ts` with Search + Resolver + LDES + Change Discovery helpers and IIIF URL normalization.</li><li>[x] ✅ Added Rijks API routes: `/api/rijks/profile`, `/api/rijks/search`, `/api/rijks/resolve`, `/api/rijks/import`, `/api/rijks/ldes`, `/api/rijks/cd`.</li><li>[x] ✅ `/explore` source toggle now supports `both` / `met` / `getty` / `rijks`; `/api/explorer/import` supports Rijks URLs.</li><li>[x] ✅ Added test coverage for adapter + routes + provider inference (`tests/adapters/rijks.test.ts`, `tests/api/rijks/*`, updated explorer/provider tests).</li><li>[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.</li><li>[x] ✅ Remaining provider slices are now landed: RKD Knowledge Graph, Louvre Collections JSON, Harvard, Smithsonian Open Access, V&amp;A Collections API, Princeton University Art Museum API, Europeana, AIC, CMA.</li><li>[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`.</li><li>[x] ✅ Rijks profile now exposes a bibliographic SRU extension-point base (`bibliographicSruBase`) and SRU URL builder coverage (`buildRijksSruSearchUrl()`), while UI flows remain unchanged.</li></ul>\n<p>Rijksmuseum integration scope now includes:</p>\n<ul><li>[x] ✅ Object metadata search and dereference pipeline (Search API + PID Resolver with content negotiation to Linked Art).</li><li>[x] ✅ Linked Data Event Streams ingest hooks for incremental refreshes.</li><li>[x] ✅ IIIF Change Discovery ingest hooks for change tracking.</li><li>[x] ✅ IIIF image/presentation compatibility via Micrio endpoints.</li><li>[x] ✅ Future bibliographic extension point via SRU (planned, not yet wired into UI flows).</li></ul>\n<h4 id=\"b5-1-rkd-knowledge-graph-provider-slice-done\">B5.1 — RKD Knowledge Graph provider slice (done)</h4>\n<p>Goal: integrate RKD Linked Data (CIDOC-CRM + Linked Art oriented) as a standards-first provider without bypassing current adapter boundaries.</p>\n<p>Planned deliverables:</p>\n<ul><li>[x] ✅ Adapter: `src/adapters/rkd.ts` (provider-interface compliant, no cross-adapter imports).</li><li>[x] ✅ Routes:</li><li>[x] ✅ `/api/rkd/profile` (GET)</li><li>[x] ✅ `/api/rkd/search` (POST, paged candidate retrieval)</li><li>[x] ✅ `/api/rkd/entity` (POST, URI-based fetch/enrichment)</li><li>[x] ✅ `/api/rkd/import` (POST, normalize + persist)</li><li>[x] ✅ optional `/api/rkd/sparql` (POST, read-only, allowlisted query templates only)</li><li>[x] ✅ UI:</li><li>[x] ✅ added `rkd` source toggle support in `/explore`</li><li>[x] ✅ provider attribution + ODC-By 1.0 license/reuse guidance rendered in RKD card/detail data.</li></ul>\n<p>Data-source constraints:</p>\n<ul><li>[x] ✅ Dataset scale is 600M+ statements and is consumed via bounded queries/pagination (`limit`/`offset` clamps and bounded search defaults).</li><li>[x] ✅ SPARQL endpoint details are deploy-time config (env vars) via `getRkdProfile()`/`buildRkdSparqlEndpoint()`.</li><li>[x] ✅ Graph scoping is supported via optional `graph` input on search/entity/template flows.</li><li>[x] ✅ Raw SPARQL usage is constrained to controlled, allowlisted query templates (`entitySummary`/`labelSearch`) for route-level access.</li><li>[x] ✅ Triply protocol-compatible SPARQL request behavior is implemented with explicit `Accept` negotiation and read-only request handling.</li></ul>\n<p>License and attribution:</p>\n<ul><li>[x] ✅ RKD dataset license is Open Data Commons Attribution License 1.0; provider output carries source URL + provider attribution + license metadata.</li><li>[x] ✅ Rights/reuse output remains conservative when image-level rights are not explicit in source payload.</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first tests for adapter + routes are landed (`tests/adapters/rkd.test.ts`, `tests/api/rkd/*.test.ts`).</li><li>[x] ✅ Standards mapping coverage includes object/digital/shared-structure/data-discovery anchors in `tests/fixtures/validation/provider-fixture-manifest.json` (`rkd` entry).</li><li>[x] ✅ B8 protocol conformance coverage includes RKD routes in `tests/quality/provider-protocol-conformance.test.ts`.</li><li>[x] ✅ Token security checks included (`Authorization: Bearer` from env-only `RKD_TRIPLY_TOKEN`, no token persistence/logging in adapter/route flows).</li></ul>\n<h4 id=\"b5-2-smithsonian-open-access-provider-slice-done\">B5.2 — Smithsonian Open Access provider slice (done)</h4>\n<p>Goal: integrate Smithsonian Open Access search/content APIs with secure API-key handling and standards-first normalization.</p>\n<p>Planned deliverables:</p>\n<ul><li>[x] ✅ Adapter: `src/adapters/smithsonian.ts` (provider-interface compliant, no cross-adapter imports).</li><li>[x] ✅ Routes:</li><li>[x] ✅ `/api/smithsonian/profile` (GET)</li><li>[x] ✅ `/api/smithsonian/search` (POST)</li><li>[x] ✅ `/api/smithsonian/content` (POST)</li><li>[x] ✅ `/api/smithsonian/import` (POST)</li><li>[x] ✅ UI:</li><li>[x] ✅ added `smithsonian` source toggle in `/explore`</li><li>[x] ✅ source attribution and conservative rights/reuse indicators are preserved in Smithsonian discovery/import card flows.</li></ul>\n<p>Official API constraints:</p>\n<ul><li>[x] ✅ API key required (`api_key`) via data.gov registration (`SMITHSONIAN_API_KEY` enforced for Smithsonian search/content and URL-import retrieval).</li><li>[x] ✅ Search pagination uses `start` + `rows`; route schema enforces integer bounds (`start &gt;= 0`, `rows` in `1..1000`) with compatibility aliases for legacy callers.</li><li>[x] ✅ Core category filters and row-group inputs are explicit enum validation at route boundary (`smithsonianSearchInputSchema` for category + `rowGroup: objects|archives`).</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first adapter + route tests with mocked Smithsonian responses.</li><li>[x] ✅ API-key security checks included (env-only secrets, no key in logs/errors/client, API key stripped from exposed source URLs).</li><li>[x] ✅ Standards Mapping note coverage includes fixture anchors for Smithsonian in `tests/fixtures/validation/provider-fixture-manifest.json`.</li><li>[x] ✅ B8 protocol checks include Smithsonian routes (`profile`, `search`, `content`, `import`) in `tests/quality/provider-protocol-conformance.test.ts`.</li></ul>\n<h4 id=\"b5-3-harvard-art-museums-provider-slice\">B5.3 — Harvard Art Museums provider slice</h4>\n<p>Goal: integrate Harvard Art Museums API with strong conformance to official usage constraints and Linked Art normalization boundaries.</p>\n<p>Status:</p>\n<ul><li>[x] ✅ Planned deliverables in this slice scope (adapter + routes) are complete.</li></ul>\n<p>Planned deliverables:</p>\n<ul><li>[x] ✅ Adapter: `src/adapters/harvard.ts` (provider-interface compliant, no cross-adapter imports).</li><li>[x] ✅ Routes:</li><li>[x] ✅ `/api/harvard/profile` (GET)</li><li>[x] ✅ `/api/harvard/search` (POST)</li><li>[x] ✅ `/api/harvard/object` (POST)</li><li>[x] ✅ `/api/harvard/import` (POST)</li><li>[x] ✅ UI:</li><li>[x] ✅ add `harvard` source toggle in `/explore`</li><li>[x] ✅ preserve attribution/link-back + rights/reuse indicators on all surfaces.</li></ul>\n<p>Official API constraints:</p>\n<ul><li>[x] ✅ API key required on all calls (`apikey` parameter).</li><li>[x] ✅ Paging uses `size` (max 100) + `page`; adapter honors `info.next/info.prev` flows.</li><li>[x] ✅ Respect call budget guidance (2500/day) and non-commercial + attribution terms.</li><li>[x] ✅ Cache/storage policy enforces a two-week max retention guidance (`&lt;=14 days`) without explicit permission.</li><li>[x] ✅ Use provider image URLs directly (no local copies).</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first adapter + route tests with mocked Harvard responses.</li><li>[x] ✅ API key handling checks included (env-only key, no key in logs/errors/client surfaces).</li><li>[x] ✅ Rate-budget and cache-TTL policy checks included (`&lt;=14 days` cache window).</li><li>[x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.</li><li>[x] ✅ B8 protocol checks included for all Harvard routes.</li></ul>\n<h4 id=\"b5-4-v-a-collections-api-provider-slice\">B5.4 — V&amp;A Collections API provider slice</h4>\n<p>Goal: integrate V&amp;A Collections API v2 with strong support for identifier/keyword filters and IIIF image/presentation link preservation.</p>\n<p>Status:</p>\n<ul><li>[x] ✅ Complete.</li></ul>\n<p>Planned deliverables:</p>\n<ul><li>[x] ✅ Adapter: `src/adapters/vanda.ts` (provider-interface compliant, no cross-adapter imports).</li><li>[x] ✅ Routes:</li><li>[x] ✅ `/api/vanda/profile` (GET)</li><li>[x] ✅ `/api/vanda/search` (POST)</li><li>[x] ✅ `/api/vanda/object` (POST)</li><li>[x] ✅ `/api/vanda/import` (POST)</li><li>[x] ✅ UI:</li><li>[x] ✅ add `vanda` source toggle in `/explore`</li><li>[x] ✅ expose IIIF manifest/image links in artwork/detail surfaces where available.</li></ul>\n<p>Official API constraints:</p>\n<ul><li>[x] ✅ API base is `https://api.vam.ac.uk/v2`.</li><li>[x] ✅ Search result pages should honor official paging constraints (`size` cap 100).</li><li>[x] ✅ API is suitable for dynamic subsets; bulk export flows should avoid naive high-volume API crawling.</li><li>[x] ✅ Terms/licensing constraints and citation requirements must be preserved in downstream usage.</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first adapter + route tests with mocked V&amp;A responses.</li><li>[x] ✅ Identifier/keyword filter behavior tests included.</li><li>[x] ✅ IIIF image/presentation field extraction tests included.</li><li>[x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.</li><li>[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`.</li><li>[x] ✅ B8 protocol checks included for all V&amp;A routes.</li></ul>\n<h4 id=\"b5-5-princeton-university-art-museum-provider-slice\">B5.5 — Princeton University Art Museum provider slice</h4>\n<p>Goal: integrate Princeton API object/search resources with strong preservation of nested research context and IIIF media references.</p>\n<p>Planned deliverables:</p>\n<ul><li>[x] ✅ Adapter: `src/adapters/princeton.ts` (provider-interface compliant, no cross-adapter imports).</li><li>[x] ✅ Routes:</li><li>[x] ✅ `/api/princeton/profile` (GET)</li><li>[x] ✅ `/api/princeton/search` (POST)</li><li>[x] ✅ `/api/princeton/object` (POST)</li><li>[x] ✅ `/api/princeton/import` (POST)</li><li>[x] ✅ UI:</li><li>[x] ✅ add `princeton` source toggle in `/explore`</li><li>[x] ✅ preserve source attribution and media link visibility.</li></ul>\n<p>Official API constraints:</p>\n<ul><li>[x] ✅ Base endpoint `https://data.artmuseum.princeton.edu`.</li><li>[x] ✅ No auth currently required, and adapter/profile surface explicit `authMode: none` + future-auth compatibility without interface breakage.</li><li>[x] ✅ Static weekly full datasets are reflected in import guidance with anti-crawl guardrails on large interactive API imports.</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first adapter + route tests with mocked Princeton responses.</li><li>[x] ✅ Nested-field preservation tests included (`texts`, `media`, `exhibitions`, `geography`, `terms`, `classifications`).</li><li>[x] ✅ IIIF URI extraction tests included.</li><li>[x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.</li><li>[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`.</li><li>[x] ✅ B8 protocol checks included for all Princeton routes.</li></ul>\n<h4 id=\"b5-6-national-gallery-of-art-open-data-provider-slice\">B5.6 — National Gallery of Art Open Data provider slice</h4>\n<p>Goal: integrate NGA public open data as a CSV-first provider while preserving Linked Art boundary contracts and provenance-safe source lineage.</p>\n<p>Planned deliverables:</p>\n<ul><li>[x] ✅ Adapter: `src/adapters/nga.ts` (provider-interface compliant, no cross-adapter imports).</li><li>[x] ✅ Routes:</li><li>[x] ✅ `/api/nga/profile` (GET)</li><li>[x] ✅ `/api/nga/search` (POST)</li><li>[x] ✅ `/api/nga/import` (POST)</li><li>[x] ✅ optional `/api/nga/refresh` (POST) deferred by design; manual refresh is supported through `/api/nga/import` with `url` + bounded `limit`.</li><li>[x] ✅ UI:</li><li>[x] ✅ add `nga` source toggle in `/explore`</li><li>[x] ✅ preserve source attribution, citation guidance, and conservative rights/reuse indicators.</li><li>[x] ✅ `/explore` includes an NGA-specific citation/reuse advisory callout (CC0 metadata + verify image/media rights per object).</li></ul>\n<p>Official data constraints:</p>\n<ul><li>[x] ✅ Primary distribution is CSV (UTF-8), refreshed frequently (typically daily), and should be ingested in bounded batches.</li><li>[x] ✅ Images/media files are not distributed in the dataset package; only links/references are included where available.</li><li>[x] ✅ Dataset is CC0; attribution/citation is still recommended for research usage.</li><li>[x] ✅ Wikidata IDs are present when known but non-exhaustive; treat as reconciliation hints, not complete authority truth.</li><li>[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.</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first adapter + route tests with fixture-backed CSV parsing and UTF-8 safety.</li><li>[x] ✅ Idempotent upsert tests for repeated daily ingest runs.</li><li>[x] ✅ Tests verifying preservation of source media-link references without assuming media binary availability.</li><li>[x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.</li><li>[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`.</li><li>[x] ✅ B8 protocol checks included for all NGA routes.</li></ul>\n<h4 id=\"b5-7-louvre-collections-json-provider-slice\">B5.7 — Louvre Collections JSON provider slice</h4>\n<p>Goal: integrate Louvre ARK-linked JSON records as a standards-first provider while preserving attribution/provenance nuance and image-rights constraints.</p>\n<p>Planned deliverables:</p>\n<ul><li>[x] ✅ Adapter: `src/adapters/louvre.ts` (provider-interface compliant, no cross-adapter imports).</li><li>[x] ✅ Routes:</li><li>[x] ✅ `/api/louvre/profile` (GET)</li><li>[x] ✅ `/api/louvre/object` (POST)</li><li>[x] ✅ `/api/louvre/import` (POST)</li><li>[x] ✅ optional `/api/louvre/search` (POST) is landed (bounded, protocol-safe endpoint)</li><li>[x] ✅ UI:</li><li>[x] ✅ add `louvre` source toggle in `/explore`</li><li>[x] ✅ preserve source attribution and image-rights disclosures on all surfaces.</li></ul>\n<p>Official data constraints:</p>\n<ul><li>[x] ✅ Access is object-entry URL plus `.json` suffix (ARK-based records).</li><li>[x] ✅ Record content is French-first; normalization must not destructively strip source language signals.</li><li>[x] ✅ Image usage and text reuse must follow Louvre Terms of Use.</li><li>[x] ✅ Image payloads include per-image rights/copyright text and must be preserved.</li><li>[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.</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first adapter + route tests with mocked Louvre JSON responses.</li><li>[x] ✅ URL normalization + ARK extraction safety tests included.</li><li>[x] ✅ Creator attribution nuance tests included (`attributionLevel`, `doubt`, `creatorRole`, attribution metadata where present).</li><li>[x] ✅ Rights/reuse mapping tests included with conservative defaults when rights are unclear.</li><li>[x] ✅ Standards Mapping note references applicable Linked Art rounds and fixture anchors.</li><li>[x] ✅ B8 protocol checks included for all Louvre routes.</li><li>Evidence:</li><li>`tests/adapters/provider-expansion.test.ts`</li><li>`tests/api/louvre/object.test.ts`</li><li>`tests/api/louvre/import.test.ts`</li><li>`tests/quality/provider-protocol-conformance.test.ts`</li><li>`docs/providers/louvre-collections-json.md`</li></ul>\n<h3 id=\"b6-authority-caching\">B6 — Authority caching</h3>\n<ul><li>[x] ✅ Replace inline authority lookups on the request path with local cache-only access.</li><li>[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).</li><li>[x] ✅ Surface in the entity profile pages: &quot;From AAT / ULAN / Wikidata&quot;.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ `src/services/authority-cache.ts` landed as the local authority cache service.</li><li>[x] ✅ `tests/quality/era-b-exit-gate.test.ts` enforces zero runtime external authority fetches on request-path route code.</li><li>[x] ✅ Scheduled authority refresh pipeline landed:</li><li>[x] ✅ `scripts/authority-cache-refresh.ts`</li><li>[x] ✅ `pnpm authority:refresh`</li><li>[x] ✅ `.github/workflows/authority-cache-refresh.yml` (weekly + manual dispatch)</li><li>[x] ✅ Entity authority-source UX landed:</li><li>[x] ✅ `src/utils/entities.ts` emits `authoritySources`</li><li>[x] ✅ `app/(workspace)/entity/[id]/page.tsx` renders `From AAT / ULAN / Wikidata` style provenance labels when present</li><li>[x] ✅ coverage in `tests/utils/entities.test.ts` and `tests/api/entities/by-id.test.ts`</li></ul>\n<h3 id=\"b6-1-exhibition-literature-reconciliation-hardening\">B6.1 — Exhibition + literature reconciliation hardening</h3>\n<p>Goal: prevent duplicate or fragmented cross-provider historical narratives by reconciling shared exhibitions and literature records without collapsing source provenance.</p>\n<ul><li>[x] ✅ Add explicit reconciliation scope beyond people/concepts:</li><li>[x] ✅ Exhibition concepts/plans (`PropositionalObject`) and exhibition activities (`Activity` classified as exhibition) are candidate-matched across providers.</li><li>[x] ✅ Literature records (`LinguisticObject`) including catalogs/publications about exhibitions or objects are candidate-matched across providers.</li><li>[x] ✅ Add deterministic candidate blocking + scoring pipeline:</li><li>[x] ✅ title/label normalization + language-aware comparison</li><li>[x] ✅ timespan overlap logic</li><li>[x] ✅ place/venue equivalence checks using local authority identifiers/labels</li><li>[x] ✅ identifier evidence (ISBN/ISSN/OCLC/DOI/local accession refs when present)</li><li>[x] ✅ participant/organizer/publisher overlap evidence</li><li>[x] ✅ Preserve Linked Art identity/provenance invariants:</li><li>[x] ✅ never rewrite source URIs in `_source.raw`</li><li>[x] ✅ never infer semantics from URI path shape</li><li>[x] ✅ link via explicit reconciliation decisions rather than destructive record collapse</li><li>[x] ✅ keep event-centric modeling (no direct object-person shortcut introduced by reconciliation)</li><li>[x] ✅ Add human-review queue gates for ambiguous matches:</li><li>[x] ✅ thresholds for auto-link vs review-required vs no-link (`&gt;=0.90`, `0.65-0.89`, `&lt;0.65`)</li><li>[x] ✅ audit metadata per reconciliation decision (`actor`, `recordedAt`)</li><li>[x] ✅ reversible decision model shape (`auto-link` / `needs-review` / `no-link`)</li><li>[x] ✅ Add failing-first fixture suite:</li><li>[x] ✅ pass cases for true exhibition/literature same-as candidates from different providers</li><li>[x] ✅ fail cases for near-title collisions, edition conflicts, and time/place mismatches</li><li>[x] ✅ regression coverage for threshold behavior and invariants</li></ul>\n<p>Definition of done:</p>\n<ul><li>[x] ✅ `tests/quality/reconciliation-exhibitions-literature.test.ts` passes with fixture-backed pass/fail coverage.</li><li>[x] ✅ Reconciliation outputs are provenance-safe and standards-mapped (round + fixture anchor references in PR).</li><li>[x] ✅ Entity pages expose linked &quot;same exhibition&quot;/&quot;same publication&quot; context without mutating source records.</li></ul>\n<p>Implementation note:</p>\n<ul><li>[x] ✅ Use reconciliation/exhibition-literature-reconciliation.md(reconciliation/exhibition-literature-reconciliation.md) as the required execution checklist for this slice.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ `src/services/reconciliation.ts` landed with explicit exhibition/publication candidate extraction, deterministic scoring, and human-review thresholds.</li><li>[x] ✅ `tests/fixtures/reconciliation/exhibitions-literature-pass.json` + `tests/fixtures/reconciliation/exhibitions-literature-fail.json` landed as fixture anchors.</li><li>[x] ✅ `app/(workspace)/entity/[id]/page.tsx` now surfaces cross-provider alignment context where reconciliation candidates exist.</li></ul>\n<h3 id=\"b8-api-protocol-profile-conformance-hardening\">B8 — API protocol + profile conformance hardening</h3>\n<ul><li>[x] ✅ Enforce JSON-LD 1.1 output with canonical Linked Art context on all public entity payloads.</li><li>[x] ✅ Add explicit content negotiation behavior:</li><li>[x] ✅ `Accept: application/ld+json;profile=&quot;https://linked.art/ns/v1/linked-art.json&quot;`</li><li>[x] ✅ graceful fallback when generic JSON/LD headers are used</li><li>[x] ✅ Add `GET` + `OPTIONS` support and baseline CORS behavior on public API endpoints.</li><li>[x] ✅ Add protocol tests asserting URI opacity: no handler or client helper may derive semantics by parsing URI path shapes.</li><li>[x] ✅ Add serialization tests ensuring multi-valued Linked Art fields remain arrays even when cardinality is one.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Complete for current Era B route inventory.</li><li>[x] ✅ Representative executable conformance tests now run for `/api/linked-art/profile`, `/api/artworks/[id]`, and `/api/entities/[id]`:</li><li>[x] ✅ `OPTIONS` + baseline CORS headers</li><li>[x] ✅ Linked Art media type negotiation for `Accept: application/ld+json;profile=...`</li><li>[x] ✅ URI opacity, array cardinality safety, and HAL separation assertions</li><li>[x] ✅ representative entity-role coverage across object/work/agent/place/set</li><li>[x] ✅ Expanded executable protocol checks to currently landed provider/search endpoints (`/api/met/<em>`, `/api/getty/</em>`, `/api/rijks/<em>`, `/api/nga/</em>`, `/api/rkd/<em>`, plus `/api/providers/</em>`) via `tests/quality/provider-protocol-conformance.test.ts`.</li><li>[x] ✅ Generic JSON-LD fallback behavior is covered (`Accept: application/ld+json` negotiates to canonical Linked Art profile media type).</li><li>[x] ✅ Ongoing policy: B8 executable checks are applied to all currently landed provider slices; continue applying the same checks for additional future sources.</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Protocol conformance tests pass for representative routes across object/work/agent/place/set.</li><li>[x] ✅ No regressions in existing inspect/import flows.</li></ul>\n<h3 id=\"b9-linked-art-modeling-guardrails-provenance-lifecycle\">B9 — Linked Art modeling guardrails (provenance + lifecycle)</h3>\n<ul><li>[x] ✅ Add conformance tests for provenance partitioning patterns:</li><li>[x] ✅ wrapper provenance `Activity`</li><li>[x] ✅ `Acquisition` + `Payment` as parts when both are asserted</li><li>[x] ✅ Add explicit ownership vs custody invariants:</li><li>[x] ✅ `TransferOfCustody` must not be rewritten into `Acquisition` unless title transfer evidence is present</li><li>[x] ✅ Add explicit unknown-transfer handling:</li><li>[x] ✅ use `Transfer` for ambiguous exchange events rather than fabricating legal outcomes.</li><li>[x] ✅ Extend inspect/import audits to flag carrier/content conflation and direct object-person shortcuts that bypass event nodes.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Complete for current Era B guardrail scope.</li><li>[x] ✅ Conformance guardrails added in `src/utils/linked-art.ts` for:</li><li>[x] ✅ wrapper provenance Activity partitioning checks</li><li>[x] ✅ `Acquisition` + `Payment` split-into-parts checks when both are asserted</li><li>[x] ✅ custody-vs-title invariants (`TransferOfCustody` vs `Acquisition`)</li><li>[x] ✅ unknown-transfer guardrails (`Transfer` for ambiguous exchanges)</li><li>[x] ✅ carrier/content conflation and direct object-person shortcut detection</li><li>[x] ✅ Failing-first pass/fail fixtures added:</li><li>[x] ✅ `tests/fixtures/b9/provenance-guardrails-pass.json`</li><li>[x] ✅ `tests/fixtures/b9/provenance-guardrails-fail.json`</li><li>[x] ✅ Executable guardrail tests added in `tests/quality/linked-art-b9-guardrails.test.ts`.</li><li>[x] ✅ Best-practices audit includes actionable B9 category output: `Provenance &amp; Lifecycle Guardrails (B9)`.</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Failing-first fixtures prove the above patterns and invariants across pass/fail cases.</li><li>[x] ✅ Best-practices audit reports actionable violations for these categories.</li></ul>\n<h3 id=\"b10-ark-conformance-slice\">B10 — ARK conformance slice</h3>\n<ul><li>[x] ✅ ULID-based ARK minting for normalized records.</li><li>[x] ✅ Add `/api/ark/resolve` resolver endpoint with suffix pass-through behavior.</li><li>[x] ✅ Add `?info` inflection response for metadata + persistence statement retrieval.</li><li>[x] ✅ Define and return a persistence statement structure for ARK `?info` responses.</li><li>[x] ✅ Add failing-first tests for ARK utility behavior and resolver route behavior.</li><li>[x] ✅ Update roadmap/README/CLAUDE standards guidance for ARK conformance expectations.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Complete for current Era B scope.</li><li>[x] ✅ Implemented `src/utils/ark.ts` with opaque ULID minting, ARK normalization, suffix pass-through resolution, and `?info` payload construction.</li><li>[x] ✅ Implemented `app/api/ark/resolve/route.ts` with:</li><li>[x] ✅ `GET` resolution (`303` redirect style)</li><li>[x] ✅ suffix pass-through for variant/service paths</li><li>[x] ✅ `?info` inflection JSON-LD payload</li><li>[x] ✅ `OPTIONS` + baseline CORS behavior</li><li>[x] ✅ `normalizeIncomingRecord` now mints ARKs via ULID-based helper (`mintArkIdentifier`) instead of non-deterministic short random tokens.</li><li>[x] ✅ Added executable tests:</li><li>[x] ✅ `tests/utils/ark.test.ts`</li><li>[x] ✅ `tests/api/ark/resolve.test.ts`</li></ul>\n<p>Acceptance:</p>\n<ul><li>[x] ✅ Resolver pass-through and inflection tests are green.</li><li>[x] ✅ ARK minting tests assert opaque ULID-form ARK output.</li><li>[x] ✅ ARK conformance behavior is now documented in project guidance.</li></ul>\n<h3 id=\"b7-api-gateway-readiness-for-multi-source-scale\">B7 — API gateway readiness for multi-source scale</h3>\n<ul><li>[x] ✅ Keep direct provider adapters as default while source count and traffic stay moderate (current implementation remains direct adapters).</li><li>[x] ✅ Gateway activation policy is implemented and threshold-gated. Activate only when one or more conditions are true:</li><li>[x] ✅ 6+ external providers in production.</li><li>[x] ✅ 2+ upstream credential/security models to centralize.</li><li>[x] ✅ cross-provider rate limiting/circuit breaking becomes operationally necessary.</li><li>[x] ✅ Candidate gateway responsibilities are defined and readiness-backed:</li><li>[x] ✅ Centralized auth/secrets policy, rate limiting, retries/circuit breakers, request/response telemetry, and provider health dashboards.</li><li>[x] ✅ Stable internal route facade (`/api/providers/:provider/...`) so UI and jobs remain unchanged as provider backends evolve.</li><li>[x] ✅ Response envelope standardization plus provider capability registry for dynamic UI feature flags.</li><li>[x] ✅ B7 non-goals are explicitly enforced:</li><li>[x] ✅ No business logic migration out of adapters.</li><li>[x] ✅ No forced microservice split of the Next app.</li><li>[x] ✅ No gateway requirement for local development.</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Complete for Era B readiness scope.</li><li>[x] ✅ Added gateway-readiness diagnostics endpoint: `/api/providers/readiness` (threshold evaluation without forcing architecture changes).</li><li>[x] ✅ Added provider capability registry endpoint: `/api/providers/capabilities`.</li><li>[x] ✅ Added stable internal facade routes: `/api/providers/:provider/profile|search|import` with standardized response envelopes.</li><li>[x] ✅ Added conformance coverage for facade + capability routes in `tests/quality/provider-protocol-conformance.test.ts`.</li><li>[x] ✅ Re-evaluated activation threshold with current production providers (Met, Getty, Rijks, NGA, RKD, Louvre, Harvard, Smithsonian, V&amp;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.</li></ul>\n<p><strong>Era B exit gate:</strong></p>\n<ul><li>[x] ✅ 100% of public-facing sample records pass validation checks (SHACL when validator service is configured; local standards fallback otherwise).</li><li>[x] ✅ All writes auth-gated; audit log row per primary write route.</li><li>[x] ✅ Postgres is the storage of record when `DATABASE_URL` is set; `storage/*.json` removed from version control.</li><li>[x] ✅ All authority lookups served from local cache policy (zero runtime authority calls on the request path).</li><li>[x] ✅ Protocol/profile conformance suite green (context, media type profile, CORS/OPTIONS, URI opacity, array cardinality safety).</li></ul>\n<p><strong>Era B completion verdict:</strong></p>\n<ul><li>[x] ✅ <strong>Engineering-complete.</strong> Core B1-B10 deliverables and Era B exit-gate checks are green.</li><li>[x] ✅ <strong>Operational sign-off complete</strong> for current pre-Era-C checklist items.</li></ul>\n<h3 id=\"pre-era-c-operational-sign-off\">Pre-Era-C Operational Sign-Off</h3>\n<ul><li>[x] ✅ Verify and record rotation of production `AUTH_SECRET` and `AUTH_GITHUB_SECRET` (with date and owner in release notes/runbook).</li><li>[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`.</li><li>[x] ✅ Record explicit gateway activation decision now that `gatewayRecommended: true` is reported (`activate now` vs `keep direct-adapter mode`) with owner + review date.</li><li>[x] ✅ Decision: <strong>keep direct-adapter mode active</strong> for now; do not force gateway activation yet.</li><li>[x] ✅ Owner: `@rsung`</li><li>[x] ✅ Review date: <strong>August 31, 2026</strong></li><li>[x] ✅ Decision record reference: `docs/progress/2026-05-31/era-c-readiness-snapshot.md`</li><li>[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`.</li></ul>\n<p>---</p>\n<h2 id=\"era-c-sota-platform-quarters-4\">Era C — SOTA platform (quarters 4+)</h2>\n<p>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.</p>\n<p>Roughly the same numbering as the SOTA spec&#39;s phases — but starting <em>here</em>, after Era A and B have shipped:</p>\n<h3 id=\"c1-multi-modal-storage-hal-hypermedia-sota-phase-1\">C1 — Multi-modal storage + HAL hypermedia (SOTA Phase 1)</h3>\n<ul><li>[x] ✅ Solr 9 + <strong>GraphDB</strong> provisioned via Helm (dev: Compose, GraphDB CE image)</li><li>[x] ✅ GraphDB SPARQL 1.1 endpoint + Lucene plugin for hybrid text+graph queries; named graphs per source institution for provenance partitioning (SOTA §8.2)</li><li>[x] ✅ RDFS + SHACL reasoning only at runtime — no full OWL DL (SOTA §8.2)</li><li>[x] ✅ `src/utils/record-materializer.ts` builds Yale-LUX-style denormalized `Record` documents + shortcut triples (SOTA §20.1)</li><li>[x] ✅ HAL `_links` on every entity response (SOTA §9.2)</li><li>[x] ✅ Entity HAL discoverability now includes stable `la:activityFeed` link (`/api/activity`) via shared link builder + conformance tests.</li><li>[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.</li><li>[x] ✅ Add `/api/concepts/[id]` and `/api/events/[id]` canonical endpoints to complete the canonical C1 role endpoint surface.</li><li>[x] ✅ `/api/search` landed with OrderedCollectionPage pagination contract and executable HAL/search conformance coverage.</li><li>[x] ✅ `/api/activity` syndication endpoint</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ Dev Compose provisioning added in `ops/docker-compose.yml` (`sota` profile: `solr:9.6`, `ontotext/graphdb:10.8.14`).</li><li>[x] ✅ Helm provisioning added in `ops/helm/metamuseum-search-graph/` (StatefulSets + Services + PVC defaults for Solr and GraphDB).</li><li>[x] ✅ GraphDB bootstrap automation added:</li><li>`scripts/graphdb-bootstrap.ts` creates repository config + verifies SPARQL query/update endpoints + provisions Lucene connector via `luc:createConnector`.</li><li>Runtime reasoning policy enforced in bootstrap: `GRAPHDB_RULESET` accepts only `rdfsplus` / `rdfsplus-optimized` (OWL-family rulesets rejected).</li><li>`scripts/graphdb-load-named-graph.ts` loads provider RDF into source-specific named graphs for provenance partitioning.</li><li>`src/utils/provenance-graphs.ts` defines stable institution graph URIs.</li><li>[x] ✅ Record materialization + index flattening foundations landed:</li><li>`src/services/records.ts` now materializes on write for all import/persist paths.</li><li>`src/utils/record-materializer.ts` emits denormalized shortcut fields/triples.</li><li>`src/utils/search-index.ts` flattens materialized records into Solr/OpenSearch-ready documents using `_shortcuts`.</li></ul>\n<h3 id=\"c2-etl-pipeline-reconciliation-sota-phase-2\">C2 — ETL pipeline + reconciliation (SOTA Phase 2)</h3>\n<ul><li>[x] ✅ `pipeline/` Dagster project — ELT, idempotent at every stage, SHA-256 dedupe keys</li><li>[x] ✅ FastAPI reconciliation service hitting Getty SPARQL / VIAF / Wikidata / GeoNames behind Redis URI cache</li><li>[x] ✅ Promote B6.1 exhibition/literature reconciliation heuristics into the C2 service as first-class pipelines (not optional post-processing)</li><li>[x] ✅ Confidence thresholds per SOTA §7.3 with a human-review queue in `app/curator/reconciliation/page.tsx`</li><li>[x] ✅ Visual ETL Mapper (ReactFlow) + `MappingTemplate` contract</li></ul>\n<p>Status:</p>\n<ul><li>[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`).</li><li>[x] ✅ ELT asset chain implemented in `pipeline/metamuseum_pipeline/assets.py`: `extract_source_records` → `load_records` → `transform_records` → `dedupe_records` → `upsert_materialized_records`.</li><li>[x] ✅ SHA-256 dedupe key policy implemented in `pipeline/metamuseum_pipeline/dedupe.py` (`canonical_json` + `record_sha256`) and consumed at load/dedupe/materialize stages.</li><li>[x] ✅ Idempotence coverage added in `pipeline/tests/test_c2_pipeline.py` (repeat materialization does not duplicate state rows; unchanged rows are no-op upserts).</li><li>[x] ✅ Reconciliation service scaffold landed in `services/reconciliation-service/`:</li><li>FastAPI app with `POST /reconcile/lookup` and `GET /health`.</li><li>Provider adapters for Getty SPARQL, VIAF AutoSuggest, Wikidata, and GeoNames.</li><li>Redis URI cache layer with deterministic SHA-256 cache keys and TTL controls.</li><li>Unit coverage in `services/reconciliation-service/tests/test_reconciliation_service.py`.</li><li>[x] ✅ B6.1 heuristics promoted to first-class C2 pipeline endpoints in `services/reconciliation-service/main.py`:</li><li>`GET /reconcile/pipelines`</li><li>`POST /reconcile/pipelines/exhibitions-literature`</li><li>`GET /reconcile/pipelines/exhibitions-literature/bands`</li><li>Heuristic parity implementation in `services/reconciliation-service/pipelines.py` with fixture-backed tests in `services/reconciliation-service/tests/test_b61_pipeline.py`.</li><li>[x] ✅ SOTA §7.3 threshold model and queue UI landed:</li><li>Threshold bands implemented in `src/services/reconciliation.ts` (`&gt;=0.95` auto-approve, `0.85-0.95` weekly digest flag, `0.70-0.85` human-review queue, `&lt;0.70` drop candidate).</li><li>Curator queue page added at `app/curator/reconciliation/page.tsx` with explicit human-review and weekly-digest sections.</li><li>Regression assertions updated in `tests/quality/reconciliation-exhibitions-literature.test.ts`.</li><li>[x] ✅ Visual ETL Mapper + MappingTemplate contract landed:</li><li>`src/contracts/mapping-template.ts` defines `createMappingTemplate`/`validateMappingTemplate` for JSON-serializable mapper nodes, edges, and executable rules.</li><li>`src/contracts/zod/mapping-template.ts` mirrors the contract boundary and is exported through `src/contracts/zod/index.ts`.</li><li>ReactFlow mapper UI shipped at `app/(workspace)/etl/mapper/page.tsx` via `src/components/etl-mapper-workbench.tsx` with dry-run projection preview.</li><li>Contract and schema coverage added in `tests/contracts/mapping-template.test.ts` and `tests/contracts/zod-mirror.test.ts`.</li><li>[x] ✅ <strong>C2 complete.</strong> 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.</li></ul>\n<h3 id=\"c3-iiif-visualizations-sota-phase-3\">C3 — IIIF + visualizations (SOTA Phase 3)</h3>\n<ul><li>[x] ✅ `&lt;IIIFCanvasViewer&gt;` (OpenSeadragon wrapper) with deep-zoom, side-by-side compare, annotations</li><li>[x] ✅ `&lt;ProvenanceTimeline&gt;`, `&lt;GeoMapViewer&gt;` (Leaflet), `&lt;NetworkGraph&gt;` (Cytoscape — partially landed in Slice 6)</li><li>[x] ✅ `&lt;ConcertinaList&gt;` + `&lt;FacetHistogram&gt;` for dense entity browses (SOTA §12.3)</li><li>[x] ✅ `&lt;EntityKnowledgePanel&gt;` merging internal + DBpedia/Wikidata/ULAN/AAT context</li><li>[x] ✅ Overlapping exhibition timelines with zoom + direct navigation to exhibition/activity records</li></ul>\n<p>Status:</p>\n<ul><li>[x] ✅ IIIF workspace route shipped at `/iiif` via `app/(workspace)/iiif/page.tsx` with imported-record-backed source selection.</li><li>[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.</li><li>[x] ✅ Canvas source normalization + fallback behavior covered in `tests/utils/iiif.test.ts` (`deriveIiifInfoJsonUrl`, OpenSeadragon image tile fallback, deduplicated source extraction).</li><li>[x] ✅ Insights workspace now composes first-class C3 visualization components:</li><li>`src/components/provenance-timeline.tsx`</li><li>`src/components/geo-map-viewer.tsx` (Leaflet)</li><li>`src/components/network-graph.tsx` (Cytoscape)</li><li>[x] ✅ `app/(workspace)/insights/page.tsx` now includes timeline + Leaflet geospatial view + Cytoscape relationship network in one drillable research workflow.</li><li>[x] ✅ Deterministic timeline-window + histogram binning logic is test-covered in `tests/utils/provenance-visualization.test.ts`.</li><li>[x] ✅ Dense entity browse route shipped at `/entities` via `app/(workspace)/entities/page.tsx`, with URL-driven `q`/`type`/`authority` filters.</li><li>[x] ✅ `src/components/concertina-list.tsx` and `src/components/facet-histogram.tsx` are now first-class C3 components for high-density entity review.</li><li>[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`.</li><li>[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.</li><li>[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`.</li><li>[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.</li></ul>\n<h3 id=\"c4-ai-layer-sota-phase-4\">C4 — AI layer (SOTA Phase 4)</h3>\n<ul><li>[x] ✅ pgvector + voyage-3 embeddings for entity summaries and statement texts; SigLIP for IIIF visual similarity</li><li>[x] ✅ `/api/ai/query` — NL → SPARQL/HAL with mandatory SHACL pre-execution validation</li><li>[x] ✅ `/api/ai/chat` — Graph-RAG with mandatory citations (`[entityId, propertyPath]` per sentence); &quot;cite or refuse&quot; rule (RSI-4 complete, proven 2026-06-09)</li><li>[x] ✅ LLM-assisted reconciliation tiebreaker (SOTA §10.4)</li><li>[x] ✅ LLM-assisted mapping for the Visual ETL Mapper</li></ul>\n<p>Status:</p>\n<ul><li>[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.</li><li>[x] ✅ pgvector persistence landed with bootstrap SQL + upsert path:</li><li>`ops/postgres/init/02-ai-layer.sql`</li><li>`persistEmbeddingsPgvector(...)` in `src/services/ai-layer.ts`</li><li>[x] ✅ AI API routes landed:</li><li>`app/api/ai/embeddings/route.ts` (document build + embeddings + optional pgvector persist)</li><li>`app/api/ai/visual-similarity/route.ts` (SigLIP service call path + heuristic fallback)</li><li>[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.</li><li>[x] ✅ Query execution logs now capture NL prompt + generated query for retraining/audit (`storage/ai-query-log.json` via `src/services/ai-query.ts`).</li><li>[x] ✅ SigLIP visual-similarity fallback + IIIF candidate extraction landed (`extractVisualSimilarityCandidates`, `rankVisualSimilarity`) with representation/access-point awareness.</li><li>[x] ✅ Dev infra for pgvector readiness updated: `ops/docker-compose.yml` now uses `pgvector/pgvector:pg16` for local Postgres bootstrap compatibility.</li><li>[x] ✅ Coverage landed for C4 behavior:</li><li>`tests/services/ai-layer.test.ts`</li><li>`tests/api/ai-embeddings.test.ts`</li><li>`tests/api/ai-visual-similarity.test.ts`</li><li>`tests/services/ai-query.test.ts`</li><li>`tests/api/ai-query.test.ts`</li><li>[x] ✅ RSI-4 complete: `/api/ai/chat` implemented with graph-driven claim extraction, per-sentence citation enforcement, and refusal path when coverage is incomplete.</li><li>Proof: `tests/api/ai-chat.test.ts`, `pnpm test`, `pnpm lint`, and `pnpm build`.</li><li>Route contracts and behavior are reflected in `app/api/ai/chat/route.ts` and `src/services/ai-chat.ts`.</li><li>[x] ✅ C4 Visual ETL Mapper AI assist is complete:</li><li>`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.</li><li>`app/api/ai/mapping-assist/route.ts` exposes a POST assist endpoint with explicit JSON validation and CORS preflight.</li><li>`src/components/etl-mapper-workbench.tsx` adds a curator-visible &quot;Suggest mapping with AI&quot; action that calls the assist endpoint and keeps outputs review-only.</li><li>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`.</li></ul>\n<h3 id=\"c5-syndication-meta-wiki-art-hardening-sota-phase-5\">C5 — Syndication + Meta Wiki Art + hardening (SOTA Phase 5)</h3>\n<ul><li>[x] ✅ ActivityStreams subscriptions endpoint open to external aggregators</li><li>[x] ✅ `/api/activity` external-consumer readiness metric capture landed (`/api/activity/readiness` + per-request consumer telemetry with explicit `x-linked-art-consumer-id` support).</li><li>[x] ✅ `/api/activity/subscriptions` landed for external aggregator registration/discovery:</li><li>`GET /api/activity/subscriptions` (ActivityStreams `OrderedCollectionPage` shape + metrics)</li><li>`POST /api/activity/subscriptions` (consumer-aware callback registration)</li><li>`DELETE /api/activity/subscriptions?id=...` (unsubscribe)</li><li>Public CORS preflight via `OPTIONS`</li><li>[x] ✅ Meta Wiki Art publish flow: `WikiDraft` → review → publish to <strong>MediaWiki + custom Wikibase</strong> with citation + rights templates</li><li>[x] ✅ C5 publish-flow implementation evidence:</li><li>`app/api/wiki-drafts/route.ts`</li><li>`app/api/wiki-drafts/[id]/review/route.ts`</li><li>`app/api/wiki-drafts/[id]/publish/route.ts`</li><li>`src/services/wiki-publish.ts`</li><li>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.</li><li>[x] ✅ Wikibase statement-level references required for every publishable claim (provenance-safe writes)</li><li>`evaluateWikiPublishPreflight(...)` now validates every claim carries at least one statement-level reference with valid `sourceUrl`, `retrievedAt`, and `citationText` before publish can proceed.</li><li>Evidence: `src/services/wiki-publish.ts`, `tests/services/wiki-publish.test.ts`, `tests/api/wiki-drafts/flow.test.ts`.</li><li>[x] ✅ Bidirectional mapping maintained between internal entity IDs and wiki item/property IDs for traceable sync</li><li>Live publish now upserts durable sync mappings in `wiki-sync-map.json` (Postgres-managed in non-file modes) for:</li><li>internal entity ID ↔ wikibase item ID</li><li>internal property ID ↔ wikibase property ID</li><li>API lookup route landed for traceable sync diagnostics:</li><li>`GET /api/wiki-sync-map?internalEntityId=...`</li><li>`GET /api/wiki-sync-map?wikiItemId=...`</li><li>`GET /api/wiki-sync-map?internalPropertyId=...`</li><li>`GET /api/wiki-sync-map?wikiPropertyId=...`</li><li>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`.</li><li>[x] ✅ WCAG 2.1 AA full audit on every public route</li><li>Evidence (2026-05-31): `pnpm a11y:check` passes with zero serious/moderate/critical axe violations across all public UI routes:</li><li>`/`, `/explore`, `/records`, `/linked-art`, `/patterns`, `/insights`, `/graph`, `/entities`, `/iiif`, `/issues`, `/agents`, `/automation`, `/etl/mapper`, `/roadmap`, `/getty`, `/curator/reconciliation`, `/artwork/[id]`, `/entity/[id]`.</li><li>Remediations landed for detected violations:</li><li>`src/components/geo-map-viewer.tsx` (removed nested-interactive conflict by replacing `role=&quot;img&quot;` map container semantics with described interactive container text)</li><li>`src/components/etl-mapper-workbench.tsx` (added keyboard focus to scrollable dry-run `&lt;pre&gt;` region).</li><li>[x] ✅ k6 load test against SOTA §20.4 SLOs (API p95 &lt; 200ms cached, &lt; 500ms cold; search p95 &lt; 300ms)</li><li>Harness landed:</li><li>`scripts/k6-slo.js` (scenario definitions + per-scenario thresholds)</li><li>`scripts/k6-slo-runner.mjs` (local binary / PATH / Docker fallback runner)</li><li>`pnpm k6:slo` and `pnpm k6:slo:ci`</li><li>runbook: `docs/ops/k6-slo.md`</li><li>Update (2026-06-10): evidence contract now covers the full SOTA §20.4 p95 set, including whitelisted SPARQL p95 `&lt; 2s` and IIIF tile serving p95 `&lt; 100ms`.</li><li>`src/services/era-c-exit-gate.ts`</li><li>`config/era-c-exit-gate-policy.json`</li><li>`tests/services/era-c-exit-gate.test.ts`</li><li>Verification (2026-05-31, `pnpm k6:slo`, summary export `artifacts/performance/k6-slo-summary.json`):</li><li>`cached_record_hit` p95: <strong>73.5495ms</strong> (target `&lt; 200ms`) ✅</li><li>`cold_record_read` p95: <strong>56.134ms</strong> (target `&lt; 500ms`) ✅</li><li>`keyword_facet_search` p95: <strong>55.0616ms</strong> (target `&lt; 300ms`) ✅</li><li>`http_req_failed` rate by scenario: <strong>0.00</strong> ✅</li><li>[x] ✅ OpenAPI 3.1 at `/api/docs`</li><li>Implementation landed:</li><li>`GET /api/openapi` returns generated OpenAPI 3.1 JSON from live route-handler discovery (`src/services/openapi.ts`).</li><li>`GET /api/docs` serves interactive Swagger UI wired to `/api/openapi`.</li><li>Evidence:</li><li>`app/api/openapi/route.ts`</li><li>`app/api/docs/route.ts`</li><li>`src/services/openapi.ts`</li><li>`tests/api/openapi.test.ts`</li><li>`tests/api/docs.test.ts`</li><li>Verification (2026-05-31): `pnpm test -- tests/api/openapi.test.ts tests/api/docs.test.ts` passing.</li><li>[x] ✅ Pen test + DR drill</li><li>Executable hardening gate landed:</li><li>`pnpm pentest:baseline` (dependency advisory regression check against committed baseline)</li><li>`pnpm dr:drill` (non-destructive restore rehearsal with SHA-256 parity checks)</li><li>`pnpm hardening:pen-dr` (combined gate)</li><li>Baselines + runbooks:</li><li>`config/security-audit-baseline.json`</li><li>`docs/ops/security-dr-drill.md`</li><li>Artifacts:</li><li>`artifacts/security/pnpm-audit-summary.json`</li><li>`artifacts/dr-drill/latest.json`</li></ul>\n<h3 id=\"era-c-principal-hardening-addenda-staff-principal-review\">Era C Principal Hardening Addenda (Staff/Principal Review)</h3>\n<p>These items are now integrated as explicit execution backlog for distributed systems, lifecycle integrity, AI safety, UX globalization, and privacy controls.</p>\n<h4 id=\"1-infrastructure-distributed-systems\">1) Infrastructure + distributed systems</h4>\n<ul><li>[x] ✅ Transactional outbox for Postgres → Solr/GraphDB consistency on write paths (`outbox_events` table + reliable projector worker + replay-safe idempotency keys).</li><li>Landed transactional write-path integration in `src/services/records.ts`:</li><li>Postgres mode now atomically upserts `storage_documents.records` and enqueues `outbox_events` in one transaction.</li><li>Idempotency key: `sha256(&quot;record.upsert|recordId|sourceHash&quot;)`.</li><li>Outbox persistence + retry lifecycle:</li><li>`src/services/outbox.ts` (`claim`/`process`/`retry`/`dead_letter` flow, SKIP LOCKED claims, backoff).</li><li>`ops/postgres/init/02-outbox.sql` bootstraps `outbox_events` + indexes.</li><li>Reliable projector worker:</li><li>`src/services/outbox-projector.ts` (Solr + GraphDB projection with per-event ack/fail handling).</li><li>`scripts/outbox-projector.ts`, `pnpm outbox:projector`, `pnpm outbox:projector:once`.</li><li>Ops docs:</li><li>`docs/ops/outbox-projector.md`</li><li>[x] ✅ Outbox failure handling policy (retry budget, dead-letter queue, operator replay tooling, and alerting).</li><li>Policy + queue health primitives:</li><li>`src/services/outbox.ts`</li><li>env-driven policy (`OUTBOX_MAX_ATTEMPTS`, queue/age thresholds)</li><li>queue health summary counters/aging</li><li>dead-letter listing, replay helpers, stale-processing requeue</li><li>Operator tooling:</li><li>`scripts/outbox-ops.ts`</li><li>`pnpm outbox:status`</li><li>`pnpm outbox:dlq:list`</li><li>`pnpm outbox:replay:dlq`</li><li>`pnpm outbox:requeue:stale`</li><li>Alerting:</li><li>`src/services/outbox-alerts.ts`</li><li>`scripts/outbox-alert-check.ts`</li><li>`pnpm outbox:alert:check` with optional webhook dispatch via `OUTBOX_ALERT_WEBHOOK_URL`</li><li>Ops docs:</li><li>`docs/ops/outbox-projector.md`</li><li>[x] ✅ OpenTelemetry end-to-end trace propagation across Next.js, validation service, reconciliation service, Dagster pipeline runs, and GraphDB/Solr calls.</li><li>[x] ✅ Correlated request/run identifiers enforced in logs + traces (`x-request-id` / traceparent continuity).</li><li>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`.</li><li>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`.</li><li>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`).</li></ul>\n<h4 id=\"2-data-lifecycle-upstream-sync\">2) Data lifecycle + upstream sync</h4>\n<ul><li>[x] ✅ Provider tombstone handling in C2 pipeline (HTTP `404/410` upstream signals mark local tombstone, deindex in Solr, and emit deletion activity).</li><li>C2 pipeline lifecycle handling landed in `pipeline/metamuseum_pipeline/assets.py`:</li><li>upstream tombstone detection from `_source.upstreamStatus` / `_source.httpStatus` / `_source.statusCode`</li><li>local tombstone registry persisted in `pipeline/state/materialized-records.json` (`tombstones` block)</li><li>Solr deindex call on tombstone transition (`/solr/&lt;core&gt;/update` delete-by-id)</li><li>deletion activity emission to `pipeline/state/deletion-activities.json` with `Delete` + `Tombstone` object semantics</li><li>Test coverage:</li><li>`pipeline/tests/test_c2_pipeline.py` (`test_tombstone_404_marks_local_tombstone_and_emits_delete_activity`)</li><li>Live drill (2026-05-31):</li><li>projected record `https://example.org/object/outbox-deindex-final-1780254290729` into Solr via outbox projector,</li><li>ran C2 tombstone materialization with `_source.upstreamStatus=410`,</li><li>Solr exact-id count transitioned `before=1` -&gt; `after=0`,</li><li>summary included `deindexed=1` + `deletion_activities_emitted=1`.</li><li>[x] ✅ ActivityStreams deletion semantics for tombstoned records (`Delete`/`Tombstone` event policy documented and implemented).</li><li>Feed policy + implementation landed in `app/api/activity/route.ts`:</li><li>`/api/activity` now merges C2 tombstone lifecycle activities from `pipeline/state/deletion-activities.json`.</li><li>Tombstoned records are emitted as ActivityStreams `Delete` events with `object.type = &quot;Tombstone&quot;` and `object.formerType = &quot;HumanMadeObject&quot;`.</li><li>Response includes explicit `policy.deletionSemantics` metadata for aggregator consumers.</li><li>Coverage:</li><li>`tests/api/activity.test.ts` (`includes Delete/Tombstone activities from tombstone lifecycle state`)</li><li>[x] ✅ Meta Wiki Art source-of-truth contract finalized (publication-target-only vs community-editable model).</li><li>Default mode: `publication-target-only` (safe default).</li><li>Optional mode: `community-editable` (explicit opt-in).</li><li>Executable policy gate landed:</li><li>`src/services/wiki-source-of-truth.ts`</li><li>`app/api/wiki-drafts/[id]/publish/route.ts`</li><li>Publish preflight now enforces source anchoring:</li><li>draft `sourceRecordId` must resolve to an internal record before publish proceeds.</li><li>Coverage:</li><li>`tests/services/wiki-source-of-truth.test.ts`</li><li>`tests/api/wiki-drafts/flow.test.ts` (`blocks publish when source record is missing under publication-target-only contract`)</li><li>[x] ✅ If community-editable: reverse-ETL conflict resolution pipeline from Wikibase back to Postgres with deterministic merge policy.</li><li>Reverse-ETL apply endpoint landed:</li><li>`POST /api/wiki-sync/reverse-etl` (`app/api/wiki-sync/reverse-etl/route.ts`)</li><li>Deterministic merge + idempotency engine landed:</li><li>`src/services/wiki-reverse-etl.ts`</li><li>precedence policy: newer `modifiedAt` wins; equal timestamp tie-break by lexicographic `changeId`; replayed `changeId` is skipped.</li><li>Back-sync state persistence landed:</li><li>`storage/wiki-reverse-etl-state.json` (`wiki_reverse_etl_state` managed in Postgres modes)</li><li>Coverage:</li><li>`tests/services/wiki-reverse-etl.test.ts`</li><li>`tests/api/wiki-reverse-etl.test.ts`</li></ul>\n<h4 id=\"3-ai-llm-reliability-evalops\">3) AI/LLM reliability (EvalOps)</h4>\n<ul><li>[x] ✅ Golden evaluation dataset for complex museum questions (minimum 100 prompts with expected grounding/citation behavior).</li><li>Dataset landed:</li><li>`evals/golden-museum-questions.v1.json` (`120` prompts, rubric + per-prompt grounding/citation/refusal expectations)</li><li>EvalOps documentation landed:</li><li>`docs/evals/golden-museum-questions.md`</li><li>Executable conformance gate landed:</li><li>`tests/quality/ai-eval-golden-dataset.test.ts`</li><li>[x] ✅ CI eval gate for AI-layer PRs (faithfulness, relevance, citation accuracy, citation freshness) using an evaluation harness (Ragas/DeepEval-equivalent workflow).</li><li>Eval harness landed:</li><li>`src/services/ai-eval-harness.ts`</li><li>`scripts/ai-eval-gate.ts`</li><li>Commands landed:</li><li>`pnpm ai:eval:report`</li><li>`pnpm ai:eval:gate`</li><li>CI workflow landed (AI-layer path-gated):</li><li>`.github/workflows/ai-eval-gate.yml`</li><li>Coverage:</li><li>`tests/services/ai-eval-harness.test.ts`</li><li>[x] ✅ Regression thresholds for model/prompt/version changes with fail-fast policy on citation and citation-freshness drift.</li><li>Versioned regression policy landed:</li><li>`config/ai-eval-regression-policy.json`</li><li>baseline identity keys: `datasetId + datasetVersion + modelVersion + promptVersion`</li><li>Drift evaluator landed:</li><li>`src/services/ai-eval-regression.ts`</li><li>citation drift is configured as fail-fast (`failFastOnCitationDrift`)</li><li>citation freshness drift is configured as fail-fast (`failFastOnCitationFreshnessDrift`)</li><li>Gate runner wiring landed:</li><li>`scripts/ai-eval-gate.ts` (`--check`, `--record-baseline`)</li><li>`pnpm ai:eval:gate`</li><li>`pnpm ai:eval:baseline:record`</li><li>CI enforcement landed:</li><li>`.github/workflows/ai-eval-gate.yml`</li><li>Coverage:</li><li>`tests/services/ai-eval-regression.test.ts`</li><li>[x] ✅ Structured evaluation artifact retention for trend analysis (per-run metrics + prompt/model version metadata).</li><li>Eval artifact retention service landed:</li><li>`src/services/ai-eval-artifacts.ts`</li><li>Gate runner now writes:</li><li>`artifacts/evals/ai-eval-gate-latest.json`</li><li>`artifacts/evals/runs/ai-eval-gate-&lt;timestamp&gt;.json`</li><li>`artifacts/evals/trend-index.json`</li><li>`artifacts/evals/summary.md` with CI badges, artifact links, and freshness-aging alerts</li><li>CI visibility:</li><li>`.github/workflows/ai-eval-gate.yml` appends `artifacts/evals/summary.md` to `$GITHUB_STEP_SUMMARY`</li><li>`.github/workflows/ai-eval-gate.yml` uploads `artifacts/evals/` for post-run inspection</li><li>Retention control:</li><li>`METAMUSEUM_EVAL_RETENTION_MAX_RUNS` (default `200`)</li><li>Coverage:</li><li>`tests/services/ai-eval-artifacts.test.ts`</li></ul>\n<h4 id=\"4-frontend-ux-research-quality\">4) Frontend UX + research quality</h4>\n<ul><li>[x] ✅ Next.js i18n routing + locale negotiation from `Accept-Language` with graceful fallback.</li><li>Locale-aware proxy routing landed:</li><li>`proxy.ts`</li><li>i18n routing policy + negotiation utilities landed:</li><li>`src/utils/i18n-routing.ts`</li><li>`src/utils/locale-preferences.ts`</li><li>Behavior:</li><li>Non-localized `GET/HEAD` page requests redirect to `/{locale}/...` using negotiated locale.</li><li>Locale-prefixed requests rewrite to canonical internal routes while preserving locale context via request header/cookie.</li><li>Unsupported locale negotiation gracefully falls back to default locale (`en`).</li><li>Write-route role checks remain enforced against locale-normalized paths.</li><li>Coverage:</li><li>`tests/utils/i18n-routing.test.ts`</li><li>[x] ✅ Linked Art language-tag selection policy in UI rendering (prefer user locale, then fallback chain, preserving source labels).</li><li>Locale and language-tag policy utilities landed:</li><li>`src/utils/locale-preferences.ts`</li><li>`src/utils/linked-art-language.ts`</li><li>UI renderers now pass request locale preferences from `Accept-Language`:</li><li>`app/(workspace)/artwork/[id]/page.tsx`</li><li>`app/(workspace)/entity/[id]/page.tsx`</li><li>`app/(workspace)/records/page.tsx`</li><li>`app/(workspace)/entities/page.tsx`</li><li>`app/(workspace)/iiif/page.tsx`</li><li>Linked Art projection layers now apply locale-aware label selection while preserving source-label fallbacks:</li><li>`src/utils/artwork-builder.ts`</li><li>`src/utils/entities.ts`</li><li>Coverage:</li><li>`tests/utils/linked-art-language.test.ts`</li><li>`tests/utils/artwork-builder.test.ts`</li><li>`tests/utils/entities.test.ts`</li><li>[x] ✅ Researcher feedback/annotation loop using W3C Web Annotation model (claim-targeted annotations without mutating canonical `_source.raw`).</li><li>W3C annotation contracts + validation landed:</li><li>`src/contracts/zod/web-annotation.ts`</li><li>`src/contracts/web-annotation.ts`</li><li>`src/contracts/zod/requests.ts`</li><li>Annotation persistence is isolated from canonical records and stored as a separate managed document (`annotations.json`):</li><li>`src/services/annotations.ts`</li><li>`src/utils/storage.ts`</li><li>API endpoints landed for create/list/get with public CORS and audit logging:</li><li>`app/api/annotations/route.ts`</li><li>`app/api/annotations/[id]/route.ts`</li><li>Research UI loop landed on artwork detail pages:</li><li>`src/components/research-annotation-loop.tsx`</li><li>`app/(workspace)/artwork/[id]/page.tsx`</li><li>Coverage:</li><li>`tests/api/annotations.test.ts`</li><li>`tests/auth/roles.test.ts`</li><li>[x] ✅ Curator triage queue for annotation-driven correction proposals with provenance-safe review flow.</li><li>Curator triage queue API landed with state-aware queue metrics and claim-target proposal payloads:</li><li>`app/api/annotations/triage/route.ts`</li><li>Provenance-safe review action API landed:</li><li>`app/api/annotations/[id]/review/route.ts`</li><li>`src/services/annotations.ts` (`review()` workflow transitions)</li><li>Role policy enforces editor/admin review access while preserving researcher submit access:</li><li>`src/auth/roles.ts`</li><li>Curator workspace queue UI landed:</li><li>`app/curator/annotations/page.tsx`</li><li>`src/components/annotation-triage-workbench.tsx`</li><li>`app/layout.tsx` (workspace navigation link)</li><li>Coverage:</li><li>`tests/api/annotations.test.ts`</li><li>`tests/auth/roles.test.ts`</li></ul>\n<h4 id=\"5-security-privacy-posture\">5) Security + privacy posture</h4>\n<ul><li>[x] ✅ PII/sensitivity scan stage in C2 ETL before public indexing/syndication.</li><li>`src/utils/sensitivity.ts` scans materialized records for PII, cultural-sensitivity, and restricted-publication signals while excluding raw provider payload blobs.</li><li>`src/utils/record-materializer.ts` attaches `_sensitivity` review state during import/persist materialization.</li><li>`src/services/outbox-projector.ts` skips Solr + GraphDB public projections for records held by sensitivity review.</li><li>Coverage:</li><li>`tests/utils/sensitivity.test.ts`</li><li>`tests/utils/record-materializer.test.ts`</li><li>`tests/utils/search-index.test.ts`</li><li>`tests/services/outbox-projector.test.ts`</li><li>[x] ✅ Human-review hold policy for flagged records (restricted publication until disposition).</li><li>Flagged records carry `_sensitivity.status = &quot;review_required&quot;` and `_sensitivity.holdPublication = true`.</li><li>Held records are excluded from Solr/GraphDB syndication and flattened with `publication_status = &quot;held_for_review&quot;` plus no public `text_all`.</li><li>[x] ✅ Culturally sensitive knowledge handling rules integrated with rights/reuse UI and syndication controls.</li><li>Cultural-sensitivity scan rules and syndication holds now flow into explorer DTOs as `held_for_review` records with explicit rights/reuse review labels.</li><li>`RightsBadge` renders sensitivity labels as review-required publication holds, keeping cultural-sensitivity warnings visible anywhere rights/reuse chips appear.</li><li>Coverage:</li><li>`tests/components/linked-atomics.test.ts`</li><li>`tests/utils/artwork-builder.test.ts`</li><li>[x] ✅ Security telemetry for sensitivity decisions (who approved, why, and when).</li><li>Scanner telemetry records version, scan time, signal count, highest severity, and hold policy.</li><li>Human disposition telemetry now requires reviewer identity, rationale, and timestamp before approval can clear a publication hold.</li><li>Materialization preserves existing disposition telemetry during record rewrites, and reviewed records only return to public indexing after an approved disposition.</li><li>Coverage:</li><li>`tests/utils/sensitivity.test.ts`</li><li>`tests/utils/record-materializer.test.ts`</li><li>`tests/utils/search-index.test.ts`</li></ul>\n<h4 id=\"6-content-credibility-engine-trust-originality-distribution-consistency\">6) Content credibility engine (trust/originality/distribution/consistency)</h4>\n<ul><li>[x] ✅ Baseline credibility-engine policy document landed:</li><li>`docs/content-credibility-engine.md`</li><li>[x] ✅ Trust-layer storage templates landed:</li><li>`provenance/ledger.json`</li><li>`provenance/source-map.yaml`</li><li>[x] ✅ Originality-layer storage template landed:</li><li>`semantic-core/originality-index.json`</li><li>baseline novelty threshold documented (`cosine_distance &gt; 0.18`)</li><li>[x] ✅ Distribution/consistency scaffolding landed:</li><li>`distribution/schedule.yaml`</li><li>runtime queue path reserved at `distribution/queue.db` (gitignored)</li><li>`generation/style-profile.md`</li><li>[x] ✅ Monitoring scaffold landed:</li><li>`monitoring/metrics.json`</li><li>[x] ✅ Enforce citation-coverage gates in code for generated/publishable artifacts.</li><li>`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.</li><li>`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.</li><li>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`.</li><li>[x] ✅ Enforce originality-score gates in code for generated/publishable artifacts.</li><li>Originality scoring utility landed with policy-driven threshold + minimum unique-insight checks:</li><li>`src/utils/originality-score.ts`</li><li>`POST /api/content/generate` now returns `422` when originality score gates fail, including originality diagnostics in output payload.</li><li>`src/services/agents.ts`</li><li>`app/api/content/generate/route.ts`</li><li>Wiki publish preflight now enforces originality gates before publishable status:</li><li>`src/services/wiki-publish.ts`</li><li>`app/api/wiki-drafts/[id]/publish/route.ts`</li><li>Coverage:</li><li>`tests/utils/originality-score.test.ts`</li><li>`tests/api/content-generate.test.ts`</li><li>`tests/quality/cite-or-refuse-conformance.test.ts`</li><li>`tests/services/wiki-publish.test.ts`</li><li>`tests/api/wiki-drafts/flow.test.ts`</li><li>[x] ✅ Add weekly credibility audit automation (drift + relevance + broken-link checks).</li><li>Weekly audit orchestration script landed:</li><li>`scripts/credibility-audit.ts`</li><li>`src/services/credibility-audit.ts`</li><li>Package commands:</li><li>`pnpm credibility:audit`</li><li>`pnpm credibility:audit:check`</li><li>Weekly GitHub Action landed:</li><li>`.github/workflows/credibility-audit.yml`</li><li>uploads `artifacts/credibility-audit/latest.json`</li><li>Coverage:</li><li>`tests/services/credibility-audit.test.ts`</li><li>[x] ✅ Add queue worker implementation for multi-channel publish orchestration (web/linkedin/medium/email/api).</li><li>Publish queue worker service landed with:</li><li>schedule parsing from `distribution/schedule.yaml`</li><li>durable queue state in `distribution/queue.db`</li><li>per-channel delivery states, retries/backoff, dead-letter handling</li><li>per-day channel cap deferral logic from schedule policy</li><li>channel adapters for `web`, `linkedin`, `medium`, `email`, `api`</li><li>Files:</li><li>`src/services/publish-queue-worker.ts`</li><li>`scripts/publish-queue-worker.ts`</li><li>`distribution/README.md`</li><li>Commands:</li><li>`pnpm publish:queue:worker`</li><li>`pnpm publish:queue:worker:once`</li><li>`pnpm publish:queue:worker:drain`</li><li>Coverage:</li><li>`tests/services/publish-queue-worker.test.ts`</li><li>[x] ✅ Add OpenTelemetry metric/span conventions for trust/originality/distribution events.</li><li>Shared conventions module landed with stable span/metric names plus common layer/event/kind/outcome attributes:</li><li>`src/utils/otel-credibility.ts`</li><li>Trust/originality gates now emit standardized spans/metrics:</li><li>`src/utils/citation-coverage.ts`</li><li>`src/utils/originality-score.ts`</li><li>Wiki publish preflight/execute and distribution queue orchestration emit the same conventions:</li><li>`src/services/wiki-publish.ts`</li><li>`src/services/publish-queue-worker.ts`</li><li>Coverage:</li><li>`tests/utils/otel-credibility.test.ts`</li><li>[x] ✅ Add eval thresholds for engagement velocity and trust/originality regression alerts.</li><li>Threshold evaluator landed for engagement velocity minimum plus trust/originality minimum and baseline-drop budgets:</li><li>`src/services/credibility-eval-thresholds.ts`</li><li>`config/credibility-eval-thresholds.json`</li><li>Weekly credibility audit now evaluates and reports these alerts:</li><li>`src/services/credibility-audit.ts`</li><li>`scripts/credibility-audit.ts`</li><li>Coverage:</li><li>`tests/services/credibility-eval-thresholds.test.ts`</li><li>`tests/services/credibility-audit.test.ts`</li></ul>\n<h4 id=\"highest-roi-priority\">Highest ROI priority</h4>\n<ul><li>[x] ✅ Implement OpenTelemetry before broader C4/C5 expansion to prevent distributed-debugging bottlenecks.</li></ul>\n<p>Meta Wiki Art bridge implementation notes:</p>\n<ul><li>[x] ✅ See meta-wiki-art-bridge.md(meta-wiki-art-bridge.md) for sequencing constraints, boundaries, and the staged publish flow.</li><li>Sequencing constraints are documented under `## Sequencing constraints`.</li><li>Bridge boundaries are documented under `## Boundaries (Out Of Scope For Era A/B)`.</li><li>Staged publish flow is documented under `## Planned C5 flow`.</li></ul>\n<p><strong>Era C exit gate:</strong></p>\n<ul><li>[x] ✅ Automated evidence pack landed for all four checks (artifact schema + nightly job + dated run history).</li><li>Schema: `docs/schemas/era-c-exit-gate-evidence.schema.json`</li><li>Policy: `config/era-c-exit-gate-policy.json`</li><li>Script + artifacts: `scripts/era-c-exit-gate.ts`, `artifacts/exit-gate/`</li><li>Trend index now carries compact failed-check reasons so agents can prioritize the next blocker without opening every historical artifact.</li><li>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`)</li><li>Nightly workflow: `.github/workflows/era-c-exit-gate-evidence.yml`</li><li>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.</li><li>[x] ✅ Deployment-foundation preflight landed for controlled beta / production launch review.</li><li>Commands: `pnpm launch:preflight`, `pnpm launch:preflight:production`</li><li>Script + service: `scripts/deployment-preflight.ts`, `src/services/deployment-preflight.ts`</li><li>Runbook: `docs/ops/deployment-preflight.md`</li><li>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.</li><li>[x] ✅ Launch review packet landed for controlled beta / production launch decision evidence.</li><li>Commands: `pnpm launch:review`, `pnpm launch:review:check`, `pnpm launch:review:production`</li><li>Script + service: `scripts/launch-review.ts`, `src/services/launch-review.ts`</li><li>Runbook: `docs/ops/launch-review.md`</li><li>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.</li><li>Evidence producers: `pnpm a11y:check` writes `artifacts/launch/a11y-latest.json`, and `pnpm smoke:explore:matrix` writes `artifacts/launch/explore-smoke-latest.json`.</li><li>[ ] ⚠️ Latest exit-gate status is <strong>failed</strong> (`2026-06-10T11:36:30.690Z`), but the artifact is now agent-actionable:</li><li>SLO failures distinguish incomplete k6 summaries from actual p95 threshold breaches via `missingMetricsInWindow` and per-sample `metricDetails`.</li><li>Uptime failures include evidence `source` and `notes`.</li><li>Activity adoption credits only declared external consumers with `class: &quot;declared&quot;` and `declaredId`.</li><li>KPI failures include source metadata, snapshot notes, and per-failed-metric source/reason details.</li><li>[ ] All SLOs in SOTA §20.4 met at p95 over a 30-day window.</li><li>Evidence contract now requires all five p95 SLO metrics: cached Record, cold Record, keyword+facet search, whitelisted SPARQL, and IIIF tile serving.</li><li>SLO evidence artifacts now include missing-metric summaries and per-sample metric details so incomplete k6 runs are actionable separately from threshold breaches.</li><li>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.</li><li>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.</li><li>[ ] 99.9% uptime on public read.</li><li>Uptime gate now rejects stale or undated availability snapshots; `uptime.maxSnapshotAgeHours` defaults to `48` so the 30-day proof must be continuously refreshed.</li><li>Uptime evidence artifacts now surface source (`prometheus`, `probe`, `unavailable`) plus notes, making missing public-read proof actionable without opening telemetry snapshots.</li><li>Current blocker: uptime source is `probe`, availability is `1`, and `sampleCount30d` is `3`; continue scheduled probes until the 30-sample window is met.</li><li>[ ] ≥ 3 external Linked Art systems consume the `/api/activity` feed.</li><li>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.</li><li>Evidence ingestion preserves `class` + `declaredId` from `storage/activity-consumers.json`, so declared external consumers can satisfy the gate when real adoption arrives.</li><li>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/`.</li><li>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.</li><li>[ ] KPIs in SOTA §26 hit.</li><li>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.</li><li>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.</li><li>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.</li><li>AI query telemetry now logs `costUsd`, `costCurrency`, `costSource`, and usage counts per query; the deterministic local planner records `costUsd: 0` with `costSource: &quot;deterministic-local-planner&quot;` instead of relying on fallback KPI defaults.</li><li>Nightly workflow now seeds one deployed `/api/ai/query` request before telemetry sync when `METAMUSEUM_EVIDENCE_BASE_URL` is configured.</li><li>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.</li></ul>\n<p>---</p>","updatedAt":"2018-10-20T01:46:40.000Z","checksum":"cc030755d1e57b3a4e99b6036fc1ff3a8e2931a9ac35111319e013c9782e0b3e","checksumPrefix":"cc030755d1e5","anchorCount":47,"lineCount":1284,"rawUrl":"/api/docs/content?path=progress%2Fera-history.md","htmlUrl":"/docs?doc=progress%2Fera-history.md","apiUrl":"/api/docs/content?path=progress%2Fera-history.md"}