feat(ownership): auto-provision canonical owner_id + hand seeded records to first admin#2021
Conversation
…rds to first admin Make record ownership correct-by-default (a mistake-proof design for AI/human authors) instead of the opt-in model that silently shipped objects with no working ownership and left seeded demo data owned by nobody a human can log in as. - objectql/applySystemFields: auto-inject a canonical, REASSIGNABLE `owner_id` lookup on user-authored business objects, next to the tenant/audit fields. Withheld for managedBy/sys_* and for `ownership: 'org' | 'none'` opt-outs (Dataverse-style). Once present, insert auto-stamp + owner RLS + owner-keyed views engage automatically. - plugin-security/claimSeedOwnership: invoked from bootstrapPlatformAdmin after the first human is promoted to platform admin, transfers ownership of seeded rows (owner_id NULL or usr_system) to that admin. Ownership twin of org-scoping's claimOrphanOrgRows; idempotent; skips managedBy/sys_*. - bootstrapPlatformAdmin: defensive read of untyped permission-set `description` (fixes a latent dts typecheck error surfaced by the cache invalidation). Tests: registry owner_id injection cases (+5), claimSeedOwnership suite (+6). Full framework suite green (128/128 tasks). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📓 Docs Drift CheckThis PR changes 4 package(s): 98 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…user.id The default ownership model leaves seed `owner_id` NULL and lets the first-admin handoff (claimSeedOwnership) re-own those rows, so most bundles never reference `os.user`. Provision the non-loginable `usr_system` placeholder lazily — only when a seed dataset actually embeds `cel`os.user.id`` (detected via the serialized CEL source) — so a typical app never creates it. `os.org` is unaffected (derived from organizationId in the loader). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eed owner Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up:
|
…d time
Eliminates the usr_system placeholder entirely (not just lazily). The seed
loader now binds os.user to a NULL identity when none is supplied, so
`owner_id: cel`os.user.id`` resolves to NULL instead of crashing or dropping the
record — semantically "owned by whoever becomes the first admin", which the
first-admin handoff (claimSeedOwnership) then fills in.
- objectql/seed-loader: bind `user: identity?.user ?? { id: null }`; the
unresolved-expression error message no longer tells authors to provision a
system user (os.user.id now resolves to null).
- runtime/app-plugin: remove `ensureSeedIdentity` (the only code that inserted a
usr_system row) and its SystemUserId import; pass no seed identity.
- spec: SystemUserId.SYSTEM redocumented as a reserved id that is NO LONGER
auto-provisioned — kept only so legacy DBs' exclusion guards / ownership
handoff still recognize a pre-existing usr_system row.
Tests: new seed-loader-os-user test (os.user.id → null under null identity, →
real id when supplied); updated the runtime seed-loader test that asserted the
old fail-loud-on-unbound contract. objectql 665, runtime 388, plugin-security 97
all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
usr_system eliminated entirely (not just lazy)Per feedback — don't create usr_system at all; scheme for seeds that use Resolve
Tests green: objectql 665 · runtime 388 · plugin-security 97. |
Problem
Record ownership was opt-in and the boot order made it impossible to seed ownership to the eventual human admin:
app-plugin.start()— before any human user exists (the login admin is minted later, onkernel:ready). Soos.useris bound to a deterministic, non-loginableusr_system, and seeded rows land owned byusr_systemorNULL.owner_id" (the platform's own RLS comment), so they reinvent a customownerlookup that the platform's ownership machinery (auto-stamp, RLS, "My" views) can't see.Net effect: seeded demo data is owned by nobody a human can log in as → My-views / owner reports / owner notifications are empty out of the box, and author-written objects silently ship with no working ownership at all. This is the worst failure mode for AI authors: it compiles, seeds and renders, but the feature is silently dead.
Fix — ownership correct-by-default
1.
applySystemFields(objectql) auto-injects a canonical, reassignableowner_idlookup (→sys_user) on user-authored business objects, next to the existing tenant/audit fields. Unlike the audit*_bylookups it is NOT readonly — ownership transfers. Withheld formanagedBy/sys_*tables and for objects that opt out viaownership: 'org' | 'none'(Dataverse-style). Once present, the existing machinery engages automatically (insert auto-stamp at security step 3.5, owner RLS, owner-keyed views/reports).2.
claimSeedOwnership(plugin-security) — invoked frombootstrapPlatformAdminright after the first human is promoted to platform admin — transfers ownership of seeded rows (owner_idNULL orusr_system) to that admin. The ownership twin of org-scoping'sclaimOrphanOrgRows. Idempotent; skipsmanagedBy/sys_*. Authors write plain seed records (noowner_id); the platform performs the handoff — nothing to remember or mistype.3. Hardens
bootstrapPlatformAdminagainst a latent dts typecheck error (defensive read of the untypeddescriptionon seed permission sets, surfaced when the cache invalidated).Why this shape (AI-authored code)
The design optimizes for correct-by-default, loud-on-mistake because the code — schemas and seeds — is largely AI-written:
managedBy/sys_*auto-excluded; only the rare catalog/junction needs one lineownership: 'org'.cel\os.user.id`` to mistype.Tests
registry.test.ts: +5 owner_id injection cases (default inject, managedBy/sys_ exclusion,ownershipopt-out, no-overwrite).claim-seed-ownership.test.ts: +6 (NULL & usr_system claim, human-owned untouched, managed/sys_ skip, idempotent, system-target no-op).Follow-ups (not in this PR)
claimOrphanOrgRows/ensureDefaultOrganizationso per-org seed clones are owned by the org's admin.owner-ish lookups (now redundant alongside the autoowner_id).owner→ canonicalowner_id.🤖 Generated with Claude Code