1778 Commits
Author SHA1 Message Date
zeekayandHanzo Dev b10cc1adf6 iam: lux.vote and lux.exchange sign in with lux.id
Two more SPA clients, PKCE in the browser like wallet and explore. lux-vote is
luxfi/vote (vote2). lux-exchange retires the exchange's own siwx login: the
wallet proof moves into IAM's wallet connector and apps consume the wallets and
did claims. One identity, one authority.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 00:05:18 -07:00
zeekayandHanzo Dev c0f5037b0f Leave each brand's zones to that brand's universe
k8s/dns globs *.yaml, so this directory was syncing seven zones it does
not own. hanzo/universe and zoo/universe glob their own zone dirs the
same way, which means two scripts held write authority over one zone and
the last one run decided what the zone said.

Nothing is lost by dropping them here. hanzo-id and hanzo-network were
byte-identical to their copies in hanzo/universe. hanzo-ai there is the
newer file: reconciled against live DNS on 2026-07-26 and carrying the
note that a proxied:false record applied over a live proxied host
publishes the origin. zoo-network and zoo-exchange were byte-identical to
zoo/universe's, and its zoo-ngo is a superset -- the donate CNAME and the
*.web3.zoo.ngo Universal-SSL notes exist only there.

zoo.cloud had no copy under zoo/universe, so it moves rather than goes.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-03 21:28:12 -07:00
zeekayandHanzo Dev 13b18d13c3 Leave each brand's zones to that brand's universe
k8s/dns globs *.yaml, so this directory was syncing seven zones it does
not own. hanzo/universe and zoo/universe glob their own zone dirs the
same way, which means two scripts held write authority over one zone and
the last one run decided what the zone said.

Nothing is lost by dropping them here. hanzo-id and hanzo-network were
byte-identical to their copies in hanzo/universe. hanzo-ai there is the
newer file: reconciled against live DNS on 2026-07-26 and carrying the
note that a proxied:false record applied over a live proxied host
publishes the origin. zoo-network and zoo-exchange were byte-identical to
zoo/universe's, and its zoo-ngo is a superset -- the donate CNAME and the
*.web3.zoo.ngo Universal-SSL notes exist only there.

zoo.cloud had no copy under zoo/universe, so it moves rather than goes.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-03 21:28:12 -07:00
zeekay 02f395f7f0 Serve three sites from the object store instead of a disk copy
The store is the sites plane's own bucket, one prefix per site, and the
edge reads it directly. Only the sites whose prefix already exists move;
a root that names an empty prefix answers 404, which lux.tel did, so it
stays on disk until the sites lane publishes it.
2026-09-03 17:22:42 -07:00
hanzo-dev c5b81b1617 Serve three sites from the object store instead of a disk copy
The store is the sites plane's own bucket, one prefix per site, and the
edge reads it directly. Only the sites whose prefix already exists move;
a root that names an empty prefix answers 404, which lux.tel did, so it
stays on disk until the sites lane publishes it.
2026-09-03 17:22:42 -07:00
zeekay a10c4b18f1 Serve every site from the edge as files, not from pods
Seventeen sites now declare the same three objects lux.credit does: a
staticFiles Middleware, the route that names it, and the endpoint-less
Service the route schema requires. No image, so no Deployment. Each was
verified answering from the kubernetescrd router on this cluster, so each
opts back in to automated sync.

The DigitalOcean-era files that put these hosts behind a pod are gone,
as are the files that declared hanzo's own services from this universe:
hanzo/universe declares those, and a second declaration of one service
is the drift this repo exists to prevent.
2026-09-03 16:55:14 -07:00
hanzo-dev 2079731cf6 Serve every site from the edge as files, not from pods
Seventeen sites now declare the same three objects lux.credit does: a
staticFiles Middleware, the route that names it, and the endpoint-less
Service the route schema requires. No image, so no Deployment. Each was
verified answering from the kubernetescrd router on this cluster, so each
opts back in to automated sync.

The DigitalOcean-era files that put these hosts behind a pod are gone,
as are the files that declared hanzo's own services from this universe:
hanzo/universe declares those, and a second declaration of one service
is the drift this repo exists to prevent.
2026-09-03 16:55:14 -07:00
zeekay 4cedf330b2 Serve lux.credit from the edge as files, not from a pod
The site is a bundle on disk. The declaration is now the staticFiles
Middleware that serves it, the route that names it, and the endpoint-less
Service the route schema requires; there is no image, so the chart renders
no Deployment. Verified on this cluster: the edge answers from the
kubernetescrd router with no pod behind it, so this file opts back in to
automated sync.
2026-09-03 16:38:36 -07:00
hanzo-dev b3b7c7131d Serve lux.credit from the edge as files, not from a pod
The site is a bundle on disk. The declaration is now the staticFiles
Middleware that serves it, the route that names it, and the endpoint-less
Service the route schema requires; there is no image, so the chart renders
no Deployment. Verified on this cluster: the edge answers from the
kubernetescrd router with no pod behind it, so this file opts back in to
automated sync.
2026-09-03 16:38:36 -07:00
zeekay 0730def03a Opt every service out of automated sync until its file is true here
Every file that opted in described the DigitalOcean estate, so CD
applying it put a pod in front of static sites the edge already serves
from disk. Automation stays per service; a file re-opts in once what it
declares has been shown to run on this cluster.
2026-09-03 16:22:24 -07:00
hanzo-dev 909d3bd962 Opt every service out of automated sync until its file is true here
Every file that opted in described the DigitalOcean estate, so CD
applying it put a pod in front of static sites the edge already serves
from disk. Automation stays per service; a file re-opts in once what it
declares has been shown to run on this cluster.
2026-09-03 16:22:24 -07:00
zeekayandHanzo Dev 19600faa74 Point the fleet at the cluster it runs on
The generator destined every Application for a DigitalOcean cluster that no
longer exists, so nothing it produced could ever sync. kubernetes.default.svc
is the cluster hanzo-cd itself runs in, which is where these workloads live now.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-03 13:23:27 -07:00
hanzo-dev dff96d8908 Point the fleet at the cluster it runs on
The generator destined every Application for a DigitalOcean cluster that no
longer exists, so nothing it produced could ever sync. kubernetes.default.svc
is the cluster hanzo-cd itself runs in, which is where these workloads live now.

Claude-Session: https://claude.ai/code/session_01WAriF9qmzQJnBHfumZMxnd
2026-09-03 13:23:27 -07:00
zeekay f753ce5254 Prove the values still render before CD tries to
A universe is values files, so what breaks is a file the chart can no longer
template -- and today that is invisible until CD syncs and the service is
already down. Rendering every file against the chart CD will use, at the
version CD will use, moves that failure onto the push that caused it.

CI does not deploy: applying is CD's one job, and a second path means the
cluster can disagree with git while both report success.

Two files moved out of deploy/ because they are not values at all -- the
generator globs deploy/*/*.yaml, so a tunnel config sitting there becomes an
Application that cannot render.
2026-09-03 11:16:20 -07:00
hanzo-dev e530eeec7c Prove the values still render before CD tries to
A universe is values files, so what breaks is a file the chart can no longer
template -- and today that is invisible until CD syncs and the service is
already down. Rendering every file against the chart CD will use, at the
version CD will use, moves that failure onto the push that caused it.

CI does not deploy: applying is CD's one job, and a second path means the
cluster can disagree with git while both report success.

Two files moved out of deploy/ because they are not values at all -- the
generator globs deploy/*/*.yaml, so a tunnel config sitting there becomes an
Application that cannot render.
2026-09-03 11:16:20 -07:00
zeekay 11b0c97116 Record the edge the chains are actually served through
The Spark reboot put the chain RPC and explorer behind a Cloudflare tunnel, and nothing in the tree said so. Also route npm through our own package host.
2026-09-03 11:13:16 -07:00
hanzo-dev f8a4a42e64 Record the edge the chains are actually served through
The Spark reboot put the chain RPC and explorer behind a Cloudflare tunnel, and nothing in the tree said so. Also route npm through our own package host.
2026-09-03 11:13:16 -07:00
zeekayandHanzo Dev a00a67b23b Let the exchange read the bridge
The bridge screen reported every corridor unreachable — Zoo, Ethereum, Arbitrum,
Optimism, Base, Polygon, BNB, Avalanche, Bitcoin, Solana, TON, XRP — with "the
bridge did not answer, failed to fetch". The bridge was answering the whole
time.

bridge.lux.network/v1/bridge/networks returns 200 and JSON to curl. To a browser
it returns 200 and no access-control-allow-origin, because the exchange's origin
is not on this list, and every other CORS header being present makes that easy
to miss: allow-credentials, allow-headers, allow-methods and max-age all come
back, so a preflight looks configured. The one header that decides it is the one
omitted, and its absence is silent — no error, no status, just a response the
browser refuses to hand to the page.

Four origins added: the exchange on mainnet under both names it is served at,
and its testnet and devnet hosts.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-02 00:28:33 -07:00
zeekay 6192925d27 Pin past the versions the proxy can no longer serve
The bulk retag moved published tags. go.sum and sum.golang.org hold the
original bytes, proxy.golang.org answers not-found, and the direct fetch
disagrees, so builds stopped at a checksum before reaching a compiler.
Pin forward to versions the proxy still serves.
2026-09-01 13:08:15 -07:00
zeekayandHanzo Dev fa448c3488 Describe the CD that is running, not the one that was planned
deploy/README.md said "There is no CD in this repository yet" and that the
ApplicationSet "deliberately has no automated block". Both stopped being true
when the templatePatch was added: it emits automated + selfHeal for any file
setting cd.automated, and 81 of the 134 do. Read the old text and a hand edit
that gets healed back is a mystery.

It also cited chart 0.1.0 while the ApplicationSet pins 0.1.11.

The chart stays at 0.1.11, with the reason written down rather than left to be
rediscovered: three values files use kms.hostAPI and kms.credentialsSecret,
which 0.1.13 removed in favour of a ZAP transport, and its KMSSecret renders in
group kms.hanzo.ai/v1 instead of secrets.lux.network/v1alpha1. The lux KMS
operator has to serve that group first and the ApplicationSet's
ignoreDifferences still names the old one, so the bump is a migration with a
cluster prerequisite. Everything else 0.1.13 asks for is already satisfied —
hanzo/cloud is a claimed name and now carries the digest its guard requires.

LLM.md's auto-bump section described service-released.yml, which is gone, and
listed a k8s/ indexer bulk-bump against manifests that no longer exist. Its
known-drift table now carries what verify actually found.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:45:56 -07:00
zeekayandHanzo Dev 3007b77670 CI lives at .hanzo/workflows
git.hanzo.ai is the forge and act_runner is what executes here; .hanzo/workflows
is where it looks. A workflow under .github/ on this forge does not fail — it
queues, indefinitely, while reading as configured CI, which is the failure mode
worth avoiding. The runners were already right (runs-on: lux-build-amd64); only
the path was not.

.hanzo/actions/kms-action goes with them: it is CI machinery, and the three
workflows that named it by its old path were the kubectl ones.

.github/hero.svg stays. It is the README's banner and a forge profile asset, not
a workflow.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:45:55 -07:00
zeekayandHanzo Dev c51d417564 Eighteen moving tags now name bytes, and one of them was already lying
latest, main and alpine are pointers. Two nodes pulling the same values file a
week apart run different code, both report the same tag, and there is nothing to
roll back to because the name that was serving means something else now. Eleven
services were on :latest or :main, two on :alpine, and five more on floating
minors (consul 1.17, nats 2.10-alpine, python 3.11-slim and 3.12-alpine).

Each now reads repo:tag@sha256:… — still legible, no longer ambiguous — with
pullPolicy IfNotPresent, because a digest is an identity and re-pulling one asks
a question whose answer cannot have changed.

WHAT THIS FOUND. zoo/exchange-api pinned 2.3.34 to sha256:007beb83, and 2.3.34
resolves to sha256:3eae48ac — the tag was re-pushed after that pin was written.
The other three exchange-api deployments carried 2.3.34 with no digest, so they
would pull 3eae48ac while Zoo ran 007beb83: four deployments, one version name,
two sets of bytes, and every pod reporting the same number. All four now name
3eae48ac, which is what 2.3.34 means today.

lux-explorer/lux-faucet had `repository: ghcr.io/luxfi/faucet:v1.0.1`. A
repository names a path; carrying the version in it means image.tag reads empty
for every check that looks. Split, with the digest confirmed against ghcr as the
one v1.0.1 resolves to.

pin.sh gained --freeze for this, and its registry read stopped guessing at auth.
It assembled each registry's token endpoint from a table shaped like ghcr's,
which called quay.io's three cert-manager images and DO's lux-admin unpullable
while all four were serving. Registries say where their tokens come from, in the
401 they answer with; the challenge is read off the refusal now, and redirects
are followed, so registry.k8s.io's 307 to a regional mirror resolves too.

A tag that YAML reads as a number is written quoted. It was not, and consul's
`tag: '1.17'` came back bare on the first run — harmless there, and `'1.10'`
would have become 1.1 and asked for an image nobody published.

121 pins, 116 verify against their registry. The five that do not are
credentials, not defects: parsdao/explore x3 and DO's registry are not readable
with the token to hand.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:45:53 -07:00
zeekayandHanzo Dev c9e1987ab4 Twelve services were declared twice; deploy/ is the one that runs
k8s/ and deploy/ both described lux-build, lux-market, explorer, lux-indexer,
explore-fe-spc, rpc-tip-gate, exchange-api, lux-exchange, zoo-bot, zoo-build,
zoo-industries and hanzo's lux-market. Only deploy/ is read by anything: the
ApplicationSet generates one Application per deploy/<ns>/<app>.yaml. So a
kubectl apply from k8s/ moved the cluster to a description CD would then heal
back, and the values files were generated from what is RUNNING while these
manifests were hand-applied over a long period.

Not one of them was a plain duplicate, which is why this took a lift rather than
a delete. Every k8s manifest carried an object the values file did not:

  lux-build, lux-market, zoo-*      IngressRoute + Middleware — the hosts users
                                    actually reach, entirely outside CD
  lux-indexer (x2)                  the chain table and the 100Gi claim, both
                                    MOUNTED BY NAME in the values file and
                                    declared nowhere in it
  explorer (mainnet)                the 50Gi claim, likewise mounted, likewise
                                    undeclared
  explore-fe-spc                    the ConfigMap its envFrom names, so the
                                    values file described a pod that would start
                                    with none of its NEXT_PUBLIC_* set
  rpc-tip-gate                      the ServiceAccount, Role and binding its
                                    serviceAccountName names
  lux-exchange                      the rate-limit Middleware and the annotation
                                    that binds it

Each moved into the values file and was verified by rendering the chart against
the manifest object by object: the routes, ConfigMaps, claims and RBAC now come
out identical. All 134 values files render.

Two objects could not move, and are split into manifests of their own rather
than left inside a duplicate:

  luxd-rpc.yaml                     luxd's Service, selecting one label MORE
                                    than luxd's pods carry — the key the gate
                                    patches on and off. A chart-rendered Service
                                    selects the release's own pods, which is
                                    every luxd node, current or not.
  exchange-api-ingress.yaml         renaming it re-issues a certificate, and
                                    with prune off the old object would keep
                                    serving beside the new one.

k8s/lux-testnet/explorer.yaml stays whole. Its own kustomization records it as
aspirational and stale and says applying it would clobber the live explorer pod,
so its ConfigMap is not something to promote into the tree CD reconciles from.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:45:49 -07:00
zeekayandHanzo Dev 756628f3ec manifests/ and the droplet script go
manifests/{devnet,production,testnet}.yml claimed to be "source of truth for
what is deployed on lux-k8s", last edited 2026-04-20, listing tag: latest and
sha: latest per service. Nothing has read them since; the ApplicationSet reads
deploy/, and the only writer that touched them was update-image.yml, which was
sed-ing into a tree CD does not open.

manifests/safes.yaml was the one live file in there and is not a deployment
manifest at all — it is Lux's Safe topology. It moves to SAFES.yaml, beside
NETWORKS.yaml and chain.yaml, where this repo keeps its fact tables.

scripts/deploy-do.sh creates three DigitalOcean droplets, cloud-inits docker on
them and runs compose over ssh. That is the arrangement DOKS replaced.
sync-pars-universe.sh no longer copies it into the Pars bundle.

renovate.json stops matching manifests/. It still does not match deploy/, and
that is now on purpose rather than an oversight: pin.sh resolves a tag and its
digest from one registry read, and a second writer offering a bare tag would
leave the pair disagreeing — which deploys the digest.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:44:44 -07:00
zeekayandHanzo Dev 0734789c4e The IAM converge is a directory, not a session
The Job that applies Lux's app graph was declared; the ConfigMap it mounts was
not. `make iam-provision` built that ConfigMap with kubectl create --from-file,
deleted the Job, applied it, and waited — all against do-sfo3-hanzo-k8s, written
into the Makefile. So the document a converge actually read existed only in
whichever cluster someone last typed at, and nothing could say whether it was
provision.yaml at HEAD.

infra/k8s/iam/kustomization.yaml declares both, with the name-suffix hash off
because the Job mounts the ConfigMap by name. `kubectl diff -k infra/k8s/iam`
now answers the question the imperative form could not be asked.

It stays a manifest rather than becoming a deploy/ values file for two reasons,
both structural: it runs on the cluster that answers lux.id, not the one this
fleet is addressed to, and the app chart expresses Deployments, StatefulSets and
DaemonSets — not one-shot Jobs.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:44:43 -07:00
zeekayandHanzo Dev 2bace3da10 The cluster has a name, and it is written once
https://04c46df5-…k8s.ondigitalocean.com appeared as a literal in the
ApplicationSet destination and in three workflows' LUX_CLUSTER_ID. That DOKS
cluster is gone, so every Application in this fleet was addressed to a dead
apiserver — and moving to a live one meant finding four copies of the same fact,
any of which could be correct on its own.

destination.name: lux-cluster instead. The credential is registered under that
name in hanzo-cd, so which apiserver Lux runs on is answered where clusters are
registered rather than restated here. The three workflows that carried the id
went with the kubectl they were carrying it for.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:44:41 -07:00
zeekayandHanzo Dev f2e139087e Nothing here writes to a cluster
Four workflows reached a cluster directly and none of them could deploy this
repo's fleet:

auto-deploy.yml ran kubectl set image against ns hanzo for iam, console, chat
and twenty other hanzoai services that are not declared here, on a workflow_run
trigger naming "Hanzo Monorepo CI" — a workflow this repo does not have.

auto-deploy-gated.yml ran kubectl apply / kubectl kustomize over k8s/, which the
ApplicationSet does not read, so a successful run changed the cluster to
something git did not describe.

reusable-deploy-service.yml was a workflow_call whose every step was kubectl;
nothing in this repo calls it.

pr-preview.yml built node/ evm/ dex/ pricing/ — four directories that do not
exist, so the loop skipped all of them — then ran kubectl set image ... || true
against lux-dev-k8s, a cluster no ApplicationSet targets, swallowing the
failure. e2e.yml already runs the whole suite on every pull request.

k8s/security/restrict-loadbalancers.sh patched live luxd Services by port INDEX.
Those Services are ClusterIP now and their ports are declared in
deploy/lux-mainnet/luxd.yaml; a patch by index also removes a different port the
moment the list is reordered.

The path is deploy/pin.sh -> deploy/ -> the ApplicationSet. There is no second
one to keep in step.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:44:37 -07:00
zeekayandHanzo Dev 6b19c245d2 The release train writes where CD reads
update-image.yml sed image tags across k8s/ and manifests/; service-released.yml
carried a per-service table of k8s/ paths, four of which (k8s/lux-k8s/{kms,
broker,cex}) have never existed. The ApplicationSet reads deploy/*/*.yaml and
neither of those trees. Both listeners reported success and deployed nothing.

One listener now, over deploy/pin.sh. Which files move is read out of the
inventory by image.repository rather than kept in a table that has to be edited
when a service changes namespace — a second inventory, and it was already wrong.

It pushes to main instead of opening a PR to auto-merge. A pin that sits in a
PR is a release that has not happened, and the PR added nothing to review: the
digest was resolved from the registry before the write and read back after it.

compose*.yml is no longer bumped here. It is the local stack, not the fleet, and
renovate.json already matches it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:44:34 -07:00
zeekayandHanzo Dev f0af376543 deploy/pin.sh — the one thing that moves an image
cd.hanzo.ai does not watch the registry; it watches deploy/. Publishing an
image changed nothing, and every path that tried to close that gap wrote to a
cluster instead: kubectl set image in three workflows, kubectl apply in a
fourth. So the pin here has to be the deploy, and something has to write it.

A pin is two fields and one fact. tag is how a human says which release, digest
is what the kubelet pulls, and _helpers.tpl renders repo:tag@digest — which
resolves by digest. Written from two registry reads they can name two different
releases: the pod then reports the tag while running the digest, and the deploy
is green. One HEAD answers both, and both are written in one edit and read back
together, so there is nothing for them to drift against.

--which reads the inventory rather than a table. A release knows the repository
it published, not which namespaces run it; the directory already says.

Invariants are charts/app/pin.sh's. Its values root is its own directory, so it
cannot be pointed here; when it takes a root, this file goes.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 12:44:32 -07:00
zeekay 513c1970eb A network is a host, not a path prefix
Both files documented public RPC as /mainnet/v1/... and /testnet/v1/..., and
LLM.md closed with a CRITICAL note telling readers to always use the network
prefix. No such prefix exists: measured against api.lux.network, every
/mainnet/ and /testnet/ form returns 404, on either segment. It also put testnet
on the mainnet host, when testnet is api.lux-test.network.

The shape is /v1/<segment>/<chain>/rpc on that network's own host, with a brand
chain addressed by alias in the same position.

Stated with the segment the estate writes, and with the part that is not yet
true said plainly: the deployed gateways still answer bc, so this table
describes what one redeploy makes true rather than what the fleet does today.

This is the documentation that sent me looking for a dead service when the
service was answering the whole time.
2026-09-01 12:07:35 -07:00
zeekay 56854e3ad8 Say which segment luxd serves, in a line that is true
The three lines here each named one path in both columns — "/v1/bc/C/rpc -> 200
… /v1/bc/C/rpc -> 404" — because the /ext/ -> /v1/ pass rewrote the column that
was the whole point of the contrast, leaving each one asserting that a path both
answers and does not.

Restated against the segment the node serves now: a chain hangs from /v1/chain
and health from /v1/health/ops, with the retired spellings as the negative
column. server/http/probe_test.go and chain_test.go pin both sides, so the
comment can be checked rather than believed.
2026-09-01 10:51:19 -07:00
zeekay 22aa69caa1 Give M-Chain and K-Chain a door
Both run on the primary network — platform.getBlockchains returns M-Chain and
K-Chain alongside C, P, X and the rest, and genesis builds them from mpcvm and
keyvm. Neither had a route, so /v1/chain/M and /v1/chain/K answered 404 while
the chains behind them produced blocks.

They are the decentralized path for MPC signing and key management, so the
gateway has to be able to reach them before anything can use them in place of
the centralized services.

Cloned from the P-Chain entry, so rate limits, encoding and backend shape match
the chain it sits beside rather than inventing new ones.
2026-09-01 10:10:41 -07:00
zeekayandHanzo Dev 8f397285e1 lux-financial v1.5.5: Lux faces, screens, news archive
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 00:49:04 -07:00
hanzo-dev 83405488fb lux-financial v1.5.5: Lux faces, screens, news archive
Claude-Session: https://claude.ai/code/session_01SibH4wSn7LyvRhgp74C9QU
2026-09-01 00:49:04 -07:00
zeekayandHanzo Dev b9228c73ec The gateway asks luxd for health where luxd reports it
Health moved onto typed ops at /v1/health/ops, so the probe route the gateway
exposes and the url_pattern it forwards both move with it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 20:15:50 -07:00
zeekayandHanzo Dev 9c3cac6476 LLM.md: name the segment luxd serves
Six of these URLs were last rewritten by the /ext -> /v1 migration; the same
convention applies to /v1/bc -> /v1/chain.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 19:58:15 -07:00
zeekayandHanzo Dev 89e2274b46 Point the gateway and the scripts at the segment luxd serves
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]>
2026-08-30 17:47:43 -07:00
zeekayandHanzo Dev 4b61030355 Let testnet and devnet be reached
Both gateways declared a host and no tls, so cert-manager was never asked for a
certificate and the ingress answered with its own default self-signed one.
Every client fails the handshake before a route is ever consulted: curl reports
"unable to get local issuer certificate" and the connection ends there, which
reads as the API being down rather than untrusted.

api.lux.network already carries this block; testnet and devnet are the two that
were missing it, on the same ingress, which is why explore.lux.network answers
on that address and these two do not.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 16:25:32 -07:00
zeekayandHanzo Dev 7417193940 A chain answers under one segment, and the fleet asks for it there
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]>
2026-08-30 16:17:20 -07:00
zeekayandHanzo Dev e0435677f9 lux.vote's source is luxfi/vote
The interface moved out of luxfi/dao, whose app submodule carried the one
derived from Decent DAO. luxfi/vote builds it and publishes ghcr.io/luxfi/vote,
which is the repository this file already names.

The tag is unchanged and stays until that workflow publishes: it is the last
image built from luxfi/dao and it is what lux.vote serves. Pinning a tag no
registry holds takes the site down, and the record above this line is about
exactly that failure in the other direction — a workflow publishing where no
declaration reads.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 15:24:44 -07:00
zeekayandHanzo Dev 1224e97a50 lux.vote: the image that has the governance route
Built from luxfi/dao 25feb87 (app 31de075) and verified before pushing:
the container serves / and /governance, and the bundle carries the
Governor address and governorFromBlock.

The tag it replaces was from 2026-07-18. Every commit since built an
image nobody pulled, because the Vote workflow published to
ghcr.io/luxfi/dao-vote while this file reads ghcr.io/luxfi/vote — which
is why lux.vote still shipped a featured DAO address that was replaced
on 2026-08-05. The workflow now targets this repository.

What changes for a visitor: / stops redirecting to the dashboard of a
Safe that has no code on 96369 — every Safe address any record names is
empty there — and lands on /governance, which reads the Governor that is
live: "Lux Governor", CLOCK_MODE mode=timestamp, 1-day delay, 7-day
period. It has never had a proposal, and the page says so rather than
showing an error.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 14:31:21 -07:00
zeekayandHanzo Dev 50646b4298 e2e: assert what 96369 says about the DAO stack
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]>
2026-08-29 22:58:59 -07:00
zeekayandHanzo Dev f603720b0b iam: declare the dao client so lux.vote can sign in
lux.vote's bundle sends client_id=lux-dao to https://lux.id, and lux-dao
was never declared here. The authorize endpoint answers it with
"unknown client_id" — the same refusal as a client that does not exist,
which is what it is. Thirteen lux apps were declared and this was not
one of them, so sign-in could never have worked on that host.

Named `dao` rather than `vote` because one bundle serves lux.vote,
zoo.vote and pars.vote and resolves issuer and client from the hostname
(lux/dao app/src/auth/config.ts). The app is the DAO; vote is where it
answers. The other two belong to their own org's universe.

Derivation checked against the live bundle rather than assumed:
`provision --dry-run` gives
  would-upsert lux-dao    https://lux.vote/auth/callback
and the deployed bundle sends exactly that pair.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-29 22:42:03 -07:00
zeekayandHanzo Dev fa8e22edf2 Our nodes serve /v1/, so ask them for /v1/
luxd registers /v1/ and nothing else; api.lux.network, api.zoo.ngo,
api.hanzo.network, api.pars.network and api.osage.network all answer 200 on
/v1/bc/C/rpc and 404 on /ext/bc/C/rpc. Every /ext/ path aimed at one of our
hosts was a request that could only fail.

/ext/ is Avalanche's prefix. Endpoints on api.avax.network keep it — they are
someone else's convention and a real bridge corridor depends on them.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-29 10:45:48 -07:00
zeekay 3557e45658 iam: the database credential comes from a secret
IAM_DATABASE_URL carried a Postgres password in the clear, in a tracked
file, while every other credential in the same file already read from a
secretKeyRef. It now does too.

This file is generated from the live Deployment, so the value in git is
a copy of the exposure rather than the exposure itself — the running
Deployment carries it in the clear as well. Both need fixing.

The credential still needs ROTATING. It is in this repository's history
(7820541), where removing a line does not reach.
2026-08-28 23:11:43 -07:00
zeekayandHanzo Dev f9bef2b2ce lux-fund: name the image that exists, and the node that can run it
The tag here was c30add15, built 22 July. Every change to lux.fund since
has built, published to GHCR and gone nowhere — the site has been serving
the July bundle for five weeks, and this file is why: whatever reconciles
it would have put the July image back.

This file's own header says live is the source, because a repo of
hand-applied manifests has unknown currency and a running fleet does not.
That was true and is not any more. The DigitalOcean account is suspended,
the estate is on evo/spark, lux-fund has no namespace there and nothing on
that cluster reconciles this — there is no ApplicationSet. Live is not a
source now; it is an outage. This file is the only statement of what
lux.fund should run, so it had better be right.

It also carries a constraint the old cluster never needed. The base image,
ghcr.io/hanzoai/spa, is amd64-only. evo is amd64 and spark is arm64, so
without a selector the scheduler is free to place this where it cannot
run.

What this does not do is deploy anything. Where lux.fund should live on
the new cluster, and what reconciles it, is a decision rather than a
lookup.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 12:42:58 -07:00
zeekayandHanzo Dev 78899f83f8 the corporate name is Lux Industries Inc
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.

lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.

Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 12:42:57 -07:00
zeekayandHanzo Dev 92228aca91 lux.finance has one owner
Two Ingresses claimed the apex — this app and bank-dash — and a host with
two claims goes to whichever was admitted first. bank-dash's claim has
been dead on arrival, which is why lux.finance served the standalone
Liquid page while the manifest said otherwise. The Liquid story moves
into bank-dash's own landing, so this surface has nothing left to serve.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 12:42:56 -07:00
zeekayandHanzo Dev f3f3c6f827 lux-financial v1.5.3: the standalone server boots
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 12:41:57 -07:00
Hanzo AI cf493dcc3a lux-financial v1.5.3: the standalone server boots
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 12:41:57 -07:00