|
| 1 | +--- |
| 2 | +"@objectstack/plugin-security": patch |
| 3 | +--- |
| 4 | + |
| 5 | +fix(plugin-security): the app default permission set resolves from the first level that NAMES one (#15298) |
| 6 | + |
| 7 | +`declaredPermissionSets` carried a docblock stating a short-circuit its code did |
| 8 | +not have: |
| 9 | + |
| 10 | +> The `packages[]` pass only supplies a set where the top level had none — which |
| 11 | +> is precisely the option-B artifact. |
| 12 | +
|
| 13 | +The code pushed the flattened top level and then **every** package body |
| 14 | +unconditionally, so on today's additive artifact (flattened level *and* |
| 15 | +`packages[]` both present) every permission set was collected twice. Nothing |
| 16 | +observable came of it — the sole caller is private and takes the first |
| 17 | +`isDefault` set, which the flattened copy still supplied — so this corrects a |
| 18 | +false written contract on a security-path reader, not a live defect. That |
| 19 | +distinction is the point: the sentence was load-bearing, because it was the |
| 20 | +stated reason the reader half was revertible on its own and safe to land before |
| 21 | +the emitter half (#14512), and the next reader would have believed the mechanism |
| 22 | +was there. |
| 23 | + |
| 24 | +The reader now walks the discipline the docblock claims — start from the |
| 25 | +expression this program replaced, `appDefaultPermissionSetName(config.permissions)`, |
| 26 | +and consult `packages[]` only where it came back `undefined`. |
| 27 | + |
| 28 | +- **The condition is the resolved NAME, never the `permissions` container.** |
| 29 | + Branching on the container re-creates the silent loss the reader program |
| 30 | + exists to remove, one shape further along: a flattened level that carries |
| 31 | + permission sets but marks none of them `isDefault` is legal today and |
| 32 | + hand-authorable in any `objectstack.config.ts`, and a container-shaped |
| 33 | + condition (`Array.isArray(flattened)`, with or without `&& length > 0`) shorts |
| 34 | + it past the whole `packages[]` pass and answers `undefined` — nothing thrown, |
| 35 | + nothing logged, every member of the app back down to the platform floor alone. |
| 36 | + Reading the answer also retires the `[]`-is-truthy trap rather than patching |
| 37 | + around it. |
| 38 | +- **The package order is resolved BEFORE the top level is consulted.** |
| 39 | + `resolveArtifactPackageOrder` refuses a malformed `packages` — not an array, |
| 40 | + an entry inlined instead of wrapped under `manifest:`, a duplicate package id |
| 41 | + — with an ADR-0112 envelope this reader does not catch, and that refusal must |
| 42 | + not become conditional on whether the flattened level happened to name a |
| 43 | + default first. An artifact is either loadable or refused; which level answered |
| 44 | + is not part of that question. |
| 45 | +- **No emitted artifact changes its answer.** Measured, not argued: 26 shapes — |
| 46 | + the composed additive artifact, its option-B derivative, the collection-zoo |
| 47 | + fixtures behind the #15004 acceptance pin, every config the unit suite drives, |
| 48 | + the three malformed-`packages` refusals, and the hand-authored mixed shapes — |
| 49 | + return byte-identical results before and after, with `@objectstack/plugin-security` |
| 50 | + rebuilt and the change proven present in `dist/` on each leg. |
0 commit comments