B1. PDPP binding
Status: normative draft. Binds PDPP Core v0.1.0 at commit 8f79a48 of PDP-Connect/pdpp, recorded with its file hashes in pdpp.lock.json and described in the PDPP adoption record. Document version 1.0-draft-1.
This binding states how an Open Personal Context read grant is issued, introspected and exercised as a PDPP Core grant, and what Open Personal Context adds that PDPP Core cannot carry. A6 remains the authorisation contract: where this binding and A6 differ, A6 wins and the difference is a defect in this binding. Open Personal Context Core stays meaningful without PDPP: this binding is the normative 1.0 grant binding, not a dependency of the model.
Section numbers below refer to PDPP Core v0.1.0, spec-core.md, at commit 8f79a48. Conformance items are cited by role and item number from its section 9.
B1.1 Scope
This binding covers read grants (kind: "read") only. A contribution grant (kind: "contribute") has no PDPP Core equivalent: PDPP Core section 12 leaves ingest to the informative Collection Profile. Contribution grants are issued and exercised through B3 and B4 only, never as a PDPP grant.
The mapping in this binding is independent of how the authorisation server is built. It applies however a provider builds its authorisation server. The Autonym reference server implements PDPP Core natively.
Every wire value this binding introduces (the B1.5 detail, the purpose codes, the stream names and record shape, the identifier derivations, the autonym_lifecycle token kind, the Autonym-Read-Id header and a pdpp receipt surface) is draft text until it is defined in the schema package and its artefacts are generated.
B1.2 The source and its declaration
A provider that offers this binding acts as one provider_native source (section 6, Source kinds). The PDPP endpoints form their own protected resource with their own identifier, distinct from the B4 resource; source.id MUST equal that identifier (section 5, Declaration acceptance).
The provider publishes one SourceDeclaration (section 5) generated from the core vocabulary, never written by hand:
protocol_versionis0.1.0.declaration_versionnames the vocabulary version and the edition of this binding, so a grant records exactly which vocabulary its streams were resolved against. It is opaque to PDPP.streams[]holds the streams of B1.3. Theirdisplay.labelanddisplay.detaildescribe the data and never the requester's purpose. The vocabulary has key labels but no domain labels, so this display text is new product copy and needs review before it is published.- No stream declares
consent_time_field, sotime_rangeis rejected for every stream (section 6, Note ontime_range). Temporal selection stays an Open Personal Context read parameter (A4.3, A5.2). - No stream supports
resourcesselection, and no stream declares views, relationships,expand,searchoraggregations. - No stream declares a
blob_reffield.
B1.3 Levels as statically declared derived streams
PDPP Core has no disclosure level. This binding expresses each level as a separate, statically declared derived stream, as the non-normative "Derived subset streams" paragraph of section 12 allows. The mapping is Open Personal Context's, not PDPP's.
For every domain <domain> in the core vocabulary and every level L<n> at which at least one of the domain's keys has a vocabulary rendering, the declaration contains:
<domain>.l<n>: the domain rendered at or belowL<n>by the A5 compiler;<domain>.l<n>.declared: the same, restricted to exactly confirmed declared entries (A5.2).
Domain names may contain dots, so a stream name is read from the right: an optional .declared suffix, then the level, then the domain.
Each stream has semantics: "mutable_state" and exactly one record for each grant that includes it. The record is always returned, even when no key is currently disclosable, so its presence reveals nothing. Its fields are:
id, the primary key and the only schema-required field: a record identifier derived as in B1.4 (Identifiers);- one top-level field per vocabulary key of the domain, whose value is an array of the compiled entries for that key, in A5.2 order. Each element has the members of a disclosure.json entry except
domainandkey:id(the client-boundrent_alias),version,level,value_type,value,context_kind,safety_critical,provenance(kind,confirmed) andstale. A key with several eligible entries, such as several allergies, has one element for each.
A key with no eligible entry, or no rendering at or below the stream's level, is absent from the record. Absence never reveals why.
Stream names MUST NOT be generated per request (section 12). A new vocabulary key or level appears only in a new declaration revision, and never widens an issued grant (section 5, Versioning and snapshots).
B1.4 Grant mapping
An Open Personal Context read grant issued through this binding is one PDPP grant (section 7) for the provider's source, plus the Open Personal Context detail of B1.5. Every property of grant.json is mapped below. A property marked "B1.5" travels only in the Open Personal Context detail.
| Open Personal Context property | PDPP Core v0.1.0 representation |
|---|---|
id |
grant_id, and introspection grant_id. |
revision |
B1.5. A PDPP grant is immutable and PDPP Core v0.1.0 has no narrowing (section 7, Grant narrowing). A change of authority is made by revoking the grant and issuing a new one with a new id (Revoke and reissue, below). |
handle |
subject.id, and introspection subject_id. The pairwise handle (A3.2) is an opaque string unique within the issuer's namespace, which is all section 7 requires. |
client_id |
client.client_id, and introspection client_id. client.client_display holds the requester metadata the authorisation server resolved (section 6, Client display metadata). |
purpose |
purpose_code and purpose_description, below. |
purpose.category |
purpose_code, the absolute URI urn:autonym:purpose:<category>. The authorisation server accepts only the ten Open Personal Context purposes and rejects any other code by local policy, which Authorization Server item 6 permits. |
purpose.text |
purpose_description. A request without it is rejected, because A6.3 requires a plain-language purpose; it is always displayed. |
issued_at |
issued_at. |
expires_at |
expires_at, always present under this binding although PDPP makes it optional. |
status |
Grant lifecycle tracked by the authorisation server (section 7), reported as introspection active and as the grant_revoked and grant_expired errors (section 8, Errors). Never a field of the PDPP grant. |
kind |
read is issued as a PDPP grant. contribute is never issued as a PDPP grant (B1.1). |
read |
streams, one StreamGrant per entry, resolved as below. |
read[].domain |
The domain part of the StreamGrant name. The wildcard *, allowed only at L0 (A6.2), is expanded at issuance to one L0 StreamGrant for each core vocabulary domain that has no explicit entry in the grant, because explicit domain ceilings take precedence over the wildcard (A6.2, Authorization Server item 4). PDPP's own * stream wildcard is not used, because it would also select every level. |
read[].keys |
StreamGrant fields: the listed keys plus id. null (all keys) is expanded at issuance to every key of the domain with a rendering at or below the granted level, and frozen (Authorization Server items 4 and 13). |
read[].max_level |
The level part of the StreamGrant name: the highest declared level of the domain at or below max_level. If the domain has no declared level at or below it, the entry is refused at issuance. The compiler renders each key at the highest vocabulary-supported level at or below the ceiling (A5.2). |
declared_only |
The .declared stream variant of B1.3 when true, so the restriction is visible on the PDPP consent surface and enforced by stream membership. Also in B1.5. |
min_confidence |
B1.5. |
max_reads |
B1.5. access_mode is single_use when max_reads is 1 and continuous otherwise; a request whose access_mode disagrees with its max_reads is rejected. |
reads_used |
Not carried. A counter in the provider's stored grant (A6.2), never part of a PDPP grant or introspection response. |
persist_until |
retention with on_expiry: "delete". max_duration is the ISO 8601 duration from issued_at to persist_until, or PT5M when persist_until is null, the longest validity an A5 envelope can carry. The exact instant is in B1.5 and governs (B1.11, B1-D7). |
allow_delegation |
B1.5. A delegated child grant (A6.4) is a separate PDPP grant with its own grant_id and its own identifiers. |
onward_share |
B1.5. Always false in version 1; the consent surface renders it as server-generated text. |
contribute |
Not carried (B1.1). |
contribute[].domain |
Not carried (B1.1). |
contribute[].keys |
Not carried (B1.1). |
max_events |
Not carried (B1.1). |
events_used |
Not carried (B1.1). |
allow_confirmation |
Not carried (B1.1). |
stream_id |
Not carried (B1.1). It names an Open Personal Context contribution event stream and is unrelated to PDPP streams. |
PDPP grant fields with no Open Personal Context counterpart take fixed values: version is 0.1.0; source is { kind: "provider_native", id } from B1.2; source_declaration.version is the declaration revision the grant was resolved against; selection_preset is absent; time_constraint and resources are absent; each StreamGrant's instance_ids holds exactly one instance handle, resolved without asking the person because there is only one eligible instance (Authorization Server item 15).
Identifiers. The record id and the instance handle are derived like an A3.2 handle local part: HMAC-SHA256 under the record partition's handle key, over RFC 8785 canonical JSON of the client ID, the handle rotation, the grant id and the stream name, with the purpose values autonym.pdpp.record.v1 and autonym.pdpp.instance.v1 respectively. Every grant, including every delegated child, therefore has its own values, and two recipients never share one. A record partition identifier or any other internal identifier is never used.
Revoke and reissue. When a grant issued through this binding is narrowed, the new grant inherits the old grant's reads_used, the read budget reserved by its delegated children and its expiry, so a reissue never restores spent authority. Each child is reissued under the new grant within its limits, or revoked. The change is logged as one authority change, and A10 applies to it on the same terms as a revision change through B3 or B4.
B1.5 The Open Personal Context detail
Beside the https://pdpp.dev/data-access detail, the authorisation request and the issued authorisation carry one RFC 9396 authorization_details entry of type urn:autonym:authorization-details:read. It holds what PDPP Core cannot express: min_confidence, max_reads, persist_until, allow_delegation and onward_share in the request, and additionally the grant id and revision once issued. Its JSON Schema will be generated from the schema package with the grant implementation; until then this list is the contract.
A request that carries the PDPP detail without this entry is completed with max_reads 1, persist_until null, allow_delegation false and onward_share false, the most restrictive values, and min_confidence 0, the A5.2 default, which filters nothing. The person sees these values before approval. A request that cannot be expressed under A6 fails with RFC 9396 invalid_authorization_details; this includes a purpose code that is not an Open Personal Context purpose and two entries that disagree.
These restrictions can refuse a read that the PDPP grant alone would allow (B1.11, B1-D1). An exhausted read budget gives 403 with code grant_exhausted. Entries below min_confidence are omitted as A5.2 omits them, never refused.
B1.6 Tokens and introspection
- Client access tokens are A6.3 tokens with the PDPP resource as audience. Under PDPP they are client tokens: introspection, or the co-located local equivalent, returns
active,pdpp_token_kind: "client",subject_id(the pairwise handle),grant_id,client_id,expandauthorization_detailsholding both entries of B1.5 (section 8, Token introspection). - A token issued for another audience, including every contribution-grant token and every read token issued outside this binding, is inactive at the PDPP resource.
- This binding issues no PDPP owner tokens. The person reads, exports and deletes their own record through the app session and A11, never through a bearer credential that could be handed to a recipient (B1.11, B1-D8).
- The A6.6 client lifecycle token introspects as
pdpp_token_kind: "autonym_lifecycle", a companion-profile kind that section 8 permits. A PDPP resource server, including this provider's, treats it as unauthorised for every PDPP operation.pdpp_token_kinds_supportedlistsclientonly. - The authorisation server, resource server and introspection run in one deployment, so the resource server uses the local equivalent and caches nothing: A6.5 requires revocation to take effect on the next attempted action, which is stricter than the 60-second bound of section 10. If an RFC 7662 endpoint is offered for a separated resource server, only that resource server's credential may call it.
- PKCE S256, exact redirect matching, single-use codes and rotating refresh families follow A6.3, which meets Authorization Server items 19 and 20. Refresh tokens are not issued for a
single_usegrant. - DPoP is required where A6.3 requires it. A DPoP-bound token is never accepted as a Bearer token.
B1.7 Reads through the compiler
A client-token request to list records or to get the record of a granted stream is an Open Personal Context read of one domain at one level. It runs the A5.2 transaction in full: lock and recheck the stored grant and the B1.5 restrictions, compile, create and sign the A5 envelope, append the A9 receipt with surface pdpp and increment the read counter, then commit. The PDPP record is a projection of that signed envelope's entries and contains every entry of the envelope and nothing else. Nothing else renders entry data for this binding.
- Each successful client-token read carries
Autonym-Read-Idwith the receipt'srd_read ID. Whether the signed envelope is also returned is not yet settled. changes_since(Resource Server items 7 and 8) returns the record only when its compiled projection at the current horizon differs from its compiled projection at the cursor's horizon. A change to an entry that is hidden, ungranted, below the stream's level or outside the projection never makes the record eligible (section 8, List records).- Getting a record by an identifier that is not this grant's record gives the same 404
not_foundas an unknown identifier, without a read. - Listing streams for a client token names the granted streams only. It carries no record count, update time or freshness that depends on the record; where the response shape requires such a member, it holds a value that does not depend on the record.
- Stream metadata for a client token is the closed projection Resource Server item 15 requires: the granted stream and its granted fields only.
- Client-token
filter[...],view,expand[]andexpand_limit[...]are rejected with 400invalid_request(Resource Server items 5, 9 and 10). - The blob endpoint answers 404
blob_not_foundto every request, because no stream declares a blob.
B1.8 Revocation, receipts and purge
PDPP Core says revocation is not deletion and defines no erasure signal (section 10, Revocation), and leaves audit events to a companion profile (section 11). This binding adds both without changing PDPP semantics:
- every read is receipted under A9, and grant issuance and revocation are logged events;
- ending a grant issues A10 purge instructions on the same terms as for any other grant, delivered and acknowledged outside PDPP.
B1.9 Transport, errors and versions
- The PDPP section 8 endpoints are served under a base path published as
pdpp_core_query_basein the RFC 9728 metadata for the PDPP resource (Resource Server item 16). The base path and resource identifier are not yet fixed. - On a 401 the response carries a
WWW-Authenticate: Bearerchallenge with the RFC 9728resource_metadataparameter, as Resource Server item 16 requires, and also aDPoPchallenge where the provider accepts DPoP-bound tokens. - Until the error mapping is settled, errors on the PDPP endpoints use the section 8 error object, with PDPP's codes where PDPP defines one and the registered Open Personal Context code otherwise, so a PDPP client falls back on the status as section 8 requires. B4 routes keep Problem Details (B4.3), and OAuth endpoints keep their OAuth error responses.
PDPP-Versionis negotiated on the PDPP endpoints (Resource Server item 11, Authorization Server item 17).Autonym-Versionkeeps its B4 meaning; the two are independent.- The authorisation request is pushed (RFC 9126) because the two
authorization_detailsentries exceed comfortable URL lengths (section 10, Grant integrity).
B1.10 PDPP Core conformance
A provider claiming this binding meets the section 9 requirements as the tables below state: Authorization Server items 1 to 21; Resource Server items 2 to 11, 15 and 16; Resource Server item 1 except for the owner operations of B1-D8; and Resource Server items 12 and 14 trivially, because this binding issues no owner tokens. Resource Server item 13, a SHOULD, is not supported. Each row summarises the requirement; section 9 is authoritative.
Authorization Server
| Item | Section 9 requirement | How this binding meets it |
|---|---|---|
| 1 | Accept selection requests in the RFC 9396 https://pdpp.dev/data-access detail |
Accepted alongside the B1.5 detail |
| 2 | Validate a request against one retained SourceDeclaration snapshot, refusing unknown streams, unsupported parameters and unrecognised presets | Validated against the retained generated declaration (B1.2). Unknown streams, time_range (no stream declares consent_time_field) and every selection preset are refused, since no grant under this binding carries one (B1.4) |
| 3 | Issue grants that follow the section 7 field tables, every field derived from the request, client registration or policy | Grants follow the B1.4 mapping and its fixed values |
| 4 | Expand wildcards and presets into explicit streams, fields, instances and constraints before issuance | The domain wildcard and null key lists are expanded and frozen at issuance (B1.4) |
| 5 | Refuse a request with both or neither of streams and selection_preset |
Refused like any request that cannot be expressed under A6, in the RFC 9396 form of B1.5 |
| 6 | Never refuse a purpose code only because the PDPP registry lacks it; local policy may refuse | Only the ten Open Personal Context purposes are accepted, a local policy the item permits (B1.4) |
| 7 | Keep requester metadata, declaration text, structured policy and client claims distinct at consent | The consent surface shows them as separate categories, attributes client claims to the client and never presents them as enforced terms |
| 8 | Track grant lifecycle and reflect revocation in introspection at once | Revocation takes effect on the next attempted action (A6.5) and introspection reports it at once (B1.6) |
| 9 | Bind access tokens to one grant, with the PDPP introspection members | A6.3 tokens bound to one grant, introspected with the members of B1.6 |
| 10 | Consume a single_use grant atomically with its first client token |
A grant with max_reads 1 is single_use (B1.4) and is consumed atomically with its first client token |
| 11 | Validate stream, field, view and resource-id shape at issuance | Stream and field shape are validated at issuance; views and resource IDs are never accepted (B1.2) |
| 12 | Define no view with fields outside the declaration | Met trivially: no stream declares a view (B1.2) |
| 13 | Resolve views at issuance; client-token reads refuse a query-time view | No views are declared, and a client-token view is refused (B1.7) |
| 14 | Obtain explicit affirmative consent before issuing an ai_training grant |
Met by refusing the ai_training purpose code, which is not an Open Personal Context purpose |
| 15 | Resolve instances before final approval and bind approval to an immutable review revision | The one eligible instance is resolved before review (B1.4); approval is bound to an immutable review revision and refused if eligibility or the revision changes before approval |
| 16 | Retain one exact declaration snapshot from validation to consent evidence | The declaration snapshot used for consent is retained with the grant, which names its revision (B1.4) |
| 17 | Refuse an unsupported PDPP-Version with status 400 and the section 8 code for it |
PDPP-Version is negotiated as B1.9 states, and an unsupported version is refused with status 400 and that code |
| 18 | For a separated resource server, authenticate it at RFC 7662 introspection | The servers are co-located and use the local equivalent; an RFC 7662 endpoint offered to a separated resource server accepts only that server's credential and answers with the complete enforcement context of B1.6 in one response |
| 19 | Consume each authorisation code on its first redemption | Codes are single-use (A6.3, B1.6) |
| 20 | Issue refresh tokens only for continuous grants, rotate them by family and revoke the family on reuse |
Refresh tokens rotate by family with replay detection (A6.3). Reuse of a superseded token revokes the grant, with every token issued for it, and fresh authorisation is required. No refresh token is issued for a single_use grant (B1.6) |
| 21 | Refuse unsupported persisted authorisation state | Unsupported persisted state is refused and never rebuilt from current configuration; fresh consent is required |
Resource Server
| Item | Section 9 requirement | How this binding meets it |
|---|---|---|
| 1 | Implement the section 8 endpoints, including the blob endpoint and owner-authenticated delete | Partly. Every section 8 endpoint exists. The blob endpoint never finds a blob, because no stream declares one (B1.7), and owner delete refuses every token (B1-D8) |
| 2 | Enforce stream membership, instances, time constraint, fields and resources on every client request | Stream membership, the instance handle and fields are enforced on every read (B1.7); no time constraint or resource filter is ever granted (B1.2, B1.4) |
| 3 | Resolve tokens through introspection or a co-located equivalent, caching for at most 60 seconds | Co-located local equivalent with no caching (B1.6, B1-D6) |
| 4 | Distinguish owner tokens from client tokens | Token kinds are distinguished; no owner token is issued, and an autonym_lifecycle token is unauthorised for every PDPP operation and not advertised (B1.6) |
| 5 | Refuse request-time predicate filters on client tokens | Refused (B1.7) |
| 6 | Use the section 8 structured errors | The section 8 error object on the PDPP endpoints (B1.9, B1-D5) |
| 7 | Support changes_since incremental sync, with tombstones, unchanged projections omitted and status 410 on cursor expiry |
The compiled projection at the cursor's horizon is compared with the current one (B1.7), with the section 8 tombstone and cursor-expiry behaviour |
| 8 | Include next_changes_since on the terminal page of every changes_since response |
Included as section 8 requires |
| 9 | Refuse client-token filter[...] with status 400 before consulting the declaration |
Refused with status 400 before the declaration is consulted (B1.7) |
| 10 | Refuse unknown query parameters and unsupported query shapes with status 400 | Refused, never silently ignored |
| 11 | Implement PDPP-Version negotiation |
Negotiated (B1.9) |
| 12 | Scope owner tokens to one subject's data | Met trivially: no owner tokens are issued (B1-D8) |
| 13 | SHOULD support owner-token self-export through the record endpoints | Not supported: the person exports through the first-party app and A11 (B1-D8) |
| 14 | Return full current stream metadata to owner tokens | Met trivially: no owner tokens are issued (B1-D8) |
| 15 | Return client tokens only a projection of stream metadata derived from the grant | The closed projection: the granted stream and its granted fields only (B1.7) |
| 16 | Publish RFC 9728 metadata with the pdpp_ members, and a Bearer challenge with resource_metadata on status 401 |
Published for the PDPP resource (B1.2, B1.9), with the Bearer challenge on status 401 and a DPoP challenge where DPoP-bound tokens are accepted (B1.9) |
Client items 1 to 8 bind a recipient that uses this binding, not the provider.
Until PDP-Connect publishes its conformance suite, conformance is shown by Autonym's own executable checks, each traced to one section 9 role and item number and never given an invented identifier; PDP-Connect's suite replaces them when it is published.
The Open Personal Context side of this binding is checked by CONSENT-01, CORE-01 and DELEGATE-01 in the conformance manifest, whose runtime evidence is pending the phases the manifest names. BINDING-01, the check of the manifest's optional pdpp-binding feature, covers the requirements specific to this binding: the generated source declaration of B1.2, the static streams of B1.3 and the owner endpoints of B1-D8. Its runtime evidence is pending too, and none of these checks is executable yet.
B1.11 Deviations from PDPP Core
Read strictly against PDPP Core v0.1.0, this binding does the following, which PDPP Core does not anticipate. None widens what a PDPP grant authorises.
- B1-D1. Extra refusals. Section 12 (Extensions) says a grant authorises exactly what sections 7 and 8 define. The B1.5 restrictions can refuse a read that the PDPP grant alone would allow.
- B1-D2.
single_useretries. Section 7 lets a client retry with the same token until it expires. Withmax_reads1, a second successful read is refused. - B1-D3. Levels rest on non-normative text. Derived streams appear only in the non-normative part of section 12.
- B1-D4. No Bearer tokens. PDPP Core carries client tokens as RFC 6750 Bearer tokens. Every token here is DPoP-bound (A6.3), so a client that cannot use DPoP obtains no grant.
- B1-D5. Error format. Open Personal Context codes appear inside the section 8 error object where PDPP defines none, until the error mapping is settled.
- B1-D6. No introspection caching. Stricter than section 10 and conformant with it.
- B1-D7. Retention measured from issue. PDPP clients may measure
retention.max_durationfrom collection rather than fromissued_at;persist_untilin B1.5 governs, and how it reaches a PDPP-only client is not yet settled. - B1-D8. No owner tokens. The owner record endpoints, including owner-authenticated delete (Resource Server item 1) and self-export (item 13, a SHOULD), refuse every token; the person uses the app and A11 instead.
Checks: BINDING-01, CONSENT-01, CORE-01, DELEGATE-01.
References to other standards and projects describe intended technical alignment only and do not imply endorsement or partnership.