The admin login rendered only the logo: Users.disableLocalStrategy hid the
email/password form (LoginView gates <LoginForm> on !disableLocalStrategy) and
no beforeLogin/SSO component or OIDC callback was ever wired, so there was no
way to sign in — the panel was a dead end (not a client mount hang).
Re-enable the local (email/password) strategy so the form renders; the IAM
bearer/JWKS strategy stays layered on top for SSO/API. email now comes from the
local strategy (dropped the duplicate from iamAuthFields). prodMigrations adds
the local-auth columns (salt/hash/reset*/login_attempts/lock_until) + the
users_sessions table on boot. The seeded z@<domain> superuser signs in with a
framework-hashed password.
The committed importMap.js held ONE entry (CollectionCards) — it was never
generated against the real config, so every plugin client component was absent:
the entire lexical editor (20), the multi-tenant admin UI (5), and the S3 upload
handler. At runtime the admin RootProvider could not resolve those client
references, so it rendered an empty tree — a BLANK white page on ALL routes
including /admin/login (RSC flight streamed, client never committed, zero console
errors). Pod logs: "getFromImportMap: CMSComponent not found ...
@hanzo/cms-storage-s3/client#S3ClientUploadHandler ... run generate:importmap".
Fix: regenerate importMap.js (6 -> 64 lines, all client comps) AND wire
`hanzo-cms generate:importmap` into the app build so it can never drift from the
config again. Also corrects generate:types/importmap to the real CLI bin
(hanzo-cms; the fork has no `payload` bin — the old script was a latent ENOENT).
Build green; regeneration is idempotent ("No new imports found").
Claude-Session: https://claude.ai/code/session_01Gq8suw7uuodAMPDRpo6iAB
Pages.access.read now returns {_status: published} for anonymous requests
and true for authenticated callers. The multi-tenant plugin composes this
(withTenantAccess): a logged-in caller stays org-scoped; an anonymous caller
has no user, so no tenant constraint is added and published docs of ANY tenant
are world-readable — correct for a headless CMS serving many brands' storefronts
(karma.style/journal fetches with no token). Drafts and every write stay gated
exactly as 3.86.1.
proof.ts step 4b: anon read sees published, hides drafts (+ by-id negative
control). Verified on fresh SQLite: publicRead=true.
Claude-Session: https://claude.ai/code/session_01Gq8suw7uuodAMPDRpo6iAB
IAM admins have owner != "admin", so the multi-tenant all-tenants gate
(iamOrg === 'admin') matched nobody and every write 403'd. Project the
verified isAdmin (and groups) claims onto the CMS user and honor isAdmin
in the gate.
- strategy: map claims.isAdmin -> user.isAdmin, claims.groups -> user.groups
- iamAuthFields: add isAdmin (checkbox) + groups (json) so Payload persists
and refreshes them on each SSO login
- types: IAMClaims gains isAdmin, groups
- demo payload.config: userHasAccessToAllTenants also honors user.isAdmin
- auth-iam 1.0.0 -> 1.0.1
Claude-Session: https://claude.ai/code/session_01Gq8suw7uuodAMPDRpo6iAB
pnpm build:all built 44/46 in Docker but failed on @tools/scripts (unrelated
tooling not needed for the runtime image). Use the validated-green command
'pnpm turbo build --filter @hanzo/cms-demo' — builds the app + its real deps,
emits the standalone bundle, skips the tooling packages.
Go-live merge — the complete de-brand (44 packages, build-verified green), the
demo-app green-build fixes, and the canonical image pipeline (Dockerfile +
hanzo.yml + cicd.yml). CI builds ghcr.io/hanzoai/cms from a repeatable trigger
for the first time.
hanzo.yml drops the deploy block (build-only first cutover — deploy via the
universe CR bump in lockstep with CMS_SECRET). cicd.yml triggers on hanzo-cms
(this repo's actual default branch) + main.
cms.hanzo.ai's image (cms:3.85.9) was built OUT-OF-BAND — no Dockerfile, no
hanzo.yml, zero GH Actions image runs ever. This commits the missing pipeline so
a rebranded image builds from a repeatable trigger instead of a dev box:
- apps/hanzo-demo/next.config.ts: output:'standalone' + outputFileTracingRoot at
the workspace root, so the build emits .next/standalone/apps/hanzo-demo/server.js
— exactly what the operator CR runs (node apps/hanzo-demo/server.js).
- Dockerfile: multi-stage, pnpm 10.27 (corepack), pnpm build:all + the app's
next build --webpack, slim runner copying the standalone tree.
- hanzo.yml + .github/workflows/cicd.yml: the canonical hanzoai/ci path (mirrors
hanzoai/world) — push to main builds ghcr.io/hanzoai/cms + rolls the cms CR;
KMS supplies the GHCR token + kubeconfig. main.yml (upstream tests) untouched.
FIRST-CUT — needs a CI build to validate (a Payload monorepo standalone image is
finicky; I did NOT build it on the box per the CI/CD-only rule). If the existing
3.85.9 Dockerfile is available, it should supersede this.
Claude-Session: https://claude.ai/code/session_013jh8aka8q8RvhhVQ1psMeW
The demo app failed "next build" for two independent, structural reasons
(neither caused by the Payload -> CMS rename):
1. Next 16 defaults to Turbopack, which refuses to resolve packages whose
realpath lies outside turbopack.root. "next" is symlinked from the root
.pnpm store, so with root = app dir it resolved outside the boundary ->
"couldn't find the Next.js package (next/package.json)". Fixed by pointing
turbopack.root at the monorepo root (two levels up).
2. In-workspace, every @hanzo/cms* package resolves to its raw TS src/ (via
its exports map); those .js import specifiers map to .tsx files. webpack
handles this via resolve.extensionAlias, but Turbopack ignores the webpack
config -> 453 module-not-found errors. The whole monorepo builds on webpack
(root next.config.mjs + the withCMS wrapper), so the app now builds and dev
with "next build --webpack" / "next dev --webpack".
Also set typescript.ignoreBuildErrors (mirrors the monorepo root next.config):
resolving packages to src/ makes the app-level type-check re-check the entire
Payload source tree (OOMs / runs for many minutes) and it is redundant with
each package's own build:types (tsc --emitDeclarationOnly) run by turbo build.
Result: turbo build --filter @hanzo/cms-demo -> 14/14 tasks green.
The deps are already forked in-tree (@hanzo/cms*, zero upstream @payloadcms,
zero bare 'payload' imports) — this strips the user-facing upstream BRAND that
still leaked on the surfaces cms.hanzo.ai actually serves:
- (frontend)/layout.tsx: title 'Payload Blank Template' → 'Hanzo CMS'.
- payload.config.ts admin.meta: title/titleSuffix/description → Hanzo CMS, so
the admin panel Antje logs into no longer shows the upstream name.
MIT license + the upstream copyright line are retained (legal attribution
requirement of the MIT fork) — everything else rebrands to Hanzo.
Claude-Session: https://claude.ai/code/session_013jh8aka8q8RvhhVQ1psMeW