- loadAuthToken now falls back to local validation for _superusers tokens
when JWKS validation fails in external-only mode. Non-superuser local
tokens are still rejected. Fixes seeding with IAM enabled.
- Scrub PocketBase references from docs, SDK, and UI config
When IAM_ENDPOINT is empty or set to 'disabled'/'none', skip JWKS
registration and ExternalAuthOnly enforcement. This allows PB superuser
tokens to work for all collections, enabling data seeding and migration
without requiring a running IAM instance.
- FullUserLifecycle: register → store → read → anchor
- MultiDeviceSync: laptop writes → phone syncs → phone edits → laptop merges
- OrgMultiTenant: same user in different orgs gets different DEKs
- ConcurrentAccess: 50 parallel writers, all readable, correct oplog
- AnchorDeterministic: same state → same root, new write → new root
- DeviceRevocation: close session zeroes DEK, reads fail after revoke
18 total tests. All pass. Zero external deps.
Local-first data SDK for Hanzo Base apps:
- Per-user SQLite shard (encrypted, isolated)
- DEK/KEK hierarchy: Master KEK → Org KEK → User DEK → AES-256-GCM
- Master KEK from HSM (AWS/GCP/Azure) or K-Chain ML-KEM threshold
- CRDT sync via ZAP for conflict-free multi-device merge
- Merkle root anchoring to chain (identity, not row data)
- API: /vault/put, /vault/get, /vault/anchor, /vault/sync, /vault/export
- All routes require auth — user ID from Base auth context
Zero integration: import plugin, register, done. Each user gets
their own encrypted database file. Reads/writes instant. Sync background.
Chain stores roots, never data.
Replace "Hanzo Base" fallbacks with generic "Base" in all Svelte UI
components. When $appName is set (e.g. "<tenant>"), it displays
that instead. Help text uses "the application" rather than a brand.
Files: App.svelte, FullPage.svelte, PageWrapper.svelte, index.html,
PageBackups, PageStorage, PageMail, TrustedProxyAccordion,
CollectionRulesTab, LarkOptions, PageExportCollections
- ProvisionOrg/User creates directories and paths correctly
- Invalid slugs rejected (path traversal, uppercase, specials)
- HKDF DEK is deterministic, unique per org, unique per user,
org≠user even with same input (different HKDF info param)
- Empty master key returns empty DEK (dev mode, no encryption)
- Lookup works after provision, returns false before
- DeleteUser removes directory and lookup entry
- Integration: OrgDB + PoolManager read/write through dual pools
- Multi-org isolation: same table name, different data per org
- OrgDB DEK derivation: raw HMAC → crypto/hkdf.Key (RFC 5869, stdlib)
- security.SHA3_256, SHA3_512 using crypto/sha3 (FIPS 202, stdlib)
- No more x/crypto for key derivation or SHA3
Go 1.26 moved these from x/crypto to stdlib:
crypto/hkdf, crypto/pbkdf2, crypto/sha3, crypto/mlkem
PoolManager now lives in hanzoai/dbx v1.13.0 (canonical home).
base/plugins/platform/db_pool.go is a thin re-export wrapper.
Stack: dbx (pool) → orm (models) → base (platform)
One and only one way: dbx.NewPoolManager()
ID field changed to max=36, pattern=^[a-z0-9\-]+$ to support UUIDs.
Tests in apis/collection_test.go and plugins/migratecmd expected
the old max=15, pattern=^[a-z0-9]+$.
Optimizations from lux/metrics patterns:
- 16 shards with RWMutex (RLock for cache hits, Lock for misses)
- Cache-line padded PoolStats (no false sharing across cores)
- Two-phase eviction (find under lock, close without lock)
- FNV hash for shard selection
Benchmarks (M1 Max, -cpu=1,4,10):
CacheHit: 118 ns (0 alloc) — scales flat across CPUs
CacheMiss: 78 µs — dominated by SQLite open, no lock contention
ConcurrentHit: 1.8 µs at 4 CPUs (1.3x faster than single)
ReadWrite: 12 µs — stable across CPU counts (WAL is the bottleneck)
ManyOrgs: 1.1 µs at 10 CPUs (6.4x faster than single) ← sharding pays off
At 10,000 org scale with eviction pressure, 10-CPU throughput
is 3.1M ops/sec with only 465 B/op.
6 tests pass: get/release, LRU eviction, no-evict-in-use,
100-goroutine concurrent access, close, data persistence.
DBPoolManager: LRU cache of dual-pool SQLite connections (read pool
with 20 conns + write pool with 1 conn per database file). Evicts
least-recently-used pools when capacity is reached.
platformOrgDBResolver middleware (priority loadAuthToken+4):
- Reads X-Org-Id and X-User-Id headers (set by upstream middleware)
- Resolves or auto-provisions the org/user SQLite file
- Stores DBPool in request context via re.Set("orgDB"/"userDB")
- Services are fully stateless — any instance serves any org/user
File layout:
{DataDir}/orgs/{orgId}/org.db
{DataDir}/orgs/{orgId}/users/{userId}/data.db
Activated when OrgIsolation="sqlite" in PlatformConfig.
Publishable keys (pk-) are restricted to GET/HEAD/OPTIONS.
Any write method (POST/PUT/DELETE) returns 403 with clear error.
Secret keys (sk-), IAM keys (hk-), and JWTs have no method restriction.
Enforcement runs at priority +3 (after identity resolution, before handlers).
Every Base app gets this for free — zero per-route code needed.
Every Base app now supports IAM-managed API keys (hk-/pk-/sk-) natively.
Keys are created and managed in IAM — Base resolves them via IAM's
GET /api/get-user?accessKey= endpoint (cached 5min).
Auth chain: JWKS JWT → IAM API key → unauthenticated
Key types:
pk-/pk_ = publishable (frontend-safe, read-only)
sk-/sk_ = secret (backend-only, full access)
hk- = IAM key (legacy, full access)
Gateway validates JWTs statelessly via JWKS.
Base validates API keys via IAM (one canonical key store).
One way to do auth. IAM is the source of truth.
Replace the local filesystem replace directive with the properly
published module at github.com/hanzoai/kms/sdk/go v1.0.0. The
local path /Users/z/work/hanzo/kms/sdk/go doesn't exist in CI.
tenant.go → org_isolation.go
tenant_db.go → org_db.go
tenant_storage.go → org_storage.go
Content was already renamed in previous commit. This adds the
new filenames after the old ones were removed.
- C1: add orgId field to _tasks/_workflows, filter all queries by org
- C2: validate spaceID/orgID with regex before Temporal visibility queries
- H1: add sync.RWMutex to orgClients map (prevent concurrent map panic)
- H2: atomic FailTask with CASE expression (eliminate TOCTOU race)
- H4: extractCreator uses authenticated user ID, not raw headers
- Add orgId to collection schemas, store methods, and API filters
- durable.go: add sync.RWMutex to orgClients map — concurrent writes
to unprotected map caused panics under load. Double-checked locking
pattern for connection caching.
- workflows.go: AgentTaskWorkflow now listens for claim/complete/fail/
progress/cancel signals. Previously signals were accepted by Temporal
but silently dropped (workflow had no signal channels). Now supports
both auto-execute (activity) and signal-driven (human/agent-in-loop)
patterns simultaneously via selector.
Embeds a complete task queue and workflow scheduler into base using
the collection/record system backed by embedded SQLite.
Plugin creates _tasks and _workflows system collections on bootstrap
and runs a background scheduler that checks timeouts, advances
workflow state, and optionally auto-executes tasks via OnExecute callback.
API endpoints: create/get/list/update/claim/start/complete/fail/cancel/
progress/next (work-stealing) for tasks, plus create/get/list for
workflows with auto-chained pipeline and parallel (fan-out) support.
10 tests covering full lifecycle: CRUD, atomic claims, retry with
re-queue, dependency blocking, priority ordering, workflow advancement.
Optional luxfi/compliance integration for KYC/AML, sanctions screening,
and payment validation. Enabled by setting ComplianceEndpoint in
PlatformConfig. Adds 5 API routes under /api/platform/compliance/*.
No new Go dependencies — uses HTTP client to call compliance service.
Remove all _hz_ prefixes from collection IDs, internal markers, and test
fixtures. The default users collection is now _users_auth_ instead of
_hz_users_auth_. Internal SQL parser tokens renamed from __hz_join__/__hz_discard__
to __join__/__discard__. Backup prefix changed from @auto_hz_backup_ to
@auto_backup_. Test database updated with correct collection IDs and indexes.
JWT tokens in test files regenerated with new collection IDs.
Add LogoURL field to MetaConfig so the admin UI logo is configurable via
app settings instead of hardcoded. FullPage.svelte and App.svelte sidebar
now read logo and app name from the settings store, falling back to
defaults when not configured.