Repository navigation
test(bind): live end-to-end binding for GitHub, X and Google, from UI to chain - #72
Draft
SupremaLex wants to merge 25 commits into
Draft
SupremaLex wants to merge 25 commits into
SupremaLex wants to merge 25 commits into
Conversation
Runs the CCDP ceremony against the real Bridge, notary and CCDP, encodes the accepted proof as the GitHub verifier's TlsNotaryProof payload, and submits IdentityRegistry.bind from the user's wallet. It then reads the binding back from the registry and from the indexer. stack.ts starts anvil with the real libID-contracts v0.15.0 stack deployed by libid-deploy, a notary signing with the key that stack trusts, Bridge, CCDP and the usernames indexer. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The live test signs in to GitHub with the test account (password and TOTP, ported from libid-server-rs' live ceremony suite), lets the ceremony notarize and prove in the browser, has an injected EIP-1193 wallet backed by an unlocked anvil account submit bind, and checks the transaction, the registry and the indexer through the page. Anvil mines a block every second so block time follows the clock: a proof's notary timestamp is checked against it. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Biome skips the app's build cache as it does apps/dev's. The Bind GitHub anchor drops role=button, so it reads as the link it is. The GitHub login loop is split into one handler per page, under oxlint's complexity limit. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
pnpm -C ts/apps/bind test:live now starts the stack and app through Playwright's webServer, runs the test and stops them; the stack saves its container logs when LIBID_STACK_LOGS names a file. The Live end-to-end workflow runs it with the GitHub test account from repository secrets, fails rather than skips when they are missing, and keeps the trace, video and logs of a failed run. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The app binds X as it binds GitHub: both are TLSNotary ceremonies with the same payload. The X test restores the saved session libid-server-rs' ceremony export writes, presents the browser as a person's Chrome with the stealth script and client hints ported from that suite, and approves the OAuth app. The live workflow passes the X secrets. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The stack runs the keeper once after the deploy, so GoogleJwtRoots trusts Google's current keys through a notarized MPC-TLS reading; the notary opens its wire port for it. The app encodes the Google verifier's payload, whose client identifier is the token audience the result carries on its identity. The test restores the saved Google session and consents in the popup; X and Google share one saved-session loader. The nightly workflow takes the Google secrets. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Pull requests from this repository get its secrets, so the suite can run on them as well as nightly; a fork's pull request is skipped. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The runner context is not available in job-level env, which made the whole workflow file invalid. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Keep both sides of the Biome ignore list, and regenerate the lockfile from main's with the bind app's dependencies. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
client.connect now generates the connection ID from a popup opened by PopupWindow.fromAnchor, and client.new takes no ceremony ID. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Main's ceremony proves with the libid-circuits v0.6.0 circuits, whose verifiers chain-configurations v0.15.0 deploys. Under 0.14.0's verifiers every bind reverts with SumcheckFailed. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
🚀 Deploying Preview to Cloudflare 🚀Preview URL: https://feat-e2e-bind.previews.lib.id, https://feat-e2e-bind-libid.grounded-systems.workers.dev (commit 4b2f2c9)This URL reflects your latest Preview deploymentPreview Deployments by commit
|
The proof wait ends as soon as the page reports the ceremony failed, with its message, instead of running out its five minutes. Google's verification pages are caught by their challenge path and by text with either apostrophe. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Google challenges the saved session when it arrives from a runner's address, so the test fails on every run there. It stays in the suite for local runs, and the workflow no longer reads the Google secrets. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
A Playwright trace records every fill and cookie, so the public artifact of a failed run held the GitHub test password and the saved sessions. CI records no trace or video, and a step refuses the upload when any file holds the password, the TOTP secret or a saved X cookie value. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- local.ts holds the stack's addresses and ports for the stack, the app, the Vite and Playwright configs and the spec. - e2e/popup.ts drives a platform's pages until the popup leaves them, for GitHub, X and Google alike, and clicks a page-marked control. - LIBID_LIVE_PLATFORMS selects the platforms a run binds; CI binds GitHub and X, and the stack rotates Google's keys only when Google is bound. liveSecret fails a missing secret under LIBID_REQUIRE_LIVE everywhere. - The page reports one outcome, and the test waits on it instead of a hand-written loop that recognised only one terminal stage. - stack.ts keeps one chain client, drops a code check quoteBind already implies, takes needsRotation's ABI from @libid/contracts, and waits for Bridge and the indexer while the deploy runs. - The failure artifact uploads only screenshots, error notes and the stack log, after scan-secrets.ts finds no secret in them. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…arted with - quoteBind and bind route on VERIFIER_VERSION, the CeremonyProofVerifier slot libid-deploy registers every launch verifier at (its LAUNCH_VERIFIER_VERSION, 1), instead of the ceremony version the proof reports. The two are different numbers that happen to agree at launch. - submit() takes the wallet and holder captured when the ceremony started, the holder its transaction data names, instead of re-reading the module's current ones. - The popup connection closes however the run ends, a rejection included. - A failed start reports its own error message rather than always blaming the Bridge configuration. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- Before `up`, `compose down --volumes --remove-orphans` removes what an interrupted run of this checkout left, so the chain and the indexer's database start empty. - With any port the stack publishes (anvil, notary, Bridge, indexer API) still taken after that, startup refuses: the anvil check could pass against another checkout's chain and the deploy would go to it. - Once the stack is stopping, every wait rejects and no later step runs: no deploy, keeper or Vite server after Compose has exited; a Vite server created during a stop is closed instead of listening. - `down` reports when Docker cannot run it. - The deploy check also requires a verifier at VERIFIER_VERSION for every platform the run binds. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
After GitHub's error page the driver went back a page and cleared its "login filled" flag, and leaving github.com counted only with that flag set. The session was already signed in, so no login form came to set it again, and the redirect was never taken for success; going back could also leave github.com without any redirect. As in libid-server-rs' github.rs, the error page now reopens the authorization request the run started on, so the popup leaves github.com only through GitHub's redirect, and leaving it is success. The popup driver loses its `done` hook, which only GitHub used. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- Dependabot's pull requests get no secrets; the job skips them as it skips forks. - Checkout keeps no credentials. - The test step has its own 45-minute timeout (stack start 15, two tests of 12, stop 2), inside a 55-minute job, so a hung run still reaches the secret scan and the upload. A test's timeout is 12 minutes: the popup driver's 3 and the page's 8-minute outcome wait. - The scan runs when the test step failed or timed out, not on a cancelled run, and the upload follows a clean scan. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- drivePopup succeeds only once the popup requests Bridge's OAuth callback (local.ts BRIDGE + /auth/callback), seen as a navigation request so an immediate redirect onward still counts. Leaving the platform for another page keeps it waiting until its budget names the page; a Chrome error page or a popup closed before the return fails at once. - Waits inside a driver go through `pause`, which rejects with the driver's own reason when the wait would pass the 180 s budget, so GitHub's rate-limit and error-page backoffs report themselves instead of a generic timeout. The budget and the page's 8-minute outcome wait fit the 12-minute test timeout. - The page text reaches every driver lowercased with typographic apostrophes as plain ones; Google's own copy of that goes. - GitHub's authorize click has a 5 s timeout, ignores a failed click as X and Google do, and is not repeated within 5 s; the two-factor goto and the rate-limit reload ignore their errors, as github.rs does. - Session renewal hints say where libid-server-rs' `ceremony` lives: branch feat/live-ceremony-tests, libid-server-rs PR #12. stealth.js names presentAsPerson, which installs it. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
… agree on - The outcome is "bound" only when the registry resolves the identity to the holder the run started with and the indexer reports that holder; any other holder, or no indexer answer within 60 s, is a failure with its own message. - A ceremony that fails to start sets the outcome too, so the test ends on its message instead of waiting out the outcome timeout. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- stack.ts takes .cache/stack.pid before touching the Compose project and refuses while another live stack.ts holds it, so a second run no longer tears down the first; a lock whose process is gone is stale. The lock also covers the CCDP build, which the running ccdp container mounts. - With LIBID_STACK_LOGS set, every child (the CCDP build, Compose with the containers' logs, libid-deploy, the keeper) writes to that file as it runs, not to our output, and the end-of-run `compose logs` goes. Under CI the Playwright web server drops the stack's stdout, so the public job log carries none of it. - Compose pulls every image, the keeper's included, before anything is timed, so a cold pull no longer eats the anvil wait. - libid-deploy is chosen by host (Linux or macOS, x86_64 or aarch64), each archive pinned by SHA-256, and a failed download reports its status instead of a digest mismatch. - Startup fails fast when local.ts, compose.yaml or keeper.toml disagree with local-dev.toml's [contracts] addresses, or bridge-config.toml does not admit local.ts' app origin. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- Each pull request has its own concurrency group, a newer push cancelling the older run; scheduled and dispatched runs share another. A single group let one PR's pending run cancel another's. - Before the scan, any libid-bind Compose project still up is taken down, and the scan and the upload use one copy of the stack log, so nothing written after the scan goes up. - The artifact comment says what the scan does: it reads bytes, so a screenshot is safe because the pages driven never display a secret, and the job log carries none of the stack's output. - The X session comment says where `ceremony export x` lives. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
An AbortController replaces the stopping flag: waits end on it and commands the stack runs are terminated by it, so the startup sequence reads straight through instead of re-checking a flag between steps. The deploy tool downloads and CCDP builds while Docker clears the old stack and pulls, with the clean-up and the pull side by side. One helper probes Bridge and the indexer API, the ports are probed at once, the Bridge address comes from local.ts, the archive is unpacked from memory, and one fail() reports a refused start. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The X and Google saved sessions live in session.ts, where the secret scan reads the same definitions the tests restore. clickFound owns the data-libid-click marking, so each driver gives only the control to find. GitHub's error-page and rate-limit back-offs share one helper, SameSite maps through a table, and the app reports a failed run through one fail(). Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
This was referenced Oct 6, 2026
This branch has not been deployed
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.
A real end-to-end test of binding a platform identity, from the UI to a mined transaction and back, for GitHub, X and Google. It also adds the app it drives: the first code that takes the CCDP ceremony all the way to
IdentityRegistry.bind.What runs
ts/apps/bind:stack.ts+compose.yamlstart the whole stack locally:libid-deployv0.15.0, which carries the libid-circuits v0.6.0 verifiers main's ceremony proves for. The deploy tool is downloaded from its release and pinned by sha256.local-dev.tomltrusts (anvil refactor(claim): port the OIDC circuit-input conversion from wasm to TypeScript #1).GoogleJwtRoots.apps/dev.quoteBind(github, 1)must equal two notary fees, and a verifier must sit at slot 1 for each platform the run binds.stack.ts, a leftover stack from an interrupted run is removed first, and a port already in use stops startup. Startup also fails fast when the addresses inlocal.ts,compose.yamlandkeeper.tomldrift fromlocal-dev.toml.local.tsholds the stack's addresses and ports for the stack, the app, the configs and the tests.The app (
src/app.ts,src/evm.ts):@libid/ceremony.TlsNotaryProoffor GitHub and X,GoogleProoffor Google.bind.Each step has a
data-testid, and oneoutcomefield reports how the run ended:boundonly when the registry and the indexer both report the run's holder.e2e/bind.live.spec.ts(Playwright,pnpm -C ts/apps/bind test:live, one command: stack, app, tests, teardown):e2e/popup.ts) runs each platform's pages: GitHub signs in with password and TOTP; X and Google restore a saved session and consent. It succeeds only when the popup reaches Bridge's callback; Chrome's error page or a popup closed early fails at once.LIBID_LIVE_PLATFORMSselects the platforms (all three by default). A platform's test is skipped without its secrets, unlessLIBID_REQUIRE_LIVEis set, which fails it instead.LIBID_TEST_ENV_FILEcan point at a dotenv file.CI
.github/workflows/live-e2e.ymlruns on pull requests that touchts/, nightly and on dispatch, withLIBID_LIVE_PLATFORMS=github,xandLIBID_REQUIRE_LIVE. Google is left out: it challenges a saved session arriving from a runner's address.e2e/scan-secrets.tsfinds no test-account secret in them. CI records no trace or video.Result
All three pass locally in about 2.1 min; CI's GitHub and X selection in about 1.6 min. I checked a binding independently with
cast:resolveId(github, id)returns the test wallet, the published handle is set,IdentityBoundwas emitted, and the indexer agrees.Known CI failure
On the runner, the GitHub test sometimes fails with "Notarization request timed out". It failed at
29510f4andfce3132and passed at86166bf. This is@libid/ceremony's 10 s budget innotary/session.ts, which runs from the request through the attestation. GitHub's sessions took 6.2–8.1 s on the runner, most of it in the reveal. X takes about 3 s and passes. The fix belongs in@libid/ceremony, as a separate budget for reveal through attestation, not a retry here.Not yet
NotProofTarget).apps/dev, and publishingsrc/evm.ts's encoders from a package.Integration friction found while building it
@libid/ceremony,@libid/ledgerand@libid/popupare private.LedgerId.bindpayload encoder (it must be one ABI tuple).oauthProofomits the operation domain and transaction data, which the app must keep.signaturein TS andproofin Solidity.AttestationAheadsurfaced as an undecodable0xa493bc37.owner/userId, notholder/id.src/evm.tsis a candidate to move into@libid/ledgerand@libid/contracts.