The frontend meets the first live OPEN market — 2026-08-27

The first time apps/dclutch-web has been pointed at a dClutch chain that has a Market on it. Six defects, every one of them fatal to a real read, none of them visible from a test suite that passed 208 cases.

Evidence level. Local-validator execution. Not devnet, not mainnet, and not an official deployment or frontend. Nothing here signed, submitted, funded, or published anything, and no address named below is registered anywhere.

The chain

tools/gauntlet/run.sh --mode full at 3b0c58839b78f556bb36ecb706334ed0712bcc09, own work root /private/tmp/dclutch-fd3. Campaign green: 23 witnesses checked, 0 failed; 100 transactions; the Market reached Open.

The campaign's supervisor kills its validator when the run returns (ValidatorChild::drop), so the post-campaign ledger was resumed as a live chain with tools/gauntlet/frontend/resume-validator.sh. A resumed ledger needs no launcher and therefore no pinned origin: it ran on 127.0.0.1:21890, which freed the global 20890 slot for another lane's campaign. That is worth knowing generally — the single global port is only needed for the ~8 minutes a campaign is actually running.

CoordinateValue
Open Market4fQNy8k7G7bZ9cak6pb2VnigV2F5fbhs7YnYFWQ2LQYH (phase Open, readiness Consumed, generation 2)
Core / Registry / Claims2rJGzu…Rqhd8o / 87syw3…u7mh7e / 9fAcEn…APBMY6
Claims aggregateCUWKSsNsuKsRjSRUJzmERh4gRc6b4XSmWiFBcsVh4Kgj, 4 claims, supplies 500000000 ×4
FounderAVPy5zFJGDSFhcRpYWeqdSjhSbygPPcVH2qL6Cp3ScaA
Founder Position129CAcrmwk33Aw8c4b2jCZQaRPVCjgSBVaefkYUyVzGC, Claims-owned, balances 500000000 ×4
Hoard vault8JdqNuFojqCfKXyrF8kdffpgcTok8G1aoWe1ZAnzN8aB, Token-2022, 500,000,000 atoms

A second Market (4fQ…'s sibling 9k8qkn…, phase Founding) exists from the abort lane, which makes the discovery surface's listing a real listing rather than a single-row special case.

How this was checked

Screenshots are not evidence — they check a decoder against itself. Three programs, in tools/gauntlet/frontend/:

Verification table

50 of 50 checks MATCH (/private/tmp/dclutch-fd3/witness/verification.md). Abridged; the file carries every row.

SurfaceFactChainBrowserVerdict
/marketsMarkets found by the Core scan22MATCH
/marketsphase chipOpenOpenMATCH
/marketsper-claim supply vector500000000 · 500000000 · 500000000 · 500000000sameMATCH
/marketsexact required backing500000000500000000MATCH
/marketsHoard is refused, not shownunderivableunderivableMATCH
/markets/:addressschema / widthDCLTCOR2 · version 2 / 352 bytes, exactsameMATCH
/markets/:addressphase / readiness / generationOpen / Consumed / 2sameMATCH
/markets/:addresssix content identitiesrealm, product record, product instance, resolution policy, manifest, release setall equalMATCH
/markets/:addressClaims aggregate / basis / revision / claim countCUWKSs…, 46e2988b…, 1, 4sameMATCH
/markets/:addressRealm record / mint / token program / adapter6EuGAck8…, 5Dqbx…, Token-2022, 228c14f9…sameMATCH
/markets/:addressmanifest record / content id / 3 entries + kinds + configs5wPMUd71…, 7dc55519…all equalMATCH
/portfolioderived aggregate / PositionCUWKSs… / 129CAc…sameMATCH
/portfolioowned claim balances500000000 ×4sameMATCH
/portfoliocomplete sets mergeable500000000500000000MATCH

Screenshots: /private/tmp/dclutch-fd3/shots-before/ (the refutation) and /private/tmp/dclutch-fd3/shots-final/ (after), each with markets-enumerated.png, markets-discovery.png, market-detail.png, market-detail-expanded.png, portfolio.png and the harvested rendered.json. Un-gate: /private/tmp/dclutch-fd3/ungate/.

What reality refuted

1. fetch could never have worked in a browser (49516db)

Every read surface answered Refused: Failed to execute 'fetch' on 'Window': Illegal invocation. SolanaRpcClient stored the ambient fetch on the instance and called this.fetcher(...), giving it the client as receiver. Chromium enforces fetch's Window receiver; Node, jsdom and every injected test double do not. Every existing case in lib/rpc.test.ts passed its own fetcher, so the default parameter — the only path the product uses — had never been executed.

2. The product surfaces decoded a Market no chain writes (22e785e, e8d80e9)

getProgramAccounts(core) returns 352-byte accounts whose magic is DCLTCOR2, the Lean-emitted Core state in crates/dclutch-market-core-codec/src/generated.rs. lib/decoders.ts knows only DCLTCAT1, so:

Four other web modules (directHotChain, dealerEquityChain, rationalTokenV2, rationalRetireReceiptV4) already decoded DCLTCOR2 correctly, so the browser had both representations side by side and the product surfaces took the dead one.

3. The economics are not fields of the Market

A Core V2 root carries identity and lifecycle and no Hoard figure, no supply vector, no settlement summary. Those three were exactly what the discovery card, the detail page's Economics section and the portfolio's complete-set arithmetic were built on.

4. /portfolio was confidently wrong about the founder

It derived [dclutch/position/v1, market, owner] under Core — the Direct family's Position — and rendered "No Position exists at the derived address… this owner has never held a claim in this Market" about the founder of the market, who holds 500,000,000 atoms of each of four claims. Those live in a Claims-owned LiabilityBasisV2 Position at [dclutch:lbv2:position, aggregate, owner]. Without a Claims program the surface now refuses rather than deriving another family's address and reporting its emptiness as an answer.

5. Four defects between the un-gate and any real chain (5129362)

Detailed in that commit. Briefly: the checked-release decoder rejected semantic kind unowned (the only honest kind for the seven role programs); the activation plan asked one getMultipleAccounts for five whole ELFs, ~5.8 MB base64-framed, over its own 4 MiB bound; the System Program was required to have an empty body when a real Agave observation carries system_program; and SYSVAR_OWNER_ID was six characters short and not a valid address at all — string-compared only, so nothing noticed.

The RL Loader-bytes prediction: HOLDS, with one structural exception

dclutch-release-tool loader-accounts constructs Loader V3 account bytes offline from an ELF, and every checked release carries their digests. Nothing had ever compared them to a runtime. tools/gauntlet/frontend/loader-prediction.mjs, run against the deployed accounts:

core        Program      IDENTICAL   36
core        ProgramData  DIFFERS     1007269 vs 1007269   first divergence at byte 13
claims      Program      IDENTICAL   36
claims      ProgramData  IDENTICAL   1073421
trading     Program      IDENTICAL   36
trading     ProgramData  IDENTICAL   1384725
resolution  Program      IDENTICAL   36
resolution  ProgramData  IDENTICAL   527549
custody     Program      IDENTICAL   36
custody     ProgramData  IDENTICAL   355805
registry    Program      IDENTICAL   36
registry    ProgramData  IDENTICAL   220773
rent        Program      IDENTICAL   36
rent        ProgramData  IDENTICAL   152357

13 of 14 constructed Loader accounts are byte-identical to the deployed accounts.

The construction is exact for every genesis-immutable artifact. The one exception is Core, and it is not a bug in the construction — it is a semantic gap nobody had named:

Loader V3 serializes ProgramData { slot, upgrade_authority: None } as thirteen bytes and writes them over a forty-five byte header. A program whose authority has been revoked therefore keeps the old key sitting inert at bytes 13..45 behind a zero tag.

Measured: deployed Core ProgramData is 03000000 0000000000000000 00 1653084445a483262ffca938…, tag 0, retained authority 2W9QQUeCfZPD8zDBwGdhhCbDFfN71hgUdQLUHPxKVP3U — the bootstrap's own ephemeral authority. The constructed bytes are all-zero there. Same length, one 32-byte window apart. The successor launcher already documents this ("including Loader-retained inactive authority bytes") and the plan pins post_revoke_programdata_sha256 separately from the genesis digest.

Consequence, and it is not mine to resolve: an offline loader-accounts construction cannot represent a revoked ProgramData, so a checked release over one can never match the account. Whether a checked release describes the artifact or the account is a dclutch-release-tool question. Reported, not patched.

The un-gate against reality: CLOSED, three times, for three correct reasons

Manifests built by tools/gauntlet/frontend/campaign-checked-release.sh over the campaign's own deployed ELFs, program addresses and semantic preimages. The derived execution release set digest is 7bf16a5995d23f1ef33d14278fcb0bca1a01a1367c0a34c8bc3470cef07f7fafthe exact set the campaign activated and the exact set the Open Market names, so the evidence is bound to this chain and not merely plausible.

ScenarioGateRefusal
Honest manifests, real chainclosed2rJGzu…Rqhd8o current Loader account digest differs from its complete checked release
One byte flipped in the trading manifestclosedtrading full checked release does not rebuild the multiprogram evidence
Internally perfect set over a one-byte-altered custody ELFclosedrelease-set record 2rxRKfHnaeCJqKfZrJeb39VYNdYZ9DB8kYEX9fsq8jVF is absent at finalized commitment

The third is the strongest case: that manifest set is not malformed in any way — every create/verify/inspect pass agrees with itself. The only thing wrong with it is that the chain runs a different program, and the refusal comes from the chain, naming the address it looked for. A gate that only caught malformed input would have opened on it.

The first refusal is the honest verdict of this exercise. The gate stays closed on truthful evidence, blocked by exactly one artifact for exactly the reason above. It is not a false refusal by the browser: the digest genuinely differs. Closing that gap is a release-tool decision, not a frontend one.

Controls

Left open, deliberately

  1. Two live Market representations. dclutch-market-contract's DCLTCAT1 and dclutch-realm-contract's Core Realm/Position still exist, still have a Rust fixture generator, and lib/decoders.ts and /explorer still use them. Whether that path is superseded is a protocol question for its owner; this lane only stopped the product surfaces from taking it.
  2. The Hoard has no chain-derivable address. Showing a Market's collateral principal honestly needs either a Market-root field naming the vault or a protocol-fixed context derivation. Today the surface refuses.
  3. The revoked-ProgramData gap above.