You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #15007, which landed as PR #15226 (merged 08:59Z). Filed as a fresh card because that PR is merged and a merged PR cannot carry follow-up work.
The divergence, on main today
packages/plugins/plugin-security/src/app-default-permission-set.ts, the declaredPermissionSets docblock:
The packages[] pass only supplies a set where the top level had none — which is precisely the option-B artifact. That is what makes this card revertible on its own and safe to land before the emitter half (#14512).
The code does not do that:
constflattened=(configas{permissions?: unknown}|null|undefined)?.permissions;if(Array.isArray(flattened))sets.push(...flattened);constpackages=(configas{packages?: unknown}|null|undefined)?.packages;if(packages===undefined||packages===null)returnsets;for(constbodyofresolveArtifactPackageOrder(config)){constdeclared=(bodyas{permissions?: unknown}|null|undefined)?.permissions;if(Array.isArray(declared))sets.push(...declared);// ← unconditional, no de-duplication}
The flattened copy goes in first, then every package body unconditionally. On today's additive artifact (flattened top level andpackages[] both present) every permission set is collected twice. There is no short-circuit and no de-duplication.
Impact: no behaviour change today — this is a false written contract, not a live defect
Stated plainly so it is not re-triaged as a P0:
the only consumer is appSecurityPluginOptions in the same file, which calls appDefaultPermissionSetName, which returns the firstisDefault set;
the flattened copy is pushed first, so "first" is still the one the old code found;
declaredPermissionSets is private (not exported, no second caller).
So the duplication is unobservable, #15007's four-step measurement and its ablation stand, and the B2 · plugin-security … ledger row it deleted was genuinely earned.
Why it still needs fixing
The docblock sells the short-circuit as the reason the card was revertible on its own and safe to land before the emitter half. That reasoning is sound (the observable result really is unchanged), but the mechanism it names does not exist in the code. The next reader will believe it does.
That next reader is already scheduled: the convergence pass that collapses the program's private packages[] walks onto one reader will read this docblock.
This program exists because readers silently lost things. Leaving a reader whose written self-description is false, in a security path, is the wrong direction.
The fix (preference, not a mandate)
Make the code match the docblock, so #15006's stack-collections.ts, this file, and the runtime reader carry one discipline — start from the expression you replaced, consult packages[] only where it came back falsy:
⚠️length > 0 is required, not fastidiousness. The empty-array truthiness trap #15006 measured runs the other way here: Array.isArray(flattened) alone would let a top-level permissions: []short-circuit the whole packages[] pass, re-creating the silent loss #15007 just fixed in a different shape. With length > 0 the return value is byte-identical to today's on every shape the platform emits, and the docblock becomes true.
The other route is also acceptable: keep the concatenation and fix the docblock — stating that the duplication is unobservable at the sole call site because it takes the first isDefault, and that this is deliberately a different discipline from the runtime reader's identity-claim de-duplication. Two readers that differ are fine; two readers that differ while reading as though they agree are not.
Acceptance
⛔ appSecurityPluginOptions' return value must be byte-identical on every shape the platform emits today, shown by measurement rather than argued.
A positive test for the trap above: a top-level permissions: [] plus a package body carrying an isDefault set must still resolve that set.
No changeset level change is owed: declaredPermissionSets is private, so the published surface does not move.
Provenance
Found by the isolated adversarial contract review of PR #15261, which flagged it as an out-of-scope note while verifying that PR's dependency claims. Relayed to #15226 as comment 5537872132 before it merged; auto-merge was disarmed for it, and it was merged anyway by an actor with merge rights, which is their call — the finding simply travels to this card instead.
Follow-up to #15007, which landed as PR #15226 (merged 08:59Z). Filed as a fresh card because that PR is merged and a merged PR cannot carry follow-up work.
The divergence, on
maintodaypackages/plugins/plugin-security/src/app-default-permission-set.ts, thedeclaredPermissionSetsdocblock:The code does not do that:
The flattened copy goes in first, then every package body unconditionally. On today's additive artifact (flattened top level and
packages[]both present) every permission set is collected twice. There is no short-circuit and no de-duplication.Impact: no behaviour change today — this is a false written contract, not a live defect
Stated plainly so it is not re-triaged as a P0:
appSecurityPluginOptionsin the same file, which callsappDefaultPermissionSetName, which returns the firstisDefaultset;declaredPermissionSetsis private (not exported, no second caller).So the duplication is unobservable, #15007's four-step measurement and its ablation stand, and the
B2 · plugin-security …ledger row it deleted was genuinely earned.Why it still needs fixing
packages[]walks onto one reader will read this docblock.The fix (preference, not a mandate)
Make the code match the docblock, so #15006's
stack-collections.ts, this file, and the runtime reader carry one discipline — start from the expression you replaced, consultpackages[]only where it came back falsy:length > 0is required, not fastidiousness. The empty-array truthiness trap #15006 measured runs the other way here:Array.isArray(flattened)alone would let a top-levelpermissions: []short-circuit the wholepackages[]pass, re-creating the silent loss #15007 just fixed in a different shape. Withlength > 0the return value is byte-identical to today's on every shape the platform emits, and the docblock becomes true.The other route is also acceptable: keep the concatenation and fix the docblock — stating that the duplication is unobservable at the sole call site because it takes the first
isDefault, and that this is deliberately a different discipline from the runtime reader's identity-claim de-duplication. Two readers that differ are fine; two readers that differ while reading as though they agree are not.Acceptance
appSecurityPluginOptions' return value must be byte-identical on every shape the platform emits today, shown by measurement rather than argued.permissions: []plus a package body carrying anisDefaultset must still resolve that set.packages/cli/test/option-b-reader-acceptance.pin.test.ts—OPTION_B_LOSSES, its set-equality assertion, and the row fix(plugin-security): the app default permission set resolves frompackages[](#15007) #15226 deleted all stay exactly as they are; the pin stays green.content/docs/releases/.declaredPermissionSetsis private, so the published surface does not move.Provenance
Found by the isolated adversarial contract review of PR #15261, which flagged it as an out-of-scope note while verifying that PR's dependency claims. Relayed to #15226 as comment 5537872132 before it merged; auto-merge was disarmed for it, and it was merged anyway by an actor with merge rights, which is their call — the finding simply travels to this card instead.
Sibling context: #15005, #15006, #15007, #15229, #15232. Related: #15293 (the
packagesguard split across the same three readers).