Add per-subject RDF export endpoint#1090
Merged
Merged
Conversation
Fixes #1087 Adds GET /neowiki/v0/subject/{subjectId}/rdf, serving one Subject's outbound bounded description as RDF: exactly the triples the page export emits for that Subject, including the native relation reification, with none of the page-metadata triples, in the hosting page's per-projection named graph. It mirrors the page endpoint's `format` (trig/turtle) and `projection` (native/edm/...) parameters and their negotiation. Mechanism: - PageProjector gains projectSubject( Page, SubjectId ): QuadList, implemented by both the native and ontology-mapping projectors by reusing their existing per-Subject internals (the former private projectSubject helpers, renamed to subjectQuads). An absent Subject, or one whose projection yields nothing (native: Schema unavailable; ontology: no Mapping for the target), yields an empty list. - RdfSubjectExporter resolves the Subject to its hosting page via the Neo4j PageIdentifiersLookup (the same dependency the subject JSON endpoint already carries), authorizes the page, loads it, and confirms the Subject is still in the current revision before projecting. A Subject that is unknown, has no readable or loadable hosting page, or is gone from the slot all return one byte-identical 404 (anti-oracle parity, cf. #1046). - ExportSubjectRdfApi validates the Subject ID shape first (400 on malformed, never a 500) and rejects an unknown projection (400) before resolving the Subject, so neither can be used to probe for restricted Subjects. - The format negotiation shared with the page endpoint moves into a new RdfFormatNegotiation trait, keeping the two endpoints byte-for-byte consistent. Docs: the rest-api.md endpoint table and the rdf-export.md endpoint section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refinements from the PR review of the per-subject RDF export endpoint: - Add an integration test proving a readable Subject whose Schema has no mapping for the requested ontology target returns a 200 empty graph, not a 404 — a readable Subject is never hidden. Documents that contract in rdf-export.md. - Reword the RdfSubjectExporter docstring: the page endpoint keeps its read gate in the REST handler, not in RdfPageExporter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integrates #1086 (one Mapping page per ontology). Re-fits the per-subject RDF export onto the restructured mapping model: - OntologyMappingProjector now takes one page-level Mapping (name = target, holding a per-Schema `schemas` map); re-applied the public projectSubject( Page, SubjectId ) + private subjectQuads onto that shape via Mapping::forSchema(). - ExportSubjectRdfApi adopts filterReadableProjectionNames() for the unknown- projection 400, matching ExportPageRdfApi's #1046 read-filtering. - OntologyMappingProjectorTest re-fitted to SchemaMapping/Mapping; the handler test's mapping fixture uses the new stored format (page title EDM, schemas map) and the case-sensitive "EDM" projection name. - rdf-export.md: kept the per-page/per-Subject endpoint intro, adopting #1086's "name of a Mapping page" wording. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The subject RDF endpoint adopted #1086's filterReadableProjectionNames() for the unknown-projection 400 during the master merge, but no subject-endpoint test covered it. Add one mirroring the page endpoint: a read-restricted Mapping page name must not leak into the 400 list (#1046), while "native" stays listed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The graph-lagging-the-slot case reads as plain absence from outside; naming it required internal knowledge and diluted the absent-vs-denied contract point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JeroenDeDauw
marked this pull request as ready for review
July 19, 2026 18:48
This was referenced Jul 19, 2026
…overy (#1097) * Surface data access in the UI: Data tab export links and RDF autodiscovery Fixes #1095 Stacked on #1090, whose per-subject RDF endpoint these menus link to; based on feature/subject-rdf-export so the diff is only this work. GitHub retargets to master once #1090 merges. The REST API served a page's RDF, its Subjects as JSON, a Subject as JSON, and (via #1090) a Subject's RDF, in native or ontology-mapped projections — none of it reachable from the UI. This adds three things: - A per-subject export menu in each expanded Data tab row, beside the ID: JSON, then Turtle and TriG for each readable projection. - A page-level export menu in the Data tab header: page-subjects JSON, then per-projection Turtle/TriG of the page RDF. The two menus share one labelling scheme so they read as one system. - `<link rel="alternate">` autodiscovery tags (Turtle + TriG, native projection) in the head of pages that carry NeoWiki data. The menus are driven by the projections the viewing user may read. SubjectsAction exposes that list as the `wgNeoWikiRdfProjections` JS config var, filtered server-side by filterReadableProjectionNames() so a restricted Mapping page title never reaches a reader who cannot see it. A later per-wiki choice to hide a projection then becomes a server-side filter with no UI change. Menu item URLs carry explicit `format=` parameters and percent-encode the projection name; each opens in a new tab so the Data tab state is not lost (Codex 1.14 menu-item anchors cannot set a target, so navigation happens on selection). Autodiscovery links are emitted only when the page has Subjects, so the advertised endpoint never 404s. Tests: Vitest covers the URL/label derivation (ordering, native-vs-mapping labels, projection encoding). PHP integration covers the config var and its permission filtering, and the head tags being present on a page with Subjects and absent on one without. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Assert RDF autodiscovery hrefs are absolute The head-tag test matched only the path substring, so a regression dropping the PROTO_CANONICAL expansion to a relative href would have shipped silently. Assert the hrefs are absolute so the canonical-URL behavior is locked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Member
Author
Member
Author
|
@malberts can you deploy this to demo wiki? @malberts @alistair3149 Thoughts on UI enhancements? Follow-ups welcome. Doing some expedited merging here since getting this live on the demo wiki allows for feedback, and it's part of the entire RDF/Ontology/QLever flow which I've self-merged and is known not-fully-reviewed yet and very much a best-guess spike. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Fixes #1087
Adds
GET /neowiki/v0/subject/{subjectId}/rdf, serving one Subject's outboundbounded description as RDF: exactly the triples the page export emits for that
Subject, including the native relation reification, with none of the page-metadata
triples, in the hosting page's per-projection named graph. It mirrors the page
endpoint's
format(trig/turtle) andprojection(native/EDM/...) parameters andtheir negotiation.
Mechanism:
PageProjectorgainsprojectSubject( Page, SubjectId ): QuadList, implemented byboth the native and ontology-mapping projectors by reusing their existing
per-Subject internals (the former private
projectSubjecthelpers, renamed tosubjectQuads). An absent Subject, or one whose projection yields nothing (native:Schema unavailable; ontology: the Subject's Schema has no entry in the target's
Mapping page), yields an empty list — a readable Subject that projects to zero quads
is a
200empty graph, never a404.RdfSubjectExporterresolves the Subject to its hosting page via the Neo4jPageIdentifiersLookup(the same dependency the subject JSON endpoint alreadycarries), authorizes the page, loads it, and confirms the Subject is still in the
current revision before projecting. A Subject that is unknown, has no readable or
loadable hosting page, or is gone from the slot all return one byte-identical 404
(anti-oracle parity, cf. Enforce per-page read permission on Subject, Layout, and RDF read endpoints #1046).
ExportSubjectRdfApivalidates the Subject ID shape first (400 on malformed, nevera 500) and rejects an unknown projection (400) — read-filtering the listed projection
names by the caller's permission — before resolving the Subject, so neither can be
used to probe for restricted Subjects.
RdfFormatNegotiationtrait, keeping the two endpoints byte-for-byte consistent.Docs: the rest-api.md endpoint table and the rdf-export.md endpoint section.
Integrates master's #1086 (one Mapping page per ontology): the per-subject projection
is re-fitted onto the single-page
Mappingmodel (Mapping::forSchema()), the handleradopts
filterReadableProjectionNames(), and projection names are now case-sensitiveMapping page titles (e.g.
EDM).In-session AI review
Reviewed in-session (pre-merge) by three independent read-only agents (correctness, security, design-optimality). No blocking findings: the anti-oracle 404 byte-parity and the hosting-page read gate were verified, and the design verdict was optimal (every element of structure warranted; the two rejected simpler alternatives — whole-page-projection-then-filter, and a fake single-subject Page — were verified to genuinely fail the goal). Two minor findings were applied: an integration test for the readable-but-unmapped-Subject
200empty-graph contract, and a docstring/doc clarification. The subsequent #1086 merge re-fit preserves these; no human has reviewed the diff.