Core — conformance, addressing, discovery¶
Development snapshot — not a release
Core 0.1-dev · Source revision and matching artifacts.
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: this text decides, and can still change. An implementation that disagrees with it is
the bug; a requirement may still be deleted, renumbered or change meaning, until
../../GOVERNANCE.md says that ends.
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 authored by sempods MUST carry a requirement
identifier of the form SPS-<AREA>-<NNN>; without one it is not normative. Obligations inherited
from an explicitly incorporated standard retain that standard's identifiers. Informative
references create no obligations.
SPS-CORE-003 — Requirement identifiers are permanent: an identifier MUST NOT be reassigned to
a different statement, MUST NOT be renumbered, and a requirement that is retired MUST be marked
withdrawn, keep its identifier and its original text, and name its successor if it has one. Those
obligations bind from the 0.1 release or from the first dependency on this contract from outside
this project, whichever comes first. Only before that point MAY a requirement be deleted outright
and an identifier be renumbered or reused.
Permanence 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, and it begins as soon as there is something to cite — which the tag
guarantees and an outside dependant can bring about sooner.
../../GOVERNANCE.md states what the window before it is for, what it is not
for, and what closes it. What ends there is deleting an identifier, renumbering one, or changing
what one stands for; the chapters go on being written. Whether the text or the reference
implementation is right is a different question, settled already: this text decides.
2. What conforms¶
The subject of this specification is one pod. Every requirement in it can be satisfied by a pod that is the only one in existence, and none of them needs a second pod to mean anything. A deployment that serves many pods is conformant when each of those pods is; how it provisions them, tells them apart, or administers them across the set is an extension of an implementation and not a part of this specification.
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 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.
A module usually has an area prefix of its own, and oidc, media and mcp do. It is not what
makes one: an area may span core and a module, and CTX does — contexts are core, creating and
deleting them is not. What decides whether a requirement is mandatory is the chapter it stands
in, which is what the index reports in its part field. Reading it from the identifier would
work only until an area was split, and one now is.
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 context-management, oidc, media and mcp.
3. Addressing¶
SPS-CORE-007 — A pod is addressed under a base URL. Every resource, context and control-plane
route of that pod lives under that base URL, except the host-rooted discovery metadata addresses
explicitly defined by SPS-AUTH-067 and the
MCP module.
This specification does not prescribe how the base URL decomposes. A path segment under a shared
origin, a host of the pod's own, and an origin that is a single pod are the same pod as far as every
other requirement is concerned. What a client is given is the base URL, and {pod} throughout this
specification means it.
SPS-CORE-019 — A pod's base URL MUST be absolute, MUST use https on any host and MAY use
http only on a loopback address, MUST carry no query and no fragment, and its path MUST NOT end in
a slash.
The scheme half is SPS-AUTH-018's rule for a redirect URI, and a pod base
cannot be looser than it: every route below carries a bearer, and the token, authorization and
registration endpoints carry credentials. A pod reachable over http on a real host would hand
them to the network. The loopback exception is the same one, and for the same case — a development
pod, which SPS-AUTH-006 already refuses outside development.
Every route in this specification written as {pod}/… is the base URL with what follows appended
to it, and each part of the form above is what makes appending mean anything. A base of
https://example.org/alice/ composes {pod}/_system/conformance into a doubled slash, which
servers and proxies do not normalise alike — a client would address a route the pod does not serve,
on some deployments and not others. A base carrying a query or a fragment does not compose into that
route at all: appending to https://example.org/alice?tenant=1 leaves the path where it was and
puts the route inside the query.
A pod that is an entire origin is https://example.org, which the same rule composes correctly. A
base handed to a client with a trailing slash names the same pod; the slash is not part of it, and a
client drops it before composing.
SPS-CORE-020 — A pod's base URL path MUST NOT contain a dot segment, a backslash, or a
percent-encoded octet.
This is what keeps the pod prefix a prefix. https://example.org/alice/.. satisfies
SPS-CORE-019 and composes {pod}/_system/conformance into a path a client
resolves to https://example.org/_system/conformance — outside the pod, and on some clients and not
on others.
The three forms are named rather than left to "a normalised path", because normalising is where this
goes wrong rather than where it is fixed. %2e%2e, %2E%2E, .%2e and a backslash separator each
hide the segment from a normaliser that runs before the parser a client actually dials with, and
each resolves the pod away. The list of spellings is not one a specification can close, so the forms
are refused whatever they would have resolved to — which is a test an implementation can run against
its own configuration, once, rather than against every client that will ever reach it.
SPS-CORE-008 — The path segments _system and .well-known, immediately below a pod base
URL, are reserved for the control plane. An implementation MUST NOT serve ordinary Linked Data
resources from {pod}/_system/… or {pod}/.well-known/…, and MUST NOT allow RDF writes to alter
control-plane state.
Two names, reserved for different reasons. _system is the one this specification chose, and
everything it defines below a pod base URL sits under it: the conformance route, the system layer,
the context registry, the authorization endpoints, and every module. No module takes a name beside
it.
.well-known is not chosen. RFC 8615 fixes the name and SPS-AUTH-045
obliges a pod to answer inside it, so it can neither move under _system nor stay addressable as
data. The whole segment is reserved rather than the one route below it, because a deployment that
later serves acme-challenge or security.txt from the same directory would otherwise mint
resource IRIs the LOD layer also claims.
A segment beginning . is not reserved as a class. Doing so would spend every such name a pod
might legitimately hold, to insure against a second fixed name that RFC 8615 exists to prevent: a
new registry entry goes below .well-known, not beside it.
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-dev",
"modules": [
{ "id": "https://schema.sempods.org/module/context-management", "version": "0.1-dev" },
{ "id": "https://schema.sempods.org/module/media", "version": "0.1-dev" },
{ "id": "https://schema.sempods.org/module/mcp", "version": "0.1-dev" }
]
}
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; a write without the required authority on its target context follows SPS-CORE-018 |
409 |
The request was well formed and authorized, and cannot be completed against the pod's current state — because that state changed underneath the request, because completing it would break an invariant this specification requires, or because the outcome would otherwise depend on state the caller may not see. A target that does not exist is 404 and never this |
500 |
Server error |
SPS-CORE-015 — On an operation that requires authentication, an implementation MUST return
401 with a Bearer challenge for a missing or rejected token, including invalid_token for a
rejected token. For a missing token, it SHOULD NOT include an error code or other error information,
following RFC 6750 §3.1.
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 — For an authenticated write on the direct HTTP surface, an implementation
MUST return 403 when the caller lacks the authority required on the target context, whether or not
that context exists. Other request failures MAY take precedence only when their applicability is
independent of target-context existence. The denial's headers and content MUST NOT distinguish an
existing context from a nonexistent one.
This includes RDF writes, media writes and context-management operations. An authorized operation
retains its specified missing-target behavior, including context creation and ensure-absent deletion.
MCP tools map the denial through SPS-MCP-018.
6. Standards profiled¶
Shared HTTP profile: RFC 9110 (HTTP Semantics) governs request and response semantics for the HTTP operations selected by core and advertised-module SPS requirements, within the boundary below.
SPS-CORE-021 — A standards profile MUST be interpreted within the actors, operations and
capabilities selected by the chapter's SPS requirements. Within that scope, the standard's
obligations MUST apply except where an SPS requirement specifies a deviation. A reference alone
MUST NOT require additional actors, endpoints, operations or capabilities.
Standards supply the behavior within that boundary; sempods conformance does not assert complete conformance to every named standard. The authoring rules define how a chapter identifies its standard, revision and profile scope. The table is an overview of chapter references, not an independent incorporation of entire standards.
This is the intended implementation shape, not a footnote: a sempods implementation should be able to use existing HTTP, RDF, SPARQL and OAuth/OIDC libraries, and focus its own code on the pod contract that composes them.
| Standard | Where |
|---|---|
| RFC 9110 (HTTP Semantics) | shared HTTP profile above — core and advertised modules |
| RFC 7396 (JSON Merge Patch) | lod-crud, resource PATCH |
| RFC 7232 (Conditional Requests) | lod-crud, ETag and If-Match |
RFC 8288 §3 (Web Linking), RFC 5023 §16.4 (edit relation) |
lod-crud, optional edit-link headers (SPS-CRUD-058) |
| 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 |