Skip to content

fix(ci): publish linked-client-pr gate as a Check Run to escape the 1000-status cap - #187

Merged
WiktorStarczewski merged 1 commit into
mainfrom
wiktor/fix-linked-client-pr-gate
Jun 15, 2026
Merged

fix(ci): publish linked-client-pr gate as a Check Run to escape the 1000-status cap#187
WiktorStarczewski merged 1 commit into
mainfrom
wiktor/fix-linked-client-pr-gate

Conversation

@WiktorStarczewski

Copy link
Copy Markdown
Collaborator

Problem

The linked-client-pr-ready gate records its verdict as a commit status. GitHub caps each (commit SHA, status context) pair at 1000 statuses. This workflow re-evaluates every 15 minutes via cron, so a long-lived PR whose head SHA doesn't move steadily burns toward that cap. Once it's exhausted:

  • every further POST /statuses returns 422 ("maximum number of statuses"), and
  • the workflow swallows that 422 as a non-fatal warning — so the job goes green while the verdict never lands.

The net effect: a pending → success flip (e.g. when the linked miden-client PR finally ships in a release) is silently dropped, and the PR is left displaying a stale pending forever.

This was observed live on #25: miden-client#2059 shipped in v0.15.0, the gate correctly computed success, but the job log ended with status cap reached … cannot POST. Intended verdict: success while the visible status stayed frozen at a weeks-old pending / v0.14.9.

Fix

Publish the verdict as a Check Run named linked-client-pr-ready instead of a commit status:

  • No per-SHA cap. Check Runs aren't subject to the 1000-status limit, so a PR re-evaluated every 15 min can always record its current verdict.
  • Updated in place. One run is created per head SHA and PATCHed on each later evaluation (looked up via GET /commits/{sha}/check-runs?check_name=…), so there's no accumulation — which also removes the need for the old idempotency guard.
  • Fail loud, not silent. Any API error now surfaces as a red job (set -euo pipefail), instead of being downgraded to a warning. A verdict the gate can't record must never look like a pass.

Verdict mapping: ready → completed/success; pending → in_progress (blocks if required, flips to success on a later run when upstream catches up).

statuses: write is dropped from permissions (no longer used); checks: write was already present.

Compatibility / risk

  • linked-client-pr-ready is not currently a required check in either ruleset (main / next), so this change cannot block merges. If it's ever made required, rulesets match Check Runs by name exactly as they match status contexts — so requiring linked-client-pr-ready keeps working unchanged.
  • Self-healing: already-stuck PRs get a fresh Check Run (uncapped) on their next evaluation; the stale commit status becomes a harmless vestige.
  • pull_request_target semantics are unchanged — still base-ref checkout, still no PR-supplied code executed, only metadata reads + the Check Run write.

Testing

The workflow only executes from the base branch under pull_request_target/schedule, so the new code can't be exercised by this PR's own CI and is validated locally instead:

  • yaml.safe_load parses the workflow.
  • bash -n on the extracted gate script: clean.
  • The jq body builders produce valid Check Run payloads for both verdicts (in_progress without conclusion; completed with conclusion: success).
  • The existing-run lookup returns empty (→ create) for an empty list and the newest run id (→ patch) otherwise.

@WiktorStarczewski WiktorStarczewski added the no changelog PR doesn't need a CHANGELOG entry (trivial / non-user-visible) label Jun 15, 2026
@WiktorStarczewski
WiktorStarczewski merged commit 7791e28 into main Jun 15, 2026
36 of 37 checks passed
@WiktorStarczewski
WiktorStarczewski deleted the wiktor/fix-linked-client-pr-gate branch June 15, 2026 17:50
WiktorStarczewski added a commit that referenced this pull request Aug 4, 2026
* fix(ci): publish linked-client-pr gate as a Check Run to escape the 1000-status cap (#187)

* release: 0.15.1 (#198)

* chore(deps): upgrade miden-client to 0.15.2

Bumps miden-client + miden-client-sqlite-store 0.15.0 -> 0.15.2 (pulls
miden-note-transport-proto-build 0.4.1; miden-protocol resolves to
0.15.3). Updates the CI node-builder ref (MIDEN_CLIENT_REF) to the
v0.15.2 tag commit so the node's miden-protocol matches the bundled
client. 0.15.2 deprecates the no-hint Client::send_private_note; the
web-client keeps the no-hint path (allow(deprecated)) since the JS API
does not expose a block hint yet.

* release: 0.15.1

* fix(ci): make Rust crates publishable to crates.io (#199)

The "Publish Rust Crates on Release" job has failed since v0.15.0:
- web-client depends on js-export-macro via a bare path dep (no version),
  so cargo publish refuses it, and js-export-macro was never in the
  publish list.
- The Rust crates share the workspace version, which releases don't bump
  (only the npm versions move), so cargo publish hits "already exists".

Fixes:
- Bump the workspace version + internal dep version refs to 0.15.1.
- Add js-export-macro as a workspace dep (path + version); web-client uses
  it via workspace = true so the published manifest carries a version.
- Publish in dependency order (js-export-macro, idxdb-store, web-client,
  mobile-prover) and add the missing js-export-macro step.
- web-client publishes with --no-verify: it's a wasm cdylib that can't
  build for the host target; CI verifies it for wasm32 instead.
- Document the dual npm/Rust version bump in CONTRIBUTING.

Verified: js-export-macro/idxdb-store/mobile-prover dry-run-publish
cleanly at 0.15.1; web-client's manifest now resolves (its deps publish
first in the workflow).

* ci: allow crates-publish to be dispatched manually (#200)

Add a workflow_dispatch trigger (with an optional ref input) to
publish-crates-release.yml so the crates can be (re-)published off a
chosen ref — e.g. to catch crates.io up to a fix that landed after a
release tag, or to recover when a release's publish failed partway.
The checkout ref falls back from the release tag to the dispatched ref.

* feat(pswap): track PSWAP order lineages and expose reads + cancel-by-order (#176)

* feat(web-client,react-sdk): track PSWAP order lineages + cancel-by-order

Persist a lineage per partially-fillable swap order — the chain of remainder
notes a PSWAP leaves behind as it is filled round by round — keyed by a stable
orderId. Adds a `pswap` resource on MidenClient (lineages / lineagesFor /
lineage / cancelByOrder) and four React hooks (usePswapLineages,
usePswapLineagesFor, usePswapLineage, usePswapCancelByOrder).

- pswap.rs: lineage reads + build_pswap_cancel_by_order with a terminal-state
  guard (only Active lineages can be cancelled), covering the raw binding, the
  pswap.js resource, and the React hook through the one shared layer.
- PswapLineageRecord model exposes remainingOffered/remainingRequested as
  FungibleAsset (faucet + amount), depth, tip, state, and block numbers.
- applyTransaction routes through the high-level apply so registered tx
  observers (PSWAP tracking) fire.
- Tracks inicio-labs/miden-client vaibhav/pswap until the PSWAP API releases.

* fix(pswap): drop redundant JS terminal-state guard, binding is authoritative

The cancelByOrder resource duplicated the FullyFilled/Reclaimed check that
buildPswapCancelByOrder already enforces in Rust, and its comment described
the pre-guard behavior (request reaching the kernel). Remove the JS state
block so the binding is the single guard, matching usePswapCancelByOrder;
keep the lineage fetch for the creator account.

* chore(deps): adopt published miden-client 0.15.2; drop the fork pin

0.15.2 ships the PSWAP API on crates.io, so pin the version and drop the
inicio-labs/vaibhav/pswap git dependency. Adapt to 0.15.2's PswapLineageRecord:
remainingOffered/remainingRequested are now plain amounts (the record no longer
carries the faucet — it's recovered from the original note when needed), and the
created/updated block accessors are gone (fields dropped upstream). Suppress the
new send_private_note deprecation; the block-hint variant can be wired later.

* refactor: remove protocol crates (#197)

* feat(web-client): expose advice map accessors on TransactionRequest (#203)

* feat(web-client): expose advice map accessors on TransactionRequest

Add TransactionRequest.adviceMap() (returns a copy of the request's
advice map) and TransactionRequest.extendAdviceMap(adviceMap) (merges
entries into an already-built request and returns a new request, with
last-write-wins semantics on key collisions).

wasm-bindgen cannot return the native &mut AdviceMap, so this exposes
the immutable builder-style shape instead. It lets a signer/guardian
flow inject advice (e.g. a signature) that only becomes available after
the request object is constructed, without going back through the
builder. The pinned miden-client 0.15.2 already exposes the native
advice_map()/advice_map_mut() methods, so no upstream change is needed.

Closes #202

* docs(changelog): reference PR #203 for advice map entry

* fix(react): add advice map methods to TransactionRequest mock

The new TransactionRequest.adviceMap() / extendAdviceMap() methods made
the hand-rolled mock in the react-sdk tests structurally incompatible
with the TransactionRequest type, failing the typecheck. Add both to the
shared createMockTransactionRequest factory.

* release: 0.15.2 (#205)

* fix: declare MSRV for js-export-macro to fix nightly (#209)

* fix(web): export full public surface from the node entry (#206)

* fix(web): export full public surface from the node entry

The node export entry (js/node-index.js, used via the "node" condition for
SSR / Next.js server / Vitest) re-exported only a hand-curated subset of the
WASM classes. The browser entry exports the full surface and the .d.ts types
advertise it, so importing an omitted class (e.g. BasicFungibleFaucetComponent,
TransactionRequest, InputNoteRecord) — or a @miden-sdk/react hook that imports
one — threw "X is not exported from '@miden-sdk/miden-sdk'" under node
resolution.

Add _reexport lines for every public napi class (71 were missing), plus the
JS-layer helpers react needs (CompilerResource, getWasmOrThrow). ESM can't
re-export a native addon's members dynamically, so the names are listed
explicitly; node_export_parity.node.test.ts enforces completeness against the
napi module so the two can't silently drift again.

* docs(test): tighten node export parity comment per review

* refactor(web): generate node re-exports instead of hand-maintaining them

Per review: replace the hand-written napi re-export list (and the runtime
parity test) with a codegen script that derives the _reexport block from the
native module's exports, written into a <generated:napi-reexports> marker
region. `gen:node-reexports` regenerates it; `check:node-reexports` (run in the
node CI job, which already builds the napi binary) fails if it drifts. The
hand-written remaps (WebClient -> WasmWebClient, the AccountType/AuthScheme enum
shadows) and JS-layer helpers (CompilerResource, getWasmOrThrow) stay manual.

* release: 0.15.3 (#210)

* feat: add batch builder implementation (#31)

* feat(web,react): add AggLayer bridge-out (B2AGG) note support (#211)

* feat(web,react): add AggLayer bridge-out (B2AGG) note support

Enables creating and submitting a B2AGG (Bridge-to-AggLayer) note entirely
within web-sdk by consuming the agglayer functionality already re-exported by
the bundled miden-client (miden_client::agglayer) — no new dependency and no
miden-client change.

- EthAddress JS model (20-byte Ethereum address)
- Note.createB2AggNote(...) and WebClient.newB2AggTransactionRequest(...)
- client.transactions.bridge(...) resource method (+ preview support)
- @miden-sdk/react useBridge() hook
- docs (CHANGELOG, READMEs, react-sdk guide) and unit tests

Closes #173

* test(web): pin B2AGG resource argument order; drop changelog version floor

Review follow-up:
- transactions.test.js now asserts the resolved sender/bridge/faucet/destination
  ids by position, so a sender<->bridge<->faucet swap in #buildB2AggRequest would
  fail the test (the most important correctness property of this change).
- CHANGELOG: drop the "(0.15.1+)" miden-client floor — unverifiable for the exact
  B2AggNote::create surface; the consumer-relevant fact is "no new dependency".

* fix(web): use as_chunks for nightly clippy chunks_exact_to_as_chunks lint

Pre-existing line surfaced by nightly clippy drift (the lint denies a
constant-size chunks_exact under -D warnings); not related to the B2AGG
feature, but it blocks this PR's Clippy WASM gate. Applies clippy's own
suggestion; as_chunks is stable at the project MSRV (1.93).

* fix(web-client): forward toU64s() on StorageResult (#194)

* fix(web-client): forward toU64s() on StorageResult

StorageResult (returned by StorageView.getItem/getMapItem) is a Word-like
wrapper that forwarded toFelts()/toHex()/toBigInt() but not toU64s(), while
being typed as Word. Reading raw u64 elements off a storage value — e.g.
`account.storage().getItem(slot).toU64s()` — threw "toU64s is not a function"
at runtime even though Word.toU64s() is declared on the type. This blocked
the OpenZeppelin multisig client's AccountInspector (which inspects every
multisig account on load), and thus every guardian transaction.

Add the missing pass-through (mirroring toFelts) plus the .d.ts declaration
and a test assertion.

* docs(changelog): add entry for StorageResult.toU64s() fix

* feat(web): expose full faucet metadata on BasicFungibleFaucetComponent (#204)

* feat(web): expose full faucet metadata on BasicFungibleFaucetComponent

Add tokenName(), tokenSupply(), description(), logoUri(), and externalLink()
to BasicFungibleFaucetComponent so consumers can extract the complete token
metadata of any fungible-faucet account (basic or network-style).

In miden-standards 0.15.x the separate BasicFungibleFaucet and
NetworkFungibleFaucet types were unified into a single FungibleFaucet
component (the basic-vs-network distinction is now account configuration,
not a component type), so the existing binding already works on network
faucet accounts -- this just surfaces the metadata it wasn't exposing. A
dedicated NetworkFungibleFaucet binding (as #162 originally proposed) would
not compile against the pinned deps.

Also adds BasicFungibleFaucetComponent to the typedoc curated exports.

* fix(test): normalize null to undefined for faucet metadata getters on Node

The new BasicFungibleFaucetComponent.description() / logoUri() /
externalLink() getters return Rust Option<String>, which napi maps to
null on Node.js (wasm-bindgen maps None to undefined on the browser).
The shared Playwright test asserts toBeUndefined(), so the Node project
failed. Register the three getters with the node-adapter's existing
patchNullToUndefined shim, matching AccountStorage / NoteConsumability.

* fix(test): normalize faucet metadata getters in the active node sdk path

The previous attempt patched node-adapter.ts, but the per-test sdk is
built by test-setup.ts's createNodeSdkWrapper -> patchNapiPrototypes, so
that patch never ran on the test path (description() still returned napi
null, failing toBeUndefined() on the Node project). Register the three
BasicFungibleFaucetComponent getters in patchNapiPrototypes instead, and
revert the node-adapter.ts change. Verified locally: the nodejs project's
basic_fungible_faucet_component tests pass.

* release: 0.15.4 (#213)

* chore: prepare 0.15.4

* chore: drop empty Unreleased header from 0.15.4 changelog

* fix(release): bump wallet example @miden-sdk/miden-sdk dep to ^0.15.4

* chore(release): bump wallet example @miden-sdk/react dep to ^0.15.4

* feat(web,react): create custom-script network notes (NetworkAccountTarget attachments) (#230)

* feat(web): add NetworkAccountTarget WASM binding

* feat(web): add Note.withAttachments/attachments/isNetworkNote bindings

* feat(web): add NoteRecipient.fromScript (random serial)

* fix(web): register NoteAttachment and NoteExecutionHint for napi by-value params

* feat(web): export NetworkAccountTarget on node + typedoc surfaces

* feat(web): declare NetworkNoteOptions/Result + createNetworkNote/buildNetworkNote types

* feat(web): add standalone buildNetworkNote builder

* fix(web): reject recipient+script and pin buildNetworkNote test assertions

buildNetworkNote silently dropped `script` when both `recipient` and
`script` were passed (via `??`); now throws instead, matching the
"exactly one of recipient/script" contract. Also strengthens the
NetworkAccountTarget construction test to pin executionHint as the
second constructor arg, and adds coverage for the pre-built-target and
assets-provided branches.

* feat(web): add transactions.createNetworkNote resource method

* fix(web): wrap script-recipient inputs in FeltArray for network notes

buildNetworkNote and createNetworkNote passed a plain JS array straight
into `new wasm.NoteStorage(...)`, which works under the napi array
polyfill but throws `expected instance of FeltArray` against the real
browser WASM bindings, breaking the script + inputs path outside of
mocked unit tests.

* test(web): integration gate for network-note attachment survives submit

* feat(react): add CreateNetworkNoteOptions/NetworkNoteResult types

* feat(react): export CreateNetworkNoteOptions/NetworkNoteResult from package barrel

* feat(react): add useCreateNetworkNote hook

* docs: document network-note creation (web + react)

* fix(web): type NetworkNoteOptions.attachment as bigint[] to match runtime

* refactor(react): reuse target.targetId() in useCreateNetworkNote

* docs: link network-note CHANGELOG entries to web-sdk#230

* docs: link CHANGELOG entries to #230 and amend stale NetworkAccountTarget comment

- CHANGELOG: use the linked ([#230](url)) form to match repo convention (per @juan518munoz).
- note_attachment.rs: NetworkAccountTarget is re-exposed by this PR, so drop the
  'type does not exist on this surface' clause (per @igamigo / #228).

* fix(web): wrap network-note `inputs` bigints into Felt before FeltArray

createNetworkNote / buildNetworkNote passed raw `inputs` into `new FeltArray(...)`,
which throws `expected instance of Felt` against real WASM for any non-empty
inputs. The React hook already wrapped each value in `new Felt(v)`; the
web-client resource + standalone builders did not, and the unit tests only
passed because `FeltArray` was mocked (the exact marshaling gap this feature's
integration test was meant to close).

- transactions.js / standalone.js: map `inputs` through `new wasm.Felt(v)`.
- api-types.d.ts: `inputs?: Felt[]` -> `bigint[]`, matching the React surface
  and the sibling `attachment` field.
- unit tests: assert the Felt-wrapping (Felt mock + FeltArray/NoteStorage args).
- network_note integration test: exercise non-empty `inputs` marshaling end to
  end against real WASM, not a mock.
- useCreateNetworkNote: reuse `senderId` for submit instead of re-parsing.

Found by independent review of #230.

* chore: prepare 0.15.5 (#232)

* feat: re-add removed ntx functionality (#236)

* chore: upgrade to client `0.15.4` (#238)

* feat(web): expose AssetCallbackFlag on FungibleAsset (#240)

* feat: expose manual transaction lifecycle on TransactionsResource (#235)

* feat(web): expose manual transaction lifecycle on TransactionsResource

Adds the four stages that submit() runs in one call as individual public
methods, so each step can be benchmarked and error-handled independently
(closes #233):

  const result = await client.transactions.executeRequest(account, request);
  const proven = await client.transactions.prove(result, { prover? });
  const { blockNumber } = await client.transactions.submitProven(proven, result);
  await client.transactions.apply(result, blockNumber);

- executeRequest: execute only — nothing proven, submitted, or persisted
- prove: per-call prover override, falls back to the client default
- submitProven: network submission, returns { blockNumber }
- apply: persists to the local store and fires transaction observers

Docs: api-types JSDoc (+ ProveOptions / SubmitProvenResult types,
ProvenTransaction / TransactionResult / TransactionStoreUpdate typedoc
re-exports), Docusaurus transactions page, web-client README, CHANGELOG
under 0.15.6 (TBD). Covered by resource unit tests and a mock-chain
integration test driving the four stages end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(web): staged handles for manual transaction lifecycle

Replace the flat executeRequest/prove/submitProven/apply methods with a
staged pipeline: executeRequest() returns a TransactionExecution handle
advanced via .prove() -> .submit() -> .apply(). Each stage carries its own
context (result, proof, blockNumber), so callers never re-thread state and
out-of-order calls are unrepresentable.

- submit() now shares the prover-fallback helper (proveResult) with the
  staged path, so the two can no longer drift.
- submitProven(proof, result) kept as the detached-proving escape hatch,
  now returning a TransactionSubmission handle.
- TransactionSubmission.waitForConfirmation() added.
- JSDoc documents the prover single-use hazard and the non-atomicity of
  the stages as a group.

Docs updated across CHANGELOG, README, Docusaurus, and api-types JSDoc.
Unit + mock-chain integration tests drive the staged handles.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Wiktor Starczewski <poszerny@gmail.com>

* release: 0.15.7

* feat(web): add BasicFungibleFaucetComponent.fromAccountStorage (#244)

* feat: add BasicFungibleFaucetComponent.fromAccountStorage with tests

* docs: changelog entry for fromAccountStorage

* fix: address review — unwrap StorageView in fromAccountStorage facade, add #243 differentiator test

* fix: patch fromAccountStorage via defineProperty — napi statics are read-only

* fix: accept StorageView in napi fromAccountStorage via FromNapiValue fallback

* feat(web): expose fungible asset vault key helpers (#247)

* feat(web): expose fungible asset vault key helpers

* docs: add fungible asset vault key changelog

* test(web): normalize fungible asset value arrays

* refactor(web): reshape vault round-trip to FungibleAsset.fromVaultEntry(key, value)

Addresses the API-shape review of the vault-key helpers.

- fromVaultKey(key, amount) -> fromVaultEntry(key, value): take the (key, value)
  word pair the vault actually stores. This mirrors the native
  from_key_value_words primitive and pairs 1:1 with the model's own getters, so
  `FungibleAsset.fromVaultEntry(a.vaultKey(), a.intoWord())` round-trips an asset
  read from vault data with zero decoding — previously a caller holding both
  words had to decompose the value word back to a scalar amount. Deletes the
  internal value-word re-encoding (which duplicated native to_value_word) and the
  redundant AssetAmount re-validation (from_key_value_words already validates),
  and the name no longer understates its inputs.
- Make the key/value duality legible: vaultKey() (key word — faucet id + callback
  flag) and intoWord() (value word — amount) now cross-reference each other and
  fromVaultEntry; fromVaultEntry documents that the callback flag comes from the
  key. intoWord() is left in place (released API), documented as the value half.
- Tests round-trip via the two getters and reject an oversized amount encoded
  into the value word plus an invalid key.

Verified: cargo check --package miden-client-web --target wasm32-unknown-unknown (clean).

* feat(web): add FungibleAsset.fromVaultKey(key, amount) convenience

Restores the key + scalar amount ergonomic as a distinctly-named convenience
alongside the symmetric fromVaultEntry(key, value): use fromVaultEntry when you
already hold both vault words, fromVaultKey when you have the key word and the
amount as a number. The key supplies the faucet id + callback flag; the amount
is encoded into the value word (layout mirrors native to_value_word, noted so
the two stay in lockstep). Test asserts fromVaultKey(key, amount) yields the
same asset as fromVaultEntry(key, value).

Verified: cargo check --package miden-client-web --target wasm32-unknown-unknown
and cargo fmt --check (clean).

* test(web): cover value-word rejection paths + correct max-amount docs

Review follow-ups:
- Correct the documented amount ceiling from 2^63 - 1 to the real
  AssetAmount::MAX (2^63 - 2^31) in the fromVaultEntry / fromVaultKey docs and
  the test comment (the enforced max is 2^63 - 2^31, not 2^63 - 1).
- Add rejection tests for the two public error branches that were uncovered:
  fromVaultKey's own AssetAmount::new guard (over-max scalar amount), and a
  value word with non-zero upper limbs fed through fromVaultEntry.

Verified: cargo check --package miden-client-web --target wasm32-unknown-unknown
and cargo fmt --check (clean).

* docs(changelog): file vault-entry feature under 0.15.8, not released 0.15.7

v0.15.7 is already tagged/published and did not contain these APIs, so the
fromVaultEntry/fromVaultKey/vaultKey entry was misfiled under its section.
Merged main (which carries the `## 0.15.8 (TBA)` staging section) and moved the
entry there. package.json stays at 0.15.7, matching the repo's release flow
(the version is bumped at release time, not when staging changelog entries).

* style: prettier-format fungible asset test

---------

Co-authored-by: Wiktor Starczewski <poszerny@gmail.com>

* release: 0.15.8 (#248)

* docs(readme): document single-threaded vs multi-threaded WASM builds (#250)

Co-authored-by: WiktorStarczewski <wiktor.s@miden.team>

---------

Co-authored-by: igamigo <ignacio.amigo@lambdaclass.com>
Co-authored-by: Wiktor Starczewski <poszerny@gmail.com>
Co-authored-by: VaibhavJindal <vaibhavjindal29@gmail.com>
Co-authored-by: Utkarsh Sharma <114555115+0xnullifier@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: WiktorStarczewski <wiktor.s@miden.team>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changelog PR doesn't need a CHANGELOG entry (trivial / non-user-visible)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant