spec: display and record client terms links, and relate Core to the agreement standards - #317
Draft
tnunamak wants to merge 4 commits into
Draft
spec: display and record client terms links, and relate Core to the agreement standards#317tnunamak wants to merge 4 commits into
tnunamak wants to merge 4 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Two entries in spec-deferred.md called this prior art "ISO MyTerms." It is not an ISO standard. It is IEEE P7012, published in January 2026 as IEEE 7012-2025, "Standard for Machine Readable Personal Privacy Terms." Leaving the wrong name in a normative-adjacent document sends a reader looking for a standard that does not exist, and it hid the fact that the thing is now published rather than a draft we could ignore. The deeper gap was that the entry said MyTerms "has not been evaluated for fit," which left open whether PDPP was quietly drifting into an incompatibility. This adds an informative entry that does the evaluation. MyTerms inverts the usual direction of an online agreement: the individual is the first party and offers privacy terms, and the service provider is the second party that accepts one. The terms are not written per relationship — they are picked from a roster kept by a neutral non-business entity (today Customer Commons), where each term is a versioned dereferenceable URL. Against that model a PDPP grant is already the recorded agreement on the owner's side: it is immutable, it names the second party, and it carries the recipient-side commitments as structured fields (purpose_code, retention, access_mode) rather than prose. The entry states the two places the analogy breaks — MyTerms expects both sides to hold a record where PDPP defines only the authorization server's copy, and MyTerms allows a counter-offer where a PDPP grant is issued after consent rather than negotiated — so a later reader does not overclaim the fit. It also names the thing that would actually cause incompatibility: inventing a PDPP-specific vocabulary for terms a roster already names. We do not have that problem today, because purpose_code is an absolute URI the authorization server must accept even when it does not recognize it, so a rostered term is already expressible with no schema change. That is the positive path, and keeping purpose_code open is what preserves it. No Core change and no v0.1 commitment: the roster is small and the second-party record format is unsettled, so this records the analysis and the hedge rather than a dependency. The same wrong name also appears twice in the 2026-08-20 working-session notes. Those are left alone deliberately — they are a dated record of what was said in the room, not a statement of fact about the standard. Assisted-by: AI
…s shown An owner approving a PDPP grant may be shown the requesting application's privacy policy and terms-of-service links, or may not be. Core left both halves of that soft. It said the authorization server "MAY display them as secondary links," and it never required the server to keep any trace of which links it put in front of the owner. So an owner who later disputes what a company was allowed to do with their data has no record of the terms they were pointed at when they agreed, and the server has no way to show one. Two changes, and the first is smaller than it looks. RFC 7591 Section 2 — the OAuth client-registration metadata vocabulary that Core already borrows `policy_uri` and `tos_uri` from — says of each field that "the authorization server SHOULD display this URL to the end-user if it is provided." Core's MAY was therefore weaker than the source it cites, which was a drafting slip rather than a decision. Raising it to SHOULD restores the strength the borrowed vocabulary already carries. The second change is a real addition: where the server displayed either link, the issued grant MUST carry the exact displayed values in `client.client_display`. This needs no new field. The grant schema already has an optional `client_display` holding server-resolved requester identity metadata, with `policy_uri` and `tos_uri` among its members — it was simply never obligatory to populate them. The rule also says a value the server resolved but did not show MUST NOT be recorded as displayed, so the record stays a record of the consent surface rather than of the server's internal lookup. That evidence is deliberately bounded, and the text now says so rather than overselling it. A recorded URI shows which documents the owner was pointed at, not what those documents said, because the target belongs to the client and can change after issuance. ISO/IEC 29184:2020 Section 5.2.8 — the ISO standard on online privacy notices and consent — sets the stronger bar, requiring that an organization "shall keep and make available the version of the notice presented when the PII principal gave consent." Core points deployments at that as a SHOULD instead of mandating it, because retaining the version means fetching and storing third-party documents: a real deployment cost and a new outbound-fetch surface, neither of which is an authorization semantic. Separately, a client requesting the `ai_training` purpose must now have a `policy_uri` in its resolved metadata, and the server must reject the request otherwise. Training on someone's personal data is already the one purpose code where Core treats consent as protocol-enforced rather than advisory, and consent to it is not informed if there is nowhere to read how the recipient handles the data. The requirement is on the resolved metadata, not on the inline request, so a client the server already knows through registration or a validated binding does not have to resend it. Confidence is uneven across the three and worth flagging for review. The SHOULD-display realignment is near-certain — it is what the cited RFC says. The recording obligation is well-precedented in the consent-receipt lineage (ISO/IEC 29184, Kantara Consent Receipt) but has no precedent in the OAuth or OpenID Connect family, so it is a genuine extension of that lineage rather than a gap-fill. The `ai_training` policy_uri rule is the weakest of the three: no regime found names AI training as a trigger for a heightened privacy-policy requirement, so it rests on Core's own existing decision to single that purpose out, not on outside prior art. Verified: `pnpm spec:check`, which regenerates the site's spec pages from these root files and fails on any drift between them, passes. `pnpm spec:dates` fails only on spec-reference-implementation-examples.md, a file this branch does not touch and which was already failing on origin/main. No reference-implementation code changes here; the consent card in packages/operator-ui renders neither link today, so the SHOULD-display rule describes work that surface has not done yet. Assisted-by: AI
An independent judge reviewed the two stances on offer for terms support and chose S1 over S2 (disagreeing with the research's own S2 recommendation at 82/100 confidence). S2 would have added an optional `agreement` object to the grant now. The judge's decisive reason: the argument for adding it early rested on RFC 9396 Section 5, which constrains what a *client* may send in an authorization request, while the proposed field was grant-side only. So Section 5 neither forces nor eases that field, and adding it would standardize a record shape before the flow that produces it exists — there is no client publishing accepted terms, no publication surface, and no acceptance step anywhere today. This is S1: say precisely how PDPP relates to the standards in question, and record the design a future version should implement, without inventing fields for it now. Section 1 gains four relationship rows. IEEE 7012-2025 (MyTerms) says a PDPP grant records an authorization and could later reference an agreement; it deliberately stops short of calling every grant an agreement record, which would overstate what the grant is. W3C DPV 2.3 is named for its consent-record vocabulary, and its extension for IEEE 7012 is described as live — it ships in DPV 2.3 as `STANDARD-IEEE-7012`, which corrects an earlier claim that the extension had been stranded at an old draft; the row keeps the caveat that DPV is a Community Group report and not a W3C standard. ISO/IEC TS 27560 is named as a deferred export mapping, and says PDPP records a notice reference only where a link was actually displayed, rather than claiming every grant carries 27560's mandatory notice field. ISO/IEC 29184 is kept as written, because it is the citation that explains why a recorded URI is weaker evidence than a retained version. ODRL was proposed as a fifth row and is deliberately omitted: the mapping is speculative and no v0.1 field depends on it. The UMA row gains one clause noting that UMA puts the resource owner's policy outside its own scope, which is why PDPP's structured commitments are an addition rather than a divergence. The deferred MyTerms entry drops the `purpose_code` route it previously offered as the positive path, and now warns against it: an agreement URI is a syntactically valid purpose code, so the route is legal and still wrong, because purpose and agreement are separate objects everywhere and the `ai_training` rule switches on the purpose code's value. In its place the entry records the two-sided design a future version should implement. The client publishes the rostered terms it accepts in its client metadata document. The authorization server holds the rostered term the owner chose from the IEEE 7012 roster. At grant time the server matches the two mechanically, with no negotiation round trip. The grant records the matched term — URI, version, acceptance time — in a future optional `agreement` object that introspection echoes. PDPP defines no terms vocabulary of its own, at this version or any later one; that is the thing that would create incompatibility with a roster-based standard. The entry also records why the request side is the half that needs a version to land in: RFC 9396 Section 5 requires the authorization server to reject an authorization details object of known type carrying unknown fields, so a request-side carrier cannot be adopted incrementally by clients and must be defined in the `https://pdpp.dev/data-access` type when that version arrives. The grant-side and metadata-side halves carry no such constraint. The client `policy_uri`/`tos_uri` display-and-record change from the earlier commits on this branch is unchanged. Verified: `pnpm spec:check` passes; `pnpm spec:dates` passes (the unrelated stale-date failure this branch reported earlier was fixed on main); and the spec prose gate, which merged into main after the earlier commits here, now reports no new findings against a merge-base baseline. That last one also required rewriting four over-long sentences this branch had itself introduced before the gate existed. Assisted-by: AI
tnunamak
force-pushed
the
spec/client-terms-myterms-0903
branch
from
September 3, 2026 22:16
7d713df to
1d10f25
Compare
Applying a weight test to the rows this branch added: a row in Section 1's standards table earns its place only if it explains something the spec does today, or names a bar the spec is measured against. A row that is merely adjacent and interesting is table weight without a reader benefit. Four rows went in; three survive that test. ISO/IEC 29184 stays as written. It is the reason Core records what was displayed rather than treating a bare URI as sufficient evidence, so it explains a rule the spec already carries. ISO/IEC TS 27560 stays as the deferred consent-record mapping, and now absorbs what the separate DPV row was carrying. The Data Privacy Vocabulary did not describe anything Core does today: it is the vocabulary such a mapping would be expressed in, which makes it a detail of the 27560 row rather than a relationship of its own. Naming it inside that row keeps the useful part — DPV keeps purpose, consent, and the consent record as distinct classes the way PDPP does, and its consent record points at a notice object rather than a bare URL — and keeps the caveat that DPV is a Community Group report and not a W3C standard. The separate row is dropped. IEEE 7012 shrinks to the two short sentences that carry the load: the individual proffers terms as the first party and both sides keep a matching record, and a PDPP grant records an authorization rather than an agreement and could later reference one. The conformance disclaimer and the counter-offer detail move out of the table; the deferred entry in spec-deferred.md already states both at length, and repeating them in a one-line-per-cell table was duplication rather than emphasis. The row is worth keeping at this size because the question it answers came out of workshop feedback, so a reader arriving at the table will look for it. ODRL is not added. It describes no current PDPP surface and the mapping to it is speculative, so it fails the same test. One consequential follow-on: the deferred entry's closing line listed the four standards Section 1 relates to. It now lists three, because claiming a DPV row that no longer exists would send a reader looking for it. Verified: pnpm spec:check, pnpm spec:dates, and the spec prose gate against a merge-base baseline all pass, the last reporting no new findings. Writing the IEEE row as a single sentence tripped the gate's 25-word cap at 43 words, so it is two sentences. Assisted-by: AI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Someone approving a grant — the permanent record of consent by which a person lets a company keep reading their personal data — hands that company ongoing access. Today the protocol does not require that the person be shown the company's privacy policy or terms of service before they agree, and it keeps no record of whether they were. If they later dispute what the company did with their data, neither side can establish which terms were on screen at the moment of approval. This PR closes that. It also states, for the first time, how PDPP relates to the standards that govern agreements and consent records — and fixes a factual error about one of them that was steering our thinking.
The protocol is PDPP, an authorization protocol for personal data: an owner (the person) issues a grant letting a client (an app or company) read specific data from a source. The authorization server, abbreviated AS in the tables below, is the component that runs the consent screen and issues grants. Throughout, Core means
spec-core.md, the normative document defining the protocol, as opposed to the reference implementation — the working Node and Postgres codebase in this repository. This PR edits only the first of those two. v0.1 is the protocol version Core currently specifies and the one this PR targets.1. Client terms and privacy policy
Core carries the requesting application's privacy-policy and terms links as
policy_uriandtos_uri, two field names Core borrows from RFC 7591, the OAuth specification that defines the standard vocabulary for describing a client application. Two things were wrong with how Core handled them.The display rule was weaker than its own source. Core said the authorization server "MAY display them as secondary links." RFC 7591 Section 2 says of each field that "the authorization server SHOULD display this URL to the end-user if it is provided." Core cites that vocabulary and then states a weaker obligation than it carries, which reads as a drafting slip rather than a decision. This raises Core to SHOULD, matching the source.
Nothing recorded what was shown. The grant is meant to be the durable artifact of consent, but it never had to say which policy and terms links the owner was shown. This makes recording mandatory where the links were displayed. It needs no new field, because the grant's field definitions in Core already include an optional
client_displayobject — the requester information the server resolved and can vouch for, written asclient.client_displaywhere the full path matters, since it sits inside the grant'sclientobject — andpolicy_uriandtos_uriare already listed among its members. Populating them was simply never required. The rule also forbids recording a value the server resolved but did not display, so the grant records the consent surface rather than the server's internal lookup.The evidence this produces is deliberately bounded, and the spec text now says so. A recorded URI shows which documents the owner was pointed at, not what those documents said, because the target belongs to the client and can change after the grant is issued. ISO/IEC 29184:2020 — the ISO standard on online privacy notices and consent — sets the stronger bar in Section 5.2.8: an organization "shall keep and make available the version of the notice presented when the PII principal gave consent." Core points deployments at that as a SHOULD rather than mandating it, because retaining the version means the authorization server must fetch and store third-party documents. That is a real deployment cost and a new outbound-fetch surface, and neither is an authorization semantic.
Finally, a client requesting the
ai_trainingpurpose — one of the values ofpurpose_code, the field stating what the data will be used for — must now have apolicy_uriin its resolved metadata, and the server must reject the request otherwise. Core already singles this purpose out: its registry entry and its "AI training consent" subsection both state that the server must obtain explicit affirmative consent before issuing such a grant, and call it "the sole purpose code with a mandatory consent requirement at the protocol level," where every other purpose code's consent properties are advisory. Consent to training is not informed if there is nowhere to read how the recipient handles the data, so this attaches the policy link to the one purpose Core has already decided is different. The requirement falls on resolved metadata rather than on the inline request, so a client the server already knows through registration does not have to resend it.Decision row — client terms and privacy policy
policy_uri/tos_uriwhen present; MUST record the exact displayed values in the grant'sclient.client_display; MUST NOT record a resolved-but-undisplayed value as displayed; SHOULD (not MUST) retain a snapshot or digest;ai_trainingrequests MUST have a resolvedpolicy_uri.ai_trainingpolicy_urirule: least confident, and the one most worth pushing back on — no regime I could find names AI training as a trigger for a heightened privacy-policy requirement, so it rests entirely on Core's own prior decision to treat that purpose differently, not on outside prior art.Prior art below, one row per standard, including the ones that turned out to have no opinion — a standard that is silent on a question is as useful to know about as one that answers it. Each row names what the standard is, since they come from several unrelated fields.
policyURLREQUIRED, pinned to the policy "in effect when the consent was obtained"One pattern in that table is worth naming because it changes how the recording rule should be reviewed: display obligations and recording obligations sit in entirely different families of standard. Nothing in the OAuth or OpenID Connect lineage requires persisting what was displayed. So this is an extension of that lineage rather than a gap-fill, and it deserves to be argued on its merits rather than waved through as "aligning with OAuth."
2. MyTerms (IEEE 7012), and how Core relates to the agreement standards
spec-deferred.md— the companion document listing concerns deliberately left out of scope for v0.1, so a future version can pick them up — called this prior art "ISO MyTerms" and said it "has not been evaluated for fit." Both parts were a problem. It is not an ISO standard: it is IEEE 7012-2025, "Standard for Machine Readable Personal Privacy Terms," published in January 2026. The wrong name sends a reader looking for something that does not exist, and it obscured that the standard is published rather than a draft we could defer thinking about. Leaving it unevaluated also left open the question this PR was asked to settle: whether PDPP is quietly drifting toward incompatibility with it.MyTerms inverts the usual direction of an online agreement: the individual is the first party and offers privacy terms, and the service provider is the second party that accepts one. Terms are not written per relationship — they are chosen from a roster kept by a neutral non-business entity, today Customer Commons, where each term is a versioned URL you can fetch.
What this PR does and does not add. Two options were on the table. The wider one would have added an optional
agreementobject to the grant now, to hold a reference to a rostered term. A prior-art survey recommended it; a separate reviewer, working from that survey and not involved in writing it, recommended against, and this PR follows the reviewer. Take the score attached to that recommendation as one reader's stated strength of opinion rather than a measurement — what should carry weight here is the argument, which is checkable and is this.The case for adding the field early rested on RFC 9396 Section 5. RFC 9396 is the OAuth extension PDPP uses to carry a structured request, and its Section 5 says an authorization server must reject a request object of a known type that carries fields the server does not recognise. The survey read that as meaning a terms field would be expensive to retrofit, so better to add it now. But Section 5 constrains what a client sends in a request, and the proposed
agreementobject lived only on the grant the server issues afterwards. Section 5 therefore neither blocks nor eases it, and the urgency does not survive the distinction. What remains is that no client publishes accepted terms today, no surface exists to publish them on, and no acceptance step exists — so adding the field now would fix the shape of a record before the flow that produces it exists. This PR adds no field.What it adds instead is precision about where PDPP stands. Three rows in Core's "Relationship to existing standards" table, chosen against a deliberate test: a row earns its place only if it explains something the spec does today, or names a bar the spec is measured against. Adjacent-and-interesting is not enough — that is table weight with no reader benefit.
Two candidate rows were cut. W3C ODRL 2.2, a general language for expressing permissions, describes no current PDPP surface and the mapping to it is speculative. A separate DPV row was written and then folded into the 27560 row above: DPV describes nothing Core does today, so it is a detail of the deferred mapping rather than a relationship in its own right. Cutting the row keeps the substance and drops the weight.
One correction worth recording, because it went the other way. An earlier draft of this work described DPV's extension for IEEE 7012 as stranded at an old version. That was wrong: it ships in DPV 2.3 as
STANDARD-IEEE-7012, which I verified against the DPV specification directly.Separately, the UMA 2.0 row gains one clause noting that UMA places the resource owner's policy outside its own scope — which is why PDPP's structured commitments are an addition rather than a divergence from it.
The design the deferred entry now records. The entry previously offered
purpose_code— the field naming what the data will be used for — as the way to reference a rostered term. That is removed, and the entry now warns against it: an agreement URI is a syntactically valid purpose code, so the route is legal and still wrong, because purpose and agreement are separate objects in every standard surveyed and theai_trainingrule switches on the purpose code's value. In its place the entry records the two-sided design a future version should implement, because a one-sided record is exactly what MyTerms rejects. The client publishes, in its client metadata document, the rostered terms it accepts. The authorization server holds the rostered term the owner chose. At grant time the server matches the two mechanically, with no negotiation round trip. The grant records the matched term — URI, version, acceptance time — in a future optionalagreementobject, echoed back through introspection, the authenticated call a resource server makes to ask the authorization server what a token actually authorizes. PDPP defines no terms vocabulary of its own, now or later; that is the thing that would actually create incompatibility with a roster-based standard.The entry also records why the request side is the half that needs a version to land in. Because of RFC 9396 Section 5, a client cannot start sending a terms field early and have servers ignore it — the server must reject the whole request. So a request-side carrier has to be defined inside
authorization_details, the structured object a client sends to describe what it is asking for, under PDPP's own type name for it. The grant-side and metadata-side halves carry no such constraint. This is the same rule that failed to justify adding a field today, applied to the thing it actually governs.Decision row — MyTerms compatibility
spec-deferred.md: fix the name to IEEE 7012, drop thepurpose_coderoute and warn against it, and record the two-sided publish-hold-match-record design for a future version.agreementobject now (term URI, version, proffering party, acceptance time, optional digest). This was the prior-art survey's own recommendation, on the argument that an optional field is cheaper added now than later, because the grant carries a schema version fixed at0.1.0that resource servers must reject if they do not support it — so a field added afterwards arrives as a version change rather than a quiet addition. Rejected on review: the RFC 9396 §5 argument for urgency does not apply to a grant-side field, and no producing flow exists to exercise the shape. A third option — say nothing beyond citations — was rejected because it leaves the standards relationship unstated, which is what was asked for.Two findings from the wider survey are worth stating because they bound what any future design can rely on. No OAuth-family standard models owner-proffered terms at all — UMA 2.0 examined the question and put the resource owner's policy outside its scope, and GNAP (RFC 9635, the IETF's redesign of delegation) mentions privacy policies and terms of service zero times. So there is no existing pattern to follow here, which is a reason to move slowly rather than a reason to move first. And IEEE 7012 defines no wire format, being deliberately protocol-agnostic, so "conformance" has no testable protocol-layer meaning today even if we wanted to claim it — which is why the IEEE row claims referenceability instead.
What I have not verified
The reference implementation — this repository's working codebase, as distinct from Core the specification — is untouched. Its consent card, the component that draws the approval screen (at
packages/operator-ui/src/components/pdpp/consent-card.tsx), renders neither the privacy-policy nor the terms link today; I checked. So the new SHOULD-display rule describes work that surface has not done yet, and this PR neither does that work nor adds a conformance test for it. A reviewer who wants the specification and the code to move together should ask for that follow-up.Two prior-art rows stayed unverified against primary sources and are marked as such above rather than quietly dropped: UK Open Banking's terms-and-conditions display pattern (found as guidance, exact mandatory clause not confirmed) and FDX's Consent API schema (primary specification is access-gated; only a press release describing "consent traceability" was reachable). Neither is load-bearing for the proposal.
The ISO/IEC TS 27560 row rests on a second-hand reading. That specification's normative text is paywalled; its structure was read through W3C DPV's published mapping guide rather than the ISO document. The row is written to survive that: it names the four sections and defers the mapping to a companion document, and makes no claim about any individual clause. Anyone writing the actual mapping should buy the specification first. The ISO/IEC 29184 quotation is stronger — I extracted it from the standard's own preview PDF — but it is still a preview rather than the full document.
IEEE 7012-2025's normative text is also unread, being behind IEEE's paywall. The relationship row is drawn from the standard's free project page, which is sufficient for stating a relationship and would not be sufficient for a conformance claim. The row makes no conformance claim, deliberately.
I did not find any regime that names AI training as a purpose triggering a heightened privacy-policy requirement. That rule therefore rests on Core's own existing decision to single out
ai_training, not on outside precedent, which is why its confidence is the lowest of the three.The same wrong "ISO MyTerms" name appears twice in
docs/community/working-sessions/2026-08-20-session-3-grants-resource-server.md. I left those deliberately: that file is a dated record of what was said in a room, not a statement of fact about the standard. Reviewers who disagree should say so and I will correct them.Verification
Rebased onto
origin/main. All three spec gates pass.pnpm spec:checkregenerates the specification pages served on the project's public documentation site from these rootspec-*.mdfiles and fails if the two ever disagree. Passing it proves the edits propagate cleanly to the published pages; it says nothing about whether the prose is correct, which is what review is for.pnpm spec:datesfails when a spec's declared date is older than its last substantive commit. It now passes on all ten specs. An earlier revision of this PR reported one failure here on a file this branch does not touch; that was fixed onmainand the rebase picked it up.The spec prose gate — a linter added to
mainafter this branch's first commits, which flags over-long sentences and similar issues against published style authorities — reports no new findings. That gate is a ratchet: it builds a baseline from the changed files as they stood at the merge base and blocks only on findings this branch introduces, so pre-existing prose is waived and new prose is not. Reproducing it locally the way CI does surfaced four over-long sentences that this branch had itself introduced before the gate existed, in its earlier commits; those are rewritten here, which is why the diff touches a few lines neither decision above otherwise required.On sourcing: the ISO/IEC 29184 Section 5.2.8 wording quoted in the spec text was extracted from that standard's own preview PDF rather than taken from a secondary summary, and the DPV extension status was checked against the DPV specification itself. Both are cited in the spec rows so a reviewer can re-check them from the text alone, without needing anything outside this repository.
Assisted-by: AI