{"id":"linked-art/conformance-matrix","relativePath":"linked-art/conformance-matrix.md","title":"Linked Art 1.0 — Conformance Matrix","markdown":"# Linked Art 1.0 — Conformance Matrix\n\nA candid, evidence-based view of how Meta Museum conforms to the [Linked Art 1.0 API](https://linked.art/api/1.0/) — at the protocol layer, the model layer, and per provider. For the full standards ledger see [LinkedArtModel1.0-Reference.md](LinkedArtModel1.0-Reference.md).\n\n## Protocol conformance (MUST-level) — verified against the live deploy\n\nThe Linked Art Protocol page defines three MUSTs (first three rows); the `HEAD` and HTTP/2 SHOULDs are also met. All are verified against the live deploy on the canonical entity endpoints (`/api/objects/[id]`, `/api/artworks/[id]`, `/api/works/[id]`, …):\n\n| Requirement | Status | Evidence |\n|---|---|---|\n| Content-Type `application/ld+json;profile=\"https://linked.art/ns/v1/linked-art.json\"` when requested via `Accept` | ✅ | Echoed exactly under content negotiation (`src/utils/protocol.ts`). |\n| `Access-Control-Allow-Origin: *` on every response | ✅ | `createPublicCorsHeaders()`. |\n| `GET` + `OPTIONS` supported | ✅ | `optionsResponse()` on every public route; auto-implemented `OPTIONS` plus explicit allow-methods. |\n| `HEAD` supported (SHOULD) | ✅ | Canonical entity/collection routes export `HEAD` (same headers as GET, no body, mirrors 200/404); `Allow-Methods` advertises `GET,HEAD,OPTIONS`. `tests/api/head-methods.test.ts`. |\n| HTTP/2 on the live deploy (SHOULD) | ✅ | Vercel edge negotiates HTTP/2 — verified `HTTP/2.0 200` on `www.metamuseum.org`. |\n\nTested by the B8 protocol-conformance suite across ~14 representative routes (media-type negotiation, CORS, URI opacity, array cardinality, HAL `_links` separation).\n\n## Core model conformance (all normalized records)\n\nApplied uniformly in `src/utils/linked-art.ts` (`normalizeIncomingRecord`) and `src/utils/artwork-builder.ts`:\n\n| Aspect | Status |\n|---|---|\n| `@context` (`linked-art.json`) always present | ✅ |\n| `id` / `type` / `_label` required | ✅ |\n| `identified_by` (Name/Identifier), `classified_as`, `referred_to_by` | ✅ |\n| `produced_by` as a `Production` activity (maker via `carried_out_by`, date via `timespan`, place via `took_place_at`) | ✅ |\n| Event-centric provenance (`Activity`-wrapped acquisition/transfer; ownership vs. custody distinct) — B9 guardrails | ✅ |\n| Multi-valued properties kept as arrays (cardinality-safe) | ✅ |\n| Carrier / content / surrogate distinct (HumanMadeObject ≠ VisualItem ≠ DigitalObject) | ✅ |\n| HAL `_links` separated from semantic assertions; `describedby` signposting | ✅ |\n| `subject_to` (Right entities with classification) | ✅ Every object record: Getty's preserved; others synthesized from rights signals, classified by CC0 / rightsstatements.org URIs (`src/utils/linked-art-rights.ts`) |\n\n## Per-provider matrix\n\nEvery production provider has committed **pass + fail fixtures** ([`provider-fixture-manifest.json`](../../tests/fixtures/validation/provider-fixture-manifest.json)), asserted in CI by [`validation-architecture-depth.test.ts`](../../tests/quality/validation-architecture-depth.test.ts): the pass fixture must conform and the fail fixture must be rejected, each mapped to explicit Linked Art reference rounds. Each pass fixture is **also** validated against the Linked Art SHACL shapes by a dedicated pyshacl CI job ([`shacl-conformance.yml`](../../.github/workflows/shacl-conformance.yml)) — proving the JSON-LD expands to valid CIDOC-CRM RDF. Rights are modeled uniformly as `subject_to` `Right` entities for every provider (Getty's preserved; the rest synthesized — see the rights row in the core-model table). The table below is **generated** from those fixtures and the capability registry, so it cannot drift from what the tests verify.\n\n<!-- GENERATED:provider-matrix:start -->\n_Generated by `pnpm conformance:matrix` from [`provider-fixture-manifest.json`](../../tests/fixtures/validation/provider-fixture-manifest.json) and the provider capability registry. Each ✓ is computed live by running `inspectLinkedArtRecord` over the committed pass/fail fixtures — **13/13 providers** pass both directions (pass conforms, fail rejected). Do not edit by hand._\n\n| Provider | Import | Search | Pass fixture conforms | Fail fixture rejected | Linked Art rounds |\n|---|:--:|:--:|:--:|:--:|---|\n| Art Institute of Chicago API | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |\n| Cleveland Museum of Art Open Access API | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |\n| Europeana | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R65 |\n| Getty Museum Collection | ✅ | ❌ | ✅ | ✅ | R2, R3, R11, R14 |\n| Harvard Art Museums | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R68 |\n| Louvre Collections | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R14 |\n| National Gallery of Art Open Data Program | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R14 |\n| Princeton University Art Museum | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |\n| Rijksmuseum Data Services | ✅ | ✅ | ✅ | ✅ | R2, R3, R65 |\n| RKD Knowledge Graph | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R65 |\n| Smithsonian Open Access | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R65 |\n| The Metropolitan Museum of Art | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |\n| Victoria and Albert Museum Collections API | ✅ | ✅ | ✅ | ✅ | R2, R3, R65 |\n<!-- GENERATED:provider-matrix:end -->\n\nCounts come from the provider capability registry (`src/gateway/readiness.ts`), also surfaced at `/api/providers/capabilities`: **13 production providers · 12 searchable · 13 importable**. Access: most providers are open; RKD uses a Triply token, Harvard and Smithsonian need an API key.\n\n## Honest gaps\n\n- **Non-Getty rights are heuristically derived.** Every object record now carries a `subject_to` `Right` classified by a CC0 / rightsstatements.org URI, so the graph is queryable for all providers. But outside Getty the *category* (public-domain / in-copyright / undetermined) is inferred from the provider's rights signals and defaults conservatively to \"Copyright Undetermined\" — precise per-provider license mapping (e.g. CC-BY vs. CC-BY-SA) is still future work.\n- **Search runs in-app, not yet on Solr.** `/api/search` now returns **relevance-ranked** results (exact label > prefix > substring > name hit) with **`type` / `provider` facet counts** and `q` / `type` / `provider` / `limit` / `offset` params, as a `ld+json` `OrderedCollectionPage` (`src/services/search.ts`). It runs over the record set; Solr 9 remains the documented scale-out backend (env-gated), with this in-app implementation as the portable default and conformance reference.\n- **SHACL shapes are minimal (but now CI-gated).** A dedicated CI job ([`shacl-conformance.yml`](../../.github/workflows/shacl-conformance.yml)) validates every provider's pass fixture against the Linked Art SHACL shapes with pyshacl, so a regression that breaks CIDOC-CRM expansion blocks the build. The shapes themselves are still minimal (object `identified_by` + `label` minimums); enriching them toward full Linked Art SHACL coverage is ongoing work.\n\n## Bottom line\n\nThe spec's MUST-level HTTP requirements and the hard part — the event-centric model — are genuinely conformant and test-backed. The remaining gaps are breadth (precise per-provider license categories, Solr-scale search, richer SHACL shapes), not correctness.\n","sections":[{"level":2,"heading":"Protocol conformance (MUST-level) — verified against the live deploy","anchor":"protocol-conformance-must-level-verified-against-the-live-deploy"},{"level":2,"heading":"Core model conformance (all normalized records)","anchor":"core-model-conformance-all-normalized-records"},{"level":2,"heading":"Per-provider matrix","anchor":"per-provider-matrix"},{"level":2,"heading":"Honest gaps","anchor":"honest-gaps"},{"level":2,"heading":"Bottom line","anchor":"bottom-line"}],"html":"<h1 id=\"linked-art-1-0-conformance-matrix\">Linked Art 1.0 — Conformance Matrix</h1>\n<p>A candid, evidence-based view of how Meta Museum conforms to the <a href=\"https://linked.art/api/1.0/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"doc-link\">Linked Art 1.0 API</a> — at the protocol layer, the model layer, and per provider. For the full standards ledger see LinkedArtModel1.0-Reference.md(LinkedArtModel1.0-Reference.md).</p>\n<h2 id=\"protocol-conformance-must-level-verified-against-the-live-deploy\">Protocol conformance (MUST-level) — verified against the live deploy</h2>\n<p>The Linked Art Protocol page defines three MUSTs (first three rows); the `HEAD` and HTTP/2 SHOULDs are also met. All are verified against the live deploy on the canonical entity endpoints (`/api/objects/[id]`, `/api/artworks/[id]`, `/api/works/[id]`, …):</p>\n<p>| Requirement | Status | Evidence |</p>\n<p>|---|---|---|</p>\n<p>| Content-Type `application/ld+json;profile=&quot;https://linked.art/ns/v1/linked-art.json&quot;` when requested via `Accept` | ✅ | Echoed exactly under content negotiation (`src/utils/protocol.ts`). |</p>\n<p>| `Access-Control-Allow-Origin: *` on every response | ✅ | `createPublicCorsHeaders()`. |</p>\n<p>| `GET` + `OPTIONS` supported | ✅ | `optionsResponse()` on every public route; auto-implemented `OPTIONS` plus explicit allow-methods. |</p>\n<p>| `HEAD` supported (SHOULD) | ✅ | Canonical entity/collection routes export `HEAD` (same headers as GET, no body, mirrors 200/404); `Allow-Methods` advertises `GET,HEAD,OPTIONS`. `tests/api/head-methods.test.ts`. |</p>\n<p>| HTTP/2 on the live deploy (SHOULD) | ✅ | Vercel edge negotiates HTTP/2 — verified `HTTP/2.0 200` on `www.metamuseum.org`. |</p>\n<p>Tested by the B8 protocol-conformance suite across ~14 representative routes (media-type negotiation, CORS, URI opacity, array cardinality, HAL `_links` separation).</p>\n<h2 id=\"core-model-conformance-all-normalized-records\">Core model conformance (all normalized records)</h2>\n<p>Applied uniformly in `src/utils/linked-art.ts` (`normalizeIncomingRecord`) and `src/utils/artwork-builder.ts`:</p>\n<p>| Aspect | Status |</p>\n<p>|---|---|</p>\n<p>| `@context` (`linked-art.json`) always present | ✅ |</p>\n<p>| `id` / `type` / `_label` required | ✅ |</p>\n<p>| `identified_by` (Name/Identifier), `classified_as`, `referred_to_by` | ✅ |</p>\n<p>| `produced_by` as a `Production` activity (maker via `carried_out_by`, date via `timespan`, place via `took_place_at`) | ✅ |</p>\n<p>| Event-centric provenance (`Activity`-wrapped acquisition/transfer; ownership vs. custody distinct) — B9 guardrails | ✅ |</p>\n<p>| Multi-valued properties kept as arrays (cardinality-safe) | ✅ |</p>\n<p>| Carrier / content / surrogate distinct (HumanMadeObject ≠ VisualItem ≠ DigitalObject) | ✅ |</p>\n<p>| HAL `_links` separated from semantic assertions; `describedby` signposting | ✅ |</p>\n<p>| `subject_to` (Right entities with classification) | ✅ Every object record: Getty&#39;s preserved; others synthesized from rights signals, classified by CC0 / rightsstatements.org URIs (`src/utils/linked-art-rights.ts`) |</p>\n<h2 id=\"per-provider-matrix\">Per-provider matrix</h2>\n<p>Every production provider has committed <strong>pass + fail fixtures</strong> (`provider-fixture-manifest.json`(../../tests/fixtures/validation/provider-fixture-manifest.json)), asserted in CI by `validation-architecture-depth.test.ts`(../../tests/quality/validation-architecture-depth.test.ts): the pass fixture must conform and the fail fixture must be rejected, each mapped to explicit Linked Art reference rounds. Each pass fixture is <strong>also</strong> validated against the Linked Art SHACL shapes by a dedicated pyshacl CI job (`shacl-conformance.yml`(../../.github/workflows/shacl-conformance.yml)) — proving the JSON-LD expands to valid CIDOC-CRM RDF. Rights are modeled uniformly as `subject_to` `Right` entities for every provider (Getty&#39;s preserved; the rest synthesized — see the rights row in the core-model table). The table below is <strong>generated</strong> from those fixtures and the capability registry, so it cannot drift from what the tests verify.</p>\n<p>&lt;!-- GENERATED:provider-matrix:start --&gt;</p>\n<p>_Generated by `pnpm conformance:matrix` from `provider-fixture-manifest.json`(../../tests/fixtures/validation/provider-fixture-manifest.json) and the provider capability registry. Each ✓ is computed live by running `inspectLinkedArtRecord` over the committed pass/fail fixtures — <strong>13/13 providers</strong> pass both directions (pass conforms, fail rejected). Do not edit by hand._</p>\n<p>| Provider | Import | Search | Pass fixture conforms | Fail fixture rejected | Linked Art rounds |</p>\n<p>|---|:--:|:--:|:--:|:--:|---|</p>\n<p>| Art Institute of Chicago API | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |</p>\n<p>| Cleveland Museum of Art Open Access API | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |</p>\n<p>| Europeana | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R65 |</p>\n<p>| Getty Museum Collection | ✅ | ❌ | ✅ | ✅ | R2, R3, R11, R14 |</p>\n<p>| Harvard Art Museums | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R68 |</p>\n<p>| Louvre Collections | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R14 |</p>\n<p>| National Gallery of Art Open Data Program | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R14 |</p>\n<p>| Princeton University Art Museum | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |</p>\n<p>| Rijksmuseum Data Services | ✅ | ✅ | ✅ | ✅ | R2, R3, R65 |</p>\n<p>| RKD Knowledge Graph | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R65 |</p>\n<p>| Smithsonian Open Access | ✅ | ✅ | ✅ | ✅ | R2, R3, R11, R65 |</p>\n<p>| The Metropolitan Museum of Art | ✅ | ✅ | ✅ | ✅ | R2, R3, R11 |</p>\n<p>| Victoria and Albert Museum Collections API | ✅ | ✅ | ✅ | ✅ | R2, R3, R65 |</p>\n<p>&lt;!-- GENERATED:provider-matrix:end --&gt;</p>\n<p>Counts come from the provider capability registry (`src/gateway/readiness.ts`), also surfaced at `/api/providers/capabilities`: <strong>13 production providers · 12 searchable · 13 importable</strong>. Access: most providers are open; RKD uses a Triply token, Harvard and Smithsonian need an API key.</p>\n<h2 id=\"honest-gaps\">Honest gaps</h2>\n<ul><li><strong>Non-Getty rights are heuristically derived.</strong> Every object record now carries a `subject_to` `Right` classified by a CC0 / rightsstatements.org URI, so the graph is queryable for all providers. But outside Getty the <em>category</em> (public-domain / in-copyright / undetermined) is inferred from the provider&#39;s rights signals and defaults conservatively to &quot;Copyright Undetermined&quot; — precise per-provider license mapping (e.g. CC-BY vs. CC-BY-SA) is still future work.</li><li><strong>Search runs in-app, not yet on Solr.</strong> `/api/search` now returns <strong>relevance-ranked</strong> results (exact label &gt; prefix &gt; substring &gt; name hit) with <strong>`type` / `provider` facet counts</strong> and `q` / `type` / `provider` / `limit` / `offset` params, as a `ld+json` `OrderedCollectionPage` (`src/services/search.ts`). It runs over the record set; Solr 9 remains the documented scale-out backend (env-gated), with this in-app implementation as the portable default and conformance reference.</li><li><strong>SHACL shapes are minimal (but now CI-gated).</strong> A dedicated CI job (`shacl-conformance.yml`(../../.github/workflows/shacl-conformance.yml)) validates every provider&#39;s pass fixture against the Linked Art SHACL shapes with pyshacl, so a regression that breaks CIDOC-CRM expansion blocks the build. The shapes themselves are still minimal (object `identified_by` + `label` minimums); enriching them toward full Linked Art SHACL coverage is ongoing work.</li></ul>\n<h2 id=\"bottom-line\">Bottom line</h2>\n<p>The spec&#39;s MUST-level HTTP requirements and the hard part — the event-centric model — are genuinely conformant and test-backed. The remaining gaps are breadth (precise per-provider license categories, Solr-scale search, richer SHACL shapes), not correctness.</p>","updatedAt":"2018-10-20T01:46:40.000Z","checksum":"53ff87000bf4beee3e6c40e0d7719f5e79f6382f671c467d61868d0758c80300","checksumPrefix":"53ff87000bf4","anchorCount":5,"lineCount":70,"rawUrl":"/api/docs/content?path=linked-art%2Fconformance-matrix.md","htmlUrl":"/docs?doc=linked-art%2Fconformance-matrix.md","apiUrl":"/api/docs/content?path=linked-art%2Fconformance-matrix.md"}