Repository navigation
feat(ceremony): generate the profiles for every language from one file - #29
Merged
Merged
Conversation
The ceremony profile was written out four times: `CeremonyProfile.sol` and the two Platform Verifiers here, a hand table in libid-rs, and the browser's own copy in libid. Nothing related them. The browser's already drifted -- it labels `api.x.com` the PLATFORM, when the platform is `x` and `api.x.com` is the authority -- and nothing failed, because a Consumer dispatching on one string and a verifier registered under another simply never meet. `solidity/contracts/ceremony/profiles.json` is now the one place those values live, and `scripts/regen-ceremony-profiles.py` writes all three languages from it, following `handles.json` exactly: generated data, hand-written logic that reads it. `CeremonyProfile.sol` becomes generated and the verifiers read it, the way `HandleNormalizer` reads `HandleVectors`. The Solidity constants are unchanged. Every platform id, authority, launch version, attestation count and protocol parameter generates byte for byte what was there; the diff against the previous file is additions only, and the 439 forge tests pass untouched. What is added is the session data the verifiers used to spell inline: the request lines, the committed-body-field count, and the two identity field names. The new `libid-profiles` crate is the Rust half -- no runtime dependencies, the `libid-identity` shape, so a prover reads a table without inheriting anything to do it. It joins the single-version invariant, `bump-version.sh`, the publish dry-run and the release publish. TypeScript gets `@libid/contracts/ceremony`. Generation makes the three agree with each other; it does not make them right. A wrong edit reaches all three at once and they would agree perfectly on a platform no verifier is registered for. So the vectors stay: `CeremonyProfile. t.sol` pins the ids to `cast keccak` values, and the new crate's tests hash the generated strings and assert the same numbers. `--check` also refuses a SHIPPED profile whose values changed while its `ceremonyVersion` stayed put, reading the last release tag to know which is which -- prose is exempt, a new platform is not a change, and a bumped version passes. The check job now fetches tags, because a shallow clone would find nothing to compare and pass in silence. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Running the profile generator as a module leaves a `__pycache__` beside it, and one landed in the previous commit. Removed, and ignored so the next run cannot repeat it. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
`publish-crates` needed verify-tag, versions, solidity, rust and publish-check, and `publish-npm` needs publish-crates -- so neither waited on the check that the committed tables still match the file they come from. A release could publish a stale table, or a shipped ceremony profile whose values changed while its version stayed put, and every other job would be green while it happened. The drift job carries no `if:`, so it already runs on a release event; it was only missing from the graph. Adding it to `publish-crates` gates npm too, which waits on crates. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The handles generator escapes every string it emits, because its vectors deliberately carry bytes a source file cannot -- that is what they test. This one emitted them raw, so a quote in a path or a field name produced broken Solidity. It did not reach a file, but only by accident: `forge fmt` rejected it and the script bailed, reporting a parser error about an unexpected token. Rust and TypeScript go through no formatter, so a value breaking only those would have been written. Escaping is the wrong answer for this data. A path, a method, a host and a JSON member name are drawn from URL and identifier characters; a quote in one is a mistake, and escaping it would emit a constant no verifier could ever match. So the charset is checked before anything is generated, and the error names the field and the value. The functions appended for Rust and TypeScript also carried no type annotations, unlike the rest of this file and unlike its sibling. 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.
The ceremony profile was written out four times —
CeremonyProfile.soland the two Platform Verifiers here, a hand table in libid-rs, and the browser's own copy in libid — with nothing relating them. The browser's copy has already drifted: it labelsapi.x.comthe platform, when the platform isxandapi.x.comis the authority. Nothing failed, because a Consumer dispatching on one string and a verifier registered under another simply never meet.This does for the profiles what
handles.jsondoes for normalization: generated data, hand-written logic that reads it.What changes
solidity/contracts/ceremony/profiles.jsonis the source of truth.scripts/regen-ceremony-profiles.pywrites three files from it:solidity/contracts/ceremony/CeremonyProfile.solHandleNormalizerreadsHandleVectorsrust/profiles/src/profiles.rs(newlibid-profilescrate)ts/packages/contracts/src/ceremony/profiles.ts(@libid/contracts/ceremony)The Solidity constants are unchanged. Every platform id, authority, launch version, attestation count and protocol parameter generates byte for byte what was already there — the diff against the previous file is additions only, and 439 forge tests pass untouched. What's added is the session data the verifiers used to spell inline: request lines, the committed-body-field count, and the two identity field names.
_tokenRequestLine()now returnsCeremonyProfile.X_TOKEN_REQUEST_LINE.Generation is not correctness
A wrong edit to
profiles.jsonreaches all three languages at once, and they would agree perfectly on a platform no verifier is registered for. So the vectors stay and gain a second side:CeremonyProfile.t.solpins the ids tocast keccakvalues, and the new crate's tests hash the generated strings and assert the same numbers.The version guard
--checkrefuses a shipped profile whose values changed while itsceremonyVersionstayed put, reading the last release tag to know which is which. Exercised across five cases:noteis exemptx/v1 changed since v0.7.0 but kept its ceremonyVersionThe check job now uses
fetch-depth: 0: a shallow clone carries no tags, would find nothing to compare, and would pass in silence.Release surface
libid-profileshas no runtime dependencies — thelibid-identityshape, so a prover reads a table without inheriting anything to do it. It joins the single-version invariant (verify-tag.sh),bump-version.sh, the publish dry-run and the release publish. TypeScript gets a./ceremonysubpath export.Verification
python3 scripts/regen-ceremony-profiles.py --checkandregen-identity-handles.py --checkforge test— 439 passed;forge fmt --checkcargo +nightly fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test -p libid-profiles— 6 vectorspnpm build,typecheck,test(24),lint,fmt:checkFollow-ups, not here
libid-profilesand deletes its hand table (feat(transcript): the launch profiles, pinned to the numbers the chain holds libID-rs#10, closed pending this).IdShapeenum is still hand-written in Solidity (IdShape.JsonStringin the verifiers) while the JSON carries the shape. Generating a cast across the enum's ordinal was more coupling than it removed; worth revisiting.