Skip to content

Commit 696cb63

Browse files
committed
fix(plugin-security): a cloned permission set is not an unowned declaration (WIP)
The boot loop tells an environment-owned set apart from a package declaration before it judges ownership. Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
1 parent a4fd82a commit 696cb63

1 file changed

Lines changed: 60 additions & 3 deletions

File tree

‎packages/plugins/plugin-security/src/bootstrap-declared-permissions.ts‎

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,48 @@ export function readDeclared(engine: any, type: string): any[] {
182182
return [];
183183
}
184184

185+
/**
186+
* ADR-0086 two-doors — is this `sys_permission_set` row ENVIRONMENT-owned?
187+
*
188+
* The one spelling of the fact this seeder already acted on: a row the PACKAGE
189+
* door does not own — `managed_by` `admin`/`platform`, the legacy `user`, or
190+
* absent (pre-provenance rows, including `bootstrapPlatformAdmin`'s platform
191+
* defaults) — is environment-authored config that package materialization
192+
* never clobbers. {@link upsertPackagePermissionSet}'s last branch and the boot
193+
* loop's unowned-vs-environment split both ask THIS predicate, so the two can
194+
* never disagree about which rows are the environment's.
195+
*/
196+
function isEnvironmentOwnedRow(row: any): boolean {
197+
return !!row && typeof row === 'object' && row.managed_by !== 'package';
198+
}
199+
200+
/**
201+
* [#21669] Does the environment already own a row under this name?
202+
*
203+
* The engine registry the boot loop walks holds MORE than package
204+
* declarations: `loadMetaFromDb` hydrates every env-wide `sys_metadata`
205+
* `permission` row into the same collection, and a set an environment
206+
* authored itself — the Setup Clone action, a Setup "New", a metadata-door
207+
* save — carries no package id there, because none exists. Read as a
208+
* declaration, such an item is "unowned"; it is not a declaration at all. Its
209+
* row stands, the environment door owns it, and Setup lists it.
210+
*
211+
* Answered from the batched oracle the loop already holds — no extra read.
212+
* The row is this pass's own, or — on a per-organization pass — the
213+
* organization-less row the ENVIRONMENT door writes (`permission-set-
214+
* projection.ts` is deliberately not per-organization; see this file's
215+
* header). `unknown` is NOT an answer: a read that failed cannot prove the
216+
* environment owns the name, so the caller falls through to the unowned
217+
* refusal exactly as before.
218+
*/
219+
async function environmentOwnsName(existingByName: ExistingByNameIndex, name: string): Promise<boolean> {
220+
const lookup = await existingByName.get(name);
221+
const row = lookup.status === 'present'
222+
? lookup.row
223+
: lookup.status === 'absent' ? lookup.organizationLessResidue : undefined;
224+
return isEnvironmentOwnedRow(row);
225+
}
226+
185227
/**
186228
* Upsert ONE declared/published PermissionSet body into `sys_permission_set`
187229
* under the owning `packageId`, applying the ADR-0086 provenance rules
@@ -297,7 +339,7 @@ export async function upsertPackagePermissionSet(
297339
return out;
298340
}
299341

300-
if (existing.managed_by === 'package') {
342+
if (!isEnvironmentOwnedRow(existing)) {
301343
if (!permissionSetNameIsForeign(existing.package_id, packageId)) {
302344
// Our own row — re-seed so the record always reflects the shipped/published
303345
// declaration (idempotent; covers version bumps without bookkeeping).
@@ -359,8 +401,9 @@ export async function upsertPackagePermissionSet(
359401
return out;
360402
}
361403

362-
// `platform`/`user` — or absent (legacy rows, incl. bootstrapPlatformAdmin
363-
// defaults): env-authored config. Never clobbered by package materialization.
404+
// {@link isEnvironmentOwnedRow}: `admin`/`platform`/`user` — or absent
405+
// (legacy rows, incl. bootstrapPlatformAdmin defaults): env-authored config.
406+
// Never clobbered by package materialization.
364407
out.skippedEnvAuthored += 1;
365408
return out;
366409
}
@@ -417,6 +460,20 @@ export async function bootstrapDeclaredPermissions(
417460
// Registry provenance first (ADR-0010 `_packageId`), author-declared
418461
// spec `packageId` (ADR-0086 D3) as fallback.
419462
const packageId: string | undefined = ps._packageId ?? ps.packageId ?? undefined;
463+
// [#21669] Tell an ENVIRONMENT-owned set apart from a package declaration
464+
// BEFORE judging ownership. A registry item with no package id whose row
465+
// the environment already owns (a Setup clone: `managed_by:'admin'`, no
466+
// `package_id`) is not an unowned declaration — it is not a declaration —
467+
// and the `permission_set_declaration_unowned` warning told the operator,
468+
// on every boot and every `metadata:reloaded`, that Setup could not see a
469+
// set Setup lists and edits. It lands where every package declaration
470+
// over an environment row lands: `skippedEnvAuthored`, never clobbered.
471+
// ⛔ A declaration with no owner AND no environment row still reaches the
472+
// unowned refusal below, unchanged — that warning is true.
473+
if (!packageId && await environmentOwnsName(existingByName, String(ps.name))) {
474+
out.skippedEnvAuthored += 1;
475+
continue;
476+
}
420477
const r = await upsertPackagePermissionSet(ql, ps, packageId, options.logger, { existingByName, organizationId, residue, refusals, collisions });
421478
out.seeded += r.seeded;
422479
out.updated += r.updated;

0 commit comments

Comments
 (0)