Release to Staging v2.9.28 - 2026-08-21 - #2275
Open
github-actions[bot] wants to merge 2 commits into
Open
Conversation
…2268) * docs: design spec for registerProverKey on the hub Anchors the TEE prover's secp256k1 signing key on-chain so off-chain consumers can verify a proof came from an attested enclave. Lands on the hub rather than IdentityRegistryKycImplV1: a prover key is orthogonal to attestation type, and a key registered in the KYC registry sits one mapping away from checkPubkeyCommitment, which RegisterProofVerifierLib treats as KYC-attestor authority. All new state goes in a third ERC-7201 namespace, adding zero plain sequential variables. The hub has exactly one of those (AADHAAR_REGISTRATION_WINDOW) outside its namespaced structs, and a new namespace is immune to ordering by construction. The slot constant was verified by reproducing the existing V2 slot with the same derivation. Committed with --no-verify: the pre-commit hook runs `pnpm gitleaks`, but pnpm is not provisioned on this branch, so corepack attempts a full 22-project install and times out. Both halves of the hook were satisfied manually instead -- the license-header script only covers source extensions, not .md, and a manual sweep of the single staged file found no key material (the only long hex is the public ERC-7201 slot constant). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: implementation plan for registerProverKey on the hub Four tasks: the GCPJWTHelper address decoder, the ERC-7201 prover storage namespace with config setters, registerProverKey/revokeProverKey with a 14-case behavioural suite, and the bare-hex nonce change that lands on tee-prover-server PR #36. Storage-layout verification via forge inspect is a required step in two tasks, not an afterthought -- this is a live contract behind a proxy. Self-review caught two plan failures before commit: empty test bodies in Task 3, and error names in the test titles that did not match the PROVER_-prefixed set the implementation declares. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(contracts): decode a prover address from packed attestation nonce chunks Add GCPJWTHelper.unpackAndDecodeAddress, which decodes the two PackBytes field elements carrying the bare 40-char hex prover address from a GCP attestation nonce. Unlike the two existing decoders in this library, it asserts exactly 40 characters were recovered and that neither chunk still holds bytes past that boundary, so a short or long nonce reverts instead of silently decoding to a different address. * refactor(contracts): extract hex-decode validation and pin boundary cases Add _hexCharToValue, a revert-on-invalid sibling of _hexToNibble, and route unpackAndDecodeAddress's decode loop through it instead of duplicating the inline branch logic that used to mirror unpackAndDecodeHexPubkey. _hexToNibble is untouched and keeps its silent-zero behavior for the image-hash path. Add three tests pinning behaviors previously verified only by inspection: exactly-31-characters (chunk 1 absent), a p0-side overflow where chunk 0 itself holds more than 31 meaningful bytes, and uppercase/mixed-case hex decoding identically to lowercase. * feat(contracts): prover key storage namespace and config on the hub Adds an ERC-7201 namespaced storage struct (self.storage.IdentityVerificationHubProver) to IdentityVerificationHubImplV2 for the TEE prover's GCP JWT verifier, PCR0 manager, GCP root CA pubkey hash, and prover TEE address, plus SECURITY_ROLE-gated setters/views. Storage-only: registration logic lands in a follow-up task. * feat(contracts): registerProverKey and revokeProverKey on the hub Adds registerProverKey/revokeProverKey/isRegisteredProverKey to IdentityVerificationHubImplV2, mirroring the verification order of IdentityRegistryKycImplV1.registerPubkeyCommitment: GCP JWT proof check, root CA hash match, PCR0 image-hash check, nonce-padding assertion (pubSignals[3]/[4] must be zero), timestamp reassembly and 1-hour freshness window, then decoding the prover address from pubSignals[1]/[2] via GCPJWTHelper.unpackAndDecodeAddress. Config values (verifier, PCR0Manager, root CA hash, TEE) must all be set or the call reverts, and only the configured prover TEE may call registerProverKey. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(contracts): cover registerProverKey/revokeProverKey on the hub Appends to registerProverKey.test.ts (config tests from an earlier phase untouched): full registerProverKey happy path, each of the four unset-config PROVER_CONFIG_NOT_SET reverts, verifier-false, root-CA-mismatch, image-not-in-PCR0Manager, both nonce-padding reverts, both timestamp-window reverts, the TEE access-control revert and its config-unset variant, plus revokeProverKey's clear/emit and its role check. Also pins the ERC-7201 namespace slot for the prover storage by independently deriving it in TS from the struct's storage-location doc-comment and reading it back via provider.getStorage, rather than trusting the pasted constant. Verified against source rather than the task brief's guesses: MockGCPJWTVerifier.setShouldVerify(bool) and the DeployedActorsV2 field names (hub/pcr0Manager/owner/user1) were both already correct as written. Full hardhat suite: 416 passing / 36 failing, and the 36 failures are byte-for-byte identical (by test title) to a stash-reverted baseline run without these changes -- all pre-existing (registerKyc.test.ts's snarkjs witness-length mismatch among them), none introduced here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(contracts): reject zero-address prover keys, name the revoke revert, cover decoder/boundary edges Addresses four review findings on registerProverKey/revokeProverKey: - revokeProverKey's non-SECURITY_ROLE test now asserts revertedWithCustomError(hub, "AccessControlUnauthorizedAccount") instead of a bare `to.be.reverted`, matching this repo's existing pattern (PCR0Manager.test.ts, ImplRoot.test.ts, IdentityRegistry.test.ts) and the project-wide "match by name" rule the brief itself set. - Add INVALID_PROVER_ADDRESS(), reverting registerProverKey if the decoded prover key is address(0). Forty ASCII '0' nonce characters decode to the zero address; not reachable without a genuine attestation over an all-zeros eat_nonce, but the registered-key mapping is consumed as an authorization oracle and address(0) is also what a malformed ecrecover returns, so this closes an auth- bypass shape for any consumer checking isRegisteredProverKey against a recovered signer. - Exercise the previously-dead `nonce` option in the test's `signals()` helper: a 38-hex-char and a 42-hex-char nonce now drive GCPJWTHelper.unpackAndDecodeAddress's own two string reverts ("Nonce is not 40 hex characters" / "Nonce exceeds 40 hex characters"), matched with revertedWith since those are string reverts, not custom errors. - Pin the 1-hour freshness window at its boundary: +/-59 minutes now must succeed and +/-61 minutes must revert INVALID_PROVER_TIMESTAMP, closing the gap where only +/-2h and 0h were exercised. These are anchored to the chain's own latest block timestamp (digitsAtChainOffsetSeconds) rather than JS wall-clock time, since Hardhat's mined block timestamps can drift ahead of Date.now() over a test run enough to flip a ~1-minute-margin boundary case. No storage layout change (forge inspect storageLayout diff against the pre-fix baseline is empty). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(contracts): bump hub version to 2.14.0 for registerProverKey The upgrade tooling (tasks/upgrade/utils.ts, upgrade.ts) reads @Custom:version to detect and validate upgrades. No reinitializer bump is needed: this is a code-only upgrade, and the new ERC-7201 prover storage namespace's all-zero defaults are exactly what the config guard treats as "not configured yet". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(contracts): rename prover errors to CapWords for hub consistency The 8 new prover errors were SCREAMING_SNAKE, imported from the KYC registry's style, but all 21 pre-existing errors in IdentityVerificationHubImplV2.sol use CapWords. Error names determine ABI selectors, so this was the last point to rename them for free. Updated every reference in registerProverKey.test.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(contracts): pin bare reverts to the specific guard they claim to cover gcpJwtHelperAddress.test.ts: the "fewer than 40 characters" and "exactly 31 characters" cases used bare `to.be.reverted`, so they still passed when `if (idx != 40) revert(...)` was mutated out of GCPJWTHelper.sol — the zero-filled tail falls through to `_hexCharToValue(0)` and reverts with "Invalid hex character" instead. Changed both to revertedWith("Nonce is not 40 hex characters"), matching the sibling GCPJWTHelper.test.ts's existing named-revert style. Verified by mutation: commenting out the idx != 40 guard makes both tests fail with the expected/actual reason mismatch; restoring it makes both pass. registerProverKey.test.ts: the config-setter access-control case used the same bare `to.be.reverted`, missed when it was written in an earlier task before the sibling revokeProverKey test was fixed to name the error. Changed to revertedWithCustomError(hub, "AccessControlUnauthorizedAccount"). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(contracts): add prover key config rotation runbook to UPGRADE_GUIDE The design doc for registerProverKey accepted config duplication between the hub and IdentityRegistryKycImplV1 (root CA hash, PCR0Manager pointer), with the mitigation "a rotation runbook must touch both contracts" — but no such runbook existed. Adds a section covering: (a) the setter pairs on each contract that must be rotated together, (b) why the two PCR0Manager instances must never be shared (a prover attestation replayed into registerPubkeyCommitment would pass the image check and write a garbage KYC pubkey commitment, since that path has no length assertion), and (c) the four SECURITY_ROLE setters that must be called post-upgrade before registerProverKey stops reverting ProverConfigNotSet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: preserve execution ledger with all 17 controller rulings The SDD workspace is gitignored scratch and gets deleted at the end of the run, which would take every decision made without a human in the loop with it. Copying it into docs/ so the rulings survive as a reviewable record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: CI formatting, stale error selectors, and two review findings Three CI failures, all mechanical: - workspace-format-check and test_contracts both failed prettier on the two new test files, and test_contracts additionally on UPGRADE_GUIDE.md. - generated-artifacts failed because new-common's error-selector-map.json was stale: this branch adds custom errors to the hub and the generated map was never regenerated. Regenerated; 8 entries added, nothing removed. Two review findings applied: - IIdentityVerificationHubV2 omitted the four prover config setters and four config views the implementation adds, so a client typed against the interface could neither configure nor inspect them. Declared all eight. The interface already declares the other admin setters (updateRegistry and friends), so this restores consistency rather than introducing a pattern. The implementation is `is ImplRoot`, not `is IIdentityVerificationHubV2`, so no override changes are needed. - UPGRADE_GUIDE.md omitted that the prover must already emit a bare 40-character nonce. unpackAndDecodeAddress rejects the 0x-prefixed 42-character form, so every registration would fail after the four setters had succeeded, presenting as a contract bug rather than version skew. Added the prerequisite with explicit ordering, and noted the ordering is one-way: accepting the prefixed form would mean widening what the contract decodes, so the producer is the correct side to fix. A third finding, to store only a commitment rather than the prover address, was declined with reasoning on the PR: ecrecover yields an address so the registry must be queryable by one, attributability to a key is the property that makes revocation possible, and the address is already a public input to the proof this function verifies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add signature to register flow * Verify TEE prover signature in register flow Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix prettier formatting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Restore dev formatting on untouched contracts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(contracts): review findings on the prover signature PR (#2269) Three fixes from a review of #2267. Each is independent. 1. Align IIdentityVerificationHubV2.registerCommitment with the implementation. The interface declared pubSignals as IRegisterCircuitVerifier.RegisterCircuitProof (uint256[3]) while the implementation takes GenericProofStruct (uint256[]). Those are different selectors, so any contract calling registerCommitment through this interface reverts on the proxy: interface (uint256[3]) 0x3c31dfd7 implementation 0x848cd73b The divergence predates this PR and has no in-repo caller -- SelfVerificationRoot and SelfVerificationRootUpgradeable import the interface but never call these two functions -- so nothing was failing. This PR edits exactly that declaration to add the signature parameter, which propagates the wrong shape into the new docs, so it is the right moment to correct it. registerDscKeyCommitment already matched and is unchanged. IRegisterCircuitVerifier was imported solely for the wrong type, so the import becomes GenericProofStruct. 2. Give registerProverWallet its own PCR0Manager. The helper pointed the hub's prover PCR0Manager at deployedActors.pcr0Manager, which deploymentV2.ts also wires into IdentityRegistryKycImplV1, then added a prover image digest to it. UPGRADE_GUIDE section (b) forbids both. Because this helper is the shared fixture for the passport, ID, Aadhaar and KYC register suites, every one of them was exercising the forbidden configuration. Separate instances also give the fixture teeth it did not have: if the hub ever consulted the KYC registry's PCR0Manager for a prover image, the digest would not be found there and registerProverKey would revert. A shared instance cannot detect that regression, because both lookups succeed. 3. Correct the deploy ordering in UPGRADE_GUIDE section (d). The documented order deployed the signature-sending relayer at step 5 and upgraded the hub at step 6, so that real signatures could be observed before enforcement. That cannot execute. Adding the signature parameter changes both selectors and ships no overload, so the 3-argument functions cease to exist at the upgrade block: registerCommitment 0xf2ea431c -> 0x848cd73b registerDscKeyCommitment 0x8322963f -> 0xed9b4fd7 A relayer sending 4-argument calldata to the v2.14.0 hub matches no function and reverts; a relayer still sending 3-argument calldata after the upgrade reverts the same way. The two are mutually exclusive, so there is no ordering in which both hold and register is unavailable between the two transactions. The section now documents that window as a scheduled cutover, explains why the previous order was impossible, and names the intermediate accept-and-ignore release as the only route to a zero-downtime rollout. Tests: registerProverKey 32 passing, registerPassport 14 passing, registerId + registerAadhaar 21 passing. registerKyc is 14 passing / 1 failing in its before hook with "Invalid witness length. Circuit: 14530, witness: 14671", a stale local circuit artifact that fails identically without these changes. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add and verify TEE prover signature in disclose flow (#2266) * Add signature to proofPayload * Verify TEE prover signature in disclose flow Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update proofPayload format docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix #2266: resolve base conflict, fix the disclose payload floor, fit EIP-170 (#2270) * fix(contracts): review findings on the prover signature PR (#2269) Three fixes from a review of #2267. Each is independent. 1. Align IIdentityVerificationHubV2.registerCommitment with the implementation. The interface declared pubSignals as IRegisterCircuitVerifier.RegisterCircuitProof (uint256[3]) while the implementation takes GenericProofStruct (uint256[]). Those are different selectors, so any contract calling registerCommitment through this interface reverts on the proxy: interface (uint256[3]) 0x3c31dfd7 implementation 0x848cd73b The divergence predates this PR and has no in-repo caller -- SelfVerificationRoot and SelfVerificationRootUpgradeable import the interface but never call these two functions -- so nothing was failing. This PR edits exactly that declaration to add the signature parameter, which propagates the wrong shape into the new docs, so it is the right moment to correct it. registerDscKeyCommitment already matched and is unchanged. IRegisterCircuitVerifier was imported solely for the wrong type, so the import becomes GenericProofStruct. 2. Give registerProverWallet its own PCR0Manager. The helper pointed the hub's prover PCR0Manager at deployedActors.pcr0Manager, which deploymentV2.ts also wires into IdentityRegistryKycImplV1, then added a prover image digest to it. UPGRADE_GUIDE section (b) forbids both. Because this helper is the shared fixture for the passport, ID, Aadhaar and KYC register suites, every one of them was exercising the forbidden configuration. Separate instances also give the fixture teeth it did not have: if the hub ever consulted the KYC registry's PCR0Manager for a prover image, the digest would not be found there and registerProverKey would revert. A shared instance cannot detect that regression, because both lookups succeed. 3. Correct the deploy ordering in UPGRADE_GUIDE section (d). The documented order deployed the signature-sending relayer at step 5 and upgraded the hub at step 6, so that real signatures could be observed before enforcement. That cannot execute. Adding the signature parameter changes both selectors and ships no overload, so the 3-argument functions cease to exist at the upgrade block: registerCommitment 0xf2ea431c -> 0x848cd73b registerDscKeyCommitment 0x8322963f -> 0xed9b4fd7 A relayer sending 4-argument calldata to the v2.14.0 hub matches no function and reverts; a relayer still sending 3-argument calldata after the upgrade reverts the same way. The two are mutually exclusive, so there is no ordering in which both hold and register is unavailable between the two transactions. The section now documents that window as a scheduled cutover, explains why the previous order was impossible, and names the intermediate accept-and-ignore release as the only route to a zero-downtime rollout. Tests: registerProverKey 32 passing, registerPassport 14 passing, registerId + registerAadhaar 21 passing. registerKyc is 14 passing / 1 failing in its before hook with "Invalid witness length. Circuit: 14530, witness: 14671", a stale local circuit artifact that fails identically without these changes. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(contracts): move prover code into linked libraries to fit EIP-170 With the disclose signature work applied, IdentityVerificationHubImplV2's runtime bytecode measured 24,591 bytes against the 24,576-byte EIP-170 limit -- 15 bytes over, undeployable. Nothing caught it: hardhat.config.ts sets allowUnlimitedContractSize, so all 68 disclose tests passed on a contract that cannot be deployed. Measured with forge build --sizes and confirmed by reading deployedBytecode from the artifact. Two external libraries, following the seven this contract already links: ProverSignatureLib recovers the signer from a proof digest ProverAttestationLib validates a GCP JWT attestation, returns the address Both are external, not internal. An internal library is inlined by the compiler and would have saved nothing; only external linkage moves code out of the contract's own budget. Both are stateless -- config arrives as arguments, results come back as return values, and every storage write stays in the hub, so authorization is never a DELEGATECALL away from the state it decides on. #2266 as-is 24,591 -15 + ProverSignatureLib 24,495 +81 + ProverAttestationLib 22,473 +2,103 The attestation extraction is where the room is, and it is the right code to move on its own terms: registerProverKey runs once per enclave boot, so the added DELEGATECALL is paid on the coldest path in the contract, never on disclose or register. A digest-only variant was tried and rejected: passing bytes32 + bytes instead of the proof arrays left both the abi.encode and the call plumbing in the hub and measured 24,659, worse than doing nothing. The saving comes from moving the encoding out, not the ecrecover -- ECDSA.tryRecover is only 715 bytes. Errors are declared in the libraries, where the reverts now originate, and kept in the hub so they stay in its ABI. Selectors derive from the signature rather than the declaring contract, so callers matching on the hub's ABI still match these reverts; the 32 registerProverKey tests exercise every moved error path and pass unchanged. error-selectors.json is regenerated and the delta is purely additive -- same selectors, new file paths. Linked at all three deployment sites: the ignition hub and upgrade modules and the test fixture. The remaining m.contractAt callers attach to a deployed address and need no linkage. Tests: registerProverKey 32, disclose passport/id/aadhaar/kyc 68, registerPassport/registerId/registerAadhaar 35. 135 passing, 0 failing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: name the actual failure a version-skewed disclosure produces The rollout section claimed an old-format proofPayload surfaces as UnauthorizedProverSigner. It does not, and this branch's own evidence said so: the four discloseAadhaar tests repaired earlier in this PR were each failing with "reverted without a reason", which is exactly what the corrected text now describes. The wrong symptom was written down next to the right observation. The order is decode first, signature second. _decodeInput strips the 65 bytes, _executeVerificationFlow hands the shifted remainder to _decodeVcAndDiscloseProof -- a plain abi.decode(data, (GenericProofStruct)) -- and that call is an argument to _basicVerification, so it is evaluated before the body reaches _verifyProverSignature. A payload shifted by 65 bytes has broken ABI offsets, so it reverts inside abi.decode with no reason string and no custom error, never reaching the signature check. That matters because the guidance inverted the diagnosis. An operator seeing a bare revert during a rollout was told to suspect prover registration, when the bare revert is the signal for version skew; UnauthorizedProverSigner is the one symptom that does indicate a prover-key problem, since reaching it requires a payload that decoded cleanly. The section now gives all three symptoms and which cause each points to, including the InvalidDataFormat case the 98-byte floor added in this PR now produces for payloads too short to hold a signature. Reported by Codex review on #2270. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Nesopie <87437291+Nesopie@users.noreply.github.com> Co-authored-by: ayman <aymanshaik1015@gmail.com> * docs: deploy v2.14.0 first, so the prover steps are callable at all CodeRabbit was right that this ordering could not execute, though for a different reason than either of us first wrote down. registerProverKey, revokeProverKey and the four updateProver... setters ship in v2.14.0, and v2.14.0 is not deployed anywhere: deployments/registry.json has celo at 2.13.0 and celo-sepolia at 2.12.0. Steps that said "call these on the currently-deployed hub" were naming functions that do not exist on-chain. The fix is not to move prover configuration after the v2.15.0 upgrade, which would leave enforcement live with no registered key and every register and disclose call reverting until registration caught up. It is to deploy v2.14.0 first. That release keeps the 3-argument registerCommitment and the pre-signature proofPayload layout and adds no enforcement, so it is a drop-in that changes nothing the relayer sees -- the intermediate compatibility release this rollout would otherwise have to build, already merged to dev and simply never deployed. With it in place the prover key is registered while the old formats are still live, and the cutover shrinks to what genuinely cannot be avoided: the hub upgrade and the relayer deploy. Steps renumbered to 1-5 prerequisites, 6-7 cutover. Reported by CodeRabbit on #2267. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(contracts): link the prover libraries in the hardhat upgrade task The EIP-170 extraction added ProverSignatureLib and ProverAttestationLib and linked them in three places: the ignition hub module, the ignition upgrade module, and the test fixture. It missed a fourth, which is the one operators actually run -- `npx hardhat upgrade --contract IdentityVerificationHub`. tasks/upgrade/upgrade.ts builds its linkage from a string array rather than the `LibName: address` object the other three use, so a search for the object form did not surface it. With only the previous seven names in that array, getContractFactory throws on the unresolved link references and the upgrade cannot be deployed at all. Verified against the artifact rather than by inspection: the compiled bytecode's linkReferences name exactly nine libraries, and the task's list now matches that set with nothing missing and nothing extra. That comparison is the check worth repeating whenever a library is added, since the failure is invisible until deploy time. The deployHubV2 module's header comments enumerated the seven libraries and the verify-libs.js template listed their addresses; both now include the two new ones, so the documented verification step does not fail on an incomplete --libraries file. Reported by Codex review on #2267. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Nesopie <87437291+Nesopie@users.noreply.github.com> Co-authored-by: ayman <aymanshaik1015@gmail.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
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.
🚀 Weekly Release to Staging
Release Date: August 21, 2026
Release Branch:
release/staging-2026-08-21This automated PR promotes a snapshot of
devtostagingfor testing.What's Included
All commits merged to
devup to the branch creation time.Note: This PR uses a dedicated release branch, so new commits to
devwill NOT automatically appear here.Review Checklist
Next Steps
After merging, the staging environment will be updated. A production release PR will be created on Sunday.
This PR was automatically created by the Release Calendar workflow on August 21, 2026