Two claims in the CLI section were wrong, both about how a host gets named.
EnvURLs over Env.DomainSuffix was described as "the one way the CLI names a
host". It names none — nothing calls it outside its own test — and it could not
name a real one if it did: DomainSuffix is hard-coded to example.com with no
env, config or flag behind it. Having no caller is the only reason a URL builder
that resolves to a domain nobody owns has never surfaced.
Env.K8sContext DOES have callers, eleven of them across operator, cluster and
status, and it returns the bare literals gke_mainnet / gke_testnet / gke_devnet
— not the gke_<project>-<env>_<region>_<suffix> map this section printed.
Neither shape exists here: Hanzo runs on DOKS, so kubectl --context gke_mainnet
finds nothing and every remote CLI command fails at its first call. Loudly, at
least. Local is unaffected: K8sContext returns "" and statuscmd keys on that to
skip the cluster.
Both are pinned now, so replacing the literals with real contexts is a
deliberate edit in two places rather than something discovered mid-incident.
Coverage while there: AddNetworkFlags was 0% and carries the property the skip
exists for — Base registers its own --dev, so re-registering would panic and
rebinding would leave Base's variable permanently false. Asserted by setting the
flag and checking the ORIGINAL variable receives it. cmd/cli 24.7% -> 25.8%.
Co-authored-by: Hanzo Dev <[email protected]>
The CLI printed a fixed name in its examples and help text while already
taking that name as a parameter, so `operator` and `rpc` now interpolate what
they were handed, the way `cluster` already did.
The config file's per-env URL block goes with it. Nothing read it: EnvURLs
computes a service address from the environment's domain suffix, which is
what every command actually calls, so the block was a second unasked answer
to where a service lives. An older config still loads — an unknown key is
ignored — and its env block is dropped on the next write. The default org is
no longer a name.
The Tasks queue is named for this repo rather than written out three times.
Fixture hostnames follow the shape the operator emits, which one of those
files was already using.
crdt/privacy_fhe.go cited a PDF that is not in the tree; the two figures it
backed are in the design doc it already points at.
Co-authored-by: Hanzo Dev <[email protected]>
Base's own registry was already org-native in every method — OrgDB(orgID),
HasOrg(orgID), orgDir(orgID) — and only the type names said tenant. So this is
the vocabulary catching up with the code, not a change of behaviour.
TenantRegistry -> Bases app.Bases().OrgDB(org)
TenantConfig -> BasesConfig
MultiTenant -> PerOrg it enables per-org databases; say that
MULTI_TENANT -> MULTI_BASE
core/tenant.go -> core/bases.go
Tenant was the wrong noun twice over: it is the multi-tenancy word, and this
product's unit is the thing a customer creates per org — a Base. Identity here
is org, user, project, and nothing else.
The UI never said it; this was Go-side only. 64 packages green.
One test was collateral and is fixed rather than renamed: TestToHTTPHeader
asserts a pure snake_case-to-Header-Case transform, so rewriting its EXPECTED
value while its input stayed "tenant_id" made it assert something false. The case
is dropped — org_id already covers that transform.
Rename / namespace:
- Finish github.com/pocketbase/* → github.com/hanzoai/base (+ /dbx) sweep
across all Go sources; restore the renamed root launcher (Base, base.go).
- Drop the upstream-only surfaces ours doesn't ship (admin ui/, the
password/OTP/oauth2/impersonate auth files, extensions) re-added by the
upstream merge.
- Decomplect the on-disk layout to one base_/ namespace: data/, migrations/,
hooks/, types.d.ts are siblings under base_/ (default data dir
base_/data). Drop the hz_ prefix everywhere; collapse migratecmd's
identical JS/Go branches to one path.
- Remove the accidentally-committed hz_data/ dev dir (db + iam.key) and
gitignore the runtime namespace dirs.
Deps / build:
- kmsclient now imported from the light github.com/hanzoai/kms/sdk/go module
(the root kms module zip exceeds Go's download limit); cloud/pkg/base
Mount removed (lives in the fused daemon).
- replicate v0.4.0 → v0.8.0; metric.Counter.Get() (no prometheus dto);
network/metrics.go import fix; auth gitlab/github tidy.
Tests / behavior:
- Wire registerNotifyWatcherHooks; debounce notify files past the kqueue
rescan window so cross-instance settings/collections reload fires.
- migratecmd: exclude createdAt/updatedAt (not created/updated); regenerate
Go+JS automigration goldens from canonical output.
- Convert the two stale archive red-probes to assert the landed defenses
(backlog cap bounds memory; forged-segment signature is rejected).
IAM-native (Base = pure IAM client):
- Rip the ~1k-line home-grown embedded IAM (iam_embedded*.go) and the
iam-user CLI. Base no longer reinvents identity as _iam_users/_superusers
rows. IAM_ENDPOINT (hanzo.id, or an in-process iam.Embed in the fused
daemon) is required; Base validates IAM JWTs via JWKS only — superuser is
the IAM admin claim, resolved to an ephemeral unpersisted record.
- Extract the IDV reverse-proxy (/v1/idv/*) into idv_proxy.go — it was
mis-colocated in the embedded onboarding file and is independent of IAM.
- Bind the SQL console (/v1/sql) and test it through real IAM JWTs against a
JWKS stub (no hardcoded record tokens, no persisted rows).
Rename the in-process OIDC provider mount + proxy mount from /api/iam to
/v1/iam. One path. We do not use /api — this is not Casdoor.
- embeddedIAMMount constant -> /v1/iam
- iam_proxy registers /v1/iam/{path...} and trims that prefix
- HTML login form POSTs to /v1/iam/oauth/login
- iam_embedded_test.go: every test URL + issuer expectation -> /v1/iam
- doc strings across platform.go, iam_embedded.go, iam_proxy.go,
record_auth.go, cmd/cli/login.go, apis/middlewares_test.go
- LLM.md + CHANGELOG.md docs updated to reflect /v1/iam
Operators booting Base with IAM_MODE=embedded need a way to seed users
without going through HTTP. This adds a base iam-user create <email>
subcommand that prompts for a password (stdin) or honors
IAM_USER_PASSWORD for CI, then bcrypts at cost 12 and persists to
_iam_users.
Exposes EnsureIAMUsersCollection / CreateEmbeddedIAMUser /
AuthenticateEmbeddedIAMUser as the package-level public surface so the
CLI doesn't have to reach into plugin internals.
Tests cover both the env-driven happy path and the empty-password
guard.
The superuser{,Upsert,Create,Update,Delete,OTP}Command tree managed
local bcrypt passwords and OTPs on _superusers. Hanzo IAM is the only
credential issuer now; the OIDC handler in apis/superuser_auth_oidc.go
auto-provisions a passwordless superuser record on first sign-in, so
there is nothing for a CLI subcommand to write. Drop the binary
sub-command and stop registering it in base.Start.
Two CLI tools were still POSTing to /collections/.../auth-with-password
that no longer exists:
cmd/cli/login.go — `base login --email --password`
cmd/typegen/main.go — `--admin-email --admin-password`
base login is reduced to an IAM-handoff stub: running it with no flags
prints the instructions and exits 0; running it with --email/--password
returns a non-zero exit with the same instructions. Tokens flow into
subsequent commands via the existing persistent `--token` flag,
$BASE_TOKEN, or ~/.config/base/token (see cmd/cli/config.go).
typegen swaps its --admin-email/--admin-password pair for a single
--token flag (also reads $BASE_TOKEN). The dead authenticate helper
and the bytes import go away.
cli_test.go: drops the auth-with-password stub and rewrites
TestCLILoginMissingFlags into TestCLILoginRejectsLocalCreds +
TestCLILoginNoArgsSucceeds matching the new contract.
Closes out the IAM-native rip's Go-build consumer sweep. Remaining
local-auth references (ui-react TypeScript, sdk/base-js, jsvm
typegen output) are non-Go and outside this commit's gate.
daemon.go called k8sNamespace() with no args but it didn't exist.
Added k8sNamespace(env string) that resolves via parseEnv → Env.K8sNamespace().
All 4 call sites now pass the env string.
Adds collection/record/login/whoami/crons + daemon lifecycle +
cluster (base-ha HA) + operator (K8s CRD) + config/status/self/rpc.
Lux/cli network flags (--mainnet/--testnet/--devnet/--dev).
All Base daemons (atsd-style binaries) get the full CLI for free
via base.Start() or manual cmd.AddCommand(cmd.NewCLICommand()).