mirror of
https://github.com/hanzoai/base.git
synced 2026-08-06 13:34:24 +00:00
main
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
570938663a |
test: align the IAM mock with the path its client actually sends
The fake IAM answered /api/get-users while LookupByAttribute has sent /v1/iam/get-users for some time, so the case was unreachable. The test still passed — the caller treats a lookup error as 'no match' — which is why the drift went unnoticed. Instrumenting the branch confirms it is still not exercised by any test here; that gap is pre-existing and worth its own test. Co-authored-by: Hanzo Dev <dev@hanzo.ai> |
||
|
|
1ac6c4638f |
fix(iam): use the canonical HIP-0111 JWKS path, derive the origin from it
JWKS was pointed at the bare /.well-known/jwks; IAM serves it at /v1/iam/.well-known/jwks (HIP-0111). Because IAM answers a 200 text/html SPA catch-all for any unregistered path, the wrong path is silent breakage rather than a 404 — the fetch "succeeds" and returns markup where keys should be. iamOrigin() recovers the IAM origin by trimming that one canonical suffix, so the retired-endpoint pointers and the auth-methods authorize URL derive from a single configured value instead of a second hand-maintained setting. Build + apis/plugins tests green. Co-authored-by: Hanzo Dev <dev@hanzo.ai> |
||
|
|
63809489b3 |
feat(admin): Twenty-grade editable record grid on @hanzo/ui (true-black) (#30)
* test+fix: clear pre-existing base test failures (iam /api→/v1, kube ns, pool timing) These failed on main already — surfaced once the zip-dep failure (#24) stopped masking the test run. None relate to zip; all are real drift/bugs: - iam userinfo path drift (5 failures): the client correctly requests the canonical /v1/iam/oauth/userinfo (HIP-0111), but the test fake-servers still registered the legacy /api/userinfo → 404. Updated iam_test.go (×4 + comment), platform_test.go, bootnode_test.go to the /v1 path. /api/get-user(s)/add-user are left as-is (client + tests agree there; a separate migration). - kube namespace bug (real): Client.New(defaultNamespace) let the in-cluster serviceaccount namespace file OVERRIDE an explicit namespace — so an ARC runner (arc-system) silently clobbered the caller's 'bootnode'. Now the pod namespace is adopted ONLY when the caller passed "". Explicit config wins. - db pool idle-eviction flake: IdleTimeout 50ms was shorter than the 3-pool setup loop on a loaded runner, so the sweeper evicted pool 0 before the Len()==3 assertion. Bumped to 300ms (+ wait 150→400ms). Co-authored-by: Hanzo Dev <dev@hanzo.ai> * build: goreleaser before-hook go mod download, not tidy (fixes release SECURITY ERROR) The release workflow runs GoReleaser on every PR; its before-hook ran `go mod tidy`, which RE-RESOLVES the module graph and trips the authenticated-git-only private module set (SECURITY ERROR / go.sum mismatch on the luxfi deps) — the Test and build steps pass because they verify against the committed go.sum via the runner's git auth instead of re-resolving. Release must never re-tidy (it mutates committed deps at release time). Swap to `go mod download`, which just populates the cache against the existing go.sum — same resolution path as the passing `go test ./...` step. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * ci(release): GOPRIVATE + git-auth so goreleaser resolves luxfi from git The goreleaser go-mod hook fetched luxfi/keys from the public proxy (re-pushed bits) instead of authenticated git, so go.sum verification failed (SECURITY ERROR). Add GOPRIVATE + the same git insteadOf auth the Docker gate uses, at the job level, so test + the download hook + the builds all fetch hanzoai/* + luxfi/* from git — matching the committed go.sum. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * feat(admin): @hanzo/ui true-black design system + buildable Vite baseline The ui-react admin was un-buildable WIP: package.json listed wouter/react-query while src used @tanstack/react-router + @luxfi/ui (Tamagui) with no Tailwind pipeline, a broken "/base" SDK import in all 19 routes, and a duplicate getRecord export. Nothing compiled. This lands a buildable baseline on the true-black @hanzo/ui shadcn design system. Verdict on @hanzo/ui consumability (settled empirically): the published @hanzo/ui@8 (gui/Tamagui, raw-TS source) and the @hanzo/ui barrel are NOT Vite-consumable — the barrel's index.mjs hard-imports next/image, next/link, cmdk, sonner, vaul, react-resizable-panels (unresolvable in Vite). @hanzo/ui@5 granular subpaths resolve but several components are coupled to bespoke theme classes. So we adopt @hanzo/ui's token table + true-black override and vendor the shadcn primitives (the pattern @hanzo/ui's own templates/vite-app uses). Verified in a browser: bodyBg #000, card #0a0a0a, hairline #171717, near-white primary, Basel + Geist Mono, .dark active. - package.json: real deps (@hanzo/ui@5.7.1, react-router, tailwind v3, radix, react-hook-form); drop wouter/@luxfi/ui - tailwind.config.cjs + postcss.config.cjs + src/index.css: shadcn HSL token scheme, true-black .dark (#000 canvas, gui surface/border ladder), Basel (self-hosted) + Geist Mono fonts - src/components/ui/{button,input,label}.tsx: vendored shadcn primitives on the shared tokens; src/lib/cn.ts - src/lib/api.ts: fix duplicate getRecord export (-> getRecordById), add getFullRecords paging + real /v1/realtime SSE client, export request - src/lib/base-client.ts: BaseClient object API over the /v1 fetch layer — unbreaks all 19 routes with zero route rewrites; base.ts drops broken "/base" - main.tsx: QueryClientProvider, import css, pin .dark, router basepath bound to BASE_ADMIN_UI_PATH (fixes /_/ mount routing); vite.config proxies /v1 - login.tsx converged onto @hanzo/ui primitives (first true-black route) - delete dead src/pages/* (old wouter generation, imported nowhere) - embed.go: correct the stale build comment (source is ./src, pnpm build) - rebuild committed dist/ (embedded by //go:embed) Co-authored-by: Hanzo Dev <dev@hanzo.ai> * feat(admin): Twenty-grade editable record grid on @hanzo/ui Replace the read-only records table with an inline-editable data grid — the heart of "edit everything". Cells own a display/edit split with a single commit/cancel protocol; the grid owns a two-tier keyboard cursor. - components/grid/RecordGrid: sortable columns (per-field sort toggle), sticky header, row selection + select-all, per-row actions (open/duplicate/delete), two-tier cursor — arrows move the active cell, Enter/F2/type-to-edit enters edit mode, Tab advances. True-black, hairline rows. - components/grid/EditableCell: display mode → floating Popover editor per type (text/number/date/textarea/json/select/relation); bool toggles in place; file/password/autodate render read-only. json validates on commit. - lib/fields: field-type → editor-kind mapping + display formatting + API value coercion, orthogonal to the grid. - routes/collections.$id.records: optimistic single-field persist (react-query onMutate cache patch + rollback on error), create (→ detail panel), duplicate, single + bulk delete behind a confirm Dialog, filter, pagination. - vendored primitives: checkbox, select, popover, dropdown-menu, dialog, textarea, badge (shadcn on the shared tokens); add the radix deps. - rebuild committed dist/ Co-authored-by: Hanzo Dev <dev@hanzo.ai> * feat(admin): record detail/edit panel on @hanzo/ui with per-type editors Rewrite the record editor on @hanzo/ui primitives + lib/fields. Each field renders the right editor by type — Input (text/number/date/url/email), Textarea (editor/json, json validated on submit), Select (single select), Checkbox (bool), file input (upload), relation as id csv. Auth collections get a password field; id + autodate render read-only. Controlled form state, optimistic invalidate, create/save/delete. True-black card, Basel type. Drops react-hook-form in favor of a small controlled form so @hanzo/ui Select/Checkbox bind cleanly. Co-authored-by: Hanzo Dev <dev@hanzo.ai> * feat(admin): converge root sidebar + collections list to true-black @hanzo/ui Phase 1 of the view convergence. The always-visible chrome and the entry point to the grid now match the true-black system. - __root: sidebar on bg-background/border-border with lucide icons + accent active state (was bg-neutral-950 + indigo) - collections: clickable collection rows -> /collections/$id/records, type/system badges, @hanzo/ui Input filter + Button delete (was raw table + indigo button) Co-authored-by: Hanzo Dev <dev@hanzo.ai> * fix(admin): flat route escapes so record grid/editor are reachable + safe default sort Two correctness fixes surfaced by end-to-end browser verification against a real Base server: 1. Routing: collections.$id[.records[.$recordId]] nested as TanStack layouts, but the parent components render content (not <Outlet/>), so /collections/$id/records rendered the collections list instead of the grid. Rename to flat escaped routes (collections_.$id_.records...) so each is a standalone route under root — URLs and all Link targets unchanged. 2. Default sort '-created' 400s on this fork's base collections (no `created` field). Default to '' (API insertion order, valid for any schema); sorting stays opt-in via column headers. Verified: grid renders true-black against /v1, inline text edit (floating editor) and bool toggle both persist to the real Base API (confirmed server-side). Co-authored-by: Hanzo Dev <dev@hanzo.ai> * docs(llm): admin @hanzo/ui rebuild — consumability verdict, IAM-native auth finding, phased plan Co-authored-by: Hanzo Dev <dev@hanzo.ai> --------- Co-authored-by: Hanzo Dev <dev@hanzo.ai> |
||
|
|
6eb8a33bf4 |
feat(bootnode): resolve web3.* and .cloud apex brands in OAuth redirect (#16)
NetworkFromRedirectURI only handled cloud.<net>.<tld> and a substring match on bootno.de. The four live brand surfaces — web3.hanzo.ai, web3.lux.network, web3.zoo.ngo, web3.pars.id, plus the apex hosts lux.cloud and zoo.cloud — fell through to "" and would have exchanged the OAuth code against the fallback IAM client id instead of the shared lux-web3 app. Add an apexNetworks table for bare brand TLDs (bootno.de, lux.cloud, zoo.cloud) and extend the subdomain rule to accept the web3 entrypoint alongside cloud. Both prefixes map to the same per-network IAM redirect; they are alternate brand surfaces over one IAM app. Tests assert every live hostname resolves to lux-web3 and that unknown hosts still fall back. No IAM logic duplicated — this only derives the client id from the redirect host. Co-authored-by: zeekay <z@zeekay.io> |
||
|
|
fd23832c90 |
feat(bootnode): Go port foundation as a Base plugin (5 modules end-to-end) (#15)
Why: consolidate the Python bootnode backend (bootnode/api/, ~100 .py files)
onto Hanzo Base, leveraging Base's IAM client and per-org/per-user tenant
infrastructure instead of reimplementing auth, sessions, and multi-tenancy.
This is the structural foundation + the 5 most-important modules, not the whole
port.
What landed
- plugins/bootnode/: blockchain developer platform mounted under /v1.
Five modules ported end-to-end:
1. auth — multi-network OAuth2 callback (lux/pars/zoo/hanzo share the
lux-web3 IAM app; client id derived from redirect_uri) + bn_
project API keys (salted SHA-256, raw key shown once, verified
in constant time). Accepts IAM JWTs and pk-/sk-/hk- keys by
reusing github.com/hanzoai/base/iam — no IAM logic duplicated.
2. team — org/member CRUD scoped to the caller's project; invited emails
resolved against IAM (active) or held pending with an invite
token.
3. networks — applies bootno.de/v1 Network CRs (white-label brand, tier,
region, validator fleet). Replaces the Python's kubectl + raw
nginx-Ingress templating with a declarative CR for the
bootno.de operator to reconcile.
4. nodes — applies bootno.de/v1 NodeFleet CRs (CRD-driven cloud path;
the Python docker provider was a local-dev concern).
5. keys — applies bootno.de/v1 KMSSecret CRs by KMS path. NO plaintext
key material ever touches this service; the request and
response carry none, and a guard rejects any private-key field.
- plugins/bootnode/kube/: dependency-free Kubernetes REST client (net/http
server-side apply). No client-go, no CGO. In-cluster SA or KUBE_APISERVER.
- plugins/commerce/: typed Hanzo Commerce (Square billing) client behind a
Client interface. bootnode depends on the interface; commerce never depends
on bootnode/iam.
- 11 SQLAlchemy models -> Base collections (models/collections.go). No `users`
collection: IAM owns identity; bootnode references IAM user ids as text.
OrgCluster is the canonical org->k8s-cluster mapping.
- examples/base/main.go: platform plugin now runs PrincipalIsolation="sqlite"
(per-org + per-user encrypted SQLite); bootnode registered (BOOTNODE_ENABLED).
Modules pending (15, tracked in PR body): chat, zap, billing-http, bundler,
fleets, gas, infra, launch, lux, mpc, nfts, observability, rpc, tokens,
transfers, wallets, webhooks-http. (chains is also ported as a bonus 6th.)
Tests (20 functions, all green incl -race)
- auth: token classification, key gen/hash/verify (tamper + wrong-salt),
redirect->clientId derivation.
- kube: server-side-apply shape (PATCH + apply-patch+yaml + fieldManager),
error propagation, 404 get/delete idempotency.
- commerce: disabled no-op, get-or-create (create + existing), usage error
propagation, immediate cancel.
- workers: HMAC-signed delivery, non-2xx-is-failure, unreachable-is-failure.
- bootnode: full end-to-end against a fake IAM + fake apiserver — /me 401 then
200, project + bn_ key, team invite/list, Network + NodeFleet CR apply/get,
KMSSecret plaintext-rejection + apply + status, public chains. Plus
fail-fast on the insecure default salt against production IAM.
Verified: `go build ./...` exits 0; `go test -race ./plugins/bootnode/...
./plugins/commerce/...` passes; live binary serves /v1/chains (200) and gates
/v1/auth/me, /v1/networks (401); per-org SQLite isolation logged active; all
10 _bootnode_ collections created on boot.
Co-authored-by: zeekay <z@zeekay.io>
|