The two mains shared no history, so this is a per-file three-way merge:
for each file the 65 forge-only commits touched (by patch-id), the base is
the newest version both histories held. 105 files merged or took one side
cleanly, 48 were already identical, and the 45 true conflicts and 31
modify/delete pairs were resolved by what runs on the cluster:
- The forge wins where it is live: explorer 1.5.56 (digest 45db11ff) and
its archive readers, explore-fe-hanzo's ConfigMap env (AI coin,
/v1/indexer/hanzo, /v1/chain/hanzo/rpc), exchange-api reading
luxd-archive, and explore-fe-{lux,zoo}-testnet, pinned to what they run
(v2.8.5-lux / v2.8.4-lux).
- GitHub wins where it is live: explore-fe-lux v2.8.5-lux, explore-fe-zoo
v2.8.4-lux, bankd v1.0.18, lux-build's routes.
- The forge's retirement of workloads that run nowhere stands: lux-mainnet
dex, lux-hb2, lux-market, traffic, wallet-web; the lux-testnet frontends,
indexer, exchange and gateways; lux-operator. deploy/lux-testnet/luxd.yaml
stays, since roll_luxd.sh reads it.
- GitHub's deletions stand: lux-finance and lux-financial (static sites
now), the k8s/ operator manifests, .hanzo/workflows/cicd.yml.
- Paths are /v1/chain/C/rpc where the forge had written /v1/chain/c. All
four spellings answer; this one is the documented form. The forge's
/{network}/v1 doc lines are dropped: a network is a host, and the prefix
404s.
Dry-run against the running objects (server-side, as CD applies), the
merged explorer shows no change where GitHub's showed 152 lines, and no
other live workload moves further from what runs. Every deploy/*/*.yaml
renders with charts/app; price.yaml, which failed on GitHub, now renders.
Claude-Session: https://claude.ai/code/session_01TzirYkGmD88w4GipqivUtV
bankd v1.0.18 takes only a lux.id access token issued to lux-financial for
the lux org, maps only a member of the admin org to _superusers, and listens
on HTTP alone. Its pod reads none of the bank-secrets keys, so the five env
references to them are gone.
bank-docs states the amd64 node selector its pod already runs with.
e2e/40-lux-financial proves the one way in: neither console serves a password
input or ships a credential in its JS; every sign-in screen hands off to lux.id
with client lux-financial, PKCE S256 and the console's /auth/callback; bankd
answers 404 on every password and Base sign-in route, the code exchange takes
only lux-financial by code or refresh, and a token lux.id did not issue to
lux-financial reads as anonymous. The password-form sign-in and the seeded
ledger totals it asserted are deleted: neither exists.
k8s/lux-k8s/{bank-api,bank-dash,bank-docs,bank-sandbox,bankd,financial} and
scripts/seed-bank-admin.ts are deleted. deploy/lux-bank declares the bank,
lux.financial is a static site, and no host those files name resolves.
Co-authored-by: Hanzo Dev <[email protected]>
The primary-network chains answer to either case; one spelling beats two.
/v1/chain/c/rpc, /v1/chain/p, /v1/chain/x across the estate.
Co-authored-by: Hanzo AI <[email protected]>
Zoo's cloud and its exchange services were declared here as well as in Zoo's own
repo, and the files had drifted apart. The copy here rendered a pod from a
cluster that is gone; Zoo's serves the site from files and is the one that syncs
clean. Zoo's wins because Zoo's is Zoo's.
Hanzo's operator and zen-gateway were declared here against Hanzo's own
declarations of both. Hanzo's are the ones running.
cert-manager was a third copy of one cluster-scoped install.
liquid-docs put a white-label site on a Lux host. It does not run and does not
belong in this repo. The pre-phase-a backup went with it: a snapshot of a
cluster that no longer exists, carrying the same aliases. The e2e annotation
that named that app says the same thing without it.
deploy/hanzo and deploy/hanzo-zt stay. lux-admin, lux-blog, lux-invest,
lux-safe-cgw and lux-zt-router are Lux services that sit in a namespace someone
named hanzo. The namespace is the thing worth renaming, not the owner.
The route table lives in configs/gateway/*.json, where each endpoint names both
the public path and the url_pattern sent upstream. Both moved: luxd answers
under /v1/chain and nothing translates between them.
The operational scripts went with it — Makefiles, the roll and validator
helpers, the e2e specs and the market-maker config all composed /v1/bc
addresses that now reach nothing. A test that dials a dead path fails in a way
that looks like the chain is down.
Lines that record an observation keep their original spelling.
Co-authored-by: Hanzo Dev <[email protected]>
luxd moved chain APIs from /v1/bc/<alias> to /v1/chain/<alias> and deleted the
old prefix, and health moved onto typed ops at /v1/health/ops/*. Both landed in
node 1.36.181. Nothing here could move before that image existed, and the image
cannot roll out while the manifests still ask for addresses it no longer serves,
so the pin and the paths move in one commit.
What moved:
image 1.36.171 -> 1.36.181, digest with it, on devnet/testnet/mainnet
internal exchange-api, lux-hb2, rpc-tip-gate, the luxd startup script
probes /v1/health/liveness -> /v1/health/ops/liveness (bare /v1/health,
which the local compose healthcheck used, no longer routes at all)
public the devnet and testnet gateway routes
Comments that RECORD an observation keep their original spelling: a note saying
/v1/bc/zoo/rpc returned 404 on a given date is a fact about that date, and
rewriting it would make the record false. Only live addresses moved.
1.36.181 also carries chains v1.7.38, so the deployed node has the zkvm
content-derived transaction identity.
Co-authored-by: Hanzo Dev <[email protected]>
The Lux block asserted three Safe addresses had code — all three are
empty — and asserted a Governor was dead, at an address that is not the
Governor. Two tests failed and the third passed about nothing, so the
suite that exists to catch this could not.
It now reads the live Governor: deployed, counting in seconds, voting
the token deployed with it. The clock assertion is the one worth having
— the deploy was fine and unusable, because OpenZeppelin's default
block-number clock turned votingDelay/votingPeriod written as seconds
into block counts, which on a demand-driven chain also stops whenever
the chain is quiet. Only the mode says which unit those numbers are in.
The Safe absence is asserted rather than skipped, over all four
addresses the various records name. The day one has code this fails, and
that failure is the reminder to point lux/dao at it.
Co-authored-by: Hanzo Dev <[email protected]>
The gate enforced only health. explorer, apps and branded each printed a
warning and fell through to an unconditional 'gate passed', so run 62877
went green with three red jobs. One rule now covers every guarded job:
green or the gate is red.
The apps matrix named itself App E2E (${{ matrix.app }}), which
stringifies the leg mapping to 'Object' — five identically named jobs,
none identifying which leg failed. Use matrix.app.name.
Explorer E2E was one of the hidden failures: it asserted SPC serves
stats, but no SPC chain exists and the explorer config disables the slug,
so api-explore-spc answers a plain-text 404. Four specs each carried
their own copy of the chain list; EXPLORERS in env.ts is now the one
list, and SPC is absent from it with the reason recorded.
httpGetJson fed error-page bodies to JSON.parse, reporting a complaint
about byte 4 of '404 page not found' instead of the endpoint and status.
It now reports both.
'OAuth authorize URL works with lux-cloud client' failed on all three
viewports. The endpoint was fine — the request was not. lux-cloud is a PUBLIC
client, and IAM refuses an authorize without a code_challenge:
/auth/callback?error=invalid_request
&error_description=PKCE+is+required+for+public+clients
That bounce is a redirect, so isRealAuthorizeResponse called it real and the
test went on to die waiting for a login form a rejected request never reaches.
The failure therefore read as 'authorize is broken' while authorize was
enforcing its contract. env.ts already documented the challenge in the URL it
had measured, and 50-login already asserts a public client must send one; this
test was the only place that did not.
Adds pkce() beside OIDC — one builder, where the OIDC contract already lives.
The verifier is fixed, not random: nothing exchanges it, and a fresh secret per
run makes a re-run non-reproducible.
The response check had to change with it. Once the request is valid the flow
lands on the real login page, which is 200 text/html — and measured today, that
page and a control request to a path that does not exist return BYTE-IDENTICAL
654-byte SPA shells. No server-side signal separates them, so this test now
asserts the one thing that does: an OAuth error never comes back to our own
callback, and the password field renders. isRealAuthorizeResponse is left alone
for 13-iam-billing, which reads it from a non-navigating request.
cloud suite: 54 passed / 3 failed -> 57 passed / 0 failed
Co-authored-by: Hanzo Dev <[email protected]>
The stLUX vault page was removed from the console — no vault is configured for
any brand, so the surface only ever said so, and staking returns are served by
/dashboard/calculator and /dashboard/validators from the P-Chain. lux.cloud is
a catch-all origin, so the old path still paints the console's "No such
section"; a bodyLen assertion against that shell passes and proves nothing.
It moves to NOT_SERVED, where the registry keeps retired paths visible.
Co-authored-by: Hanzo Dev <[email protected]>
Two of the five explorer brands pinned NEXT_PUBLIC_FONT_FAMILY to Geist off fonts.googleapis.com: every visitor to explore.lux.network and the SPC explorer was announced to Google on page load, and the two hosts rendered in a face the other three do not use. Dropping the pair leaves all five brands with the same env and the image's own faces.
The branded matrix declared Basel Grotesk as the brand token for all four orgs, and the broker audit had already been switched to test for Zen while still calling the result hasGeist and printing it under a Geist label — a check whose name denied its own predicate. The README hero card asked for Inter.
A docs-site template repeated across these repos named three different faces in
one place: a binding called `inter` or `geist`, constructed from next/font/google
so it actually held Inter, publishing a custom property called
`--font-geist-sans` that @hanzo/font does not set. Whichever of the three a
reader trusted, two of them were wrong.
They take Zen and ZenMono from @hanzo/font — bindings named `sans` and `mono`
for the role they fill, setting --font-zen-sans and --font-zen-mono, which is
what the package publishes.
The gate enforced only health. explorer, apps and branded each printed a
warning and fell through to an unconditional 'gate passed', so run 62877
went green with three red jobs. One rule now covers every guarded job:
green or the gate is red.
The apps matrix named itself App E2E (${{ matrix.app }}), which
stringifies the leg mapping to 'Object' — five identically named jobs,
none identifying which leg failed. Use matrix.app.name.
Explorer E2E was one of the hidden failures: it asserted SPC serves
stats, but no SPC chain exists and the explorer config disables the slug,
so api-explore-spc answers a plain-text 404. Four specs each carried
their own copy of the chain list; EXPLORERS in env.ts is now the one
list, and SPC is absent from it with the reason recorded.
httpGetJson fed error-page bodies to JSON.parse, reporting a complaint
about byte 4 of '404 page not found' instead of the endpoint and status.
It now reports both.
'OAuth authorize URL works with lux-cloud client' failed on all three
viewports. The endpoint was fine — the request was not. lux-cloud is a PUBLIC
client, and IAM refuses an authorize without a code_challenge:
/auth/callback?error=invalid_request
&error_description=PKCE+is+required+for+public+clients
That bounce is a redirect, so isRealAuthorizeResponse called it real and the
test went on to die waiting for a login form a rejected request never reaches.
The failure therefore read as 'authorize is broken' while authorize was
enforcing its contract. env.ts already documented the challenge in the URL it
had measured, and 50-login already asserts a public client must send one; this
test was the only place that did not.
Adds pkce() beside OIDC — one builder, where the OIDC contract already lives.
The verifier is fixed, not random: nothing exchanges it, and a fresh secret per
run makes a re-run non-reproducible.
The response check had to change with it. Once the request is valid the flow
lands on the real login page, which is 200 text/html — and measured today, that
page and a control request to a path that does not exist return BYTE-IDENTICAL
654-byte SPA shells. No server-side signal separates them, so this test now
asserts the one thing that does: an OAuth error never comes back to our own
callback, and the password field renders. isRealAuthorizeResponse is left alone
for 13-iam-billing, which reads it from a non-navigating request.
cloud suite: 54 passed / 3 failed -> 57 passed / 0 failed
Co-authored-by: Hanzo Dev <[email protected]>
The stLUX vault page was removed from the console — no vault is configured for
any brand, so the surface only ever said so, and staking returns are served by
/dashboard/calculator and /dashboard/validators from the P-Chain. lux.cloud is
a catch-all origin, so the old path still paints the console's "No such
section"; a bodyLen assertion against that shell passes and proves nothing.
It moves to NOT_SERVED, where the registry keeps retired paths visible.
Co-authored-by: Hanzo Dev <[email protected]>
The .network host was retired 2026-07-27 and no longer resolves at all, so every
reference to it named an address that cannot answer. The brand host is .cloud,
matching kms.zoo.cloud and the rest of the white-label convention.
Left alone deliberately, because rewriting them would invert what they say: the
LLM.md line that RECORDS the retirement, the e2e spec that ASSERTS
kms.lux.network must not resolve, and the recorded applies + cluster backups,
which are faithful accounts of what was applied and are not config.
Co-authored-by: Hanzo Dev <[email protected]>
The .network host was retired 2026-07-27 and no longer resolves at all, so every
reference to it named an address that cannot answer. The brand host is .cloud,
matching kms.zoo.cloud and the rest of the white-label convention.
Left alone deliberately, because rewriting them would invert what they say: the
LLM.md line that RECORDS the retirement, the e2e spec that ASSERTS
kms.lux.network must not resolve, and the recorded applies + cluster backups,
which are faithful accounts of what was applied and are not config.
Co-authored-by: Hanzo Dev <[email protected]>
IAM routes exactly ONE authorize path (iam internal/oidc/oidc.go
PathAuthorize) = /v1/iam/oauth/authorize. Two specs hand-built
`${iam}/oauth/authorize` instead, and the IdP SPA answers ANY unrouted
path with 200 text/html — so both specs asserted a 2xx/3xx and PASSED
on a dead endpoint.
Measured on lux.id 2026-07-27, same query string on each:
/v1/iam/oauth/authorize 302 <- REAL (redirect to login)
/v1/iam/oauth/authorize 400 text/plain "invalid redirect_uri" (unregistered uri)
/oauth/authorize 200 text/html (SPA shell)
/oauth2/authorize 200 text/html (SPA shell)
/zzz-control 200 text/html (SPA shell) <- CONTROL: 200 proves nothing
/v1/iam/.well-known/jwks 200 application/json (real keys)
env.ts now owns the path (OIDC.authorizePath/tokenPath/jwksPath) and
exports authorizeUrl() — the same "one way" that orgRpc() already gives
RPC URLs. isRealAuthorizeResponse() rejects the shell: a 200 text/html
can no longer pass.
Proof both directions, playwright 13-iam '-g OAuth authorize endpoint':
canonical path -> 6 passed, 3 skipped (Zoo DNS); logs "400 text/plain"
NEGATIVE CONTROL, authorizePath flipped back to /oauth/authorize
-> 2 failed (Hanzo, Lux)
The old assertion passed on that identical response.
05-cloud also gains a positive DOM assertion (a visible password input)
instead of "current URL contains lux.id", which the shell satisfies.
Also: cm/exchange-config's gateway URL. The live ConfigMap in ns
lux-mainnet still carries /mainnet/ext/bc/C/rpc and this source had been
half-converted to /mainnet/v1/bc/C/rpc — still dead. Measured:
api.lux.network/mainnet/ext/bc/C/rpc 404
api.lux.network/mainnet/v1/bc/C/rpc 404 <- the /mainnet/ segment is dead too
api.lux.network/v1/bc/C/rpc 200 eth_chainId 0x17871 (96369)
Co-authored-by: Hanzo Dev <[email protected]>
IAM routes exactly ONE authorize path (iam internal/oidc/oidc.go
PathAuthorize) = /v1/iam/oauth/authorize. Two specs hand-built
`${iam}/oauth/authorize` instead, and the IdP SPA answers ANY unrouted
path with 200 text/html — so both specs asserted a 2xx/3xx and PASSED
on a dead endpoint.
Measured on lux.id 2026-07-27, same query string on each:
/v1/iam/oauth/authorize 302 <- REAL (redirect to login)
/v1/iam/oauth/authorize 400 text/plain "invalid redirect_uri" (unregistered uri)
/oauth/authorize 200 text/html (SPA shell)
/oauth2/authorize 200 text/html (SPA shell)
/zzz-control 200 text/html (SPA shell) <- CONTROL: 200 proves nothing
/v1/iam/.well-known/jwks 200 application/json (real keys)
env.ts now owns the path (OIDC.authorizePath/tokenPath/jwksPath) and
exports authorizeUrl() — the same "one way" that orgRpc() already gives
RPC URLs. isRealAuthorizeResponse() rejects the shell: a 200 text/html
can no longer pass.
Proof both directions, playwright 13-iam '-g OAuth authorize endpoint':
canonical path -> 6 passed, 3 skipped (Zoo DNS); logs "400 text/plain"
NEGATIVE CONTROL, authorizePath flipped back to /oauth/authorize
-> 2 failed (Hanzo, Lux)
The old assertion passed on that identical response.
05-cloud also gains a positive DOM assertion (a visible password input)
instead of "current URL contains lux.id", which the shell satisfies.
Also: cm/exchange-config's gateway URL. The live ConfigMap in ns
lux-mainnet still carries /mainnet/ext/bc/C/rpc and this source had been
half-converted to /mainnet/v1/bc/C/rpc — still dead. Measured:
api.lux.network/mainnet/ext/bc/C/rpc 404
api.lux.network/mainnet/v1/bc/C/rpc 404 <- the /mainnet/ segment is dead too
api.lux.network/v1/bc/C/rpc 200 eth_chainId 0x17871 (96369)
Co-authored-by: Hanzo Dev <[email protected]>
The nine screens the owner named read the chain, and the render check clears at
2.5s. MEASURED on /dashboard/treasury at 3/8/15/25/40s: at 2.5s it still shows
"…" and "Reading token balances…", and it has settled by 3s — no permanent
skeleton, just a check that was looking too early. So the evidence screenshots
were of a half-loaded page and the chain-context assertion was reading an
ellipsis where the address had not arrived yet.
One 3s settle in the economy block only. It does not soften the assertion —
/dashboard/{safes,mpc,bridge,dex,deploy} still show no network, chainId,
address or height at 3/3 viewports.
Co-authored-by: Hanzo Dev <[email protected]>
The nine screens the owner named read the chain, and the render check clears at
2.5s. MEASURED on /dashboard/treasury at 3/8/15/25/40s: at 2.5s it still shows
"…" and "Reading token balances…", and it has settled by 3s — no permanent
skeleton, just a check that was looking too early. So the evidence screenshots
were of a half-loaded page and the chain-context assertion was reading an
ellipsis where the address had not arrived yet.
One 3s settle in the economy block only. It does not soften the assertion —
/dashboard/{safes,mpc,bridge,dex,deploy} still show no network, chainId,
address or height at 3/3 viewports.
Co-authored-by: Hanzo Dev <[email protected]>
Ten screens plus the value boundary, per-network, asserted by what the page
CONTAINS. Two false passes in one day came from asserting the absence of an
error string — a blank page, a 404 body and a login redirect all contain no
error text. Every assertion here names a section title, a chain ID or a live
figure, so a page that failed to render fails the test.
The first case, "the operator console is reachable", is RED until cutover and
must stay that way: the console is built and committed (bootnode/bootnode
efe3b9d) but the image carrying /admin is not published and the Ingress pointing
admin.lux.cloud at it is owner-gated. The remaining cases skip while it 404s,
so the suite reports one honest finding rather than fifteen copies of it.
ADMIN_BASE points the suite at a local static export during development.
admin.lux.cloud is deliberately not the default: it is behind admin-guard, so an
unauthenticated run would be asserting the guard, not the console.
Beyond rendering, the spec pins the two properties the console exists to have:
every value-moving control opens a preview that names its authority, its
parameters and its stop, and confirming yields a document that says
"executed": false — no signature is collected anywhere; and the risk ledger
still names degree-0 MPC shares, 0.0000 uptimes, the dead graph layer, devnet's
missing Access-Control-Allow-Origin and "check the image tag". A console that
stops surfacing those has regressed into the thing it replaced.
Co-authored-by: Hanzo Dev <[email protected]>
Ten screens plus the value boundary, per-network, asserted by what the page
CONTAINS. Two false passes in one day came from asserting the absence of an
error string — a blank page, a 404 body and a login redirect all contain no
error text. Every assertion here names a section title, a chain ID or a live
figure, so a page that failed to render fails the test.
The first case, "the operator console is reachable", is RED until cutover and
must stay that way: the console is built and committed (bootnode/bootnode
efe3b9d) but the image carrying /admin is not published and the Ingress pointing
admin.lux.cloud at it is owner-gated. The remaining cases skip while it 404s,
so the suite reports one honest finding rather than fifteen copies of it.
ADMIN_BASE points the suite at a local static export during development.
admin.lux.cloud is deliberately not the default: it is behind admin-guard, so an
unauthenticated run would be asserting the guard, not the console.
Beyond rendering, the spec pins the two properties the console exists to have:
every value-moving control opens a preview that names its authority, its
parameters and its stop, and confirming yields a document that says
"executed": false — no signature is collected anywhere; and the risk ledger
still names degree-0 MPC shares, 0.0000 uptimes, the dead graph layer, devnet's
missing Access-Control-Allow-Origin and "check the image tag". A console that
stops surfacing those has regressed into the thing it replaced.
Co-authored-by: Hanzo Dev <[email protected]>
The suite was green while mainnet had not produced a block in 59 hours.
Nothing in it asked whether the chain was alive. `00-health` has nine tests
that all reduce to `eth_blockNumber > 0` — "node reports healthy", "C-Chain is
bootstrapped", "all primary chains bootstrapped" — and a frozen chain answers
every one of them. Readiness agreed: 15 pods at 5/5 Ready, every public API
200, on all three networks, one of which was stopped.
mainnet 96369 tip 1,098,191 age 58.9h testnet 96368 tip 1,354 age 3.6m
devnet 96367 tip 528 age 3.3m
e2e/00-liveness.spec.ts gates on the TIP. A fixed "block every N seconds" is
wrong for demand-driven chains, so the gate CALIBRATES ITSELF: the tip may be
no older than 4x the chain's own median gap over its last 8 blocks, floored at
600s. Measured cadences are 7s on mainnet and ~300s on testnet/devnet (the
heartbeat CronJob) — no constant spans both. Median, not mean: one 27,375s gap
IS the freeze, and a mean would let a stall widen its own budget.
Four orthogonal tests: fresh, monotonic (the tip never goes backwards and a
served block keeps its hash — the reorg shape devnet had at height 512),
consistent (net_version vs NETWORKS.yaml), and honest — a full validator set
must imply a fresh tip, which asserts the readiness lie head-on. Mainnet is RED
on three of them. That is the correct result; do not widen the budget.
Coverage was a function of which spec somebody happened to write: Safe's 31
routes, MPC's 15 and the whole admin surface had ZERO tests between them while
api.lux.financial, whose API is dead, carried 293. So routes become DATA —
e2e/routes.ts, 156 declared pages across 11 surfaces, iterated by 60-surfaces
(anonymous) and 61-console (signed in). A new page is one line there.
HTTP status is not evidence: SEVEN of the nine origins answer 200 for a path
that does not exist (measured with a /zzz-bogus control each; only Safe and the
explorer 404 honestly, and those two get a live control test every run). Every
route is therefore opened in a browser and must have PAINTED — real text into a
non-empty root, AND not the app's error boundary. Two assertions, because a
crash screen sails through a length check: exchange /pool is 200 with 553
characters that all say "Something went wrong". One reload before failing,
which is exactly the line between a page with no retry and a broken page.
First run found, each with a screenshot:
exchange /positions /pool /pool/v2 /bridge /teleport crash screen at 200
exchange /vote white screen, 0 chars
explorer /tokens /stats /advanced-filter crash screen at 200
console /dashboard/{safes,mpc,bridge,dex,deploy} no network, chainId,
address or height anywhere on screen — five of the nine surfaces the owner
named, and nothing on them tells an operator which chain they are on
Also fixed, all of them tests that could not fail:
- Peer connectivity was `catch { console.log('firewalled — PASS'); return }`.
/v1/info is not uniformly firewalled, it is inconsistently deployed (POST:
testnet 200, mainnet 404, devnet 404). Now asserts mesh depth wherever it is
served and falls back to the P-Chain validator set, which a single-node
network fails.
- 02-exchange and 20-exchange-deep skipped 58 cases behind lux.exchange, whose
TLS handshake fails from this network in curl, python and chromium alike.
exchange.lux.network is the same deployment at 200 on all 22 routes.
helpers.exchangeBase() picks the door that answers; the vanity domain being
down is now one deliberate assertion instead of a silence over 22 pages.
- 13-iam asserted a hand-built /.well-known/jwks that returns 200 text/html —
the SPA catch-all — so JSON.parse threw. Follows the discovery document now,
which declares /v1/iam/.well-known/jwks, and checks the content-type.
- 50-login sliced userinfo to 300 chars and then parsed it, so a real sign-in
failed on "Unterminated string in JSON" AFTER proving IAM accepted the token.
Parsed in-page now, three claims out, nothing else in the log.
Login proven end to end: [email protected] -> lux.id -> /dashboard, iss=lux.id,
aud=lux-cloud-org-lux, userinfo 200, sign-out present. 3/3.
Read-only throughout. Every screen in 61-console can move value — treasury,
safes, mpc, bridge. It navigates and screenshots. No form submitted, nothing
prepared, nothing signed, nothing broadcast, nothing re-keyed.
Co-authored-by: Hanzo Dev <[email protected]>
The suite was green while mainnet had not produced a block in 59 hours.
Nothing in it asked whether the chain was alive. `00-health` has nine tests
that all reduce to `eth_blockNumber > 0` — "node reports healthy", "C-Chain is
bootstrapped", "all primary chains bootstrapped" — and a frozen chain answers
every one of them. Readiness agreed: 15 pods at 5/5 Ready, every public API
200, on all three networks, one of which was stopped.
mainnet 96369 tip 1,098,191 age 58.9h testnet 96368 tip 1,354 age 3.6m
devnet 96367 tip 528 age 3.3m
e2e/00-liveness.spec.ts gates on the TIP. A fixed "block every N seconds" is
wrong for demand-driven chains, so the gate CALIBRATES ITSELF: the tip may be
no older than 4x the chain's own median gap over its last 8 blocks, floored at
600s. Measured cadences are 7s on mainnet and ~300s on testnet/devnet (the
heartbeat CronJob) — no constant spans both. Median, not mean: one 27,375s gap
IS the freeze, and a mean would let a stall widen its own budget.
Four orthogonal tests: fresh, monotonic (the tip never goes backwards and a
served block keeps its hash — the reorg shape devnet had at height 512),
consistent (net_version vs NETWORKS.yaml), and honest — a full validator set
must imply a fresh tip, which asserts the readiness lie head-on. Mainnet is RED
on three of them. That is the correct result; do not widen the budget.
Coverage was a function of which spec somebody happened to write: Safe's 31
routes, MPC's 15 and the whole admin surface had ZERO tests between them while
api.lux.financial, whose API is dead, carried 293. So routes become DATA —
e2e/routes.ts, 156 declared pages across 11 surfaces, iterated by 60-surfaces
(anonymous) and 61-console (signed in). A new page is one line there.
HTTP status is not evidence: SEVEN of the nine origins answer 200 for a path
that does not exist (measured with a /zzz-bogus control each; only Safe and the
explorer 404 honestly, and those two get a live control test every run). Every
route is therefore opened in a browser and must have PAINTED — real text into a
non-empty root, AND not the app's error boundary. Two assertions, because a
crash screen sails through a length check: exchange /pool is 200 with 553
characters that all say "Something went wrong". One reload before failing,
which is exactly the line between a page with no retry and a broken page.
First run found, each with a screenshot:
exchange /positions /pool /pool/v2 /bridge /teleport crash screen at 200
exchange /vote white screen, 0 chars
explorer /tokens /stats /advanced-filter crash screen at 200
console /dashboard/{safes,mpc,bridge,dex,deploy} no network, chainId,
address or height anywhere on screen — five of the nine surfaces the owner
named, and nothing on them tells an operator which chain they are on
Also fixed, all of them tests that could not fail:
- Peer connectivity was `catch { console.log('firewalled — PASS'); return }`.
/v1/info is not uniformly firewalled, it is inconsistently deployed (POST:
testnet 200, mainnet 404, devnet 404). Now asserts mesh depth wherever it is
served and falls back to the P-Chain validator set, which a single-node
network fails.
- 02-exchange and 20-exchange-deep skipped 58 cases behind lux.exchange, whose
TLS handshake fails from this network in curl, python and chromium alike.
exchange.lux.network is the same deployment at 200 on all 22 routes.
helpers.exchangeBase() picks the door that answers; the vanity domain being
down is now one deliberate assertion instead of a silence over 22 pages.
- 13-iam asserted a hand-built /.well-known/jwks that returns 200 text/html —
the SPA catch-all — so JSON.parse threw. Follows the discovery document now,
which declares /v1/iam/.well-known/jwks, and checks the content-type.
- 50-login sliced userinfo to 300 chars and then parsed it, so a real sign-in
failed on "Unterminated string in JSON" AFTER proving IAM accepted the token.
Parsed in-page now, three claims out, nothing else in the log.
Login proven end to end: [email protected] -> lux.id -> /dashboard, iss=lux.id,
aud=lux-cloud-org-lux, userinfo 200, sign-out present. 3/3.
Read-only throughout. Every screen in 61-console can move value — treasury,
safes, mpc, bridge. It navigates and screenshots. No form submitted, nothing
prepared, nothing signed, nothing broadcast, nothing re-keyed.
Co-authored-by: Hanzo Dev <[email protected]>
The companion to 51-institutional-baseline: the same screen list, run
against the rebuilt console, so every "after" frame has a "before" to sit
beside.
It is a test, not a slideshow. It asserts positively — what must BE on the
page, never the absence of an error string:
• no screen renders blank (five used to be white voids)
• the page body never scrolls sideways at any viewport
• nothing is UNREACHABLE outside the viewport. "Outside the viewport" is
not the same as clipped — a cell inside a horizontal scroller can be
swiped to. The check walks up for a scrollable ancestor first, so it
catches the real defect (Safes putting THRESHOLD and OWNER off a 390px
screen with no affordance) and not every wide table.
• no bare unexplained zero: a `0` counts as a defect only when nothing
near it says idle / empty / none / unknown / unavailable
• every figure shown carries provenance
• the screens that state money or custody render through the shared
primitive, which is what makes a bare number impossible on them
Two caveats are written on the file rather than left to be discovered.
The chain data is real and live — balances, heights, validators, Safe
owners and thresholds are read from mainnet at capture time. The SESSION
is synthetic: lux.id's redirect_uri is https://lux.cloud/auth/callback, so
a local build cannot complete an OIDC round trip, and a fake token is
seeded into the sessionStorage keys the IAM SDK reads (it checks expiry and
calls userinfo; it does not verify the signature client-side). This spec
therefore proves nothing about authentication — 12-iam-login.spec.ts does
that, against the deployed surface.
Read-only. It signs nothing and submits nothing.
Co-authored-by: Hanzo Dev <[email protected]>
The companion to 51-institutional-baseline: the same screen list, run
against the rebuilt console, so every "after" frame has a "before" to sit
beside.
It is a test, not a slideshow. It asserts positively — what must BE on the
page, never the absence of an error string:
• no screen renders blank (five used to be white voids)
• the page body never scrolls sideways at any viewport
• nothing is UNREACHABLE outside the viewport. "Outside the viewport" is
not the same as clipped — a cell inside a horizontal scroller can be
swiped to. The check walks up for a scrollable ancestor first, so it
catches the real defect (Safes putting THRESHOLD and OWNER off a 390px
screen with no affordance) and not every wide table.
• no bare unexplained zero: a `0` counts as a defect only when nothing
near it says idle / empty / none / unknown / unavailable
• every figure shown carries provenance
• the screens that state money or custody render through the shared
primitive, which is what makes a bare number impossible on them
Two caveats are written on the file rather than left to be discovered.
The chain data is real and live — balances, heights, validators, Safe
owners and thresholds are read from mainnet at capture time. The SESSION
is synthetic: lux.id's redirect_uri is https://lux.cloud/auth/callback, so
a local build cannot complete an OIDC round trip, and a fake token is
seeded into the sessionStorage keys the IAM SDK reads (it checks expiry and
calls userinfo; it does not verify the signature client-side). This spec
therefore proves nothing about authentication — 12-iam-login.spec.ts does
that, against the deployed surface.
Read-only. It signs nothing and submits nothing.
Co-authored-by: Hanzo Dev <[email protected]>
The owner's ask is a Fireblocks/Alchemy-grade experience for institutional
investors. That is unmeasurable as an opinion, so this walks the whole
signed-in surface and records the six things such a console is actually
judged on — renders / says what it is / explains its zeros / had its data
calls succeed / finished loading / can be verified against the chain — at
all three viewports, with the evidence written next to the screenshots.
What it found on the deployed console (api.lux.network, mainnet):
• /dashboard/dex at 390px never finishes rendering. It fans out ~55
parallel eth_calls from the browser and 26 of them come back 429, so
the page sits on "Reading pools, reserves, and markets from chain…".
Analytics does the same with ~60. The two most data-dense screens are
therefore non-deterministic — 2567ch at laptop, 701ch at mobile.
• Seven routes silently render the Overview instead of themselves
(api-keys, wallets, team, settings, webhooks, usage, assets). The
router's catch-all redirects to /dashboard, so a bookmarked
key-management URL lands on a page that looks legitimate and is not
the one that was asked for. A 404 would be information; this is not.
Both are asserted, not just reported — they are the two defects here that
are never a judgement call. Everything else the run measures is a design
opinion and belongs in the review.
Two things were decomplected to make room for it rather than copied:
• signIn() moves into helpers. 50-login.spec.ts remains the PROOF that
the flow is real (token claims, userinfo 200, sign-out present); every
other authenticated spec now just uses it. A second copy of the click
path is how a run silently ends up at github.com/login when a button's
accessible name changes.
• Screenshots gain a SET (LUX_SHOT_SET), defaulting to the env name, so
a deliberate one-off capture reuses the existing shotPath machinery
instead of growing a second naming scheme beside it.
Measured, not assumed: the first version of the stuck-loading detector
only matched loading/fetching/please-wait, and it passed the DEX page at
390px while the screenshot plainly said "Reading … from chain…". A
detector that misses the real copy is just a slower way of not looking.
Read-only throughout. It signs in and it looks; it never submits a form,
never signs and never broadcasts.
Co-authored-by: Hanzo Dev <[email protected]>
The owner's ask is a Fireblocks/Alchemy-grade experience for institutional
investors. That is unmeasurable as an opinion, so this walks the whole
signed-in surface and records the six things such a console is actually
judged on — renders / says what it is / explains its zeros / had its data
calls succeed / finished loading / can be verified against the chain — at
all three viewports, with the evidence written next to the screenshots.
What it found on the deployed console (api.lux.network, mainnet):
• /dashboard/dex at 390px never finishes rendering. It fans out ~55
parallel eth_calls from the browser and 26 of them come back 429, so
the page sits on "Reading pools, reserves, and markets from chain…".
Analytics does the same with ~60. The two most data-dense screens are
therefore non-deterministic — 2567ch at laptop, 701ch at mobile.
• Seven routes silently render the Overview instead of themselves
(api-keys, wallets, team, settings, webhooks, usage, assets). The
router's catch-all redirects to /dashboard, so a bookmarked
key-management URL lands on a page that looks legitimate and is not
the one that was asked for. A 404 would be information; this is not.
Both are asserted, not just reported — they are the two defects here that
are never a judgement call. Everything else the run measures is a design
opinion and belongs in the review.
Two things were decomplected to make room for it rather than copied:
• signIn() moves into helpers. 50-login.spec.ts remains the PROOF that
the flow is real (token claims, userinfo 200, sign-out present); every
other authenticated spec now just uses it. A second copy of the click
path is how a run silently ends up at github.com/login when a button's
accessible name changes.
• Screenshots gain a SET (LUX_SHOT_SET), defaulting to the env name, so
a deliberate one-off capture reuses the existing shotPath machinery
instead of growing a second naming scheme beside it.
Measured, not assumed: the first version of the stuck-loading detector
only matched loading/fetching/please-wait, and it passed the DEX page at
390px while the screenshot plainly said "Reading … from chain…". A
detector that misses the real copy is just a slower way of not looking.
Read-only throughout. It signs in and it looks; it never submits a form,
never signs and never broadcasts.
Co-authored-by: Hanzo Dev <[email protected]>
LUX_ENV=local|devnet|testnet|mainnet is now the only environment knob. e2e/env.ts
resolves every base URL, RPC endpoint, network ID and chain ID from NETWORKS.yaml
at runtime; playwright.config.ts and helpers.ts derive from it instead of keeping
their own copies. Those copies had already drifted — the config resolved the
testnet explorer to test-explore.lux.network, which is NXDOMAIN, while helpers.ts
used the real explore-test.lux.network. The retired two-axis ENV/LUX_NETWORK
model now fails loudly rather than being half-honoured.
Projects are viewports (mobile 390x844, tablet 834x1194, laptop 1440x900), so
"mobile and desktop" is the same three frames every run. Screenshots go to
~/Desktop/lux-e2e/<env>/<surface>-<viewport>.png on pass, and shot-reporter.ts
copies a failing test's dying state next to them — a failure you cannot look at
is a failure you cannot fix.
50-login.spec.ts proves sign-in for real: lux.cloud -> lux.id -> /dashboard, with
the password from LUX_E2E_PASSWORD only. It refuses the cheap answers, because
each one lies here — /dashboard is an SPA that returns 200 and keeps its URL when
anonymous, and /auth/callback renders "Sign-in failed" at 200. The proof is the
token: a JWT in sessionStorage whose iss is the IdP and whose email is the
account typed, which lux.id/v1/iam/oauth/userinfo accepts (401 bare, 401 for a
junk token). That is the leg that breaks under invalid_client. It also asserts
the handoff is a public PKCE client and carries a negative control so the
positive assertions cannot go vacuous. Verified with a wrong password: red.
Measured while building this, not assumed:
- workers are per-env. Mainnet is CDN-fronted and ran the gate at 4 with
342/342; testnet hits the raw LB and lost 66 cases to throttling at 4 that
all passed at 2.
- `locator('text=/block/i').first()` in two specs resolved to a hidden Radix
menu trigger, so both tracked the nav's collapse rather than the blocks
table. Replaced by one shared assertion on the section AND a block link.
- the token check runs inside the page: page.request.get attaches request
headers to a thrown error, which printed a live bearer token into the log.
- 40-lux-financial carried a plaintext password; it now comes from the
environment.
Co-authored-by: Hanzo Dev <[email protected]>
LUX_ENV=local|devnet|testnet|mainnet is now the only environment knob. e2e/env.ts
resolves every base URL, RPC endpoint, network ID and chain ID from NETWORKS.yaml
at runtime; playwright.config.ts and helpers.ts derive from it instead of keeping
their own copies. Those copies had already drifted — the config resolved the
testnet explorer to test-explore.lux.network, which is NXDOMAIN, while helpers.ts
used the real explore-test.lux.network. The retired two-axis ENV/LUX_NETWORK
model now fails loudly rather than being half-honoured.
Projects are viewports (mobile 390x844, tablet 834x1194, laptop 1440x900), so
"mobile and desktop" is the same three frames every run. Screenshots go to
~/Desktop/lux-e2e/<env>/<surface>-<viewport>.png on pass, and shot-reporter.ts
copies a failing test's dying state next to them — a failure you cannot look at
is a failure you cannot fix.
50-login.spec.ts proves sign-in for real: lux.cloud -> lux.id -> /dashboard, with
the password from LUX_E2E_PASSWORD only. It refuses the cheap answers, because
each one lies here — /dashboard is an SPA that returns 200 and keeps its URL when
anonymous, and /auth/callback renders "Sign-in failed" at 200. The proof is the
token: a JWT in sessionStorage whose iss is the IdP and whose email is the
account typed, which lux.id/v1/iam/oauth/userinfo accepts (401 bare, 401 for a
junk token). That is the leg that breaks under invalid_client. It also asserts
the handoff is a public PKCE client and carries a negative control so the
positive assertions cannot go vacuous. Verified with a wrong password: red.
Measured while building this, not assumed:
- workers are per-env. Mainnet is CDN-fronted and ran the gate at 4 with
342/342; testnet hits the raw LB and lost 66 cases to throttling at 4 that
all passed at 2.
- `locator('text=/block/i').first()` in two specs resolved to a hidden Radix
menu trigger, so both tracked the nav's collapse rather than the blocks
table. Replaced by one shared assertion on the section AND a block link.
- the token check runs inside the page: page.request.get attaches request
headers to a thrown error, which printed a live bearer token into the log.
- 40-lux-financial carried a plaintext password; it now comes from the
environment.
Co-authored-by: Hanzo Dev <[email protected]>
Deployed + verified: bridge-zoo (font, leak, console) and bridge-pars
(font, leak) now pass at all 4 breakpoints — Basel Grotesk webfont loads,
description meta is white-labeled per brand, and networks load same-origin
(no cross-brand CORS). zoo-cloud overflow is gone (cloud-web v1.3.2). Only
bridge-lux font remains — its host runs a stale Go all-in-one image; flag
reworded with the real flip condition.
Co-authored-by: Hanzo Dev <[email protected]>
Deployed + verified: bridge-zoo (font, leak, console) and bridge-pars
(font, leak) now pass at all 4 breakpoints — Basel Grotesk webfont loads,
description meta is white-labeled per brand, and networks load same-origin
(no cross-brand CORS). zoo-cloud overflow is gone (cloud-web v1.3.2). Only
bridge-lux font remains — its host runs a stale Go all-in-one image; flag
reworded with the real flip condition.
Co-authored-by: Hanzo Dev <[email protected]>
Durable regression suite proving every public surface across Lux/Zoo/Hanzo/Pars
(explorers, wallets, id, votes, work boards, bridges, safe, clouds, exchanges)
is live, functional, and correctly branded at 390/768/1280/1920 — real
Chrome/Blink (channel:'chrome'), read-only (no txns, no wallet-connect, no auth;
the login form is asserted, never submitted).
- e2e/branded/surfaces.ts — ONE source-of-truth table (surface × posture × flags);
breakpoint-scoped flags carry a reason + flip condition
- e2e/branded/branded.spec.ts — table-driven 8-check battery (the owner's 6-point
rubric: 200 · branded head + zero cross-brand leak ·
no overflow · no app-console-errors · key control
visible · brand font); gate vs flag vs stale-flag
- e2e/branded/matrix-reporter.ts — renders the surface × breakpoint PASS/FLAG/FAIL
grid + defect ledger; writes branded-report/matrix.{md,txt,json}
- playwright.branded.config.ts — breakpoints as projects, chrome channel, matrix reporter
- e2e.yml `branded` job + README + LLM.md section
Baseline 104 cells: 67 pass · 37 flagged (each with a flip condition) · 0
regressions. Only a NEW un-flagged failure turns the run red. Screenshots →
~/Desktop/lux-shots-e2e/. Bumps @luxfi/universe 1.0.1 → 1.0.2.
Co-authored-by: Hanzo Dev <[email protected]>
Durable regression suite proving every public surface across Lux/Zoo/Hanzo/Pars
(explorers, wallets, id, votes, work boards, bridges, safe, clouds, exchanges)
is live, functional, and correctly branded at 390/768/1280/1920 — real
Chrome/Blink (channel:'chrome'), read-only (no txns, no wallet-connect, no auth;
the login form is asserted, never submitted).
- e2e/branded/surfaces.ts — ONE source-of-truth table (surface × posture × flags);
breakpoint-scoped flags carry a reason + flip condition
- e2e/branded/branded.spec.ts — table-driven 8-check battery (the owner's 6-point
rubric: 200 · branded head + zero cross-brand leak ·
no overflow · no app-console-errors · key control
visible · brand font); gate vs flag vs stale-flag
- e2e/branded/matrix-reporter.ts — renders the surface × breakpoint PASS/FLAG/FAIL
grid + defect ledger; writes branded-report/matrix.{md,txt,json}
- playwright.branded.config.ts — breakpoints as projects, chrome channel, matrix reporter
- e2e.yml `branded` job + README + LLM.md section
Baseline 104 cells: 67 pass · 37 flagged (each with a flip condition) · 0
regressions. Only a NEW un-flagged failure turns the run red. Screenshots →
~/Desktop/lux-shots-e2e/. Bumps @luxfi/universe 1.0.1 → 1.0.2.
Co-authored-by: Hanzo Dev <[email protected]>
Swap the Zoo (200200) and Pars (494949) Bounty/Reputation constants to the newly
deployed DAO-Safe-owned work-market (supersedes the smoke set). Selectors
(bountyCount/stateOf/treasury/writer) unchanged — addresses only. Lux 96369 untouched.
Co-authored-by: Hanzo Dev <[email protected]>
Swap the Zoo (200200) and Pars (494949) Bounty/Reputation constants to the newly
deployed DAO-Safe-owned work-market (supersedes the smoke set). Selectors
(bountyCount/stateOf/treasury/writer) unchanged — addresses only. Lux 96369 untouched.
Co-authored-by: Hanzo Dev <[email protected]>
Add console.lux.cloud (Lux Cloud Console — the web3/infra console app) as a
lux surface with its real route list (networks, nodes, gateway, api-keys,
tokens, indexer, oracles, wallet, settlement, usage/billing, kms/secrets, …)
and the valid lux.id password-grant login ([email protected]).
Harness login() now waits for the sign-in form and matches Tamagui inputs
(autocomplete="email" / placeholder="Email"), and treats /signin as a login
gate — without this the harness could not fill or detect this console's form.
Capture currently yields the /signin gate only: login is blocked IAM-side —
the lux-cloud app in lux.id is provisioned for host cloud.lux.network, so
console.lux.cloud/auth/callback is not an allowed redirect URI. Durable fix
(separate, out of this change): add console.lux.cloud to the `cloud` app hosts
in infra/k8s/iam/provision.yaml and reconcile IAM.
Co-authored-by: Hanzo Dev <[email protected]>
Add console.lux.cloud (Lux Cloud Console — the web3/infra console app) as a
lux surface with its real route list (networks, nodes, gateway, api-keys,
tokens, indexer, oracles, wallet, settlement, usage/billing, kms/secrets, …)
and the valid lux.id password-grant login ([email protected]).
Harness login() now waits for the sign-in form and matches Tamagui inputs
(autocomplete="email" / placeholder="Email"), and treats /signin as a login
gate — without this the harness could not fill or detect this console's form.
Capture currently yields the /signin gate only: login is blocked IAM-side —
the lux-cloud app in lux.id is provisioned for host cloud.lux.network, so
console.lux.cloud/auth/callback is not an allowed redirect URI. Durable fix
(separate, out of this change): add console.lux.cloud to the `cloud` app hosts
in infra/k8s/iam/provision.yaml and reconcile IAM.
Co-authored-by: Hanzo Dev <[email protected]>
work.lux / work.hanzo came up during rollout: they now serve the Dework shell
(HTTP 200) but render the amber FIXTURE fallback (owner 0x0000…0000 + "Missing or
invalid parameters") because their C-Chain work-market is undeployed (brands.ts
bounty == 0x0 — Lux frozen at flag-day tip, Hanzo key-gap). Keep them skip-gated
(un-skipping would fail the emerald-LIVE invariant), but fix the stale
"HTTP-unreachable" reason to the accurate go-live condition: flip when BountyV1 is
deployed on the org C-Chain and the board shows the emerald LIVE badge.
Co-authored-by: Hanzo Dev <[email protected]>
work.lux / work.hanzo came up during rollout: they now serve the Dework shell
(HTTP 200) but render the amber FIXTURE fallback (owner 0x0000…0000 + "Missing or
invalid parameters") because their C-Chain work-market is undeployed (brands.ts
bounty == 0x0 — Lux frozen at flag-day tip, Hanzo key-gap). Keep them skip-gated
(un-skipping would fail the emerald-LIVE invariant), but fix the stale
"HTTP-unreachable" reason to the accurate go-live condition: flip when BountyV1 is
deployed on the org C-Chain and the board shows the emerald LIVE badge.
Co-authored-by: Hanzo Dev <[email protected]>
32-work-boards: work.zoo + work.pars now both serve the redesigned Dework board
(luxfi/work-board). Replace the stale literal assertions (BountyV1 / #0 / "N
bounties" — removed by the redesign, the 3 false failures) with the real structural
contract via one parameterized factory: Dework chrome (icon-rail + w-64 <aside>
sidebar + Overview / Community Suggestions / Leaderboards / Combined Board nav),
inline-<svg> BrandMark (the logo is not an <img>), the Bounties Space-category grid,
the To Do / In Progress / In Review / Done kanban columns, the emerald LIVE badge
(not the amber FIXTURE fallback) + org chainId, and a UI<->chain check (the rendered
treasury truncation equals on-chain BountyV1.treasury()). Un-skip Pars (now live,
chain 494949); Lux/Hanzo stay skip-gated (HTTP-unreachable, brands.ts bounty==0x0).
31-vote: the vote shells are retiring -> work.<org>.network. Arm a hanzo.vote
shell-health guard that hammers cold loads and fails on the intermittent blank-#root
white-screen (Buffer-crash class) OR the "Made with love by Lux" cross-brand leak —
a real, known-flagged regression, kept armed (not muted/skipped). lux/zoo/pars green.
work-boards: 1 -> 10 passing (3 false failures fixed, Pars un-skipped, 2 skip).
vote: hanzo.vote deterministically flagged; lux/zoo/pars (6) green, 4 skip.
Co-authored-by: Hanzo Dev <[email protected]>
32-work-boards: work.zoo + work.pars now both serve the redesigned Dework board
(luxfi/work-board). Replace the stale literal assertions (BountyV1 / #0 / "N
bounties" — removed by the redesign, the 3 false failures) with the real structural
contract via one parameterized factory: Dework chrome (icon-rail + w-64 <aside>
sidebar + Overview / Community Suggestions / Leaderboards / Combined Board nav),
inline-<svg> BrandMark (the logo is not an <img>), the Bounties Space-category grid,
the To Do / In Progress / In Review / Done kanban columns, the emerald LIVE badge
(not the amber FIXTURE fallback) + org chainId, and a UI<->chain check (the rendered
treasury truncation equals on-chain BountyV1.treasury()). Un-skip Pars (now live,
chain 494949); Lux/Hanzo stay skip-gated (HTTP-unreachable, brands.ts bounty==0x0).
31-vote: the vote shells are retiring -> work.<org>.network. Arm a hanzo.vote
shell-health guard that hammers cold loads and fails on the intermittent blank-#root
white-screen (Buffer-crash class) OR the "Made with love by Lux" cross-brand leak —
a real, known-flagged regression, kept armed (not muted/skipped). lux/zoo/pars green.
work-boards: 1 -> 10 passing (3 false failures fixed, Pars un-skipped, 2 skip).
vote: hanzo.vote deterministically flagged; lux/zoo/pars (6) green, 4 skip.
Co-authored-by: Hanzo Dev <[email protected]>
Regression-test the full DAO/governance/work-market surface across the live
orgs, read-only (no txs/writes/signing), asserting REAL functionality:
- 30-dao-onchain: BountyV1/ReputationV1/Governor/Safe on Zoo 200200 + Pars
494949 via eth_call/eth_getCode — code present, bountyCount>=1, stateOf(0)==
Paid(6), treasury()==org DAO Safe, ReputationV1.writer()==BountyV1 (wiring).
Lux 96369: canonical CREATE2 Safe suite present, dead Governor asserted 0-byte
(staged post-flag-day). Hanzo 36963 skip-gated until deploy lands.
- 31-vote-surfaces: lux/zoo/hanzo/pars.vote render + correct brand + ZERO cross-
brand/competitor leakage + no white-screen (#root descendants + body text) +
Connect Wallet present. reads-chain gated skip-until-live (Decent-fork shells).
- 32-work-boards: work.zoo.network LIVE badge (green, not FIXTURE), bounty in
Paid column, on-chain ReputationV1 block, UI bounty count == chain bountyCount.
work.{lux,hanzo,pars} skip-until-live.
- 33-bounty-bot: bounties.zoo.network webhook routed+POST-only (405), unsigned
POST rejected (401), healthz internal-scoped (404 public correct). No secrets.
- 34-pars-chain: Pars 494949 chainId + monotonic height + explorer renders.
helpers.ts: additive ethCall/ethGetCode/hasCode/decodeAddress/encodeUint +
HANZO/PARS API domains + orgRpc() (one way to resolve any org's C-Chain RPC).
17-governance: drop dead unused SELECTORS block (on-chain contract asserts now
own their home in 30-dao-onchain). package.json: wire specs into test:full +
per-spec scripts.
36 active tests pass live, 8 skip-gated (ready to flip). Suite stays GREEN.
Co-authored-by: Hanzo Dev <[email protected]>
Regression-test the full DAO/governance/work-market surface across the live
orgs, read-only (no txs/writes/signing), asserting REAL functionality:
- 30-dao-onchain: BountyV1/ReputationV1/Governor/Safe on Zoo 200200 + Pars
494949 via eth_call/eth_getCode — code present, bountyCount>=1, stateOf(0)==
Paid(6), treasury()==org DAO Safe, ReputationV1.writer()==BountyV1 (wiring).
Lux 96369: canonical CREATE2 Safe suite present, dead Governor asserted 0-byte
(staged post-flag-day). Hanzo 36963 skip-gated until deploy lands.
- 31-vote-surfaces: lux/zoo/hanzo/pars.vote render + correct brand + ZERO cross-
brand/competitor leakage + no white-screen (#root descendants + body text) +
Connect Wallet present. reads-chain gated skip-until-live (Decent-fork shells).
- 32-work-boards: work.zoo.network LIVE badge (green, not FIXTURE), bounty in
Paid column, on-chain ReputationV1 block, UI bounty count == chain bountyCount.
work.{lux,hanzo,pars} skip-until-live.
- 33-bounty-bot: bounties.zoo.network webhook routed+POST-only (405), unsigned
POST rejected (401), healthz internal-scoped (404 public correct). No secrets.
- 34-pars-chain: Pars 494949 chainId + monotonic height + explorer renders.
helpers.ts: additive ethCall/ethGetCode/hasCode/decodeAddress/encodeUint +
HANZO/PARS API domains + orgRpc() (one way to resolve any org's C-Chain RPC).
17-governance: drop dead unused SELECTORS block (on-chain contract asserts now
own their home in 30-dao-onchain). package.json: wire specs into test:full +
per-spec scripts.
36 active tests pass live, 8 skip-gated (ready to flip). Suite stays GREEN.
Co-authored-by: Hanzo Dev <[email protected]>