Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/21486-warm-boot-seed-claim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@objectstack/plugin-security": patch
---

The seed-ownership claim now runs whenever a seed settles, on every boot, not only on the boot that promotes the first platform admin.

Clause-②: no

- **Before:** a later boot whose seed replay inserted rows into a database that already had a platform admin left those rows `owner_id` NULL for good. An in-budget seed settles before `kernel:ready`, and the bootstrap that runs there finds the existing admin (`already_have_admin`) and promotes nobody, so neither path reached the claim. A `readScope: 'own'` grant never saw those rows.
- **Now:** when a seed settles (`app:seeded`) before this boot's bootstrap has named a claim target, the handler resolves the target itself: the existing platform admin, by the bootstrap's own `already_have_admin` rule. The claim then hands the replayed rows to that admin. The handler subscribes in `init()`, so a seed that settles before this plugin's `start()` is heard too. That happens on any composition that registers the app first.
- Unchanged: the claim's predicates (`owner_id` NULL or `usr_system`), its object filter and the first-boot promotion path. A row someone else owns is never touched. Under a walled tenancy posture no claim runs, as before.
- Log lines: the claim report reads `handed N seeded record(s) to platform admin USER_ID`, where it used to say `first admin`. Its provisional and failure lines now say when the claim actually runs next: the next seed settle, on this boot or a later one, or the next platform-admin promotion. `os meta resync` is not such a run.
9 changes: 6 additions & 3 deletions content/docs/data-modeling/seed-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,12 @@ whatever identity the load run carries (`config.identity`). During the normal
boot sequence no identity is supplied, so `os.user` resolves to a null
identity and `cel\`os.user.id\`` evaluates to `null` — the record still seeds
successfully, with the field left `null` rather than the load failing. Once
the first human user is promoted to platform admin, a one-time ownership
handoff re-owns every orphaned row (`owner_id` `null`, or the legacy
`usr_system` value from older databases) to that admin.
the first human user is promoted to platform admin, an ownership handoff
re-owns every orphaned row (`owner_id` `null`, or the legacy `usr_system`
value from older databases) to that admin. The handoff is not one-time: it
runs again whenever a seed settles, on that boot and on every later one, so
rows a later boot's seed replay inserts go to the existing platform admin the
same way. A row someone already owns is never touched.

- Because `cel\`os.user.id\`` resolves to `null` before an admin exists, a
**required** (non-nullable) owner-style field must not depend on it —
Expand Down
Loading
Loading