docs(specs): define CCDP and ceremony service contracts - #13
Open
Wondertan wants to merge 21 commits into
Open
Conversation
Wondertan
force-pushed
the
docs/ceremony-browser-architecture
branch
2 times, most recently
from
August 20, 2026 02:10
1795a93 to
42298b3
Compare
Wondertan
force-pushed
the
docs/ceremony-browser-architecture
branch
10 times, most recently
from
August 20, 2026 09:40
cebe2ed to
f055711
Compare
Wondertan
force-pushed
the
docs/libid-ceremony-specs
branch
3 times, most recently
from
August 20, 2026 10:05
b98ba12 to
da4f501
Compare
Wondertan
force-pushed
the
docs/ceremony-browser-architecture
branch
from
August 20, 2026 16:49
52db663 to
4deb2a5
Compare
Wondertan
force-pushed
the
docs/ceremony-browser-architecture
branch
5 times, most recently
from
August 20, 2026 17:49
fe6cf6c to
1add82b
Compare
This was referenced Aug 20, 2026
Wondertan
force-pushed
the
docs/ceremony-browser-architecture
branch
2 times, most recently
from
August 24, 2026 12:51
9074ebb to
04bb956
Compare
Wondertan
changed the base branch from
main
to
docs/popup-connection-architecture
September 12, 2026 23:39
…checks them The token request's headers were hidden. §5.2 left the request direction's headers out of its table, and §6.4 said so outright -- REQ-PLAT-43D bounded the disclosure to "the seven rows marked `yes`", and the prose named the `Host` header as one "this table hides". That leaves a region of the request the Platform Verifier cannot read, in the one direction it has to reason about. It reads `grant_type`, `client_id` and `code_verifier` out of the body with a form-encoding reading -- and common REQ-COMMON-21B fixes the media type precisely because it "selects the platform's request parser". A media type the verifier cannot see is a value the profile pins and nothing checks, and the platform may have parsed those bytes into fields other than the ones read. So the request line and every request header are revealed, and REQ-PLAT-56A has the verifier compare them against a fixed list, byte for byte and in order. Both halves are needed: revealing without comparing would leave the bytes public AND unconstrained, which is neither private nor checked. Both profiles' requests are now enumerated the way §5.3 and §6.5 already enumerate the identity request's headers, which is the precedent this follows. Nothing here is user data. The request is composed byte for byte by the implementation and driven over a raw MPC-TLS session, not the browser's HTTP stack, so there is no cookie jar and no ambient credential: the four headers are profile constants. Revealing them discloses nothing and leaves the sent direction with no region a verifier cannot read -- and concealment would not have protected a user in any case, since the implementation is what would have put anything private there. The response direction is unchanged and still hides its status line and headers: those are the platform's own bytes and nothing reads them. That asymmetry is now stated rather than left to be inferred. The `Host` argument survives and had to. A revealed `Host` header is still not the authority -- it is prover-composed text -- so the authority still reaches the verifier as the authenticated TLS server identity, and the header is compared against the profile like every other. `client_secret` is untouched: still committed, still ordered last by REQ-COMMON-22 so the revealed run stays contiguous. The linter reports what it reported before this change: 3 errors, 9 warnings, all pre-existing. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
… around it The header list in the previous commit was wrong, and wrong in the way that would have been found by a failing launch rather than by review: it enumerated four headers and required the verifier to reject any other set, but a request carrying a body carries a `Content-Length`. hyper emits one for every known-size body -- `set_length` calls `set_content_length` -- and libid sets it nowhere, so the GitHub Token Service's own exchange would have produced a head of five headers and been refused by its own profile. As written the rule rejected every genuine attestation. `Content-Length` cannot be a profile constant: its value is the body's byte count. So REQ-PLAT-56B pins the value against something the verifying side can derive rather than against a literal -- the signed transcript length of the sent direction less the head is the body, whether the body is revealed whole as in X's request or revealed up to a committed suffix as in GitHub's, because common REQ-COMMON-35 makes the direction tile exactly. That is not bookkeeping. The verifier takes the body to be everything after the sole CRLFCRLF; the platform takes it to be `Content-Length` bytes. Where those disagree the fields the verifier reads are not the fields the platform parsed, which is the same divergence REQ-PLAT-56's `grant_type` check exists to stop, reached by a different route. `Transfer-Encoding` overrides `Content-Length` outright and so is refused rather than described. REQ-PLAT-56C carries the line-ending discipline over from the identity request. Common REQ-COMMON-39 already refuses a bare line feed and an obsolete line fold there, because a parser that accepts either ends the head somewhere the verifier does not -- and the token head, which no requirement had ever covered, is the same head with the same parsers reading it. REQ-PLAT-56A now also says the match is exhaustive rather than a presence test. "These five appear" is satisfied by a request carrying a sixth. Found by an audit of the previous commit, which was written from a reading of the request that no HTTP client produces. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Order carries no meaning here. Field order is insignificant in HTTP except for repeated names, which REQ-PLAT-56A forbids outright, and the `transfer-encoding`/`content-length` precedence REQ-PLAT-56B settles is by presence rather than position. Nothing a reordering does is not already refused by requiring the exact set. What fixing it would cost is real. It binds every prover to the header order its HTTP library happens to emit: the browser reaches the wire through tlsn's wasm prover, whose `HttpRequest` carries headers in a `HashMap`, so the order is whatever that iteration gives on the day. Pinning it also pins `content-length` last, which is not a promise anyone made -- it is where hyper's `set_length` inserts it, and a patch release could move it. So the set is fixed and the order is not, and the prose says so once rather than three times. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Sections 5.3 and 6.5 said "in this order" of headers no verifier orders: the identity request is held to coverage, one line-anchored authorization and the bearer framing, and common section 6 already says header order carries no proof semantics unless a profile commits it. The token request's headers were freed of order in this branch; these two now say the same. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
…rows The tables listed each body field as its own revealed range. The verifiers, the prover and the attested record carry the request as one range -- the record cannot hold adjacent ranges apart, it merges them before signing -- so a browser planning one range per field would see its count change under it. Say so, once per platform. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
api.github.com refuses any request without one: 403, with a body that says so, where the same request with one gets the 401 an absent bearer earns. Section 6.5 listed five headers and left it out, so a prover built from the text alone could never reach the identity read. The browser draft already sends one. X's endpoint and both token endpoints do not care, checked the same way, so the token request's pinned set is unchanged. The value is the runtime's to choose: nothing verifies it, and GitHub requires only that it exist. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Both identity requests said "exactly N headers". No verifier holds them to that: the contract compares the request line and the authorization line, and the prover's layout finds the same line and nothing else. A count nothing checks is a rule nobody can rely on and a prover can only fail to meet, as GitHub's user-agent just showed. Say which lines are compared and leave the rest to the runtime. The token request is unchanged: there the verifier does hold the set. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
REQ-PLAT-56A had the verifier reject any header outside the profile's list. A header outside it changes only what the platform answers, and a wrong answer is a response the verifier cannot read, not one it can be fooled by; the rule bound every prover to one HTTP library's habits for nothing. It now requires `host` and the media type, forbids the five names that change what the platform does with the request in a way no revealed byte shows, and ignores the rest. `transfer-encoding` moves from 56B to that list, beside `content-encoding`. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The linter says so, and it is right: two MUSTs in one sentence read as one rule with a clause. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
REQ-PLAT-56A said values are compared exactly; the verifier removes the optional whitespace around them first, and lowercases names with their whitespace removed, which is the normalization REQ-COMMON-39 already gives the identity request. A verifier built from the text alone would have refused `content-type:application/...`, which the contract accepts. 56C now also names a line with no colon, which the contract refuses. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
…uests REQ-COMMON-39 counted `authorization:bearer` lines, so a second header under Basic or a platform's token scheme was never counted and the Identity Platform answered for whichever credential it honoured; the committed bearer is the one thing the cross-bind fixes. The needle is now `authorization:` under any scheme. REQ-COMMON-39A states the line-ending rule the verifier already applies to the identity request -- no bare line feed, no fold -- which nothing in this file said, and adds the bare carriage return. REQ-COMMON-39B forbids on the identity request the names that change what the platform does with it, `cookie` above all, with `_` read as `-`; the token request's REQ-PLAT-56A now refers to that one list plus `authorization`, and 56C names the bare carriage return and points at 39A. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The verifier requires exactly one empty line in the revealed token request and a declared length with no leading zero; REQ-PLAT-56B said neither. A second empty line is a second place a parser could end the head, and a second spelling of the count is a second thing to compare one spelling of. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
GitHub pretty-prints `/user` for the media type the profile pins, so the compact delimiters this specification spells matched nothing it serves. REQ-COMMON-19F fixes what libid-org/libid-contracts#37 does: the Platform Verifier removes the JSON whitespace touching a structural byte and matches, counts and reads over what is left; the Implementation reveals a member as the wire carried it, whitespace inside, and never commits that whitespace with a bearer; the compact spellings name the member after removal. TEST-COMMON-10A lists the vectors. REQ-PLAT-51 judges GitHub's terminator after that removal and REQ-PLAT-60 keeps the whitespace in the reveal. Carries the vectors of #32 in the form the merged verifier implements. Co-authored-by: Wondertan <hlibwondertan@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Preserve downstream verification while assigning local parsing and correlation to Prover, keeping original requirement IDs and the framing prerequisite. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Keep the readable routes, messages, events and phases. Delegate transport to the popup specification and move implementation APIs, build tooling and qualification to the implementation PR. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Report valid OAuth denial with one-way Denied. Application cancellation retires the local run; popup navigation and closure stay composition-owned. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Define Event as an interface like the other messages. Nest operationId and attributes under optional instrumentation and preserve their validation and protocol boundaries. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Wondertan
force-pushed
the
docs/ceremony-browser-architecture
branch
from
September 13, 2026 14:00
2ff7040 to
01d5fb0
Compare
Rename Denied to UserDenied and Abort to CeremonyFailed, including wire discriminators, message links, conformance cases, and sequence labels. Preserve outcome semantics and local cancellation. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Accept a resolved notary address for any platform without requiring notarization. Keep nullable wire input and let the selected profile require an address only when needed. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Align browser identity ownership, GitHub routing and admission, and transport failure and closure semantics. Distinguish ledger-local verifier versions from ceremony versions without changing the browser flow or ledger verification. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
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.
Normative browser and service contracts
CCDP relies on the popup transport specification in #22; this PR is based on that branch. It also includes #31's framing prerequisite without reverting its canonical changes.
The existing readable document structure is retained, with stable requirement/test anchors and explicit security boundaries. Browser validation ownership is reconciled separately: Prover parses and correlates evidence, Application validates delivered structure, and ledger verification remains authoritative. Neither endpoint adds local notary-signature verification or a separate Google nonce-versus-expected-digest comparison.
Implementation APIs, module layout, proving/notary integration, asset build tooling, UI, metrics, and qualification now live in implementation PR #28, with rendered package docs. No implementation code is changed here.
The fixed-callback migration and Callback-to-Prover authenticated-origin handoff remain explicitly tracked implementation work in #28. The previous architecture snapshot is preserved; this PR's package placeholders and implementation documents are removed from its scope.