cartoql
v0.2.0
Published
GraphQL over RDF, compiled with a plan-level security kernel — typed, versioned, security-compiled query plans over any SPARQL 1.1 store
Readme
CartoQL
GraphQL over RDF, compiled with a plan-level security kernel and provenance-first semantics.
📛 CartoQL is a working name (Latin: truthful). All identifiers keep the project renameable — see docs/01-vision.md.
CartoQL turns any RDF graph — an ontology, a triple store, a SPARQL endpoint — into a versioned GraphQL API, and compiles every GraphQL document down to SPARQL plans that can carry access-control joins, cost budgets, and provenance bindings inside the query plan, where they can't be bypassed by resolvers or client cleverness.
SHACL shapes + ontology ──► SDL schema (generated, version with your TBox)
GraphQL query ──► directive expansion ──► ACL-joined SPARQL algebra
──► cost check ──► execute ──► shaped GraphQL responseWhy another GraphQL-over-RDF?
Existing options make one of two mistakes:
| Approach | The problem | |---|---| | GraphQL-LD, HyperGraphQL | Mapping-first: hand-written field→predicate contexts rot the moment your ontology evolves. No security story beyond "whatever your endpoint does." | | Vendor GraphQL endpoints (Stardog, GraphDB) | Security compilation is a single-vendor feature, and the semantics live in proprietary inference layers. |
CartoQL's three commitments:
- Generated, never mapped. SDL is a build artifact of your ontology + SHACL shapes.
Rename a class, republish, get a new typed schema with
@deprecatedmigration scaffolding — no mapping file to maintain. - Security compiles into the plan. Authorization isn't a resolver filter or an
output scrubber — constraint joins are injected into the compiled SPARQL algebra,
fail-closed, indistinguishable-hidden (invisible data never announces itself in
errors[]on probe/join paths). Bring your own permission model: CartoQL exposes a smallPermissionResolverSPI, everything else is directives. - Provenance ride-along. Fields can carry
prov-style run identifiers and grounding pointers in their payload contract, so API consumers can cite what they read — or skip it entirely. Your choice, per field.
Feature summary
- SHACL → SDL generation (node shapes → types;
minCount/maxCount→ nullability; ranges → field types; renamed terms → deprecations) - Directive registry:
@scope,@traversalScope,@requireGroup,@requireRole,@tenantScoped,@redactWith,@minConfidence,@provenance,@maxDepth,@budget— all generator-stamped, never client-satisfiable - SPARQL 1.1 execution with federated
SERVICEsupport; pluggable store adapters - Principal-scoped schema slicing (serve each consumer only the modules it uses)
- Apollo-federation-ready entity keying (
@keyon IRIs) with auth-forwarding requirements spec'd - Stable error-code contract (
CQL_*) — branch on codes, never messages - TypeScript core, designed to embed as a standalone gateway service
Status
Pre-alpha, first implementation slices live. The golden path is green end to end: SHACL shapes → generated SDL → compiled plans (ADR-1 IR) → executed responses, all snapshot-tested against the reviewed corpus — 51 tests, 7 packages. The gateway now serves GraphQL over your own data:
npm run serve -- --ontology corpus/shards/core/ontology.ttl --shapes corpus/shards/core/shapes.ttl --data corpus/shards/core/data.ttl --port 4137
# → http://localhost:4137/playground
curl -s -X POST localhost:4137/graphql -H 'content-type: application/json' -d '{
"query": "query Q($iri: ID!) { publication(iri: $iri) { name year authoredInverse { name } } }",
"variables": {"iri": "https://cartoql.example/corpus/core/data#pub-a3"}
}'
# {"data":{"publication":{"name":"Plan-level authorization","year":"2025",
# "authoredInverse":[{"name":"Ada Ionescu"},{"name":"Cleo Marchetti"}]}},"errors":[]}SPARQL mode is live too, against any SPARQL 1.1 store — response-equivalence with the reference adapter (docs/09 L0) is enforced by a parity suite that runs against a real Oxigraph in CI:
# load your data into a named graph, then:
npm run serve -- \
--ontology my-onto.ttl --shapes my-shapes.ttl \
--sparql http://localhost:7878/query --graph urn:my:graphOxigraph (and any store that ignores SPARQL protocol variable bindings) is handled by the adapter's auto-selected VALUES transport — never by weakening the no-interpolation rule. The gateway now serves the security path end to end — one flag set:
npm run serve -- \
--ontology sec/ontology.ttl --shapes sec/shapes.ttl --data sec/data.ttl \
--graph urn:cartoql:shard:sec \
--stamps sec/stamps.json # @requireGroup rules (docs/03 stamping)\
--auth-file claims.json # {"alice": ["hr-comp","legal"]}
# principal per request: header x-cartoql-principal: aliceThe M2 security kernel has begun: SHACL shape + stamp configurations generate
@requireGroup directives; the compiler translates them into plan-level
constraints, and the adapter enforces the spec's two-track semantics (docs/03) —
field denial is visible (null + CQL_PERMISSION_DENIED), entity gating is
existence-blind (invisible ≡ absent, pair-tested). The reviewed evidence lives in
corpus/shards/sec/ with per-principal response snapshots.
Remaining for M1: npm packaging for npx cartoql-gateway and the external-tester
milestone. See the roadmap.
Documentation
Full set with reading paths: docs/README.md. Highlights:
| Doc | Covers |
|---|---|
| Vision | The gap, positioning, principles, non-goals, license rationale |
| Directive & Error Spec | The normative contract: directive registry, enforcement rules, CQL_* codes |
| Semantic Mapping | RDF→GraphQL impedance manual — the decision points an implementer hits on day one |
| Threat Model | T1–T10 with mitigations; coordinated-disclosure policy |
| Testing & Conformance | Gold shards, leak probes, adapter conformance levels |
| Roadmap | M0–M4 milestones; M1 = usable-by-anyone endpoint first |
Plus: architecture (02), integration paths (04), performance engineering (08), governance/ADR log/metrics (10), glossary (11).
License
Apache-2.0 (see LICENSE) — chosen over MIT for the explicit patent grant, which enterprise adopters ask for. See docs/01-vision.md.
Contributions
Design-first project: the spec docs are the contract. Contributions that change directive semantics or error codes go through a doc PR before an implementation PR. See CONTRIBUTING.md.
