Repository navigation
feat(transcript): take the ceremony profiles from the chain's own table - #12
Merged
xgreenx merged 3 commits intoSep 8, 2026
Merged
Conversation
A layout is built from values a Platform Verifier compares byte for byte, and this crate had none of them: every `"id"`, `"username"` and `client_secret` in it was a test literal, and every real caller kept private copies. A wrong one failed on chain, where the error names an offset rather than the constant behind it. `libid-profiles` is generated in libid-contracts from `solidity/contracts/ceremony/profiles.json` -- the same file `CeremonyProfile. sol` is generated from -- so what a prover reveals and what a verifier compares it against now come from one place. Released as 0.8.0, no dependencies of its own, so taking it costs a published consumer nothing. `IdShape` was the duplication that mattered. This crate declared one and the generated table carries another, saying the same thing about the same profile fact, and a caller holding a profile had to translate before it could build a layout. The local one goes; the generated one is re-exported in its place, along with the table as `ceremony::profiles`. The two identity constructors follow #11's own argument to its end. `identity_response` took `id_field`, `id_shape` and `handle_field` as three loose arguments, and `token_request` took a bare `Option<&str>` -- but those are not independent choices. They are readings of ONE profile, and mixing X's handle field with GitHub's id shape describes a session nobody ran. They now take `&IdentitySession` and `&TokenSession`, the way `AttestedData:: from_observed` takes an `ObservedSession` rather than four arguments that must already agree. The tests take their arguments from the table too, so they exercise the values a verifier actually pins rather than restating plausible ones. One test is new and could not have been written before: the ceremony profiles and the identity system must name the same platforms, which libid-contracts asserts across its own two tables in `PlatformIdentity.t.sol`. Both crates are generated there, so this is not checking a transcription -- it is checking that a consumer holding both, at versions it resolved independently, holds one keyspace. They are separate crates with separate requirements, and a lockfile can pin a pair that never shipped together. `libid-identity` is a dev-dependency, so nothing published carries it. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Every `identity_response` case here used X's, so the bare-integer path had no coverage through a layout at all -- only through the range finder underneath it. The two shapes are not the same: `login` closes on a quote, `id` closes on the structural byte after its digits, and that byte is revealed WITH them because it is what proves they are the whole number rather than a prefix (REQ-PLAT-51). Both terminators GitHub can send are covered, `,` and `}`; a layout producing only one would refuse half of its honest responses. `github_identity()` exists now because a test needed it. It was missing for the same reason the coverage was: the helpers were written for the call sites that already existed. The third case crosses the profiles, which is what taking them as one value is for. It fails both ways but NOT symmetrically, and asserting the field each names is the point: X's shape on GitHub's body fails on the id, while GitHub's shape on X's body gets past the id -- the bare reader finds `"id":` and stops at the `,`, returning a quoted value read as though it were a number -- and fails on the handle instead. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The finder took the digits to be everything up to the first `,` or `}`. `CeremonyFields.tryJsonInteger` does the opposite: it scans DIGITS and then demands one of those two bytes closes them. The difference is not academic -- `"id":"7",` satisfied the old scan, so a quoted value came back as though it were a number, and the chain refused it as noncanonical. Same answer, given three components away from the reason. Now mirrored exactly, including the two rules the scan never had: at least one digit, and no leading zero unless the value is `0` -- `end - at > 1 && data[at] == "0"` on chain. Digits running to the end with nothing after them are `Found.None` there and `None` here. The crossed-profile test found this. It asserted that GitHub's shape on X's body fails on the HANDLE, because the id slipped through; it now fails on the id, where the shapes actually differ. A test written to document behaviour, and then changed by fixing it, is the test earning its place. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@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.
Stacked on #2 (
feat/ceremony-constructions). This is what closed #10 was waiting for: libid-contracts now generates the profiles, andlibid-profiles 0.8.0is on crates.io.What was missing
A layout is built from values a Platform Verifier compares byte for byte, and this crate held none of them. Every
"id","username"andclient_secretin it was a test literal, and every real caller kept private copies — so a wrong one failed on chain, where the error names an offset rather than the constant behind it.libid-profilesis generated in libid-contracts fromsolidity/contracts/ceremony/profiles.json, the same fileCeremonyProfile.solis generated from. What a prover reveals and what a verifier compares it against now come from one place. It has no dependencies of its own, so taking it costs a published consumer nothing.IdShapewas the duplication that matteredThis crate declared one and the generated table carries another — the same thing said twice about the same profile fact, so a caller holding a profile had to translate before it could build a layout. The local one goes; the generated one is re-exported in its place, with the table as
ceremony::profiles.The constructors follow #11's argument to its end
Layout::identity_response(recv, id_field, id_shape, handle_field)Layout::identity_response(recv, &IdentitySession)Layout::token_request(sent, Option<&str>)Layout::token_request(sent, &TokenSession)Those were never independent choices — they are readings of one profile, and mixing X's handle field with GitHub's id shape describes a session nobody ran. Same reasoning
AttestedData::from_observedapplies toObservedSession: four arguments that must already agree belong in the value they agree about.The tests take their arguments from the table too, so they exercise the values a verifier actually pins rather than restating plausible ones.
One test that could not be written before
The ceremony profiles and the identity system must name the same platforms — libid-contracts asserts this across its own two tables in
PlatformIdentity.t.sol::test_theTwoTablesAgree.Both crates are generated there, so this isn't checking a transcription. It checks that a consumer holding both, at versions it resolved independently, holds one keyspace: they are separate crates with separate version requirements, and a lockfile can pin a pair that never shipped together. That is not hypothetical —
libid-identity 0.6.0and libid-contracts main disagreed on exactly these strings until 0.7.0, and nothing anywhere noticed.libid-identityis a dev-dependency, so nothing published carries it.Verification
cargo +nightly fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspace— 112 passedcargo publish --dry-runover all four published crates —libid-transcriptpacks and rebuilds with its new dependency