393 Commits
Author SHA1 Message Date
zeekay b35511af28 Merge branch 'platform/web3-iam-hip0111-convergence'
Docker Release / gate (push) Waiting to run
Docker Release / api (push) Blocked by required conditions
Docker Release / web (push) Blocked by required conditions
v1.1.0
2026-06-30 14:32:59 -07:00
zeekay 94d61456ce docs(brand): add hero banner 2026-06-28 20:10:22 -07:00
zeekay c8a911c861 chore(brand): dynamic hero banner 2026-06-28 20:10:21 -07:00
zeekayandGitHub 76b6f5bf61 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 fcd0260bd0 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 1d30279516 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 d1ecf27f98 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 33849ab656 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
7383c4ca24 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
25e5943454 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
8fd3094e07 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
a7aa34604d 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
5d3136b904 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 f6158ef0fc Merge remote-tracking branch 'origin/main' into consolidate-bootnode 2026-06-16 18:40:23 -07:00
zeekay 245e7349a8 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 bf115d2bf3 chore: update 2026-06-10 14:14:26 -07:00
zeekay b73808d8f2 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 b3915f7c1a 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 2f913cbfa3 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 6bc0fba761 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 9621147b0b 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 03d5f00624 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 1a534203ca 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 5014384ab1 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 72bfffd9b2 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 df6919b6ff 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 fb40f6f924 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 f0abbd762d api-go/client: declare canonical Bootnode interface 2026-05-13 02:08:16 -07:00
zeekay 745b33ebbf api-go/client: declare canonical Bootnode interface 2026-05-13 02:08:16 -07:00
zeekay d6f22abd9a 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 1e4d93b149 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 99e7e6ce71 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 765ed642de 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 bf4405a605 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 7ace22a34c 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.
v1.0.0
2026-05-07 08:34:59 -07:00
zeekay 5319a83303 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 7b4aa95cfa 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 b097ac0e71 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 940a440547 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 e43123cf64 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 616edf00ae 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 0e5efbed94 fix(brand): remove hardcoded Bootnode leaks (terms, navbar, ai-chat, region-data, wallets sdk sample) + zoo IAM → zoolabs.id
- web/lib/brand.ts: zoo iam.url + iam.domain → zoolabs.id (zoo.id is NXDOMAIN)
- web/components/navbar.tsx: brand-aware `npm i {sdk}` snippet
- web/components/ai-chat.tsx: brand-namespaced localStorage keys
- web/components/lux/region-data.ts: brand-namespaced localStorage keys
- web/app/products/wallets/page.tsx: brand-aware SDK class + package in sample
- web/app/terms/page.tsx: BOOTNODE → {brand.name.toUpperCase()} in legal text
- web/lib/sdk-package.ts: shared brand→pkg/client map (single source of truth)
2026-04-30 00:24:49 -07:00
zeekay 1d316ce4e6 fix(brand): remove hardcoded Bootnode leaks (terms, navbar, ai-chat, region-data, wallets sdk sample) + zoo IAM → zoolabs.id
- web/lib/brand.ts: zoo iam.url + iam.domain → zoolabs.id (zoo.id is NXDOMAIN)
- web/components/navbar.tsx: brand-aware `npm i {sdk}` snippet
- web/components/ai-chat.tsx: brand-namespaced localStorage keys
- web/components/lux/region-data.ts: brand-namespaced localStorage keys
- web/app/products/wallets/page.tsx: brand-aware SDK class + package in sample
- web/app/terms/page.tsx: BOOTNODE → {brand.name.toUpperCase()} in legal text
- web/lib/sdk-package.ts: shared brand→pkg/client map (single source of truth)
2026-04-30 00:24:49 -07:00
zeekay dc3f91cbd5 ci: retrigger docker-release after bootstrapping bootnode-web GHCR package 2026-04-29 15:57:46 -07:00
zeekay 9c7e01b5a4 ci: retrigger docker-release after bootstrapping bootnode-web GHCR package 2026-04-29 15:57:46 -07:00
zeekay ec9815745b feat(brand): SSR also detects zoo.cloud as zoo brand 2026-04-29 15:39:30 -07:00
zeekay 699ac1fb80 feat(brand): SSR also detects zoo.cloud as zoo brand 2026-04-29 15:39:30 -07:00
zeekay 3dedff1fbf feat(brand): route zoo.cloud through bootnode-web-zoo
- web/lib/brand.ts: detect zoo.cloud as zoo brand (alongside zoo.ngo, zoo.network)
- infra/k8s/ingress.yaml: add zoo.cloud + www.zoo.cloud as additional hosts on
  bootnode-zoo-ingress (TLS SAN via cert-manager, rules → bootnode-web-zoo:3001)

Makes zoo.cloud serve the same Bootnode platform as web3.zoo.ngo with Zoo brand.
2026-04-29 15:37:15 -07:00
zeekay efb0d61c10 feat(brand): route zoo.cloud through bootnode-web-zoo
- web/lib/brand.ts: detect zoo.cloud as zoo brand (alongside zoo.ngo, zoo.network)
- infra/k8s/ingress.yaml: add zoo.cloud + www.zoo.cloud as additional hosts on
  bootnode-zoo-ingress (TLS SAN via cert-manager, rules → bootnode-web-zoo:3001)

Makes zoo.cloud serve the same Bootnode platform as web3.zoo.ngo with Zoo brand.
2026-04-29 15:37:15 -07:00
zeekay aebe9a81ed chore: symlink AGENTS.md and CLAUDE.md to LLM.md
Canonical project context lives in LLM.md. Symlinks ensure
agentic coding tools (Claude Code, Cursor, etc.) find context
automatically regardless of which filename they look for.
2026-04-01 14:10:56 -07:00