Skip to content

fix: restore green CI — 23 type errors and 28 failing tests - #1038

Open
arkhindi12 wants to merge 2 commits into
conduit-protocol:mainfrom
arkhindi12:fix/restore-green-ci
Open

arkhindi12 wants to merge 2 commits into
conduit-protocol:mainfrom
arkhindi12:fix/restore-green-ci

Conversation

@arkhindi12

@arkhindi12 arkhindi12 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

main has not typechecked since 2026-10-31. CI has been red for 36 consecutive runs: Lint & typecheck and Test both fail, which skips Build, @streamfi/react, and the bundle-size check.

Before:  23 type errors   28 failed / 1615 passed   lint 14 errors
After:    0 type errors    0 failed / 1643 passed   lint  0 errors

CI on this branch — every job that main never reached now passes:

Lint & typecheck    pass
Test                pass   (2m18s)
Build               pass
Bundle size check   pass
Size limit          pass
Build TypeDoc       pass
@streamfi/react     fail   <- pre-existing, unrelated (see below)

main has never gotten past Lint & typecheck, so Build, @streamfi/react, the bundle-size check and the E2E suite have been skipped on every run since 2026-10-31. This PR is the first branch to reach them.

Reproduced locally with a clean npm ci, and the local output matches the CI log line for line — so this is the repo, not an environment difference.

Most of these are runtime bugs, not type noise

Several errors reference identifiers that do not exist in scope, which means the affected paths cannot have executed since they were merged.

custom-fetch.ts — the whole adapter was dead code

sendOnce referenced four names that exist nowhere in the file:

src/custom-fetch.ts(133,44): Cannot find name 'customFetch'.
src/custom-fetch.ts(147,13): Cannot find name 'controller'.
src/custom-fetch.ts(154,17): Cannot find name 'registration'.
src/custom-fetch.ts(157,9):  Cannot find name 'timer'.
src/custom-fetch.ts(157,43): Cannot find name 'timer'.
src/custom-fetch.ts(158,49): Cannot find name 'onCallerAbort'.

It builds its abort plumbing from controller, timer and onCallerAbort, and calls registration.fetch even though registration is local to fetchAdapter. As written the function cannot compile, so every custom-fetch RPC request was unreachable. src/tests/custom-fetch-gateway-retry.test.ts failed as a direct consequence and passes now.

Rebuilt around a single AbortController so the caller's signal and the request timeout both cancel the in-flight fetch (they cannot be two independent signals), and wired up registration.timeoutMs — stored by registerCustomFetch but never read, so the documented per-registration timeout silently did nothing.

soroban.ts — ledger timestamp was always NaN

timestamp: Math.floor(new Date(ledger.closedAt).getTime() / 1000),

getLatestLedger returns closeTime, not closedAt. Verified against a live testnet node:

{"jsonrpc":"2.0","id":1,"result":{"id":"5fe0c99c…","protocolVersion":30,
 "sequence":5555414,"closeTime":"1793800710","headerXdr":"…"}}

@stellar/stellar-sdk 12.3.0 omits the field from GetLatestLedgerResponse entirely, which is why this typechecked in isolation but failed at runtime. new Date(undefined) gives NaN, so getLatestLedgerInfo returned timestamp: NaN on every uncached call. Now reads closeTime through a narrowed cast and falls back to now when absent.

client.ts — RPC failover could never run

streams, governor and tokens were readonly but reassigned in tryFailoverRpc(). That does not compile, so the failover path was unreachable — the client would keep hammering a failing RPC URL forever. Dropped readonly (the fields are genuinely mutable across failover) and guarded the index lookup, which noUncheckedIndexedAccess widens to string | undefined.

with-retry.ts — Retry-After was parsed, then destroyed

RateLimitError already carried retryAfterMs and withRetry already read it — then ran it through jitter:

const baseWait = retryAfterMs ?? delay;
const waitTime = applyJitter(baseWait, jitter, prevDelay);  // full: random in [0, baseWait]

With the default full jitter, a Retry-After: 2 directive could retry after 0ms — immediately re-triggering the 429 it was told to back off from. A server-supplied Retry-After is a directive, not an estimate; jitter now applies only to client-guessed backoff.

Two merged features had been silently dropped

Worth flagging separately, because it suggests a process gap rather than a one-off.

simulateBatch (#959) — added by #1034 (edc14d66, merged 2026-10-22 21:56), then removed when #1026 (9c71ec1, committed 19:10 the same day) rewrote batch-tx.ts from an older base:

git show edc14d66:src/batch-tx.ts | grep -c simulateBatch   -> 2
git show 9c71ec1:src/batch-tx.ts  | grep -c simulateBatch   -> 0

index.ts kept exporting it, so consumers importing simulateBatch from the package root got undefined — a silently broken public API rather than a loud failure.

StreamStatus / deriveStreamStatus — same shape. Added by 1662982 ("feat(types): add StreamStatus enum and deriveStreamStatus helper"), absent from types/index.ts on main, with stream-status.test.ts left behind testing it (7 failures).

Both are restored verbatim from their original commits rather than reimplemented, so the semantics are exactly what was reviewed and merged. preBatchSimulate and BatchSimulationResult were unchanged since, so the restore is a clean re-application.

Overload fixes

onStreamEvent in events.ts and streams.ts declared overloads whose narrower single-parameter callbacks were incompatible with the implementation signature under strictFunctionTypes (TS2394). The implementation now takes the union of the two callback shapes.

Test fixes

Four of these were tests asserting something the code never promised:

  • streams.test.ts — three call sites used a 55-character contract address, so token validation threw before reaching the durationSeconds / ratePerSecond assertions. Confirmed invalid via StrKey.isValidContract; swapped for the valid 56-character address already used elsewhere in the same file. The validation order is correct as-is.

  • soroban-retry-after.test.ts — the no-header fallback test asserted an exact 500ms boundary while default full jitter picks a random delay in [0, 500], so it passed only by luck. Stubbed Math.random rather than changing the production default, since every other withRetry test already pins jitter: 'none' explicitly.

  • relayer-stress.test.ts — the heartbeat tests awaited connect() with fake timers installed, but the mock fires onopen via setTimeout(0), which never ran. connect() therefore hung until the 5s test timeout. Timers are now advanced around the await. File runtime: 26s → 1s.

  • freighter-wallet-adapter.test.ts, batch-tx-simulate-batch.test.ts — mock and fixture shapes adjusted for exactOptionalPropertyTypes.

@streamfi/react still fails — pre-existing and separate

Once Lint & typecheck started passing, @streamfi/react ran for the first time and failed with 20 type errors across 10 files. This PR does not touch packages/react at all — git diff --name-only origin/main...HEAD | grep packages/react is empty — and none of the errors involve code I changed.

The headline failure is an unfinished feature: StreamFiProvider reads rpcHeaders and calls makeHeaderFetch:

const customFetch = rpcHeaders && Object.keys(rpcHeaders).length > 0
  ? makeHeaderFetch(rpcHeaders)
  : undefined;

Neither exists — rpcHeaders is not a member of StreamFiProviderProps (which has only config, defaultNetwork, children, queryClient), and makeHeaderFetch appears nowhere in the repo. So the header-forwarding feature was merged as a call site without its prop or its helper. The remaining errors are the familiar exactOptionalPropertyTypes and rootDir shapes.

Reproducible on a clean checkout of this branch:

cd packages/react && npm ci --ignore-scripts && npm run build
-> 4 errors, all in StreamFiProvider.tsx

I left this alone deliberately: fixing it means deciding what makeHeaderFetch should do (merge headers into ConduitConfig.customFetch?) and adding a public prop — a design decision for whoever owns the React package, not something to invent inside a CI-recovery PR. Say the word and I will take it as a follow-up.

Lint errors cleared

Lint & typecheck runs lint before typecheck, so the 13 pre-existing lint errors would have kept the job red even with a clean typecheck. Cleared all 13:

  • unused imports dropped — StrKey, SorobanRpc (only ever appeared in prose and the SorobanRpcError class name), TopUpEvent, ClawbackEvent, BatchWithdrawItem.
  • client.ts — require("./soroban.js") inside tryFailoverRpc became a static import of getConsecutiveTimeouts; soroban.ts does not import client.ts, so no cycle needed the lazy form.
  • walletconnect.ts — (...args: any[]) → unknown[] on the emitter shims.
  • builder.ts — (builder as any)._startTime → narrow { _startTime: number } cast.
  • signer.ts — the lazily imported @ledgerhq modules now carry a local LedgerStrApp interface matching LedgerDevice, plus an explicit create guard. The old any was hiding that a missing create threw a TypeError instead of the intended LedgerHardwareError.
  • indexer.ts — while (true) → for (;;) in fetchAll.
  • streams.ts — the operation bag stays Record<string, any> with a scoped disable and a justification comment; each switch branch narrows the keys it reads, and tightening it to unknown produced 16 errors.

Warnings are unchanged at 54.

Verified on a clean checkout: tsc --noEmit clean, eslint src --ext .ts 0 errors, vitest run 1643/1643 across 150 files, npm run build succeeds.

zaguza added 2 commits November 4, 2026 11:19
`main` has not typechecked since 2026-10-31; CI has been red for 36
consecutive runs. `Lint & typecheck` and `Test` both fail, which skips
`Build`, `@streamfi/react`, and the bundle-size check.

Most of these are not type-only problems: several reference identifiers
that do not exist in scope, so the affected paths could not have executed
since they were merged. Two features that were merged were then silently
dropped by a later merge and are restored here from their original commits.

Typecheck: 23 errors -> 0
Tests:     28 failed / 1615 passed -> 0 failed / 1643 passed
Lint:      14 errors -> 13 (none introduced)

Runtime bugs fixed
------------------

* custom-fetch: `sendOnce` referenced `controller`, `timer`, `onCallerAbort`
  and `registration`, none of which existed in the file. The function could
  not compile, so every custom-fetch RPC request was dead code. Rebuilt the
  abort plumbing around a single AbortController so the caller's signal and
  the request timeout both cancel the in-flight fetch, and wired up
  `registration.timeoutMs`, which was stored but never read.

* soroban: `getLatestLedgerInfo` read `ledger.closedAt`, which does not exist.
  `getLatestLedger` returns `closeTime` (Unix seconds, as a string) — verified
  against a live testnet node and the JSON-RPC schema. `new Date(undefined)`
  yielded NaN, so the returned ledger timestamp was always NaN. Read the
  correct field and fall back to now when it is absent.

* client: `streams`, `governor` and `tokens` were declared `readonly` and then
  reassigned in `tryFailoverRpc()`. That did not compile, which means RPC
  failover could never run. Dropped `readonly` (the fields are genuinely
  mutable across failover) and guarded the index lookup against
  `noUncheckedIndexedAccess` widening it to `undefined`.

* with-retry: a server-supplied `Retry-After` was parsed and then destroyed by
  jitter — `Math.random() * delay` meant a 2s directive could retry after 0ms,
  immediately re-triggering the 429. Jitter now applies only to client-guessed
  backoff, since `Retry-After` is a directive rather than an estimate.

Features restored (merged, then dropped by a later merge)
---------------------------------------------------------

* `simulateBatch` / `BatchPayload` / `BatchSimulationSummary` (conduit-protocol#959): added by
  conduit-protocol#1034, removed when conduit-protocol#1026 rewrote `batch-tx.ts` from an older base.

* `StreamStatus` / `deriveStreamStatus`: added by 1662982, absent from
  `types/index.ts` on main. Restored verbatim.

Both were re-added from their original commits rather than reimplemented.

Overload fixes
--------------

`onStreamEvent` in both `events.ts` and `streams.ts` declared overloads whose
narrower single-parameter callbacks were incompatible with the implementation
signature under `strictFunctionTypes`. The implementation now takes the union
of the two callback shapes.

Test fixes
----------

* streams.test.ts: three call sites used a 55-character contract address, so
  token validation failed before reaching the duration/rate assertions. Swapped
  for the valid 56-character address already used elsewhere in the file.

* soroban-retry-after.test.ts: the no-header fallback test asserted an exact
  500ms boundary while the default `full` jitter picks a random delay in
  [0, 500]. Stubbed Math.random instead of changing the production default.

* relayer-stress.test.ts: the heartbeat tests awaited `connect()` while fake
  timers were installed, but the mock fires `onopen` via `setTimeout(0)` —
  which never ran, so `connect()` hung until the 5s test timeout. Now advance
  timers around the await. File runtime drops from 26s to 1s.

* freighter-wallet-adapter.test.ts / batch-tx-simulate-batch.test.ts: mock and
  fixture shapes adjusted for `exactOptionalPropertyTypes`.
`Lint & typecheck` runs lint before typecheck, so the 13 pre-existing lint
errors on main kept the job red even after the typecheck step passed.

Lint: 13 errors -> 0 (warnings unchanged at 54)

* unused imports dropped: `StrKey` (constants), `SorobanRpc` (errors — it only
  appeared in prose and the `SorobanRpcError` class name, never as a value),
  `TopUpEvent` / `ClawbackEvent` (events), `BatchWithdrawItem` (streams).

* client: `require("./soroban.js")` inside `tryFailoverRpc` replaced with a
  static import of `getConsecutiveTimeouts`. `soroban.ts` does not import
  `client.ts`, so there is no cycle to protect against with a lazy require.

* walletconnect: `(...args: any[])` -> `unknown[]` on the emitter shims. The
  listener bodies never touch the arguments.

* builder: `(builder as any)._startTime` -> a narrow
  `{ _startTime: number }` cast, keeping the accessed shape explicit.

* signer: the lazily imported `@ledgerhq` modules now carry a local
  `LedgerStrApp` interface matching `LedgerDevice`, plus an explicit guard on
  `TransportWebUSB.create`. The previous `any` was hiding the fact that a
  missing `create` would throw a TypeError rather than the intended
  `LedgerHardwareError`.

* indexer: `while (true)` -> `for (;;)` in `fetchAll`. Same semantics, but the
  loop no longer trips `no-constant-condition`.

* streams: the operation bag stays `Record<string, any>` with a justification
  comment and a scoped disable. A concrete type here would have to be a union
  of every operation variant, and each switch branch already narrows the keys
  it reads — tightening it to `unknown` produced 16 errors.

Verified: tsc --noEmit clean, eslint 0 errors, vitest 1643/1643, build ok.
@arkhindi12

Copy link
Copy Markdown
Contributor Author

Cross-PR verification: both green together

The two PRs are mutually blocking in CI, so neither can prove the other:

To prove they are green together rather than just locally, I built a throwaway branch on top of main with both PRs' commits (2d748b1 + 5e98cd4 from this PR, 1e29484 from #1039) and ran the real CI matrix on it — PR #1040, a draft used only to trigger the workflow:

@streamfi/react            pass   1m38s
Bundle size check          pass     59s
Build                      pass   1m11s
Build TypeDoc              pass   1m09s
Lint & typecheck           pass   1m04s
Size limit                 pass     50s
Test                       pass   2m14s

7/7 pass, including the @streamfi/react job that has failed on every run in this repository. No skipped jobs besides the ones that are always skipped (Browser compatibility (E2E) and Deploy to GitHub Pages only run on main).

Local state on that combined branch:

SDK (root)      tsc --noEmit   0 errors
packages/react  tsc --noEmit   0 errors
packages/react  vitest         111/111 passed

Merge order

  1. This PR (fix: restore green CI — 23 type errors and 28 failing tests #1038) — turns Lint & typecheck, Test, Build etc. green, which un-skips Build and therefore @streamfi/react.
  2. fix(react): build @streamfi/react again — 20 type errors, 40 failing tests #1039 — will then run @streamfi/react and pass on its own, no further changes needed.

The two branches touch disjoint files (git diff --name-only for #1039 is entirely under packages/react/), so they can be reviewed in parallel — but they must merge in the order above for CI to stay green in between.

I am closing the temporary verification PR now; it contains no changes of its own.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant