diff --git a/.github/workflows/spec-check.yml b/.github/workflows/spec-check.yml
index a60a17163..aaa13df20 100644
--- a/.github/workflows/spec-check.yml
+++ b/.github/workflows/spec-check.yml
@@ -10,6 +10,7 @@ on:
- "package.json"
- "scripts/spec-check.ts"
- "scripts/spec-date-check.ts"
+ - "scripts/check-owner-filter-grant-coupling.ts"
- "spec-*.md"
pull_request:
paths:
@@ -19,6 +20,7 @@ on:
- "package.json"
- "scripts/spec-check.ts"
- "scripts/spec-date-check.ts"
+ - "scripts/check-owner-filter-grant-coupling.ts"
- "spec-*.md"
workflow_dispatch: {}
@@ -72,3 +74,6 @@ jobs:
- name: Check all spec Date headers are not stale
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.event.before == '0000000000000000000000000000000000000000')
run: pnpm spec:dates
+
+ - name: Check owner-filter / client-grant coupling
+ run: pnpm spec:owner-filter-check
diff --git a/apps/site/spec-headers/spec-collection-profile.header.md b/apps/site/spec-headers/spec-collection-profile.header.md
index ab5fef79b..1b0393177 100644
--- a/apps/site/spec-headers/spec-collection-profile.header.md
+++ b/apps/site/spec-headers/spec-collection-profile.header.md
@@ -1,4 +1,8 @@
---
title: "Collection Profile"
-description: "Companion to the Personal Data Portability Protocol (PDPP) core spec."
+description: "Informative builder guidance for connector collection. Not a conformance requirement."
---
+
+
+ Builder guidance for connector collection. This document defines no conformance requirements. A connector conforms to PDPP by producing a source declaration valid under [Core Section 5](/specification#source-declaration) and serving its data through a resource server conforming to [Core Section 8](/specification#resource-server-interface). No particular collection method is required.
+
diff --git a/apps/site/spec-headers/spec-discovery-and-trust.header.md b/apps/site/spec-headers/spec-discovery-and-trust.header.md
index d127ab138..77acf7a5b 100644
--- a/apps/site/spec-headers/spec-discovery-and-trust.header.md
+++ b/apps/site/spec-headers/spec-discovery-and-trust.header.md
@@ -1,4 +1,8 @@
---
title: "Discovery and Trust"
-description: "How an authorization server discovers, validates, and accepts PDPP source declarations."
+description: "Informative implementation guidance for discovering and retrieving a source declaration. The normative requirements live in Core."
---
+
+
+ Implementation guidance. This document defines no conformance requirements. The requirements it used to carry now live in Core: equivocation in [Versioning and snapshots](/specification#versioning-and-snapshots), [Source acceptance](/specification#source-acceptance), and publisher attribution in [SourceDeclaration fields](/specification#source-declaration).
+
diff --git a/apps/site/src/lib/spec-nav-slugs.ts b/apps/site/src/lib/spec-nav-slugs.ts
index 45be36ccd..21561b9e7 100644
--- a/apps/site/src/lib/spec-nav-slugs.ts
+++ b/apps/site/src/lib/spec-nav-slugs.ts
@@ -19,9 +19,17 @@ export const docsRoute = "/specification";
// MAINTAINER_DOC_SLUGS.
export const maintainersRoute = "/maintainers";
-// The specification IS the page. The rail lists the normative core, its
-// companion specification, and the three extension profiles — the whole of what
-// the protocol normatively defines, and nothing else.
+// The specification IS the page. Core is the only normative document; the two
+// guidance documents and the three extension profiles follow it.
+//
+// Discovery and Trust and the Collection Profile are informative as of
+// 2026-09-02 — their load-bearing requirements moved into Core (equivocation,
+// source acceptance, publisher attribution). They stay listed here because both
+// are still worth reading and their URLs must not break; each page carries an
+// "Informative" callout from its header sidecar so a reader can tell them apart
+// from Core. PR #283 regroups them under a separate "Implementer guidance" rail
+// heading, which is the better presentation; this list is deliberately left
+// unrestructured so the two changes do not collide.
//
// Order is the reading order of the specification set, not alphabetical.
export const PRIMARY_SLUGS = [
diff --git a/spec-collection-profile.md b/spec-collection-profile.md
index 539641a29..d3f0fddd1 100644
--- a/spec-collection-profile.md
+++ b/spec-collection-profile.md
@@ -1,7 +1,7 @@
# PDPP Collection Profile v0.1.0
-Status: Companion profile draft
-Date: 2026-08-29
+Status: Informative. Builder guidance for connector collection. Not a conformance requirement.
+Date: 2026-09-02
Companion to the Personal Data Portability Protocol (PDPP) core spec.
diff --git a/spec-core.md b/spec-core.md
index e76b2866f..0e73e8f9f 100644
--- a/spec-core.md
+++ b/spec-core.md
@@ -1,7 +1,7 @@
# Personal Data Portability Protocol (PDPP) v0.1.0
Status: Normative draft
-Date: 2026-08-30
+Date: 2026-09-02
---
@@ -45,6 +45,7 @@ Sections 4-8 define the protocol surfaces that implementations evaluate independ
| [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) (Bearer Token) | PDPP transports both owner tokens and client tokens as RFC 6750 Bearer Tokens on the wire. The resource server distinguishes token kind via `pdpp_token_kind` in the introspection response, not by token syntax. |
| [RFC 7662](https://www.rfc-editor.org/rfc/rfc7662) (Token Introspection) | PDPP uses authenticated RFC 7662 token introspection where the authorization server and resource server are separated, so the resource server can resolve grant-bound tokens. Co-located deployments may use a local equivalent. |
| [OAuth 2.0 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591) (RFC 7591) | PDPP reuses the RFC 7591 client metadata vocabulary (`client_name`, `logo_uri`, `policy_uri`, and similar fields) for the consent display. A dynamic client registration endpoint is a deployment choice and is required only where deployments need it; Core functions without it. |
+| [Client ID Metadata Documents](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/) (CIMD, IETF OAuth WG draft) | A client identifier that is itself an `https` URL the authorization server fetches to obtain RFC 7591-shaped client metadata, with no prior registration handshake. Control of the URL's domain is the trust root. CIMD is how deployed MCP clients present themselves: the MCP authorization specification revision 2025-11-25 states that authorization servers and clients SHOULD support CIMD and MAY support RFC 7591 dynamic client registration, which is retained for backward compatibility. Core treats a validated CIMD document as one source of validated binding metadata (Section 6) and its verified domain as a trust signal; the fetch and validation obligations belong to the OAuth binding rather than to Core. |
| [SMART on FHIR](https://hl7.org/fhir/smart-app-launch/) | Follows the domain-profile-over-OAuth pattern PDPP adopts: OAuth handles authorization, and the profile adds a domain data model, consent semantics, and a conformance regime. SMART on FHIR reached ubiquity through regulatory adoption of SMART-on-FHIR-patterned API requirements (the ONC Cures Act rule). |
| [UK Open Banking](https://www.openbanking.org.uk/standards/) | Also follows the domain-profile-over-OAuth pattern PDPP adopts: OAuth handles authorization, and the profile adds a domain data model, consent semantics, and a conformance regime. UK Open Banking reached ubiquity through the CMA's Open Banking mandate for the largest UK banks. |
| [UMA 2.0](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html) (Kantara) | UMA is important prior art for PDPP's user-managed, standing, revocable access model, particularly where an outside party seeks access to user-controlled resources. PDPP's authorization protocol derives directly from OAuth 2.0 and RFC 9396. |
@@ -58,6 +59,30 @@ Sections 4-8 define the protocol surfaces that implementations evaluate independ
**Note:** The PDPP Collection Profile is one fulfillment mechanism. A conformance test suite for this specification is planned but is not defined in v0.1 (see Section 9).
+#### Why a profile rather than a new authorization framework {#profile-not-framework}
+
+PDPP is a data-portability profile, not a general authorization framework. It
+rides OAuth 2.0 and RFC 9396 because that is what is deployed, and it adds what
+neither UMA nor GNAP defines: a portable record model, purpose-bound durable
+grants, a declared collection and source layer, and a query surface with
+grant enforcement. UMA is prior art for the user-managed standing access model
+and GNAP is a candidate future foundation; the table above records both. A GNAP
+transport binding remains possible later without changing the record model, the
+grant, or the resource server interface, because none of those depend on the
+authorization protocol that produced the grant.
+
+#### Relationship to sector-specific consent regimes {#sector-regimes}
+
+PDPP is a floor, not a ceiling. A source already operating under a stricter or
+more specific consent regime — open banking, or a health regime built on FHIR —
+does not lose or weaken any obligation of that regime by also being reachable
+through PDPP. Where the two differ, the stricter requirement governs that
+source. Nothing in this specification authorizes a disclosure that the source's
+own regime forbids, and a PDPP grant is not a substitute for a consent that
+regime requires. Compatibility profiles that map a sector regime's consent
+vocabulary onto PDPP's are a possible future addition and are not defined in
+v0.1.
+
---
## 2. Terminology and Actors
@@ -165,6 +190,74 @@ flowchart TB
Source -.->|collected by| Runtime
```
+**Core defines no ingest path.** Core specifies the authorization server, the
+resource server, and what passes between them and a client. It does not specify
+how records reach the resource server, and conformance to Core does not depend
+on any particular answer. Examples A and B are illustrations, not a
+classification: a resource server holding data placed there by regulatory
+export, manual import, a provider's own write path, or a mechanism not yet
+described is equally within Core. The [PDPP Collection Profile](spec-collection-profile)
+describes one such mechanism, connector collection, and describes it for
+builders rather than as a conformance requirement.
+
+**On-behalf-of access is two ordinary grants, not one delegated grant.** A
+third arrangement neither example draws follows from the two above rather than
+extending them: a personal server that fetches from an upstream platform on the
+owner's behalf is an ordinary PDPP client of that platform, holding an ordinary
+grant the owner issued there. It is then an ordinary resource server to the
+apps the owner authorizes against it. The chain is two independent grants — the
+owner to the platform, and the owner to the personal server — each issued by the
+owner, each revocable by the owner on its own, and each an ordinary grant under
+this specification.
+
+PDPP therefore defines no delegated-grant, sub-grant, or grant-chaining
+construct, and none is needed to build this. The same shape is already how
+account aggregation works in deployed systems: an open banking aggregator holds
+the user's consent at each bank and its own consent from the user, rather than
+subdividing one authorization. Where a binding needs to exchange one credential
+for another across the two relationships, OAuth Token Exchange (RFC 8693) is the
+existing mechanism, and it is a concern of the binding rather than of Core.
+
+What Core does not yet state is what the second grant may say about the first:
+whether the onward grant can outlive, or exceed the scope of, the grant the
+records arrived under. Until that is settled, an implementation SHOULD NOT issue
+an onward grant broader in scope or longer in duration than the grant under
+which it obtained the records.
+
+**Onward transfer by a third party is forbidden by default.** The two-grant
+chain above is not a licence for a client to pass its access along. A client
+holding a grant MUST NOT transfer, re-delegate, or otherwise make that access
+available to another party; a second party obtains access only through a grant
+the owner issues to it. This is the ordinary posture of the mechanisms PDPP
+profiles — an OAuth access token is not transferable to a third party, open
+banking authorizations are issued per recipient, and GNAP routes a new party
+back through the authorization server rather than allowing hand-off — and it
+follows directly from purpose limitation: a grant records the purpose the owner
+approved for one named requester, and a transfer would carry the access without
+the approval that scoped it.
+
+The two arrangements are distinct, and the distinction is where the owner's own
+personal server sits. A personal server that holds the owner's data is not a
+third party receiving transferred access: the copy it holds is the owner's own
+data, and the grant it issues to an app is issued fresh by the owner at that
+server. No delegation construct is involved, because no access is being passed
+along — the owner is authorizing, twice, in two places.
+
+In the passthrough posture, where the personal server does not retain a copy
+but reads from the platform to fulfill a request as it arrives, the owner's
+grant to their own server at the platform covers fulfillment for the clients
+that owner authorizes at that server. This is the account-aggregation
+precedent: the user's consent at the bank covers the services that user chose,
+and the bank never sees the downstream app. It is not onward transfer, because
+the same owner authorized both relationships, and the platform-side grant is
+held by the owner's own server rather than handed to anyone.
+
+A different topology — the owner running their own authorization server in front
+of a platform's data, so that the platform holds the data but the owner's server
+decides who may read it — is not introduced by this specification. It is the
+arrangement UMA describes, it is not the arrangement above, and it is recorded
+as deferred rather than left ambiguous.
+
### Protocol layering
PDPP separates three concerns that other systems conflate:
@@ -473,7 +566,7 @@ Each source publishes a `SourceDeclaration` describing its identity, publisher,
| `protocol_version` | Version of the PDPP SourceDeclaration schema. This contract requires exactly `0.1.0`. |
| `source` | Exactly `{ kind, id }`. `kind` is `connector` or `provider_native`; `id` is the absolute URI authorization identity for the source's data surface. |
| `declaration_version` | Opaque, non-empty revision identifier for this source declaration. It is not the connector software version and has no implied ordering. |
-| `publisher.id` | Absolute URI identifying the declaration publisher. Discovery and trust policy determine how this attribution is authenticated. |
+| `publisher.id` | Absolute URI identifying the declaration publisher. It is an attribution claim, not an authenticated identity. The authorization server MUST treat `publisher.id` as authenticated only where an accepted channel or configured mapping binds that publisher to the declaration; absent that binding it MUST NOT support source acceptance, redirect policy, attribution, or any other trust decision. |
| `display.name` | Human-readable source name for consent UIs. It is display metadata, not source identity. |
| `selection_presets` | Optional preset selections. The authorization server expands a selected preset into explicit stream terms before issuing a grant. |
| `streams[].name` | Unique non-empty stream name, source-local. `*` is request-only and is not a declaration stream name. |
@@ -516,7 +609,7 @@ Streams MAY include a `display` object with human-readable metadata for the cons
| `display.label` | string | Short human-readable name shown in the consent card (e.g., "Who you follow"). If absent, the AS SHOULD display `streams[].description` or fall back to the stream name. |
| `display.detail` | string | Consent-oriented description of what data is included and, where relevant, what is excluded (e.g., "Usernames and account IDs of accounts you follow. No DMs, profile details, or follower lists."). If absent, the AS MAY generate a description from the stream schema, or display no detail. |
-**Authorship principle:** `display.label` and `display.detail` describe the data itself, not the requester's purpose. They are attributed to the accepted declaration publisher. The requesting client MUST NOT override or supplement these descriptions in the selection request. The authorization server's discovery and trust policy determines whether publisher attribution is authenticated.
+**Authorship principle:** `display.label` and `display.detail` describe the data itself, not the requester's purpose. They are attributed to the accepted declaration publisher. The requesting client MUST NOT override or supplement these descriptions in the selection request. Publisher attribution is authenticated only under the conditions in [SourceDeclaration fields](#source-declaration).
```json
{
@@ -601,6 +694,16 @@ the user approved. The Resource Server enforces the resolved grant without a
current declaration lookup. Current serving metadata may only route, describe
current capabilities, narrow, or reject.
+An accepted declaration revision is keyed by the accepted source authority,
+`source.id`, and the opaque `declaration_version`. Once the authorization
+server has accepted a revision under that key, later content retrieved under
+the same key MUST compare equal as parsed JSON. Different parsed content under
+an accepted key is equivocation: the authorization server MUST reject the later
+content and retain the previously accepted revision. An implementation MAY use
+an internal content fingerprint to accelerate this comparison; the fingerprint
+algorithm is not a protocol identity and need not be portable between
+implementations.
+
---
## 6. Selection Request {#selection-request}
@@ -665,16 +768,19 @@ Inside `client_display`, PDPP drops the `client_` prefix from `client_name` and
`client_display` is entity-scoped: it describes the client, not a specific authorization request. It appears at the top level of the authorization request, outside `authorization_details`.
-`client_display` is an inline carrier, not necessarily the AS's final rendered identity record. The AS MAY replace or augment inline values with locally registered metadata, validated software-statement metadata, or trust-registry metadata.
+`client_display` is an inline carrier, not necessarily the AS's final rendered identity record. The AS MAY replace or augment inline values with locally registered metadata, validated binding metadata, validated software-statement metadata, or trust-registry metadata.
+
+**Validated binding metadata** is client metadata the AS obtained and verified through the mechanism that binds the client to the authorization protocol in use, rather than metadata the client asserted inline in this request. Under the OAuth binding it is the metadata a client ID metadata document or a dynamic registration record supplies, after the binding's own validation succeeded. Core does not define how a binding validates it; Core defines only that validated binding metadata outranks inline `client_display`, because the AS checked it and the client did not merely assert it.
**Metadata resolution and rendering obligations:**
-1. The AS MUST resolve requester identity metadata from the best available source. Source precedence is local registration or trust-registry metadata, then validated software-statement metadata if supported, then inline `client_display`, then `client_id` fallback.
+1. The AS MUST resolve requester identity metadata from the best available source. Source precedence is local registration or trust-registry metadata, then validated software-statement metadata if supported, then validated binding metadata, then inline `client_display`, then `client_id` fallback.
2. If the resolved metadata contains a display name, the AS MUST display it to the user during consent. If no display name is available, the AS MUST display `client_id` as the requester identity.
3. If the resolved metadata contains `policy_uri` or `tos_uri`, the AS MAY display them as secondary links or disclosures.
-4. If the server has a positive trust signal for the client (e.g., domain verification, trust registry membership), it MUST render that status distinctly (e.g., a "verified" badge). If it has no positive trust signal, it MUST treat the client as unverified and SHOULD display an "unverified app" indicator.
-5. The AS MUST treat `logo_uri` as untrusted content until it has been accepted under local policy. It MUST NOT fetch and render a client-supplied remote logo in the consent UI unless the client is verified or the asset has been proxied, cached, and approved under local policy. For unverified clients, the AS SHOULD generate a monogram from the resolved display name.
-6. If neither resolved metadata nor inline `client_display` provides a display name, the consent UI SHOULD clearly indicate that the client has not provided display metadata.
+4. If the server has a positive trust signal for the client (e.g., verified domain control, trust registry membership), it MUST render that status distinctly (e.g., a "verified" badge). If it has no positive trust signal, it MUST treat the client as unverified and SHOULD display an "unverified app" indicator.
+5. **Domain control as a trust signal.** Where the binding identifies a client by a URL it controls, and the AS both retrieved that client's metadata from that URL over HTTPS and confirmed the retrieved document identifies the same client, the AS has verified that the client controls that domain. The AS MAY treat verified domain control as a positive trust signal under obligation 4, and when it does it MUST name the verified domain rather than assert an unqualified verification (for example "Verified domain: example.com", not "Verified app"). Domain control establishes only that the operator of that domain published this client's metadata. It is not an assertion about the client's conduct, its data practices, or any review by the AS operator, and the AS MUST NOT present it as one.
+6. The AS MUST treat `logo_uri` as untrusted content until it has been accepted under local policy. It MUST NOT fetch and render a client-supplied remote logo in the consent UI unless the client is verified or the asset has been proxied, cached, and approved under local policy. For unverified clients, the AS SHOULD generate a monogram from the resolved display name.
+7. If neither resolved metadata nor inline `client_display` provides a display name, the consent UI SHOULD clearly indicate that the client has not provided display metadata.
### Pre-registered public client discovery {#pre-registered-public-clients}
@@ -742,6 +848,15 @@ provenance from the accepted declaration and records it in consent evidence
and any issued grant. The OAuth/RAR binding returns RFC 9396
`invalid_authorization_details` for invalid authorization details.
+#### Source acceptance {#source-acceptance}
+
+A selection request SHALL name only a source the authorization server has
+already accepted. A client SHALL NOT select a source authority, declaration
+location, or declaration revision as part of an authorization request. How a
+source becomes accepted — owner or operator onboarding, an installed catalog,
+a registry entry, or explicit local provisioning — is a deployment concern and
+is out of scope for this document.
+
#### AI training consent {#ai-training-consent}
The AS MUST obtain explicit affirmative user consent before issuing any grant with `purpose_code` value `https://pdpp.dev/purpose/ai_training`. This is the sole purpose code with a mandatory consent requirement at the protocol level.
@@ -1059,11 +1174,11 @@ The resource server stores records and serves them to clients filtered by grants
On every request, the resource server:
1. Resolves the access token through authenticated RFC 7662 introspection or a local equivalent for co-located deployments. Positive introspection results MUST NOT be cached longer than `min(token_exp, 60 seconds)`.
-2. Verifies that the grant is active (`active: true` in the introspection response).
-3. Verifies that the requested stream appears in the grant's `streams` list.
-4. Selects records only from the explicitly granted `instance_ids` and verifies that the request falls within the grant's `time_constraint`, `fields`, and `resources` constraints.
-5. If all checks pass, returns records filtered accordingly.
-6. If any check fails, returns a structured error (see Errors below).
+2. Determines `pdpp_token_kind` from the introspection response, then branches:
+ - **Client:** requires an active resolved authorization context (`active: true`, a resolved grant). Verifies that the requested stream appears in the grant's `streams` list. Selects records only from the explicitly granted `instance_ids` and enforces the grant's `time_constraint`, `fields`, and `resources` constraints.
+ - **Owner:** enforces subject, source, connection, and operation scope derived from the introspection response. Does not require or synthesize a client grant — an owner token carries none.
+3. If all checks pass, returns records filtered accordingly.
+4. If any check fails, returns a structured error (see Errors below).
For owner-token current-capability reads, the effective filter is the permitted
owner request filter alone: an owner token carries no grant, so there is no grant
@@ -1123,7 +1238,7 @@ GET /v1/streams
Authorization: Bearer
```
-Returns the streams available under the current grant with record counts.
+Returns streams with record counts, scoped by token kind: for a client token, the streams present in the resolved authorization context; for an owner token, the streams in the subject-scoped data store the owner token is scoped to.
**Response:**
```json
@@ -1163,7 +1278,12 @@ GET /v1/streams/{stream}
Authorization: Bearer
```
-Returns full source stream metadata. A client-token caller may fetch metadata only for a stream present in its resolved authorization context. An owner-token caller may fetch metadata for streams in the subject's data store the owner token is scoped to. Once access is authorized, the metadata document is returned whole rather than field-projected by the grant. Response:
+A client-token caller may fetch metadata only for a stream present in its resolved authorization context. An owner-token caller may fetch metadata for streams in the subject's data store the owner token is scoped to. Once access is authorized, the response body is actor-specific:
+
+- **Owner token:** the metadata document is returned whole — full current schema, query capabilities, views, and relationships — rather than field-projected by any grant. An owner token carries no grant, so there is nothing to project against.
+- **Client token:** the response is a closed projection derived from the resolved authorization context: only the granted stream's explicitly granted fields, and only immutable/frozen grant facts. Current query, view, relationship, filter, expansion, and aggregation capabilities MUST NOT appear unless that capability is explicitly part of a future frozen grant vocabulary. Current metadata MAY report availability/freshness or reject an unavailable operation, but MUST NOT make the grant appear broader or semantically different than what was issued. A source declaration change made after the grant was issued (e.g., a new field) MUST NOT become visible through this endpoint for that grant.
+
+**Owner-token response** (full current metadata):
```json
{
@@ -1199,6 +1319,37 @@ Returns full source stream metadata. A client-token caller may fetch metadata on
}
```
+**Client-token response** (closed projection of a grant frozen to fields `id`, `name`, `source_updated_at`; no range filters, views, or relationships were granted):
+
+```json
+{
+ "object": "stream_metadata",
+ "name": "top_artists",
+ "schema": {
+ "properties": {
+ "id": { },
+ "name": { },
+ "source_updated_at": { }
+ }
+ },
+ "primary_key": ["id"],
+ "cursor_field": "source_updated_at",
+ "consent_time_field": "source_updated_at",
+ "selection": {
+ "fields": true,
+ "resources": false
+ },
+ "query": { },
+ "freshness": {
+ "captured_at": "2026-04-06T15:01:00Z",
+ "status": "current",
+ "last_attempted_at": "2026-04-06T15:01:00Z"
+ },
+ "views": [],
+ "relationships": []
+}
+```
+
#### Freshness metadata
A resource server MAY attach a `freshness` object to stream listings, stream metadata, and record-list responses.
@@ -1500,6 +1651,8 @@ A conformant Core RS:
11. Implements the `PDPP-Version` header negotiation.
12. Scopes owner token access to a single subject's data store; derives `subject_id` from introspection response.
13. SHOULD support owner-authenticated access to the `/v1/streams/{stream}/records` query endpoints without a client grant, allowing the data subject to export their own data directly (self-export).
+14. For owner-token stream-metadata reads, returns the full current stream metadata within the owner's subject/source/connection scope, including current query, view, and relationship capabilities.
+15. For client-token stream-metadata reads, returns only a projection derived from the resolved authorization context: the granted stream and its explicitly granted fields, and immutable/frozen grant facts. MUST NOT include current view, relationship, filter, expansion, or aggregation capability unless that capability is explicitly part of a future frozen grant vocabulary, and MUST NOT surface a source-declaration change made after grant issuance.
Collection Resource Server, runtime, and connector conformance are separate
claims defined in the [PDPP Collection Profile](spec-collection-profile).
@@ -1584,7 +1737,7 @@ In the Collection Profile, connectors receive credentials via the INTERACTION ch
| Role | Responsibilities |
|------|----------------|
| **Authorization Server** | Validates purpose-code syntax and local policy; authenticates user; preserves semantic distinctions on the consent surface; validates stream/field/view/resource-id shape at grant issuance; resolves views to field lists; issues access tokens; maintains grant lifecycle. |
-| **Resource Server** | Validates token via introspection; enforces stream, instance, fields, frozen time constraints, and resources on every request; never reinterprets authorization from a current declaration; scopes owner access to one subject. |
+| **Resource Server** | Validates token via introspection; for client requests, enforces stream, instance, fields, frozen time constraints, and resources against the resolved grant; for owner requests, enforces subject/source/connection/operation scope without requiring a grant; never reinterprets authorization from a current declaration. |
| **Client** | Submits well-formed selection requests; uses access tokens; terminates on revocation; honors retention commitments. |
### Revocation {#revocation}
@@ -1659,6 +1812,8 @@ The `retention` field is a structured policy declaration and policy commitment b
| Point-in-time reconstruction | Deferred (reconstructing full state at a past timestamp) |
| Canonical view naming vocabulary | Deferred; will be informed by implementation experience |
| Predicate-based grant scoping | Deferred; see spec-deferred for subset template design direction |
+| Derivative data | Deferred; v0.1 authorizes reads of declared streams and is silent on the output of compute over them. Silence is not permission. See spec-deferred |
+| Cross-source category grants | Deferred; grants bind to a single `source.id` in v0.1. See spec-deferred |
| Real-time streaming | Different spec needed |
### Predicate-based grant scoping {#predicate-based-grant-scoping}
diff --git a/spec-data-query-api.md b/spec-data-query-api.md
index 736643416..877afe2c4 100644
--- a/spec-data-query-api.md
+++ b/spec-data-query-api.md
@@ -1,7 +1,7 @@
# PDPP Data Query API v0.1.0
Status: Superseded
-Date: 2026-08-29 (original); superseded 2026-04-12
+Date: 2026-08-29 (last corrected); superseded 2026-04-12
> **Note:** This document was written before Core Section 8 was expanded to normatively define the RS query interface. [Core Section 8 (Resource Server Interface)](spec-core.md#resource-server-interface) is authoritative for current query syntax, including the canonical `filter[{field}]` / `filter[{field}][op]` shapes, declaration-driven `query.range_filters` and `query.expand`, and the `limit_clamped` warning behavior. This file is retained for historical reference and should not be used for implementation. Where it disagrees with Core Section 8, Core Section 8 prevails — in particular, the `limit`, `expand_limit`, search, and error-table sections below predate the current contract and are stale.
diff --git a/spec-deferred.md b/spec-deferred.md
index 2cfa6ada4..ddea2bf33 100644
--- a/spec-deferred.md
+++ b/spec-deferred.md
@@ -1,7 +1,7 @@
# Spec v2: Deferred Concerns
Status: Informative
-Date: 2026-08-14 (revised)
+Date: 2026-09-02 (revised)
Issues identified during design and review that are intentionally out of scope for v0.1. Each item is named precisely so it can be referenced from the core spec and tracked for future versions.
@@ -153,6 +153,88 @@ The main still-live issue from that pass is not terminology but default posture:
This is a design philosophy question, not just a technical one.
+### Derivative Data
+
+_Newly deferred (2026-09-02)._
+
+**Description:** Data produced by computing over data a grant already covers — an embedding, a summary, a classification, a model fine-tuned on the records, an inference about the owner. PDPP today authorizes reads of declared streams. It says nothing about what a client may do with the output of compute over what it read, and nothing about whether that output is itself owner data requiring its own grant.
+
+**Why it is open:** Answering it means deciding, at minimum: whether derivative data is in scope for PDPP at all; whether producing it needs its own grant or is implied by the read; whether it is a distinct semantic class or a source in its own right; how revocation of the underlying grant reaches an artifact already derived; and whether the answer differs for a reversible transformation (an index) and an irreversible one (trained weights). `purpose_code`, and the explicit protocol-level consent rule for `ai_training`, are the only places v0.1 touches this, and they constrain declared purpose, not derived artifacts.
+
+**v0.1 posture:** Out of scope. Nothing in v0.1 asserts that derivative data is authorized, and nothing asserts it is not. The specification is silent, and silence should not be read as permission.
+
+**Design constraint for a future version:** A derivative-data model should be addable without redefining the existing grant, most plausibly as an additional semantic class or an additional grant kind rather than as a change to `StreamGrant`.
+
+### Cross-Source Category Grants
+
+_Newly deferred (2026-09-02)._
+
+**Description:** Granting by category across sources — "my health data", not five named providers. PDPP has the shape of this within one source: a named view is a source-declared subset a user can consent to by name. The open question is the same idea one level up, spanning sources.
+
+**Why it is open:** It needs a shared category vocabulary that is meaningful across arbitrary sources, and a rule for what happens when a source is added to the owner's server after the grant was issued — whether it joins an existing category grant automatically, and if so, how a user consented to something that did not exist yet. That second question is the harder one: it is the same widening problem the specification forbids elsewhere (a field added after a grant is issued MUST NOT become visible to that grant). The related but distinct problem of naming views consistently across connectors is tracked separately as [Canonical View Naming Vocabulary](#canonical-view-naming-vocabulary).
+
+**Candidate prior art, not yet evaluated:** W3C Verifiable Credentials, ODRL, DCAT, schema.org category vocabularies, FHIR resource categories, Solid type indexes, and ToIP's Trust Registry Query Protocol were all named as possibilities. None has been assessed for fit. The evaluation should judge each on whether it supplies a category vocabulary that survives arbitrary sources, not on general standing.
+
+**v0.1 posture:** Out of scope. Grants bind to a single `source.id`. A user wanting a category across five providers issues five grants today.
+
+### Subgrants
+
+_Newly deferred (2026-09-02; raised in the 2026-08-19 working session)._
+
+**Description:** Whether a client holding a grant may pass a narrower piece of that access on to another party, without the owner issuing a second grant directly.
+
+**Why it is open:** A grant is immutable and bound to one `client_id`. A subgrant needs a rule for what the owner sees and approves, whether the subgrantee is visible to the owner at all, how revocation of the parent reaches the child, and whether a subgrant can outlive its parent. Answering it badly produces exactly the re-delegation surface the grant model exists to prevent.
+
+**v0.1 posture: forbidden by default, not merely unspecified.** Core Section 3 states the rule: a client MUST NOT transfer or re-delegate its access, and a second party obtains access only through a grant the owner issues to it. What remains open is whether a future version should define a supervised mechanism that relaxes this — with owner visibility, bounded scope, and revocation that reaches the subgrantee — not whether transfer is permitted today.
+
+**Not this:** the owner's own personal server is not a subgrant case, and neither is the passthrough posture. Both are covered in Core Section 3, and both involve the same owner authorizing two relationships rather than one party passing access to another. A future subgrant design should be careful not to re-describe those as delegation.
+
+### Change of client ownership and undisclosed sub-processing
+
+_Newly deferred (2026-09-02; raised in the 2026-08-19 working session)._
+
+**Description:** An owner grants access to a client on assumptions about that specific relationship. If the client is acquired, changes legal structure, or outsources processing to a third party the owner never saw, those assumptions no longer hold, and the grant does not know it.
+
+**Why it is open:** The protocol has no representation of who the client is as a legal entity, and no event by which a change of control could reach an issued grant. One suggestion from the session was to require clients to declare ownership type. Whether that belongs in the protocol, in the conformance programme, or nowhere is undecided, as is whether a change of control should force revocation, force re-consent, or merely be disclosed.
+
+**Related:** ISO MyTerms was raised in the same discussion as prior art for owner-specified terms under which a first party holds data. It has not been evaluated for fit, and whether owner-specified terms belong in Core, in a companion RFC with the authorization server holding templates, or outside PDPP entirely is itself open.
+
+**v0.1 posture:** Out of scope. `client_claims` carries client-authored, explicitly non-enforceable statements about a specific request; it is not an ownership record and must not be read as one.
+
+### Bulk export as a distinct access path
+
+_Newly deferred (2026-09-02; raised in the 2026-08-19 working session)._
+
+**Description:** PDPP has focused on continuous synchronization rather than a Takeout-style bulk export. The question raised was whether export deserves its own defined access path rather than being left as a special case of query.
+
+**Why it is open:** Core already has owner-token self-export as a SHOULD-level resource server conformance item (Section 9), so the primitive exists. What does not exist is a defined export path for a client under a grant, or an answer to whether a bulk export differs from paginating the same query to its end in anything other than convenience.
+
+**v0.1 posture:** Owner self-export is in scope at SHOULD level. Client-side bulk export is not defined and is served, if at all, by ordinary paginated query under the grant.
+
+### Trust Registry Query Protocol (TRQP) as the register interface
+
+_Newly deferred (2026-09-02)._
+
+**Description:** ToIP's Trust Registry Query Protocol is a read-only interface for asking "is X authorized to do Y in this ecosystem", and registries using it can recognize one another. That is the shape of the question an authorization server asks about a source declaration under Section 6, and the shape of what the PDP-Connect register answers.
+
+**Why it is open:** TRQP is in public review and has no test suite, so it cannot be a dated commitment. The design question for the specification is narrower than adoption: whether Core should describe the register lookup in terms general enough that a TRQP endpoint is one conforming implementation of it, rather than describing a PDP-Connect-specific interface a TRQP endpoint would then have to be adapted to.
+
+**v0.1 posture:** Core does not define a register interface, so nothing in v0.1 forecloses this. Trust registry and connector certification are already listed as deferred in Core Section 12.
+
+**Design constraint for a future version:** If the register is exposed as a TRQP endpoint from phase 2, recognition of another registry becomes a TRQP query rather than a bilateral arrangement, and withdrawal of recognition propagates through the same query path. Any interface Core describes in the meantime should not assume a single register.
+
+**Where it would live:** In a profile or binding, not in Core's normative text. Core states what an authorization server must establish about a source declaration before accepting it; it does not name the protocol by which the server asks a third party. Naming TRQP in Core would bind the specification to a document still in public review and without a test suite. Naming it in a binding keeps the commitment real without dating it, and matches how this specification treats other binding-level mechanisms.
+
+### Owner-operated authorization server over a platform's data (UMA-style)
+
+_Newly deferred (2026-09-02)._
+
+**Description:** A topology in which the platform continues to hold the owner's data, but the owner runs their own authorization server in front of it, so that the owner's server — not the platform's — decides who may read what. This is the arrangement UMA describes, and it is distinct from the on-behalf-of chain in Core Section 3, where a personal server holds an ordinary grant against the platform and the two relationships stay separate.
+
+**Why it is open:** It requires the platform's resource server to accept grants issued by an authorization server the platform does not operate and did not choose, which is a trust relationship PDPP does not currently define and platforms have no incentive to accept unilaterally. It also needs a rule for what happens when the platform's own policy and the owner's authorization server disagree.
+
+**v0.1 posture:** Not introduced. Core's Section 3 topologies are source-native fulfillment, personal-server fulfillment, and the on-behalf-of chain that follows from them. This is a fourth arrangement, and its absence from Core is deliberate rather than an oversight.
+
---
## Decided (recorded for history)
diff --git a/spec-discovery-and-trust.md b/spec-discovery-and-trust.md
index c28647949..cf3c4ef60 100644
--- a/spec-discovery-and-trust.md
+++ b/spec-discovery-and-trust.md
@@ -1,7 +1,7 @@
# PDPP Source Declaration Discovery and Trust v0.1.0
-Status: Companion specification draft
-Date: 2026-08-14
+Status: Informative. Implementation guidance. The load-bearing requirements moved into Core on 2026-09-02: equivocation into Core §5 Versioning and snapshots, source acceptance into Core §6 Source acceptance, and publisher attribution into Core §5 SourceDeclaration fields.
+Date: 2026-09-02
---