Skip to content

fix(seed): bind seed records to users via os.user + fail loudly on unresolved refs#1392

Merged
os-zhuang merged 1 commit into
mainfrom
fix/seed-data-user-binding
May 30, 2026
Merged

fix(seed): bind seed records to users via os.user + fail loudly on unresolved refs#1392
os-zhuang merged 1 commit into
mainfrom
fix/seed-data-user-binding

Conversation

@xuyushun441-sys
Copy link
Copy Markdown
Contributor

Summary

Fixes #1389"Seed data: no way to bind records to users (required lookup('user') makes objects un-seedable, fails silently)".

Records seeded via defineDataset / defineStack({ data }) can now bind to a platform user with cel`os.user.id` (and to the org with cel`os.org.id`). Previously this never resolved at boot, so any object with a required owner lookup was un-seedable and the records were silently dropped.

What changed

1. os.user / os.org now resolve (first-class binding)

  • New SeedLoaderConfig.identity (@objectstack/spec) carries the os.user / os.org subject into CEL evaluation.
  • The seed loader threads it into the eval context; os.org falls back to organizationId when not explicitly set.
  • Convention is the existing os.user / os.org — no new syntax (currentUser(), @admin, …).

2. Admin availability / ordering guarantee

  • AppPlugin provisions a deterministic, non-loginable system user (usr_system, role system) before any seed runs and binds it as os.user, so identity-derived seeds resolve even on a fresh boot — before the first human sign-up.
  • The human login admin stays a separate better-auth identity (ADR-0010 lock on sys_user respected — no credential is minted here). Analogous to Salesforce's "Automated Process" user.
  • Exposed as the canonical SystemUserId.SYSTEM constant.

3. Loud failure instead of silent drop

  • A record whose CEL value can't resolve (e.g. a required cel`os.user.id` with no identity) — or that fails to write — is now counted as an error, marks the load success === false, and logs an actionable message.
  • Applied to both the inline seed run and the per-tenant replayer; boot logs a success/warn summary.

4. Docs

  • content/docs/guides/seed-data.mdx gains a "Dynamic Values (CEL)" section covering the os.user binding, the usr_system ordering guarantee, and the loud-failure behavior.

Reviewer notes

  • A changeset (minor for @objectstack/spec + @objectstack/runtime) is included.
  • @objectstack/spec must be rebuilt so its dist .d.ts exposes the new identity field + SystemUserId (vitest resolves spec from src, but tsc resolves via dist).

Testing

  • 4 new seed-identity tests (resolve os.user.id; loud failure when unbound; os.orgorganizationId fallback; write-failure surfaced).
  • Full runtime suite: 307 tests / 19 files passing. tsc --noEmit clean on both runtime and spec.

…resolved refs

Fixes #1389. Seed records can now bind to a platform user with
`cel`os.user.id`` (and to the org with `cel`os.org.id``), which previously
never resolved at boot — making objects with a required lookup('user')
un-seedable, and silently dropping the records.

- Resolve os.user / os.org: thread a new SeedLoaderConfig.identity through
  the CEL eval context in the seed loader (os.org falls back to
  organizationId).
- Deterministic seed owner: AppPlugin provisions a non-loginable system user
  (usr_system, role `system`) before any seed runs and binds it as os.user,
  so identity-derived seeds resolve on a fresh boot — before the first human
  sign-up. The better-auth login admin stays separate (ADR-0010 respected).
  Exposed as SystemUserId.SYSTEM.
- Loud failures: unresolved CEL values and write failures are now counted as
  errors (result.success === false) with actionable messages, instead of
  being silently dropped — in both the inline run and the per-tenant replayer.
- Docs: seed-data guide documents the os.user binding, the usr_system
  ordering guarantee, and the loud-failure behavior.

Tests: 4 new seed-identity cases; full runtime suite 307 passing.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment May 30, 2026 10:39am

Request Review

@os-zhuang os-zhuang merged commit 5e831de into main May 30, 2026
9 of 12 checks passed
@xuyushun441-sys
Copy link
Copy Markdown
Contributor Author

CI diagnosis — both failures are pre-existing on main, not introduced by this PR

I looked into the Test Core and Build Docs failures. Neither is caused by the seed changes in this PR — both are latent issues that live on main:

Test Core — stale assertion in packages/objectql/src/protocol-meta.test.ts

"should load records of different types" asserts:

expect(registry.getItem('object', 'task')).toEqual(objDef)

The loaded record now carries extra _packageId: "sys_metadata" + _provenance: "package" annotations added by the package-bound-metadata feature (loadMetaFromDbregisterObject(data, record.packageId || 'sys_metadata')). The assertion was never updated for those fields. This test is already failing on main (main's Test Core conclusion = failure), and it has nothing to do with seed binding — running it with -t confirms the seed PR's new describe block never executes yet the failure persists.

Build Docs — doc generator emits unescaped angle brackets into MDX

Turbopack fails on ./content/docs/references/ui/app.mdx with Expected a closing tag for <id>. Root cause: the schema→MDX generator passes .describe() text through verbatim, so placeholders like <id> (e.g. os projects bind <id> in cloud/marketplace.mdx:40), Promise<void> and Record<string, any[]> (memory.zod.ts), <pkg>, <h1> are rendered as raw JSX-like tags and break the MDX parse. Build Docs is skipped on main, so the breakage is latent there; it only surfaced here because this PR touched a docs-adjacent path and triggered the job.

Recommendation

Both belong in their own fix against main (update the provenance-aware test assertion; make the doc generator HTML-escape </> in description text), owned by the respective subsystems — not on this merged seed PR. Flagging here for the record.

xuyushun441-sys pushed a commit that referenced this pull request May 31, 2026
…n@objectos.ai

bootstrapPlatformAdmin promoted the earliest-created sys_user, but #1392
(ensureSeedIdentity) provisions a non-loginable system identity
(usr_system, role 'system') before the first human sign-up to own seeded
rows. On any app shipping seed data it won the promotion, leaving the real
admin at role:user — login worked but Setup/Studio (gated by
setup.access / studio.access on admin_full_access) stayed invisible.

- bootstrap-platform-admin.ts: exclude the system account
  (id === SystemUserId.SYSTEM || role === 'system') when selecting the
  first user; the "admin already exists" check ignores any admin_full_access
  grant held by usr_system, so a wrongly-promoted DB self-heals on reboot.
- cli/dev.ts: --admin-email / --admin-password defaults change from
  admin@dev.local / admin12345 to fixed admin@objectos.ai / admin123.
- Docs/skill: AGENTS.md, security.mdx, objectstack-platform SKILL.md,
  CHANGELOG.md + changeset updated to the new creds and the human-first
  promotion rule.

Verified: pnpm dev → seeded admin get-session resolves role:admin and the
cross-tenant admin_full_access grant belongs to the human admin.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Seed data: no way to bind records to users (required lookup('user') makes objects un-seedable, fails silently)

2 participants