Skip to content

Commit 2bc1d44

Browse files
committed
fix(plugin-security): skip external federated objects in seed-ownership backfill
claimSeedOwnership walks every registered object exposing an owner_id column and re-owns unowned rows to the first admin. owner_id is auto-injected into external (ADR-0015 federated) object schemas, so they passed the filter and the backfill ran `select id from <remote_table> where owner_id is null` against a read-only remote datasource whose table may not be provisioned at boot — e.g. the showcase example logged `Find operation failed … no such table: customers / orders` on startup. External objects are read-only and their ownership is not the platform's to reassign, so skip them alongside managedBy / sys_* objects. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111PrLebaDQAsjWmYrrcZmE
1 parent dadb4f7 commit 2bc1d44

3 files changed

Lines changed: 36 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@objectstack/plugin-security": patch
3+
---
4+
5+
`claimSeedOwnership` now skips **external (federated) objects** — those with an `external` remote-table binding (ADR-0015) — the same way it already skips `managedBy` and `sys_*` objects.
6+
7+
The seed-ownership backfill walks every registered object that exposes an `owner_id` column and re-owns its unowned rows to the first admin. Federated objects get `owner_id` auto-injected into their schema, so they passed the filter and the backfill issued `select id from <remote_table> where owner_id is null` against a read-only remote datasource whose table may not be provisioned yet at boot — producing startup errors like `Find operation failed … no such table: customers`. External objects are read-only (DDL forbidden, writes double-opt-in) and their ownership is not the platform's to reassign, so they are excluded from the scan entirely.

packages/plugins/plugin-security/src/claim-seed-ownership.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ describe('claimSeedOwnership', () => {
5555
expect(updates).toHaveLength(0);
5656
});
5757

58+
it('skips external (federated) objects even when they expose owner_id', async () => {
59+
// Federated read-only objects (ADR-0015) bind to a remote table; the
60+
// platform must not scan or re-own them — and the remote table may not even
61+
// exist at boot, so a scan would error with "no such table".
62+
const schemas = [
63+
{
64+
name: 'showcase_ext_customer',
65+
external: { remoteName: 'customers' },
66+
fields: [{ name: 'owner_id' }],
67+
},
68+
];
69+
const { ql, updates } = makeQL(schemas, {
70+
showcase_ext_customer: [{ id: 'c1', owner_id: null }],
71+
});
72+
expect(await claimSeedOwnership(ql, ADMIN)).toEqual([]);
73+
expect(ql.find).not.toHaveBeenCalled();
74+
expect(updates).toHaveLength(0);
75+
});
76+
5877
it('skips objects without an owner_id field', async () => {
5978
const schemas = [{ name: 'crm_pricebook', fields: [{ name: 'name' }] }];
6079
const { ql, updates } = makeQL(schemas, { crm_pricebook: [{ id: 'p1' }] });

packages/plugins/plugin-security/src/claim-seed-ownership.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ function hasOwnerField(schema: ServiceObject): boolean {
5454
* Walks `ql.registry.getAllObjects()`, filters to schemas that
5555
* (a) are not `managedBy` (skip sys_/auth/platform tables),
5656
* (b) are not `sys_*`-namespaced,
57-
* (c) declare an `owner_id` field,
57+
* (c) are not `external` (federated remote-table bindings — read-only, DDL
58+
* forbidden, and their `owner_id` is not ours to reassign),
59+
* (d) declare an `owner_id` field,
5860
* and updates the unowned rows as `isSystem`. Returns a per-object summary.
5961
*/
6062
export async function claimSeedOwnership(
@@ -80,6 +82,13 @@ export async function claimSeedOwnership(
8082
if (!schema?.name) continue;
8183
if ((schema as any).managedBy) continue;
8284
if (schema.name.startsWith('sys_')) continue;
85+
// External (federated) objects bind to a remote table on another datasource
86+
// (ADR-0015): reads are remapped, DDL is forbidden, and writes need a double
87+
// opt-in. Their `owner_id` — if the remote even has the column — is not the
88+
// platform's to reassign, and the remote table may not be provisioned when
89+
// this runs at boot (e.g. a fixture that seeds later), so a scan errors with
90+
// "no such table". Skip them entirely.
91+
if ((schema as any).external) continue;
8392
if (!hasOwnerField(schema)) continue;
8493

8594
try {

0 commit comments

Comments
 (0)