Skip to content

Add subject-IRI dereferencing and a copy-the-IRI Data-tab control#1107

Merged
JeroenDeDauw merged 1 commit into
masterfrom
feature/subject-iri-dereferencing
Jul 20, 2026
Merged

Add subject-IRI dereferencing and a copy-the-IRI Data-tab control#1107
JeroenDeDauw merged 1 commit into
masterfrom
feature/subject-iri-dereferencing

Conversation

@JeroenDeDauw

@JeroenDeDauw JeroenDeDauw commented Jul 20, 2026

Copy link
Copy Markdown
Member

Follows-up to #1090

Subject IRIs ($wgNeoWikiRdfBaseUri/entity/{subjectId}, the neo-subj: IRI every RDF export mints) did not
dereference — nothing routed /entity/…. This makes them dereferenceable concept URIs and surfaces each
Subject's IRI in the Data tab.

Concept-URI negotiator: GET /neowiki/v0/entity/{subjectId}

A new REST handler (ResolveSubjectIriApi) content-negotiates a Subject's concept URI and replies
303 See Other with an absolute Location:

Accept Redirects to
application/trig the Subject's TriG RDF (…/subject/{id}/rdf?format=trig)
text/turtle the Subject's Turtle RDF (…/subject/{id}/rdf?format=turtle)
text/html, */*, absent, anything else the Subject's hosting page

TriG wins when both RDF types are acceptable (matching the existing RdfFormatNegotiation); the RDF
redirects target the native projection, keeping the entity route Accept-only (no projection/format
params). The Subject is resolved and read-authorized before the Accept header is inspected, so an absent
Subject and one on a page the caller may not read return one byte-identical 404 (anti-oracle parity with
the per-Subject RDF endpoint, #1046); a malformed id returns 400.

Resolution + read-gating live in a new Application service (SubjectHostingPageResolver) — the
resolve→authorize half of RdfSubjectExporter without loading page content.

Serving the bare IRI

MediaWiki's REST router matches on the request path, so the bare /entity/{id} must reach it as
/w/rest.php/…. A plain internal rewrite leaves REQUEST_URI unchanged (the router would 404), so the dev
image serves it with an internal self-proxy (no client-visible redirect), on Apache with mod_proxy +
mod_proxy_http:

RewriteRule ^/?entity/(.+)$ http://127.0.0.1/w/rest.php/neowiki/v0/entity/$1 [P,L]

The REST path (/w/rest.php/neowiki/v0/entity/{id}) works with no server config. This applies when
$wgNeoWikiRdfBaseUri is the wiki's own host (the default); an external base URI is the operator's routing
concern.

Copy-the-IRI control (Data tab)

Each expanded Subject row footer now shows the Subject's full IRI with a copy-to-clipboard control next to
the existing ID one — copy-only, not a link: on installs without the rewrite the bare IRI 404s, and LD users
paste IRIs into tools. The frontend does not rebuild IRI logic: SubjectsAction::onView exposes
wgNeoWikiSubjectIriBase (the $base/entity/ prefix from the same RdfNamespaces rule the export uses, via
a new subjectIriBase() accessor), and the component only appends the Subject id. Relates to the export-menu
work in #1097.

Docs and tests

docs/rdf/rdf-export.md gains a "Dereferencing subject IRIs" contract section (conneg table, the
always-available REST path, the shipped rewrite snippet); docs/api/rest-api.md gains the route row. Handler
integration tests cover the full conneg matrix (turtle/trig/both/html/*/*/absent), absolute Locations, 404
unknown, denied == absent byte-identical, and 400 malformed; unit tests cover the resolver, the
subjectIriBase() accessor (including trailing-slash trimming), and the config var. make phpunit,
make cs, and the TypeScript build/lint/vitest all pass.

Verified live on both the REST path and the bare /entity/ rewrite path: the full conneg matrix (following a
303 to a Turtle body and to the HTML page), 404/400, and the Data tab showing ID + IRI controls with the
IRI matching the export's neo-subj: expansion.

AI-authored — Claude Code, Opus 4.8 (max); detailed spec from @JeroenDeDauw, no human revisions mid-task; diff not yet human-reviewed; make phpunit/cs/vitest green locally, live-verified both dereference paths and the Data-tab UI, CI green.

Follows-up to #1090

Subject IRIs (`$wgNeoWikiRdfBaseUri/entity/{subjectId}`, the `neo-subj:` IRI every RDF export mints) did not
dereference — nothing routed `/entity/…`. This makes them dereferenceable concept URIs and surfaces each
Subject's IRI in the Data tab.

## Concept-URI negotiator: `GET /neowiki/v0/entity/{subjectId}`

A new REST handler (`ResolveSubjectIriApi`) content-negotiates a Subject's concept URI and replies
**303 See Other** with an absolute `Location`:

| `Accept` | Redirects to |
|---|---|
| `application/trig` | the Subject's TriG RDF (`…/subject/{id}/rdf?format=trig`) |
| `text/turtle` | the Subject's Turtle RDF (`…/subject/{id}/rdf?format=turtle`) |
| `text/html`, `*/*`, absent, anything else | the Subject's hosting page |

TriG wins when both RDF types are acceptable (matching the existing `RdfFormatNegotiation`); the RDF
redirects target the native projection, keeping the entity route Accept-only (no `projection`/`format`
params). The Subject is resolved and read-authorized before the Accept header is inspected, so an absent
Subject and one on a page the caller may not read return one byte-identical `404` (anti-oracle parity with
the per-Subject RDF endpoint, #1046); a malformed id returns `400`.

Resolution + read-gating live in a new Application service (`SubjectHostingPageResolver`) — the
resolve→authorize half of `RdfSubjectExporter` without loading page content.

## Serving the bare IRI

MediaWiki's REST router matches on the request path, so the bare `/entity/{id}` must reach it as
`/w/rest.php/…`. A plain internal rewrite leaves `REQUEST_URI` unchanged (the router would 404), so the dev
image serves it with an internal self-proxy (no client-visible redirect), on Apache with `mod_proxy` +
`mod_proxy_http`:

    RewriteRule ^/?entity/(.+)$ http://127.0.0.1/w/rest.php/neowiki/v0/entity/$1 [P,L]

The REST path (`/w/rest.php/neowiki/v0/entity/{id}`) works with no server config. This applies when
`$wgNeoWikiRdfBaseUri` is the wiki's own host (the default); an external base URI is the operator's routing
concern.

## Copy-the-IRI control (Data tab)

Each expanded Subject row footer now shows the Subject's full IRI with a copy-to-clipboard control next to
the existing ID one — copy-only, not a link: on installs without the rewrite the bare IRI 404s, and LD users
paste IRIs into tools. The frontend does not rebuild IRI logic: `SubjectsAction::onView` exposes
`wgNeoWikiSubjectIriBase` (the `$base/entity/` prefix from the same `RdfNamespaces` rule the export uses, via
a new `subjectIriBase()` accessor), and the component only appends the Subject id. Relates to the export-menu
work in #1097.

## Docs and tests

`docs/rdf/rdf-export.md` gains a "Dereferencing subject IRIs" contract section (conneg table, the
always-available REST path, the shipped rewrite snippet); `docs/api/rest-api.md` gains the route row. Handler
integration tests cover the full conneg matrix (turtle/trig/both/html/`*/*`/absent), absolute Locations, 404
unknown, denied == absent byte-identical, and 400 malformed; unit tests cover the resolver, the
`subjectIriBase()` accessor (including trailing-slash trimming), and the config var. `make phpunit`,
`make cs`, and the TypeScript build/lint/vitest all pass.

Verified live on both the REST path and the bare `/entity/` rewrite path: the full conneg matrix (following a
303 to a Turtle body and to the HTML page), 404/400, and the Data tab showing `ID` + `IRI` controls with the
IRI matching the export's `neo-subj:` expansion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JeroenDeDauw
JeroenDeDauw marked this pull request as ready for review July 20, 2026 20:26
@JeroenDeDauw
JeroenDeDauw merged commit d2a6f9b into master Jul 20, 2026
12 checks passed
@JeroenDeDauw
JeroenDeDauw deleted the feature/subject-iri-dereferencing branch July 20, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant