@@ -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