Commit Graph
402 Commits
Author SHA1 Message Date
zeekay 5c7e9fd3bd build(web): serve the static export via hanzoai/static, not a Node server
The image build could not succeed at all. next.config.ts declares
`output: "export"`, which emits ./out and never emits .next/standalone, but the
Dockerfile's runner stage did:

  COPY --from=builder /app/.next/standalone ./
  CMD ["node", "server.js"]

so every build failed with `"/app/.next/standalone": not found` after compiling
successfully. Two mutually exclusive deploy models in one repo; the deployed
image simply stayed frozen at its last pre-`export` build.

One model now — the static export, served by ghcr.io/hanzoai/static:v0.5.1
(scratch + a single Go binary; no nginx, no caddy, no Node in the runtime
image), matching hanzo.ai/Dockerfile.production.

Port stays 3001 via static's --port flag, so the existing contract is untouched:
the bootnode-web App CR declares containerPort/servicePort 3001 and the
hand-made bootnode-web-ingress routes to bootnode-web:3001 by number.

Includes the deep-link fix the house template documents: the export is flat
(out/pricing.html), static serves a directory's index.html, and in --spa mode a
miss on extensionless /pricing falls through to the homepage — so each page also
gets materialized as out/pricing/index.html.

Also stops tracking web/tsconfig.tsbuildinfo, a build artifact that was dirtying
the tree on every typecheck.
2026-07-26 10:35:31 -07:00
zeekay 4b9de98537 build(web): pin the toolchain and bump every dependency to latest
The repo could not be built at all. Two independent faults, both from an
unpinned toolchain:

1. `corepack enable pnpm` with no `packageManager` field fetched whatever pnpm
   was newest at build time. pnpm 11 REMOVED `onlyBuiltDependencies` (replaced
   by `allowBuilds`) and made `strictDepBuilds` default to true, so the sharp
   allowlist that package.json still carried was silently ignored and install
   died with ERR_PNPM_IGNORED_BUILDS. pnpm 11 also demotes `.npmrc` to
   auth/registry only, so settings must live in pnpm-workspace.yaml.

   Fixed by pinning `packageManager: [email protected].0` and moving the setting to
   pnpm-workspace.yaml in its current form (`allowBuilds: {sharp: true}`).

2. The `pnpm` field in package.json is dead weight under pnpm 11 — removed
   rather than left to rot as a second, ignored source of the same setting.

Dependencies taken to latest, including four majors:

  typescript      5.9.3   (NOT 7.0.2 — see below)
  zod             3 -> 4
  recharts        2 -> 3
  lucide-react    0.544 -> 1.26
  next            16.0.1 -> 16.2.11
  react/-dom      19.2.0 -> 19.2.8
  @hanzo/iam      0.11 -> 0.13.8
  tailwindcss     4.1 -> 4.3.3
  @types/node     22 -> 24 (aligned to the runtime, not bumped past it)
  + every Radix, TanStack and support package

TypeScript is deliberately held at 5.9.3, the newest that works. 7.0.2 is the
Go-native rewrite with a different package shape; Next 16.2 cannot resolve it
and the build fails after compiling, in Next's TypeScript step, with
`The "id" argument must be of type string. Received undefined` after it tries
to reinstall typescript itself. Revisit when Next declares support.

Base image node:22-alpine -> node:24-alpine (Active LTS; 26 is Current, not
LTS), digest-pinned so it cannot drift the way pnpm did. pnpm 11 needs Node
>= 22 regardless.

Verified locally, not assumed: `pnpm install` exits 0, `tsc --noEmit` reports
0 errors across the whole bumped tree, and `pnpm run build` exits 0 with every
route prerendered.
2026-07-26 10:32:54 -07:00
zeekay b53533db53 fix: drop the dangling skills gitlink that made the repo unbuildable
`skills` was committed as a gitlink (mode 160000, a0665829) but the repo has
no .gitmodules, so the entry has no URL. Any checkout that initialises
submodules dies on it:

  fatal: No url found for submodule path 'skills' in .gitmodules

BuildKit's git source always updates submodules, so this failed EVERY
platform-native build of this repo before it could even read the Dockerfile.
That is why the deployed bootnode-web image still dates from June while main
moved on.

The path is an empty directory with no upstream recorded anywhere, so the
gitlink points at nothing recoverable — removing the index entry is the whole
fix. If a skills submodule is wanted later it needs a real .gitmodules URL.
2026-07-26 10:23:52 -07:00
zeekay c289ba602c fix(web): one way to resolve IAM — the brand catalog, not a global env
Four of the five hosts this deployment serves could not sign in at all, and
the fifth signed in under the wrong company's name.

bootnode-web serves bootno.de, web3.hanzo.ai, cloud.hanzo.network,
cloud.pars.network and cloud.zoo.network from ONE image. The IAM issuer and
client_id were set by NEXT_PUBLIC_IAM_URL / NEXT_PUBLIC_IAM_CLIENT_ID, which
are inlined into the client bundle at build time — a single global value that
every host shares. It cannot express per-host white-labeling, and it silently
outranked the correct per-brand value because lib/iam.ts read
`process.env.X || brand.iam.X`.

Measured against the live /v1/iam/oauth/authorize (302 = registered pair,
400 = unregistered redirect_uri), with the env in force today:

  bootno.de            302   (but branded "Sign in to Hanzo ID")
  web3.hanzo.ai        400
  cloud.hanzo.network  400
  cloud.pars.network   400
  cloud.zoo.network    400

The env pinned client_id=bootnode-platform for all five, and that client only
carries bootno.de's callback — so every other host was rejected.

The white-label mechanism the app already has is the runtime brand catalog:
hostname -> brand key -> BrandConfig, resolved after hydration. It was never
usable, for two reasons, both fixed here:

  * getBrand() REPLACED the default with the catalog entry, so any partial
    entry yielded a BrandConfig with undefined name/colors and crashed
    rendering. Entries are now merged over the built-in default, so a brand
    that only needs a different issuer writes only its `iam` block.
  * public/config.json shipped empty with a comment claiming the deploy
    overwrites it. Nothing does — /config.json 404s in production, so every
    host fell back to bootnode. The real catalog now ships in the image.

Removes both IAM env knobs entirely (app, Dockerfile ARG/ENV). The Dockerfile
defaults were also both wrong — iam.bootno.de serves no TLS (525) and
bootnode-web is not a registered client — so any rebuild taking the defaults
would have broken sign-in outright.

After this change, each host resolves its own verified issuer:

  bootno.de            id.bootno.de  bootnode-platform  302
  web3.hanzo.ai        iam.hanzo.ai  hanzo-web3         302
  cloud.pars.network   pars.id       pars-app           302

cloud.hanzo.network and cloud.zoo.network still answer 400: their callbacks
are not on the respective clients' redirect lists. Both hosts also do not
currently resolve, so this is not a regression — registering those two
redirect URIs in IAM is what remains before they can go live.

Zoo's issuer is zoolabs.id, not zoo.id: zoo.id serves no OIDC discovery and
no TLS.
2026-07-26 10:21:14 -07:00
zeekay 5d1620ab8e fix(api): stop advertising a /docs URL that production disables
The FastAPI app deliberately turns off docs/redoc/openapi in production:

    docs_url="/docs" if not settings.is_production else None

but the root endpoint returned `"docs": "/docs"` unconditionally. So the
public API root promised a URL that 404s — api.bootno.de/ says
{"name":"Bootnode API","version":"2.0.0","docs":"/docs","status":"operational"}
while /docs, /redoc and /openapi.json all return {"detail":"Not Found"}.

Anyone reading the root to find the schema followed a dead link, and the
disabled-in-prod decision was correct, so the answer is to tell the truth
rather than re-enable them.

root() now mirrors app.docs_url and omits the key entirely when docs are off,
so the two cannot drift again — there is no second place to remember when the
setting flips. Verified both ways: with docs_url None the key is absent, with
"/docs" it is present.
2026-07-26 10:03:31 -07:00
zeekayandHanzo Dev 37b6492d08 iam: canonical endpoints; drop the hand-rolled login button
The API resolved JWKS at {iam_url}/.well-known/jwks.json and exchanged
codes at {iam_url}/oauth/token — measured 401 on both; the canonical
/v1/iam/.well-known/jwks and /v1/iam/oauth/token answer.

brand.json pointed at iam.bootno.de, which does not resolve (525). The
brand's canonical issuer is id.bootno.de, already what web/lib/brand.ts
uses.

HanzoLoginButton hand-rolled a /oauth2/authorize URL that exists on no
IAM deployment. It was imported but never rendered — the login page has
used getIam().signinRedirect() (@hanzo/iam PKCE) since HIP-0111 — so it
is deleted rather than repointed. One way in.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-25 13:34:40 -07:00
zeekayandHanzo Dev 2307ce8f48 chore(kms): retire Node hanzoai/kms → luxfi/kms:1.11.9 in local-dev compose
Swap the local-dev KMS onto Go/ZAP luxfi/kms (embedded ZapDB): ZAP_PORT=0
(secrets-only — v1.11.x gates the consensus authorizer behind ZAP, so no
consensus/luxd needed locally), fixed dev KMS_MASTER_KEY_B64 /
KMS_ENCRYPTION_KEY_B64, /healthz healthcheck, kms_data volume. Drops the
Node env (DB_CONNECTION_URI/ENCRYPTION_KEY/AUTH_SECRET/SITE_URL) and the
Postgres/Redis deps only the Node kms used (both DBs kept for other services).
Live prod (luxfi/kms:1.11.9) untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-19 00:49:24 -07:00
zeekayandHanzo Dev 6b0e4399de fix(cors): allow lux.cloud + console.lux.cloud origins
lux.cloud is the canonical Lux web3 console; its RPC & API-Keys section calls
the control plane (api.cloud.lux.network) from the browser. The production CORS
allowlist had cloud.lux.network but not lux.cloud, so key create/list/revoke
preflights 400'd. Add the canonical console origins to production_origins.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-15 23:09:59 -07:00
zeekay 46ebd33d0e merge platform/web3-iam-hip0111-convergence to main 2026-07-01 06:33:10 -07:00
zeekay 5aeb6ec6b9 Merge branch 'platform/web3-iam-hip0111-convergence' 2026-06-30 14:32:59 -07:00
zeekay 532e89b3ad docs(brand): add hero banner 2026-06-28 20:10:22 -07:00
zeekay 880beacbcb chore(brand): dynamic hero banner 2026-06-28 20:10:21 -07:00
zeekayandGitHub 36ade30e19 Web3/Bootnode: converge on @hanzo/iam (HIP-0111) — SPA PKCE FE + JWKS backend (#8)
* feat(web3): restore canonical @hanzo/iam SPA PKCE auth (HIP-0111)

The brand-neutral refactor regressed login to a hand-rolled OAuth authorize
URL (no PKCE, wrong path `${iamUrl}/oauth/authorize`) plus a backend
code-exchange POST to /v1/auth/oauth/callback — a route api-go does not
expose. Both break IAM login: the hand-rolled URL hits a path IAM serves
its 200 SPA catch-all for (silent breakage), and the callback 404s.

Restore the one canonical way per HIP-0111:
- lib/iam.ts: @hanzo/iam/browser IAM client, white-labelled by the active
  brand (serverUrl + clientId), PKCE S256, endpoints resolved via OIDC
  discovery. No hand-rolled authorize URL, no genericOAuth, no /api/auth.
- login: getIam().signinRedirect() in production (SDK owns PKCE + state).
- callback: getIam().handleCallback() does the PKCE token exchange
  client-side; the IAM access token becomes the session bearer.
- brand default: bootnode IAM is id.bootno.de / bootnode-platform (was
  hanzo.id / bootnode-web — wrong issuer + unregistered client).
- package.json: add @hanzo/iam ^0.11.0 (used but missing from deps).
- public/config.example.json: in-repo reference for the per-brand catalog
  (registered clientId per host) so the deploy provisions real clients.

Build + typecheck green.

* feat(api): validate IAM tokens via JWKS in production (HIP-0111)

The api-go backend only had an HS256 verifier (shared JWT_SECRET). After the
FE completes IAM PKCE it presents an IAM-issued RS256 access token, which the
HMAC verifier rejects (SigningMethodHMAC check) — IAM login fails at the API.

Converge on the canonical IAM validation path:
- iam/jwks.go: JWKSVerifier validates RS256/ES256 tokens against the issuer's
  JWKS, resolved via OIDC discovery ({issuer}/.well-known/openid-configuration
  -> jwks_uri). Keys cached, refreshed on unknown kid (rate-limited). stdlib
  only — no new dependency. Issuer is checked (WithIssuer).
- main.go: production selects JWKSVerifier(IAM_URL); dev keeps HS256 for the
  built-in email/password flow. One way per environment.
- config: ValidateSecrets() fails fast if production boots with placeholder/
  empty JWT_SECRET or API_KEY_SALT — secrets MUST come from KMS, never the
  in-code default. IsProduction() gates it.
- iam.go: drop redundant exp/iat int64 fields from Claims — duplicate json
  tags shadowed jwt.RegisteredClaims and silently disabled expiry validation
  (latent bug in the HS path too; now exp is enforced).

Tests: JWKS valid/expired/wrong-issuer/unknown-kid/HS-rejected + config
secret guard. go build + go vet + go test all green.

* harden(api): enforce JWT audience when IAM_CLIENT_ID is set

Adversarial review follow-up: without an aud check, a token minted for ANY
client of the same IAM issuer would be accepted. NewJWKS now takes the
expected audience (the deployment's IAM_CLIENT_ID); when set, tokens whose
aud does not match are rejected (token minted for a different client is no
longer accepted). Empty audience preserves issuer+signature-only behavior.

Test: matching aud passes, mismatched aud rejected.

* fix(web): sync lockfile for @hanzo/iam + remove invalid pnpm-workspace.yaml

test-web failed at `pnpm install --frozen-lockfile` for two reasons:

1. web/pnpm-workspace.yaml carried only `allowBuilds: {sharp: true}` — not a
   valid pnpm 9 field, and a workspace file without a `packages:` key makes
   pnpm 9 abort with ERR_PNPM_INVALID_WORKSPACE_CONFIGURATION (pre-existing
   on main). web/ is a single package, not a workspace, so the file is removed
   and sharp's build allowance moved to package.json `pnpm.onlyBuiltDependencies`.

2. This branch added `@hanzo/iam@^0.11.0` to package.json but never regenerated
   pnpm-lock.yaml, so --frozen-lockfile rejected the out-of-date lock.

Regenerated the lockfile (pnpm 9.15.9) — it now pins @hanzo/[email protected].0. Verified
locally: `pnpm install --frozen-lockfile` is clean and `pnpm run build`
(next build) compiles all routes incl. the refactored /login + /auth/callback.

---------

Co-authored-by: zeekay <[email protected]>
2026-06-25 15:13:17 -07:00
zeekay 5c9746a7a2 fix(web): sync lockfile for @hanzo/iam + remove invalid pnpm-workspace.yaml
test-web failed at `pnpm install --frozen-lockfile` for two reasons:

1. web/pnpm-workspace.yaml carried only `allowBuilds: {sharp: true}` — not a
   valid pnpm 9 field, and a workspace file without a `packages:` key makes
   pnpm 9 abort with ERR_PNPM_INVALID_WORKSPACE_CONFIGURATION (pre-existing
   on main). web/ is a single package, not a workspace, so the file is removed
   and sharp's build allowance moved to package.json `pnpm.onlyBuiltDependencies`.

2. This branch added `@hanzo/iam@^0.11.0` to package.json but never regenerated
   pnpm-lock.yaml, so --frozen-lockfile rejected the out-of-date lock.

Regenerated the lockfile (pnpm 9.15.9) — it now pins @hanzo/[email protected].0. Verified
locally: `pnpm install --frozen-lockfile` is clean and `pnpm run build`
(next build) compiles all routes incl. the refactored /login + /auth/callback.
2026-06-25 15:03:33 -07:00
zeekay 8d72ed288d harden(api): enforce JWT audience when IAM_CLIENT_ID is set
Adversarial review follow-up: without an aud check, a token minted for ANY
client of the same IAM issuer would be accepted. NewJWKS now takes the
expected audience (the deployment's IAM_CLIENT_ID); when set, tokens whose
aud does not match are rejected (token minted for a different client is no
longer accepted). Empty audience preserves issuer+signature-only behavior.

Test: matching aud passes, mismatched aud rejected.
2026-06-25 14:00:06 -07:00
zeekay c08bf1006f feat(api): validate IAM tokens via JWKS in production (HIP-0111)
The api-go backend only had an HS256 verifier (shared JWT_SECRET). After the
FE completes IAM PKCE it presents an IAM-issued RS256 access token, which the
HMAC verifier rejects (SigningMethodHMAC check) — IAM login fails at the API.

Converge on the canonical IAM validation path:
- iam/jwks.go: JWKSVerifier validates RS256/ES256 tokens against the issuer's
  JWKS, resolved via OIDC discovery ({issuer}/.well-known/openid-configuration
  -> jwks_uri). Keys cached, refreshed on unknown kid (rate-limited). stdlib
  only — no new dependency. Issuer is checked (WithIssuer).
- main.go: production selects JWKSVerifier(IAM_URL); dev keeps HS256 for the
  built-in email/password flow. One way per environment.
- config: ValidateSecrets() fails fast if production boots with placeholder/
  empty JWT_SECRET or API_KEY_SALT — secrets MUST come from KMS, never the
  in-code default. IsProduction() gates it.
- iam.go: drop redundant exp/iat int64 fields from Claims — duplicate json
  tags shadowed jwt.RegisteredClaims and silently disabled expiry validation
  (latent bug in the HS path too; now exp is enforced).

Tests: JWKS valid/expired/wrong-issuer/unknown-kid/HS-rejected + config
secret guard. go build + go vet + go test all green.
2026-06-25 13:54:11 -07:00
zeekay 6b2e1bc86f feat(web3): restore canonical @hanzo/iam SPA PKCE auth (HIP-0111)
The brand-neutral refactor regressed login to a hand-rolled OAuth authorize
URL (no PKCE, wrong path `${iamUrl}/oauth/authorize`) plus a backend
code-exchange POST to /v1/auth/oauth/callback — a route api-go does not
expose. Both break IAM login: the hand-rolled URL hits a path IAM serves
its 200 SPA catch-all for (silent breakage), and the callback 404s.

Restore the one canonical way per HIP-0111:
- lib/iam.ts: @hanzo/iam/browser IAM client, white-labelled by the active
  brand (serverUrl + clientId), PKCE S256, endpoints resolved via OIDC
  discovery. No hand-rolled authorize URL, no genericOAuth, no /api/auth.
- login: getIam().signinRedirect() in production (SDK owns PKCE + state).
- callback: getIam().handleCallback() does the PKCE token exchange
  client-side; the IAM access token becomes the session bearer.
- brand default: bootnode IAM is id.bootno.de / bootnode-platform (was
  hanzo.id / bootnode-web — wrong issuer + unregistered client).
- package.json: add @hanzo/iam ^0.11.0 (used but missing from deps).
- public/config.example.json: in-repo reference for the per-brand catalog
  (registered clientId per host) so the deploy provisions real clients.

Build + typecheck green.
2026-06-25 13:50:12 -07:00
12549c80ec ci: run on self-hosted ARC pool (hanzo-build-linux-amd64/deploy), not GitHub-hosted (#6)
Co-authored-by: zeekay <[email protected]>
2026-06-19 20:33:05 -07:00
f2d722a735 web: decomplect brand into runtime white-label config (mirror @hanzo/id) (#5)
* fix(web): use real Casdoor client zoo-console for Zoo brand IAM

The zoo brand preset used clientId "zoo-app-client-id", which is not a
registered Casdoor application — Casdoor's get-app-login returns
"Invalid client_id" for it, so login from zoo.cloud / web3.zoo.ngo via
zoolabs.id never completed.

Switch to "zoo-console" (org "zoo"), the actual registered app whose
redirectUris already include https://zoo.cloud/* and
https://zoolabs.id/callback, so the OAuth round-trip resolves with no
IAM-side changes required.

* web: static-export build for lux.cloud (Lux Cloud) Cloudflare Pages deploy

Deploy the web3 console as lux.cloud (Lux brand). Convert from SSR
(output: standalone) to static export so it serves as static assets on
Cloudflare Pages with no edge/Node runtime.

- next.config.ts: output "export" + images.unoptimized; drop headers()/
  redirects() (unsupported in export) — moved to public/_headers and
  public/_redirects for Pages.
- app/layout.tsx: bake brand from NEXT_PUBLIC_BRAND/BRAND; remove
  force-dynamic + headers() host detection (client still auto-detects brand
  from window.location.hostname via lib/brand.ts).
- app/api/health, app/llms.txt: mark route handlers force-static.
- Remove app/dashboard/api/keys route handler (backend proxy unsupported in
  export; the dashboard talks to brand.apiUrl directly).
- nodes/[id]: split into a server page.tsx (generateStaticParams) + client
  node-detail-client.tsx so the dynamic segment is export-compatible.
- unified-navigation.tsx: fix API Keys link to the real /dashboard/api-keys
  page (was pointing at the removed route handler).
- lib/brand.ts: point lux IAM clientId at the real Casdoor app "lux-cloud"
  (clientId lux-app-client-id does not exist in IAM).

Built with NEXT_PUBLIC_BRAND=lux, IAM lux.id / client lux-cloud; deployed to
CF Pages project lux-cloud, custom domain lux.cloud.

* web: decomplect brand into runtime white-label config

Remove the hardcoded 5-brand catalog from web/lib/brand.ts; resolve the
active brand at runtime by hostname from an external catalog
(window.__BRAND_CATALOG__ / /config.json), mirroring the @hanzo/id SPA.
Ship one neutral built-in bootnode fallback. Add BrandProvider that loads
the per-deploy catalog client-side after hydration.

---------

Co-authored-by: Antje Worring <[email protected]>

Co-authored-by: hanzo-dev <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-17 23:45:10 -07:00
8efc252ad0 fix(brand): pars twitter handle + npm scope -> parsdao (parsnetwork is the stale name) (#4)
Co-authored-by: hanzo-dev <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-16 23:43:03 -07:00
29775a5f51 fix(brand): pars skillsOrg parsnetwork -> parsdao (#3)
The skills GitHub org for pars is parsdao (DAO org, matching the luxfi/zooai/hanzoai
pattern); parsnetwork is not a tracked GitHub org.

Co-authored-by: hanzo-dev <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-16 23:29:20 -07:00
34c2fe8a5b Lux/Zoo Cloud: static-export build + Zoo brand IAM clientId (⚠️ SSR→export config change) (#2)
* web: static-export build + Zoo/Lux brand IAM clientId fixes

Two source-only changes for the Lux/Zoo Cloud console (web/), with no
committed build output.

next.config.ts (SSR -> static export):
- output "standalone" -> "export" + images.unoptimized for Cloudflare Pages.
- Drop headers()/redirects() (unsupported in export); moved to public/_headers
  and public/_redirects for Pages. Keep poweredByHeader: false.

Static-export compatibility (no SSR/server runtime):
- layout.tsx: bake brand from NEXT_PUBLIC_BRAND/BRAND; drop force-dynamic +
  headers() host detection (client still auto-detects brand from
  window.location.hostname via lib/brand.ts).
- app/api/health, app/llms.txt: mark route handlers force-static.
- Remove app/dashboard/api/keys route handler (backend proxy unsupported in
  export; dashboard talks to brand.apiUrl directly).
- nodes/[id]: split into server page.tsx (generateStaticParams) + client
  node-detail-client.tsx so the dynamic segment is export-compatible.
- unified-navigation.tsx: point API Keys link at the real /dashboard/api-keys
  page (the removed route handler is gone).

IAM clientId fixes (real Casdoor apps):
- lux: "lux-app-client-id" -> "lux-cloud".
- zoo: "zoo-app-client-id" -> "zoo-console" (the old value is not a registered
  Casdoor client and returns "Invalid client_id", breaking zoo.cloud login).

* web: restore skillsOrg field on BrandConfig (fixes broken export)

The /agents page references brand.skillsOrg at 4 sites, but the field was
dropped from BrandConfig during the consolidate-bootnode merge (it had been
added in 3ef7773). This broke `next build` (output: export) on this branch
AND on main. Restores the field + each brand's GitHub skills org, exactly as
originally introduced.

---------

Co-authored-by: Antje Worring <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-16 23:26:25 -07:00
zeekay 766da81cc6 Merge remote-tracking branch 'origin/main' into consolidate-bootnode 2026-06-16 18:40:23 -07:00
zeekay ef2c77299e ingress: drop dead traefik.* annotations (hanzoai/ingress parses ingress.kubernetes.io/* only; these were inert router.tls/entrypoints/v1-redirect syntax) 2026-06-15 11:33:02 -07:00
zeekay 7ac40c53d0 chore: update 2026-06-10 14:14:26 -07:00
zeekay c8cd2a2809 Per workspace rule, hanzo/bootnode chain catalog must not enumerate
Liquidity's white-label EVM. Keep Ethereum, Lux C-Chain, Pars, Zoo,
Hanzo.
2026-06-10 12:24:08 -07:00
zeekay 1f369c4fe2 cleanup: remove AI-slop summary / status / plan / report files
Reverts violations of the durable rule that current-state docs belong
in LLM.md and history belongs in git log. Removed files were session
handoffs, agent-style "complete success" / "1000%" reports, dated
audit dumps, and stub NOTES.
2026-06-07 13:34:25 -07:00
zeekay d4e503f8a7 deps: bump nested go.mod Go to 1.26.4
Workspace-wide sync. luxfi/node already shipped on 1.26.4 in v1.30.6
(commit 121aca1fa9); nested module catch-up.
2026-06-07 12:41:37 -07:00
zeekay 1fd1081d5c charts/lux: kill etnaTimestamp — quasarTimestamp canonical
Strips etnaTimestamp from both top-level networkUpgrades/upgradeConfig
and the embedded cChainGenesis config blob in all three envs
(mainnet, testnet, devnet). Required for compatibility with
luxfi/evm v0.19.0 which enforces DisallowUnknownFields and has
renamed Etna -> Quasar.
2026-06-07 12:41:37 -07:00
zeekay b52fa408a8 infra: delete dead DO App Platform manifests
Per universe-wide policy, hanzo workloads ship on DOKS only. DO App Platform
was decommissioned February 2026 — all hanzo-apps/hanzo-platform/operator
specs here have been superseded by infra/k8s/. Removing the stale specs to
keep the only-one-way invariant.
2026-06-07 12:41:37 -07:00
zeekay 59bd643328 dockerfile: scratch runtime for api-go, drop gcr.io
Switch from gcr.io/distroless/static-debian12:nonroot to FROM scratch.
CA certs, tzdata, /etc/passwd and /etc/group (nonroot uid/gid 65532)
are copied from the build stage.
2026-06-07 12:41:37 -07:00
zeekay a6c2aee6cf chore(repo): merge bootnode-org/main back into the Hanzo-side work
These two repos share root commit 8999f3c3 ("Initial commit") but
diverged long ago — hanzoai/bootnode evolved the recent stack
(api-go, brand package, etc.) while bootnode-org/main retained the
mainline 2026 history (175 commits we don't have).

Brand-leak fix: this work belongs in github.com/bootnode/bootnode,
not github.com/hanzoai/bootnode. Per the no-cross-brand-leak policy,
merge both histories together and push the result back to
bootnode/bootnode as the canonical home.

After this lands:
- bootnode/bootnode is the canonical repo
- hanzoai/bootnode gets archived with a redirect notice
- @bootnode-io/brand npm package's `repository.url` already points at
  bootnode/bootnode (set in the earlier package.json scaffold)

# Conflicts:
#	.github/workflows/ci.yml
#	api/bootnode/api/__init__.py
#	api/bootnode/api/billing/__init__.py
#	api/bootnode/config.py
#	api/bootnode/core/billing/__init__.py
#	api/bootnode/db/models.py
#	api/bootnode/main.py
#	api/tests/test_billing.py
#	web/app/dashboard/infrastructure/page.tsx
#	web/app/dashboard/organization/billing/page.tsx
#	web/app/dashboard/organization/page.tsx
#	web/app/dashboard/page.tsx
#	web/app/layout.tsx
#	web/components/navbar.tsx
#	web/components/unified-navigation.tsx
#	web/lib/brand.ts
#	web/public/logo/zoo-icon.svg
#	web/public/logo/zoo-logo-white.svg
#	web/public/logo/zoo-logo.svg
#	web/tsconfig.tsbuildinfo
2026-05-19 10:23:43 -07:00
zeekay b2af6aff33 feat(brand): scaffold @bootnode-io/brand npm package
Matches the canonical brand-package shape (parallel to @hanzo/brand,
@luxfi/brand, @zooai/brand, @osage/brand, etc.):

- package.json — name @bootnode-io/brand, version 0.1.0, public,
  exports ./, ./brand.json, ./loader, ./assets/*
- brand.json — canonical schema (brand, chains, iam, rpc,
  walletConnect, api blocks)
- src/index.ts + src/loader.ts — TS source compiled to dist/ via tsup
  (cjs+esm+dts)
- tsconfig.json — ES2020 + bundler resolution
- .npmignore — ship dist/, assets/, brand.json only

Existing config.ts + components.tsx + DESIGN.md preserved.

Publish (when npm @bootnode-io scope is reserved + creds available):
  cd brand && npm publish --access public
2026-05-19 08:56:13 -07:00
zeekay df0c5161a6 feat(brand): scaffold @bootnode-io/brand npm package
Matches the canonical brand-package shape (parallel to @hanzo/brand,
@luxfi/brand, @zooai/brand, @osage/brand, etc.):

- package.json — name @bootnode-io/brand, version 0.1.0, public,
  exports ./, ./brand.json, ./loader, ./assets/*
- brand.json — canonical schema (brand, chains, iam, rpc,
  walletConnect, api blocks)
- src/index.ts + src/loader.ts — TS source compiled to dist/ via tsup
  (cjs+esm+dts)
- tsconfig.json — ES2020 + bundler resolution
- .npmignore — ship dist/, assets/, brand.json only

Existing config.ts + components.tsx + DESIGN.md preserved.

Publish (when npm @bootnode-io scope is reserved + creds available):
  cd brand && npm publish --access public
2026-05-19 08:56:13 -07:00
zeekayandGitHub ba01d2e29e feat: Go rewrite scaffold — ZAP-first, Mount() per HIP-0106 (#1)
Adds a Go subsystem alongside the existing Python FastAPI service so
bootnode can fold into `superbase` per HIP-0106. The Python service in
`api/bootnode/` stays untouched.

What landed:

- `schema/bootnode.zap` — public typed surface (chains, RPC, tokens,
  NFTs, transfers, smart wallets, ERC-4337, gas, webhooks, fleets,
  nodes, usage). No `.capnp` anywhere — ZAP only, per brand policy.
- `zap/gen/` — hand-rolled Go bindings that match what `zapc generate`
  emits, so `go build` works without zapc installed locally.
- `api/mount.go` — the HIP-0106 contract:
    func Mount(r chi.Router, deps Deps) error
  mounts every FastAPI-parity route group through `api/handlers/`.
- `api/deps.go` — cross-subsystem ZAP client interfaces (IAM, KMS,
  Base, O11y, Commerce). No business logic, no hard imports — keeps
  the Bootnode module independent of sibling subsystems until they
  ship their typed clients.
- `api/handlers/` — 149 chi routes mounted (Python has 150). Each
  handler body is a 501 stub with a `TODO(bootnode-port)` pointing at
  the Python file + symbol to port.
- `api/middleware/` — IAM-backed auth, OTEL-friendly telemetry,
  per-org rate limit (stub).
- `api/store/` — per-tenant SQLite access via `deps.Base.Open(orgID,
  "bootnode")` per HIP-0302.
- `api/web3/` — chain registry + RPC pool surface (production version
  prefers luxfi/* over upstream go-ethereum).
- `cmd/bootnode/main.go` — standalone shim; same `api.Mount` superbase
  calls in production.
- `docs/PYTHON_TO_GO_PORT.md` — endpoint-by-endpoint port map.

Build: `go build ./...` and `go vet ./...` clean. Standalone shim
smokes 200 on /health, /ready, /v1/chains; 401 on auth-required
endpoints with no JWT; 501 on every TODO route.

Out of scope (not in this commit):
- Handler bodies (Phase 2 — porting in RPC → tokens → NFTs → ...
  priority order).
- superbase wiring (lands when the superbase repo opens its main).
- Python service removal (stays until handlers reach prod parity).
2026-05-18 23:36:46 -07:00
zeekayandGitHub 97321f67b0 feat: Go rewrite scaffold — ZAP-first, Mount() per HIP-0106 (#1)
Adds a Go subsystem alongside the existing Python FastAPI service so
bootnode can fold into `superbase` per HIP-0106. The Python service in
`api/bootnode/` stays untouched.

What landed:

- `schema/bootnode.zap` — public typed surface (chains, RPC, tokens,
  NFTs, transfers, smart wallets, ERC-4337, gas, webhooks, fleets,
  nodes, usage). No `.capnp` anywhere — ZAP only, per brand policy.
- `zap/gen/` — hand-rolled Go bindings that match what `zapc generate`
  emits, so `go build` works without zapc installed locally.
- `api/mount.go` — the HIP-0106 contract:
    func Mount(r chi.Router, deps Deps) error
  mounts every FastAPI-parity route group through `api/handlers/`.
- `api/deps.go` — cross-subsystem ZAP client interfaces (IAM, KMS,
  Base, O11y, Commerce). No business logic, no hard imports — keeps
  the Bootnode module independent of sibling subsystems until they
  ship their typed clients.
- `api/handlers/` — 149 chi routes mounted (Python has 150). Each
  handler body is a 501 stub with a `TODO(bootnode-port)` pointing at
  the Python file + symbol to port.
- `api/middleware/` — IAM-backed auth, OTEL-friendly telemetry,
  per-org rate limit (stub).
- `api/store/` — per-tenant SQLite access via `deps.Base.Open(orgID,
  "bootnode")` per HIP-0302.
- `api/web3/` — chain registry + RPC pool surface (production version
  prefers luxfi/* over upstream go-ethereum).
- `cmd/bootnode/main.go` — standalone shim; same `api.Mount` superbase
  calls in production.
- `docs/PYTHON_TO_GO_PORT.md` — endpoint-by-endpoint port map.

Build: `go build ./...` and `go vet ./...` clean. Standalone shim
smokes 200 on /health, /ready, /v1/chains; 401 on auth-required
endpoints with no JWT; 501 on every TODO route.

Out of scope (not in this commit):
- Handler bodies (Phase 2 — porting in RPC → tokens → NFTs → ...
  priority order).
- superbase wiring (lands when the superbase repo opens its main).
- Python service removal (stays until handlers reach prod parity).
2026-05-18 23:36:46 -07:00
zeekay 41a22e0f5e api-go/client: declare canonical Bootnode interface 2026-05-13 02:08:16 -07:00
zeekay 7745e3a39b api-go/client: declare canonical Bootnode interface 2026-05-13 02:08:16 -07:00
zeekay 06270f76c5 api-go: Go port scaffold (foundation + auth + chains + projects + wallets)
Lays down the Go skeleton parallel to api/ (FastAPI). Foundation:
  - config: env-driven Settings
  - db: pgx pool + idempotent schema migrations + typed row structs
  - iam: HS256 JWT Verifier + bearer-auth middleware
  - api: chi router with /healthz + /readyz + versioned /v1 mount

Routes ported:
  - /auth: login + signup + refresh (bcrypt + JWT)
  - /chains: registry + status
  - /projects: full CRUD scoped by org
  - /wallets: list + create scoped by project+org

Stubs (route wired, handler returns 501):
  - /api-keys, /webhooks, /rpc, /tokens, /nfts

cmd/bootnode/main.go is the production entrypoint with graceful
shutdown (SIGINT/SIGTERM) and slog JSON logging. Dockerfile builds a
distroless image with CGO disabled.

Remaining surface to port (in order of priority): rpc upstream
dispatcher, api-keys CRUD + auth, tokens + nfts read-only, webhooks
delivery worker, fleet + node + cluster routes, bundler + gas, billing.
The Python service in api/ keeps running until parity ships.
2026-05-13 02:00:07 -07:00
zeekay a9497e711c api-go: Go port scaffold (foundation + auth + chains + projects + wallets)
Lays down the Go skeleton parallel to api/ (FastAPI). Foundation:
  - config: env-driven Settings
  - db: pgx pool + idempotent schema migrations + typed row structs
  - iam: HS256 JWT Verifier + bearer-auth middleware
  - api: chi router with /healthz + /readyz + versioned /v1 mount

Routes ported:
  - /auth: login + signup + refresh (bcrypt + JWT)
  - /chains: registry + status
  - /projects: full CRUD scoped by org
  - /wallets: list + create scoped by project+org

Stubs (route wired, handler returns 501):
  - /api-keys, /webhooks, /rpc, /tokens, /nfts

cmd/bootnode/main.go is the production entrypoint with graceful
shutdown (SIGINT/SIGTERM) and slog JSON logging. Dockerfile builds a
distroless image with CGO disabled.

Remaining surface to port (in order of priority): rpc upstream
dispatcher, api-keys CRUD + auth, tokens + nfts read-only, webhooks
delivery worker, fleet + node + cluster routes, bundler + gas, billing.
The Python service in api/ keeps running until parity ships.
2026-05-13 02:00:07 -07:00
zeekay 6ca448543f ci: add id-token: write to caller permissions
Required for hanzoai/.github/.github/workflows/docker-build.yml@main —
without it the workflow_call dies as startup_failure with no jobs
dispatched. Caller permissions are a CEILING.
2026-05-07 09:09:27 -07:00
zeekay 834171d7dd ci: add id-token: write to caller permissions
Required for hanzoai/.github/.github/workflows/docker-build.yml@main —
without it the workflow_call dies as startup_failure with no jobs
dispatched. Caller permissions are a CEILING.
2026-05-07 09:09:27 -07:00
zeekay 0687420325 deploy: pin :latest → semver across image refs
Replaces all ghcr.io/hanzoai/<svc>:latest pins with the latest published
semver tag for each service. Per CLAUDE.md auto-bump policy: mutable
branch tags (:latest, :main, :dev) are deprecated for cluster pins —
only immutable semver permitted.

Bulk update across services with published v* tags. Services without a
published semver remain on :latest until their release pipeline cuts a
v* tag.
2026-05-07 08:34:59 -07:00
zeekay 6c1878d606 deploy: pin :latest → semver across image refs
Replaces all ghcr.io/hanzoai/<svc>:latest pins with the latest published
semver tag for each service. Per CLAUDE.md auto-bump policy: mutable
branch tags (:latest, :main, :dev) are deprecated for cluster pins —
only immutable semver permitted.

Bulk update across services with published v* tags. Services without a
published semver remain on :latest until their release pipeline cuts a
v* tag.
2026-05-07 08:34:59 -07:00
zeekay a14d73f4dd fix(brand): RootLayout brand also detected from Host header
generateMetadata() already used the host header, but RootLayout itself
read only process.env.BRAND with a hard-coded "bootnode" fallback. That
made the body theme/SSR brand drift from the metadata brand on per-host
deploys. Make RootLayout async and reuse the host detection so both
paths agree.
2026-05-01 20:29:24 -07:00
zeekay 8d5fe3bfe7 fix(brand): RootLayout brand also detected from Host header
generateMetadata() already used the host header, but RootLayout itself
read only process.env.BRAND with a hard-coded "bootnode" fallback. That
made the body theme/SSR brand drift from the metadata brand on per-host
deploys. Make RootLayout async and reuse the host detection so both
paths agree.
2026-05-01 20:29:24 -07:00
zeekay d18390449c ci: push web image under bootnode package with -web tag suffix
GHCR 403s on first-push package creation for ghcr.io/hanzoai/bootnode-web.
Push web build to the existing bootnode package with tag-suffix=-web,
api with tag-suffix=-api, so they coexist without colliding tags.

Deployment image becomes:
  ghcr.io/hanzoai/bootnode:vX.Y.Z-web
  ghcr.io/hanzoai/bootnode:sha-XXXXXXX-web

Update K8s deployments via kubectl set image after CI succeeds.
2026-05-01 20:14:48 -07:00
zeekay 912d4a6475 ci: push web image under bootnode package with -web tag suffix
GHCR 403s on first-push package creation for ghcr.io/hanzoai/bootnode-web.
Push web build to the existing bootnode package with tag-suffix=-web,
api with tag-suffix=-api, so they coexist without colliding tags.

Deployment image becomes:
  ghcr.io/hanzoai/bootnode:vX.Y.Z-web
  ghcr.io/hanzoai/bootnode:sha-XXXXXXX-web

Update K8s deployments via kubectl set image after CI succeeds.
2026-05-01 20:14:48 -07:00
zeekay 8c26e7cc93 fix(brand): add missing zoo logo SVGs to public/logo
The zoo brand config in web/lib/brand.ts references /logo/zoo-icon.svg,
/logo/zoo-logo.svg, /logo/zoo-logo-white.svg but only the references
existed — the files were missing, producing 404s on web3.zoo.ngo and
zoo.cloud. Added the canonical Zoo CMYK Venn mark from
zoo.industries/public/zoo-logo.svg.
2026-05-01 20:14:38 -07:00
zeekay db3390e0a5 fix(brand): add missing zoo logo SVGs to public/logo
The zoo brand config in web/lib/brand.ts references /logo/zoo-icon.svg,
/logo/zoo-logo.svg, /logo/zoo-logo-white.svg but only the references
existed — the files were missing, producing 404s on web3.zoo.ngo and
zoo.cloud. Added the canonical Zoo CMYK Venn mark from
zoo.industries/public/zoo-logo.svg.
2026-05-01 20:14:38 -07:00