Core — conformance, addressing, discovery¶
The chapter every other chapter rests on. It says what it means to conform, how a normative statement is identified, how a pod is addressed, and how an implementation tells a client what it provides.
Status: descriptive. Until this specification tags 0.1 it is being extracted from the
reference implementation, and where the two disagree the implementation is right. See
../../GOVERNANCE.md.
1. Conformance language¶
SPS-CORE-001 — The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD,
SHOULD NOT, RECOMMENDED, MAY and OPTIONAL in this specification are to be interpreted as
described in RFC 2119 and RFC 8174, and only when they appear in all capitals.
Prose that carries no such keyword is explanation. Where prose and a requirement appear to disagree, the requirement is what binds.
SPS-CORE-002 — Every normative statement in this specification carries a requirement
identifier of the form SPS-<AREA>-<NNN>. A statement without one is not normative and an
implementation is not obliged by it.
SPS-CORE-003 — Requirement identifiers are permanent. An identifier MUST NOT be reassigned to
a different statement, and MUST NOT be renumbered. A requirement that is retired is marked
withdrawn, keeps its identifier and its original text, and names its successor if it has one.
That promise is what makes an identifier safe to cite from a conformance report, an implementation note or a bug tracker that this project never sees. It is the same promise the vocabulary makes for RDF terms, for the same reason.
2. What conforms¶
SPS-CORE-004 — A conformant sempods implementation MUST satisfy every MUST and
REQUIRED statement in the core chapters: this chapter, contexts, grants, auth, lod-crud,
sparql and find. There is no partial core.
SPS-CORE-005 — A module is an optional chapter set with its own area prefix and its own
version. An implementation that advertises a module MUST satisfy every MUST and REQUIRED
statement in it. An implementation that does not advertise a module MAY omit it entirely.
SPS-CORE-006 — An implementation MUST NOT advertise a module it satisfies only in part. There
is no partial module, for the same reason there is no partial core: a client that has to probe which
half it received has no contract.
The modules defined by this specification are oidc, media and mcp.
3. Addressing¶
SPS-CORE-007 — A pod is addressed under a base URL of the form {origin}/{pod}, where {pod}
is the pod's identifier within the deployment. Every resource, context and control-plane route of
that pod lives under that prefix.
SPS-CORE-008 — The path segment _system immediately below a pod base URL is reserved for the
control plane. An implementation MUST NOT serve ordinary Linked Data resources from
{pod}/_system/…, and MUST NOT allow RDF writes to alter control-plane state.
SPS-CORE-009 — A pod's resource IRIs are minted from the address the server is publicly known
by, not from the address a particular request arrived at. An implementation MUST mint the same IRI
for the same resource regardless of the Host header a request carried.
Pod IRIs end up in other people's data. A pod that mints them from the request would emit a different identifier for the same resource depending on how it was reached, and every one of those would be wrong somewhere.
4. Conformance discovery¶
An implementation states what it provides at a well-known control-plane route, so that "optional" is something a client can ask about rather than a heading in a document.
SPS-CORE-010 — An implementation MUST serve GET {pod}/_system/conformance. The route MUST be
readable without authentication.
SPS-CORE-011 — The response MUST be application/json and MUST carry a specVersion string
naming the core version implemented, and a modules array. Each entry MUST carry an id — the
module's IRI — and a version string.
{
"specVersion": "0.1",
"modules": [
{ "id": "https://schema.sempods.org/module/media", "version": "0.1" },
{ "id": "https://schema.sempods.org/module/mcp", "version": "0.1" }
]
}
SPS-CORE-012 — A module absent from modules MUST be treated by a client as not provided. An
implementation MUST NOT rely on a client probing a module's routes to discover it.
SPS-CORE-013 — A client MUST tolerate a modules entry whose id it does not recognise, and
MUST NOT fail on unknown members of the response object. Discovery is expected to grow.
5. Error model¶
The chapters that follow use these status codes with these meanings and do not restate them.
SPS-CORE-014 — An implementation MUST answer with the following status codes:
| Status | When |
|---|---|
400 |
Malformed request — including an invalid grant string and invalid SPARQL |
401 |
Authentication is required and was missing, or was present and rejected |
403 |
Authenticated, but lacking the grant or scope the operation requires |
404 |
The resource or context does not exist, or the caller cannot see that it does |
409 |
The request was well formed and authorized on arrival, and the state changed underneath it |
500 |
Server error |
SPS-CORE-015 — On an operation that requires authentication, a missing bearer token and a
rejected bearer token MUST produce the same response: 401 with invalid_token. An implementation
MUST NOT let a caller distinguish the two.
The qualifier is load-bearing and was missing. A public read requires no authentication
(SPS-GRANT-031), so a request carrying no token is not a failed
authentication there — it is a request by a caller who never claimed to be anyone.
What does not depend on the operation: a token that was presented and rejected is always
401, on every route, including one an anonymous caller could have used without any token at all.
An implementation MUST NOT fall back to anonymous when a presented credential fails
(SPS-AUTH-043).
SPS-CORE-016 — Where an operation is one an unauthenticated caller may perform, the absence of
a bearer token is not an error. 401 is correct only where the operation itself requires
authentication.
SPS-CORE-017 — On a read, an implementation MUST NOT disclose the existence of a context
the caller has no read grant for. A context named in a read downscope that the caller cannot read
MUST be excluded silently: no 403, no 404, no diagnostic header, and no difference from a
context that holds nothing.
This is the cross-cutting rule an implementation is most likely to break by accident, usually by
answering 403 where silence was required — which tells the caller the context exists. What an
empty result then means is the chapter's business: a resource read answers 404, and a query or a
find answers success with nothing in it.
SPS-CORE-018 — On a write, the two failures are currently distinguished: a context that is
not registered produces 404, and a registered context the caller may not write produces 403.
This is a known defect, recorded rather than blessed. It is what the reference implementation
does, and while this specification is descriptive that is what it says — but the asymmetry with
SPS-CORE-017 is a context-enumeration oracle, not a design. A caller who can reach the write path
learns which guessed context IRIs are registered by watching which answer comes back, and context
names are freely chosen, so guessing is not hard.
What makes it narrower than the read path, and only narrower: a write names one context per request rather than accepting a list, so enumeration costs one request per guess.
What an implementation is asked to weigh, given that this requirement will change: answering 404
for both costs a caller the ability to tell "no such context" from "not yours", and a client that
cannot tell them apart retries a permission problem forever. Checking authorization before
existence — which context deletion already does (SPS-CTX-020) — gives
403 without confirming anything, and is the shape this should take. Closing it is on the
specification's roadmap, before 0.1 becomes prescriptive.
6. Standards profiled¶
Named, not re-explained. A chapter states the deviations from these; where it is silent, the standard applies unchanged.
| Standard | Where |
|---|---|
| RFC 9110 (HTTP Semantics) | throughout — verbs, status codes, conditional requests |
| RFC 7396 (JSON Merge Patch) | lod-crud, resource PATCH |
| RFC 7232 (Conditional Requests) | lod-crud, ETag and If-Match |
| RFC 8288 (Web Linking) | lod-crud, edit-URL advertisement |
| RFC 4648 §5 (base64url, no padding) | lod-crud, embedded IRIs in paths |
| RFC 6749 / OAuth 2.1 | auth |
| RFC 7636 (PKCE) | auth |
| RFC 7591 (Dynamic Client Registration) | auth |
| RFC 9728 (Protected Resource Metadata) | auth |
| RFC 8414 (Authorization Server Metadata) | auth |
| SPARQL 1.1 Query | sparql |
| JSON-LD 1.1 | lod-crud |
| Linked Data Principles (Berners-Lee) | lod-crud, resource GET |