Skip to content

Contexts

Development snapshot — not a release

Core 0.1-dev · Source revision and matching artifacts.

A context is a named graph in a pod, and it is the permission boundary. Not the resource, not the property, not a role. One concept carries the whole access-control model, which is why this chapter comes before grants rather than inside it.

Creating and deleting contexts is a separate, optional surface: ../modules/context-management.md. What is here is what every pod has — what a context is, what its IRI may be, how a caller discovers the ones it may reach, and the boundary that keeps the control plane out of the data path. The naming rules are here rather than there for the reason that decides every such split: they bind however a context came into existence, and a pod provisioned outside that module still has contexts.

Status: this text decides, and can still change. See ../../GOVERNANCE.md.

Profiles: RDF 1.1 (named graphs). Registry representations use the graph vocabulary from SPARQL Service Description, 21 March 2013, §3, RDF Schema 1.1, 25 February 2014 and DCMI Terms, 20 January 2020. Only their term meanings are used; no service-description endpoint is required. RFC 9110 §§8.8, 12 and 13 govern negotiated registry representations, validators and conditional reads; RFC 9111 governs caching. Error codes are index.md §5.

1. The boundary

SPS-CTX-001 — Every statement stored in a pod MUST belong to exactly one context. There is no default context, no unassigned statement, and no statement in two contexts at once.

SPS-CTX-002 — A context MUST be identified by its full canonical IRI. An implementation MUST NOT expose an internal identifier in its place, and MUST NOT require a client to construct one.

SPS-CTX-003 — An implementation MUST NOT introduce a permission abstraction above or beside the context — no project, no folder, no role, no group. Every permission attaches to a context.

That last one is a requirement about what an implementation may add, which is unusual and deliberate. A role layer bolted on top would still be expressible in grants, and the two would disagree the first time somebody edited one of them.

2. Where context IRIs live

SPS-CTX-004 — Context IRIs MUST live under {pod}/_system/contexts/. A context is control-plane state, so it inherits the reserved area's protection rather than needing a rule of its own.

These bind however a context comes into existence. They are about what a context IRI is — which names are reserved, which shapes can be addressed again, what a path says about delegation — and a pod provisioned outside the context-management module holds itself to them exactly as one that creates contexts through a route does. What the module adds is the duty to enforce them at creation.

SPS-CTX-006 — A context delegated to someone MUST carry a delegation type as its first path segment, followed by the delegate's identifier. A context the pod owner keeps MUST NOT carry one, and is otherwise named freely.

{pod}/_system/contexts/contacts                    ← the owner's own area
{pod}/_system/contexts/projects/alpha              ← still the owner's; nesting is not a type
{pod}/_system/contexts/apps/notes/public           ← delegated to an app

A type means delegation, not ownership. apps/notes does not say "belongs to the notes app"; it says "the area it may work in".

SPS-CTX-007 — The pod owner's identity MUST NOT appear in a context path.

Pod ownership is transferable and may be held by an organisation. Put the owner's WebID in every path and a transfer turns each of those IRIs into a lie — and SPS-CTX-002 has already promised that the IRI is the identity, so it cannot be rewritten afterwards.

SPS-CTX-008 — The delegation type users is reserved for guest contexts. This version of the specification does not define the path shape below it, and an implementation MUST NOT mint one.

The intent is recorded so the name is not taken by something else: a guest's access is bound to them personally, so naming them in the path would say something that stays true — the deliberate exception to SPS-CTX-007, where the pod owner must never appear because ownership transfers.

What is not settled is how an identity URI occupies a path segment. A WebID such as https://id.example/alice#me carries a scheme, an empty segment and a fragment, every one of which SPS-CTX-013 refuses — so the shape cannot simply be written down, and specifying an encoding for it before any implementation needs one is how a specification acquires a rule nobody can check. It waits for a guest implementation.

SPS-CTX-010 — A context path MUST NOT contain the segment _system, anywhere in it.

It is held free so that a context IRI can later carry <context-iri>/_system/<operation> for per-context operations. Context names and operation names are both open sets; without a reserved separator they eventually collide, and a name banned after the fact would break pods already using it.

SPS-CTX-011 — The delegation types apps and users MUST be reserved as the first path segment. users is reserved without being implemented, so that the name cannot be taken by something else first.

SPS-CTX-013 — A context path MUST be one that can be addressed again. Specifically, it MUST NOT contain an empty segment, a relative segment (. or ..), a percent-encoded character, a fragment, or a query, and MUST be one an implementation's URI parser can parse.

Each of these produces a registry entry no route can reach. Percent-encoding is the one that looks harmless: both producers receive the path already decoded, so a stored foo%23bar is only ever asked for as foo#bar — which is refused — and foo%2Fbar as foo/bar, which finds a different entry. A fragment is worse than unreachable: <pod>/_system/contexts/foo#bar cannot be parsed back out of the grant string <context-iri>#<permission>, because …foo#bar#read has two candidate split points and the wrong one wins.

SPS-CTX-028 — A pod that does not provide the context-management module MUST have at least one registered context.

Without the module, contexts are provisioned outside this interface. The minimum keeps such a pod able to hold statements. With the module, a pod can start or become empty: the owner can create a context through PUT. Every stored statement still belongs to a context (SPS-CTX-001).

SPS-CTX-030 — A context MUST be private unless it was made public by an explicit choice. This holds however the context came into existence, including provisioning outside any interface this specification defines.

Pods are isolated by default (AGENTS.md §"Non-negotiable invariants"), and a context becomes readable without a grant because somebody said so — never because nobody said anything. The rule is stated here rather than only where a context is created, because the route that creates one is optional (../modules/context-management.md) and the guarantee is not: a pod without that module still has the context SPS-CTX-028 requires, and it arrived without anybody calling anything.

3. Discovery

SPS-CTX-021 — GET {pod}/_system/contexts MUST list the contexts visible to the caller, each with the permissions the caller effectively holds on it, and MUST separately name the contexts the caller may write to.

SPS-CTX-022 — This route is the authoritative client-visible view of effective context permissions. An implementation MUST NOT require a client to derive them from an access token.

Client guidance: take context IRIs from the catalogue or another pod response, and pass the full IRI in ?context=. This keeps client code independent of the namespace layout. Read effective permissions from the catalogue too; a token's scope claim is not a context catalogue.

SPS-CTX-024 — GET {pod}/_system/contexts/{path} MUST return what the registry holds for that context. Where the caller cannot see the context, the response MUST be 404 — never 403.

RDF representations

Let C be a Context IRI and L be {pod}/_system/contexts. The response describes registry state; resource reads remain the separate surface for ordinary statements about those IRIs.

SPS-CTX-031 — Successful GET responses for L and C MUST represent registry RDF using the canonical JSON-LD shape and content negotiation of SPS-CRUD-023, SPS-CRUD-024, SPS-CRUD-026 and SPS-CRUD-027. N-Quads MUST express the same RDF in its default graph. JSON-LD @type values MUST be arrays of absolute IRIs. These responses MUST NOT use the legacy Context or ContextList JSON envelopes.

SPS-CTX-032 — A Context description MUST identify C as sd:NamedGraph, have exactly one sd:name equal to C, and have exactly one Boolean sps:public expressing its registry public-read setting, using a native JSON Boolean value in JSON-LD. It MUST include the registry's label and description, when present, as rdfs:label and dcterms:description string literals; a known creation time as dcterms:created with datatype xsd:dateTime; and an rdfs:seeAlso IRI {pod}/_system/resources/{base64url(C)} using UTF-8 and SPS-CRUD-005. It MUST NOT include caller permissions, stored policy rules or ordinary statements about C. Clients MUST tolerate additional descriptive predicates.

Prefixes in this section have their published RDF meanings; sps: is https://schema.sempods.org/. rdfs:seeAlso asserts relevance, not data existence, equivalence or authority. Its construction needs no read of the ordinary data. Following it can return 404 even when the registry description is visible. Known timestamps are reported, not invented.

SPS-CTX-033 — A catalogue MUST identify L as sd:GraphCollection and link every registered Context visible to this caller with sd:namedGraph. Its caller permissions MUST be direct sps:readableContext, sps:writableContext and sps:manageableContext IRI relationships from L to those Contexts. The sets MUST be complete, with no absent Contexts or hypothetical descendants; empty sets have no RDF triples. The catalogue MUST NOT embed the individual Context descriptions. An empty catalogue MUST still return 200 with L's identity and collection type.

SPS-CTX-034 — The catalogue relationships MUST report the requesting client's current effective read, write and manage permissions under grants, including implied modes, delegation limits and applicable public read. Visibility MUST require at least one such mode. A server MUST NOT substitute a person's broader rights or treat a copied catalogue statement as authorization. Clients MUST treat the summary as response-relative information, not permission for a later request.

Write therefore appears in both readable and writable sets; manage appears in all three. Anonymous callers see public Contexts through the readable relationship; invalid supplied credentials remain 401 under the authentication contract. Membership does not require data in the named graph.

SPS-CTX-035 — Successful registry and catalogue GET responses MUST supply strong ETags and support If-None-Match under RFC 9110. The server MUST establish current authorization and the normal response status before evaluating a conditional read. Hidden and absent individual Contexts MUST both return indistinguishable 404 responses, including bodies, links and validators; a previously visible but now hidden Context MUST NOT return 304. Individual tags MUST describe the registry representation, independently of graph contents or ordinary assertions about C. A catalogue tag MUST change when its represented membership or permissions change.

SPS-CTX-036 — Registry responses, including errors, MUST NOT be reused across caller/client authorization contexts or without current authorization. Any stored response MUST preserve that isolation and representation freshness under RFC 9111. A request validator MUST NOT extend access or disclose a hidden Context.

Cache-Control: no-store on all registry responses is one sufficient strategy. Private storage with mandatory revalidation can also satisfy the guarantees when cache selection distinguishes the credential/authorization inputs and negotiated variants. These are alternatives; neither storage layout nor one specific cache-header combination is prescribed.

The registry representation cases illustrate descriptions, empty and permission-bearing catalogues, conditional reads and authority separation.

4. _system is protected, not undescribable

This is the distinction most likely to be lost, and losing it costs the model.

SPS-CTX-025 — Control-plane state MUST NOT be reachable through the data path. No RDF write — CRUD or otherwise — may create, alter or delete a context, a grant or a registration.

SPS-CTX-026 — A statement whose subject is a _system IRI MUST be treated as ordinary data. An implementation MUST NOT refuse it on the grounds of its subject.

A pod may hold <{pod}/_system/contexts/contacts> rdfs:label "Privat" exactly as it holds statements about did:web:bob.example or about another pod's resources. What a statement is about is independent of where it is stored; the writable context is the authorization boundary, and the subject IRI never is.

So: ask GET {pod}/_system/contexts/{path} what a context is, and the resource routes what anyone has said about it. Reading the second as the first is the error this section exists to prevent — no amount of RDF about a context IRI changes the context.