Skip to content

test: pin cyclo-site-tokens.json claims on-chain via fork tests - #42

Open
thedavidmeister wants to merge 28 commits into
mainfrom
2026-05-05-cyclo-site-decimals-fork-test
Open

test: pin cyclo-site-tokens.json claims on-chain via fork tests#42
thedavidmeister wants to merge 28 commits into
mainfrom
2026-05-05-cyclo-site-decimals-fork-test

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented May 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

canonical/cyclo-site-tokens.json is the single source of truth for every cyToken's vault, decimals, underlying, underlying decimals, receipt, and chain. cyclo.site will consume this same JSON in a follow-up PR; for now both repos stay in sync by hand and the fork test guards the on-chain claims for the values it can verify.

For every entry on the test's chain (filtered by chainId), the fork test asserts:

  • vault decimals() matches the JSON
  • underlying decimals() matches the JSON
  • vault asset() matches the JSON's underlyingAddress

receiptAddress is not verified — older vault impls (cysFLR) don't expose receipt() as a getter. Follow-up tracked at #43.

Closes cyclofinance/cyclo.site#370 in part (decimals + asset linkage).

Test plan

  • Flare suite locally: 1 test, all entries (cysFLR / cyWETH / cyFXRP) pass on a public RPC
  • Arbitrum suite locally: same trie-state error the existing prod arbitrum tests hit on a non-archive RPC; CI's RPC_URL_ARBITRUM_FORK is archive-capable
  • CI green

🤖 Generated with Claude Code

thedavidmeister and others added 2 commits May 5, 2026 12:21
Two fork-test files (one per chain) that assert IERC20Metadata.decimals()
on every cyToken vault and its underlying ERC20 matches the value
hardcoded in cyclofinance/cyclo.site:src/lib/stores.ts. Drift in either
direction fails CI here, so PR review on either repo surfaces the
discrepancy.

Coverage:
  Flare: cysFLR (sFLR), cyWETH (Stargate WETH), cyFXRP (FAsset XRP)
  Arbitrum: cyWETH/cyWBTC/cycbBTC/cyLINK/cyDOT/cyUNI/cyPEPE/cyENA/cyARB/
    cywstETH/cyXAUt/cyPYTH (.pyth variants)

Local: Flare suite 6/6 pass on a public RPC. Arbitrum requires an
archive-capable RPC (the non-archive public RPC fails for the pinned
PROD_TEST_BLOCK_NUMBER_ARBITRUM with the same trie-state error the
existing Arbitrum prod tests get). CI RPC_URL_ARBITRUM_FORK is archive-
capable.

Closes cyclofinance/cyclo.site#370 (in part — covers the decimals
verification half; the broader on-chain-claim verification asset()/
receipt() linkage stays open under that issue).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the prior hardcoded decimals tests with a fixture-driven
approach. canonical/cyclo-site-tokens.json is the single source of
truth for every cyToken's vault address, decimals, underlying address,
underlying decimals, receipt address, and chain. cyclo.site is
expected to consume this same JSON in a follow-up PR; for now both
repos are kept in sync by hand and the foundry test guards the
on-chain claims for the values it can verify.

For every entry on the test's chain, the fork test now asserts:
  - vault decimals() matches the JSON
  - underlying decimals() matches the JSON
  - vault asset() matches the JSON's underlyingAddress

receiptAddress is left unverified — the older cysFLR vault
implementation doesn't expose receipt() as a getter. Tracked at #43.

Local: Flare suite passes on a public RPC. Arbitrum requires an
archive-capable RPC (same trie-state error the existing prod arbitrum
tests hit on a non-archive RPC); CI's RPC_URL_ARBITRUM_FORK is archive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister changed the title test: pin on-chain decimals against cyclo.site stores.ts claims test: pin cyclo-site-tokens.json claims on-chain via fork tests May 5, 2026
thedavidmeister and others added 3 commits May 5, 2026 12:52
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous pin (455000000) is outside the CI RPC's archive window;
all prod arbitrum suites fail at setUp() with "missing trie node /
state not available". Bump forward to a block the RPC can serve.

Verified locally at the new pin: 44/45 prod arbitrum tests pass. The
one regression is testProdCycloVaultCanMintArbitrum, a fuzz test that
panics with division-by-zero at calldata 0x3a99 (15001) — the vault
state at this block produces a zero in the mint preview's divisor.
This is state-sensitive and pre-exists my changes; flag to address
separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pyth feeds aren't all fresh at the pinned block. Per-vault helper
mints if previewMint succeeds, otherwise pins the full revert chain:
the bare try/catch in `_nextId()` swallows `StalePrice()` and returns
id=0, which divides-by-zero in `_calculateMint`. Assert both ends —
the inner Pyth selector and the outer Panic(0x12) — so the test
documents the swallow site as well as passing deterministically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thedavidmeister and others added 4 commits May 6, 2026 10:00
Public Arbitrum RPC keeps a bounded archive window. The previous pin
459580000 was outside that window and surfaced as universal trie-state
errors on CI. Pin to a recent block so CI's archive RPC has full state
for the tests to run against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`testProdCycloVaultMintRevertsOnStalePythSwallowArbitrum` mocks the
WETH oracle's `price()` to revert with `StalePrice()`, then asserts
`previewMint` surfaces `Panic(0x12)`. Real vault, real `_nextId()`
try/catch — only the oracle revert is forced, so the test is
independent of which Pyth feeds happen to be fresh at the pinned
block.

Mutation-tested: changing the expected panic from 0x12 to 0x11 makes
the test fail with the exact mismatch shown by forge — confirming the
assertion is sensitive to div-by-zero specifically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hardcoded prices break every pin bump because the value depends on
the exact block. The public RPC has a bounded archive — older pins
get pruned, so we can't even probe historical prices to update
the constants. Switch to: oracle either returns a positive price
or reverts with `StalePrice()`. The test still confirms the oracle
is wired up and not in some unknown failure mode, while staying
robust across pin moves and Pyth feed timing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
At pin 459885576 only WBTC ($80830.6380941) and XAUT ($4626.78794311)
have fresh Pyth pushes; the other 10 feeds are older than the 1800s
stale threshold. Hardcode the fresh prices precisely and pin the stale
ones with `vm.expectRevert(StalePrice())`. Each oracle is still
exercised; the staleness pattern at this pin is captured deterministically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thedavidmeister and others added 13 commits May 7, 2026 13:35
CI's archive RPC has a bounded window — pin 459885576 was pruned in the
hours since it was set, surfacing as universal trie-state errors across
the whole arbitrum suite. Bump pin to a fresh head-500. At this block
WBTC, UNI and XAUT are fresh (precise prices hardcoded); the other 9
feeds are stale (`vm.expectRevert(StalePrice)`).

This is a recurring-maintenance pattern until cyclo.sol#51 lands a
script to refresh prices automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously only decimals + asset linkage were verified. Add:
- vault.symbol() == JSON.symbol
- underlying.symbol() == JSON.underlyingSymbol
- JSON.name == JSON.symbol (the site uses `name` as a display label
  that mirrors `symbol`; on-chain `vault.name()` is a longer rendered
  string and is not directly comparable)

Closes more of cyclofinance/cyclo.site#370. Open: #43 (receiptAddress
linkage) plus the per-field issues to pull remaining JSON fields
on-chain (#44#50).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
assertOnChainMatchesJson now also checks:
- block.chainid matches the expected chainId (catches mistakenly
  forking to the wrong chain)
- entry.networkName matches the expected network name passed in
  by each per-chain test (Flare → "Flare", Arbitrum → "Arbitrum One")

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JSON `name` was previously a UI display label that mirrored `symbol`.
Replace with the actual on-chain `vault.name()` value so the JSON is
canonical for both fields. The site can use `symbol` for display and
`name` for the long form (or both); the choice is now site-side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously vaultAddress was only verified implicitly — getter calls on
it would have failed if it were zero or an EOA. Add explicit checks:
`vaultAddress != address(0)` and `vaultAddress.code.length > 0`. These
fire with a clear, address-specific error before the getter chain runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each test populates a `knownVaults` mapping in setUp from
`LibCycloProdVault.sol` constants. The test then asserts every JSON
entry's `vaultAddress` is in that set. A typo or stray address in
the JSON now fails loudly with a clear per-entry error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trailing-underscore parameter naming was avoiding shadowing the local
`vault` of type CycloVault. Drop the convention by renaming the
address-typed parameters to `vaultAddress`/`oracleAddress`; the typed
local stays as `vault`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cysFLR's older vault impl (PROD_FLARE_VAULT_IMPLEMENTATION_CYSFLR)
does not expose `receipt()` as a getter so it stays skipped (open at
cyclo.sol#43). Every other entry now has its `receiptAddress` pinned
against `vault.receipt()` on-chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cysFLR's older vault impl doesn't expose `receipt()` as a getter, but
the receipt itself exposes `manager()` which returns the vault. Add
`IReceiptV3(receipt).manager() == vault` as a universal check that
works for every entry, including cysFLR. The forward-direction
`vault.receipt()` check still runs for non-cysFLR entries on top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lib (`assertOnChainMatchesJson`):
- no two entries on the same chain share the same vaultAddress or
  receiptAddress
- the receipt supports the ERC1155 interface (via type(IERC1155).interfaceId
  through ERC165 supportsInterface)

Per-chain test files:
- vaultAddress is a 1167 proxy to the expected vault impl with the
  expected CBOR-trimmed codehash (Arbitrum: V2 across the board;
  Flare: cysFLR special, cyWETH on V1, cyFXRP on V2)
- vault.priceOracle() matches the expected prod oracle constant per
  vault
- receiptAddress is a 1167 proxy to the expected receipt impl with the
  expected codehash (mirrors the per-vault impl mapping)

`active` is left unasserted — it is UI state with no on-chain notion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each per-chain test now declares which vaults are expected to be
active in setUp via `expectedActive[vault] = true`. The default
(uninitialised mapping) is `false`, so vaults not listed are
expected inactive. The loop asserts `entry.active ==
expectedActive[entry.vaultAddress]`, surfacing any drift between
the JSON and the test as a clear per-entry failure.

Currently active: cysFLR, cyWETH, cyFXRP.ftso (Flare); cyWETH.pyth,
cyWBTC.pyth, cyARB.pyth (Arbitrum). All others inactive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the existing vaultAddress check: each of underlyingAddress
and receiptAddress is non-zero and has bytecode. Catches typos that
make an entry point at an EOA or zero before the getter chain runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Existing check is JSON ⊆ constants. Add the inverse: every prod
vault constant in LibCycloProdVault.sol must also have a matching
JSON entry, unless explicitly excluded. Catches the case where a
new vault constant lands but the JSON wasn't updated.

cyJOULE is intentionally excluded — its constant is kept for
historical bytecode tests but it is not listed on cyclo.site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thedavidmeister
thedavidmeister force-pushed the 2026-05-05-cyclo-site-decimals-fork-test branch from 02c7743 to de8934f Compare May 9, 2026 14:14
Calling `initialize` again on every entry's vault and receipt must
revert with the canonical OZ string. Pass well-formed init data
(`CycloVaultConfig` for vaults, manager `address` for receipts) so
the OZ `initializer` modifier fires before any abi-decode in the
function body, regardless of which impl version (cysFLR / V1 / V2)
is behind the proxy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thedavidmeister
thedavidmeister force-pushed the 2026-05-05-cyclo-site-decimals-fork-test branch from de8934f to 78f355d Compare May 9, 2026 14:15
thedavidmeister and others added 2 commits May 9, 2026 18:17
Belt-and-braces JSON-internal pairing: the on-chain `vault.symbol()`
formula composes from the asset symbol, so the existing
`vault.symbol() == entry.symbol` and `underlying.symbol() ==
entry.underlyingSymbol` checks already enforce this transitively.
The explicit prefix check guards the JSON pairing directly so a
symbol/underlyingSymbol drift surfaces with a clear message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flare: exactly 3 entries (cysFLR, cyWETH, cyFXRP.ftso).
Arbitrum: exactly 12 entries (cyWETH.pyth, cyWSTETH.pyth, cyWBTC.pyth,
cyCBBTC.pyth, cyLINK.pyth, cyDOT.pyth, cyUNI.pyth, cyPEPE.pyth,
cyPYTH.pyth, cyENA.pyth, cyARB.pyth, cyXAUT.pyth).

Catches accidental JSON deletion or stray addition. The reverse
coverage check (every prod constant has a JSON entry) plus this count
together pin both directions: count = constants - exclusions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

Producer note: structural CI blockers, not PR code (last run 2026-05-09, stale). (1) rainix-sol-test: all 25 failures are Arbitrum fork tests dying in setUp with 'missing trie node … change your RPC url to an archive node' — the CI ARBITRUM RPC secret is not an archive node; no assertion is ever reached. (2) rainix-sol-static: slither annotation errors inside the nested submodule lib/ethgild/lib/rain.math.fixedpoint (LibFixedPointDecimalFormat.sol 'slither-disable-end without slither-disable-start') — needs a submodule bump decision, not a source edit here. A code push cannot green this PR; leaving in the needs-human queue.

…tHub outage flake, HTTP 418 nix substituter) [3b-attempt]
…od tests (repo-pinned formatter; retrigger surfaced fmt drift after the 2026-05-09 outage snapshot cleared)
@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

🤖 ai:producer
Producer note: the recorded reds were a 2026-05-09 GitHub-outage snapshot (HTTP 418 nix substituter) — empty-commit re-triggered. The re-run surfaced the real state: (1) rainix-sol-static was forge-fmt drift on the two CycloSiteTokens.prod.*.t.sol files — fixed and pushed (437b0ee, repo-pinned formatter). (2) rainix-sol-test is a STRUCTURAL env red: the fork tests' pinned block state is no longer served by CI's RPC ("missing trie node … state not available" on Arbitrum) — a re-run reproduces it identically and no code fix short of a pin/RPC decision helps; leaving it for the interactive pin work. Heads-up: rainix-HEAD sol-shell forge fmt reformats 12 files in this repo (formatter drift vs the repo's pinned flake) — use the repo's own nix develop -c forge fmt.

@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

🤖 ai:producer
Producer note: 3b HAND-OFF — rainix-sol-static is red AGAIN after the 437b0ee fmt fix, now with a fmt diff in test/lib/LibCycloSiteTokens.sol (run 28742777852). This matches the formatter-drift trap flagged earlier (rainix-HEAD forge fmt vs this repo's pinned flake format differently), so a local fmt push risks ping-ponging; per back-off (one attempt per check, already spent) this is parked for a human with the repo-pinned formatter. rainix-sol-test remains the STRUCTURAL env red previously noted (Arbitrum fork state no longer served: -32000 missing trie node / metadata not found on testProdCloneFactoryBytecodeArbitrum + testProdCycloReceiptManagerArbitrum) — no code fix applies.

@thedavidmeister thedavidmeister added the ai:needs-work Needs rework — the producer's inbox (vetter verdict or human ruling) label Jul 6, 2026
@thedavidmeister

thedavidmeister commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 ai:vetter
Reviewed 437b0ee: reject — test/lib/LibCycloSiteTokens.sol is a library pinned =0.8.25, org pragma convention requires caret fo
cost 585 — fork-test suite pinning on-chain prod claims

@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

🤖 ai:vetter
Reviewed 592b2a7: reject — closes cyclo.site#370 in part — unchanged since prior reject (head moved by empty CI-retrigger commit only): test/lib/LibCycloSiteTokens.sol is a library pinned =0.8.25 where org pragma convention requires caret for library/abstract files (rainix#250); also adds bare test/ first-party import in CycloSiteTokens.prod.arbitrum.t.sol (breaks consumption as dependency); fork-test substance otherwise sound — fix pragma + import and re-push
cost 585 — fork-test suite pinning on-chain prod claims

@thedavidmeister thedavidmeister added ai:blocked-infra AI producer: blocked on an infra/tooling gap or can't classify (human) and removed ai:needs-work Needs rework — the producer's inbox (vetter verdict or human ruling) labels Jul 12, 2026
@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

🤖 ai:producer
Blocked-infra: Two reds, both past back-off: (1) rainix-sol-test run 29051045944 — Arbitrum fork suites fail with 'missing trie node / older block with a non-archive node' (-32000); RPC_URL_ARBITRUM_FORK secret IS set but points at a pruned node; SAME failure class red on main (run 24800484874) — infra: the secret needs an archive endpoint. Flare fork suites pass. (2) rainix-sol-static — forge fmt --check diff in test/lib/LibCycloSiteTokens.sol after a prior fmt [3b-attempt]: local forge fmt output ≠ CI's (version skew; CI wants the multi-line require(...) collapsed). Both need infra/human resolution; no further producer pushes.

@thedavidmeister thedavidmeister added ai:design AI vetter: raises a design question and removed ai:blocked-infra AI producer: blocked on an infra/tooling gap or can't classify (human) labels Jul 28, 2026
@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

🤖 ai:producer
Design-question: Both reds are PRE-EXISTING main reds this branch cannot green, and 3b back-off is exhausted on both checks (prior attempts: a forge-fmt [3b-attempt] plus two empty-commit retriggers, 2026-05-09 and 2026-07-05). cyclo.sol main has been red since 2026-04-22 (last green 2025-12-22) and every branch run since fails the same way. (1) rainix-sol-static: Slither aborts on a VENDORED dependency, not this repo's code — 'slither-disable-end without slither-disable-start' at lib/ethgild/lib/rain.math.fixedpoint/src/lib/format/LibFixedPointDecimalFormat.sol#43 and 'Consecutive slither-disable-starts without slither-disable-end' at #37. Greening it means a cross-repo fix in rain.math.fixedpoint (then an ethgild bump), which the producer must not decide unilaterally. (2) rainix-sol-test: the Arbitrum prod-pin fork tests testProdCloneFactoryBytecodeArbitrum and testProdCycloReceiptManagerArbitrum die on 'missing trie node ... state is not available' / 'metadata is not found', with foundry's own diagnosis in the log: 'trying to fork from an older block with a non-archive node which is not supported'. No code change reaches that; it needs an archive-node Arbitrum RPC provisioned for CI (or a human ruling to re-pin FORK_BLOCK to a block a non-archive node still serves, which would move every pinned expectation in the suite). Questions for the human: who fixes the rain.math.fixedpoint slither markers, and do we provision an archive Arbitrum RPC or re-pin the fork block?

@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

👤 human
Ruled 592b2a7: design — Misrouted: neither question needs a human, and the PR's real blocking state is the vetter's reject at this same head naming two fixable in-repo defects — the library pragma at test/lib/LibCycloSiteTokens.sol:3 (confirmed in the tree where convention wants a caret) and a bare first-party test/ import. On the Arbitrum archive red, the option space in the question is wrong: I read .github/workflows/rainix.yaml and this repo runs a hand-rolled inline job that does not call the rainix reusables at all, so the answer is neither provisioning a new RPC nor re-pinning FORK_BLOCK but adopting the reusable, where archive-aware fork RPC selection now lives. On the slither markers, who fixes it answers itself — the repo that owns the file — so that is an upstream issue to file, not a ruling to wait on.

@thedavidmeister

Copy link
Copy Markdown
Collaborator Author

Rework note @592b2a74b6de1ca9db62e9198dcd78217da1c2c8: Neither question needs a human ruling, and neither is what is actually blocking this PR. Work, in order:

  1. FIX WHAT THE VETTER ALREADY NAMED. Its reject at this exact head lists two concrete in-repo defects and they are the PR's real blocking state: test/lib/LibCycloSiteTokens.sol:3 is pragma solidity =0.8.25; on a LIBRARY where the org convention requires a caret for library/abstract files (I confirmed line 3 in the tree); and CycloSiteTokens.prod.arbitrum.t.sol adds a bare first-party test/ import, which breaks consumption as a dependency. Both are yours, need no permission, and the vetter called the fork-test substance otherwise sound.

  2. THE ARBITRUM ARCHIVE RED HAS A THIRD OPTION YOU DID NOT LIST, and it is the org-conventional one. Your question offered "provision an archive Arbitrum RPC" or "re-pin FORK_BLOCK". This repo's .github/workflows/rainix.yaml is a hand-rolled inline job — DeterminateSystems/nix-installer-action@v4, magic-nix-cache-action@v2, nix develop -c ${{ matrix.task }} — and does NOT call the rainix reusables at all. Shared CI belongs in rainix reusables rather than in consumers, so the move is to adopt rainlanguage/rainix/.github/workflows/rainix-sol.yaml@main (or the individual reusables) with secrets: inherit.

VERIFY THIS BEFORE ACTING ON IT, do not take it from me: I believe rainlanguage/rainix#289 merged today adds an archive-aware RPC preflight to those reusables that picks an endpoint able to serve the pinned fork block, with hardcoded archive defaults so it needs zero configuration, and that its issue #288 named cyclo.sol as one of the repos reddened by exactly this archive error. I also believe its demand scan handles this repo's raw-secret env form (RPC_URL_ARBITRUM_FORK) as well as the <NET>_RPC_URL form, and exports the selection under both. Check the merged reusable yourself and confirm it covers this repo's shape before adopting; if it does not, say so rather than adopting blind.

  1. DO NOT RE-PIN FORK_BLOCK. Moving every pinned expectation in the suite to work around an RPC that cannot serve historical state is fixing the wrong thing, and it destroys the pins' value as prod assertions. That option is off the table.

  2. THE SLITHER MARKERS ARE AN UPSTREAM BUG, and "who fixes it" answers itself: the repo that owns the file. FILE AN ISSUE on rain.math.fixedpoint for the mismatched markers you found at lib/ethgild/lib/rain.math.fixedpoint/src/lib/format/LibFixedPointDecimalFormat.sol — slither-disable-end without a start at Verify cyclo-site-tokens.json receiptAddress linkage on-chain #43, consecutive starts without an end at 2025 11 12 cbbtc #37 — with your log. Do not fix it here, do not vendor a patch, and do not block this PR on it. If adopting the reusable in (2) does not also resolve the static red, say so on the PR and leave it to the upstream fix.

  3. Re-push and let the vetter judge it again. Note that main has been red since 2026-04-22, so a red on this branch that matches main's is not evidence about this diff — but it is also not a reason to stop, since main-red is fixable on a branch and (2) is how.

@thedavidmeister thedavidmeister added ai:needs-work Needs rework — the producer's inbox (vetter verdict or human ruling) and removed ai:design AI vetter: raises a design question labels Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:needs-work Needs rework — the producer's inbox (vetter verdict or human ruling)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fork-test cyclo.site/stores.ts token config against on-chain contracts

1 participant