Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell): permission matrix bulk grants merge the row instead of replacing it - #6623
Conversation
…f replacing it (#6605) The R / CRUD / All bulk buttons in the permission matrix replaced the object's row wholesale, silently deleting the spec-declared keys the matrix does not author: allowExport and the ADR-0057 access-depth axis readScope / writeScope. Both save doors persisted the truncated row (the environment door writes the whole record; at package scope mergePermissionSlice takes in-scope rows entirely from edited, ADR-0086 P0, so base cannot restore them). The sharpest shape: the button labelled All could WIDEN effective read access by dropping a readScope: 'own' narrowing with no diff and no error. The granting arms now start from the current row, reset the keys the matrix authors (OBJECT_ACTIONS), and set the granted ones — unmodelled keys ride through exactly as they do on the per-checkbox path (updateObjectPerm's spread). None deliberately keeps replacing with {}: it grants nothing, so nothing survives for a scope to narrow, and merging there would leave allowExport: true alive after a click on the button labelled None — a permissive outcome that does not exist today. That fence is pinned by PermissionMatrixEditor.bulkMergeKeys.test.tsx, which asserts the SAVED payload (both doors), never editor state. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-sales
commented
Aug 27, 2026
ACCEPT on substance (ui execution seat, The fence held, and it held on measurement rather than on my say-soMy dispatch order fenced the ⭐⭐ Leg B of the ablation is the part I want kept. It mutated the A hazard the order did not name, caught anywayA naive merge would have made CRUD-after-All leave Every pin asserts the SAVED payloadNot editor state — which matters here specifically, because the card's own argument for why this persists is that both save doors carry the truncated row and The root-cause hypothesis was assessed and declined, correctlyI offered deriving Scope
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6605
What
bulkSetObjectinPermissionMatrixEditor.tsxreplaced an object's permission row wholesale on every bulk click. Three spec-declared keys are modelled by neither the localObjectPerminterface nor theOBJECT_ACTIONScolumn list —allowExport, and the ADR-0057 access-depth axisreadScope/writeScope— so one click on R / CRUD / All silently deleted whatever those held, and both save doors persisted the truncated row (the environment door writes the whole record; at package scopemergePermissionSlicetakes in-scope rows entirely fromedited, ADR-0086 P0, sobasecannot restore them). The sharpest shape: the button labelled All could WIDEN effective read access by deleting areadScope: ownnarrowing, with no diff shown and no error.The granting arms (
all/crud/read) now start from the current row, reset the keys the matrix authors (OBJECT_ACTIONS), then set the granted ones — so unmodelled keys ride through exactly as they already do on the per-checkbox path (updateObjectPermspreads). Resetting the authored keys first keeps today's bulk semantics for the matrix's own columns: CRUD after All still means exactly CRUD, not "add CRUD".noneis deliberately UNCHANGED — it still replaces the row with{}The card's suggested fix asked for the same merge treatment on
none. Per the PM dispatch on #6605 this PR does not do that, deliberately: the defect is a grant that silently drops a narrowing;nonegrants nothing, so nothing survives for a scope to narrow. Mergingnonewould leaveallowExport: true(and the scopes) alive after a click on the button labelled None — a permissive outcome that does not exist today, on a surface whose whole problem is silent permissiveness. What an admin's "None" means is a behaviour decision, and it was made on the card thread, not here. Thenonearm is pinned (PermissionMatrixEditor.bulkMergeKeys.test.tsx) so a later refactor cannot quietly adopt the card's suggestion without going red and surfacing that decision.Load-bearing measurement behind that fence, re-taken on the merged ref (post #6607):
OBJECT_ACTIONScontains exactlyallowCreate, allowRead, allowEdit, allowDelete, allowTransfer, viewAllRecords, modifyAllRecords— noallowExport, no scopes — so all three dropped keys are "shown to reviewers, not authored by the matrix", as the dispatch asserted.Tests (all assert the SAVED payload, never editor state)
New
PermissionMatrixEditor.bulkMergeKeys.test.tsx, 5 pins:allowExport: true, readScope: own, writeScope: owngets every matrix column granted AND keeps all three keys in the saved payload.allowTransfer/viewAllRecords/modifyAllRecordsstill reset — the falsification direction that merge did not decay into "add".allowReadplus the three unmodelled keys.{}— with a positive control in the same payload (an untouched sibling row still carries itsreadScope), so the emptiness measuresnone, not a key-dropping save path.packageId— the merged slice keeps the three keys, another package's out-of-scope row survives byte-for-byte, and the save went through the draft door (mode: draft).Every pin was shown going red (fix committed first; restores proven by observation —
git diff HEADempty AND blob-hash match against HEAD, never exit codes; mutations proven on disk by anchored grep counts flipping plus a blob-hash difference; no rebuild needed for either leg — the suite imports the mutated file by relative path from source, no dist resolution in the loop):2a23000f): predicted the four merge pins red,nonepin green — observed exactly that (Tests 4 failed | 1 passed), failing in the predicted direction (expected undefined to be 'own').noneto the card's suggested merge): predicted only thenonepin red — observed exactly that (Tests 1 failed | 4 passed), failing withexpected { allowExport: true, … } to deeply equal {}— the precise hazard the dispatch fence names.Verification at final commit
b0517791(all via the shared verify lock where heavy):pnpm exec vitest run(repo root) over the new file +retiredLifecycleKeys+packageDoorFacets+ bothpermission-slicesuites:Test Files 5 passed (5) · Tests 21 passed (21), lock VERDICTcommand-exit 0.pnpm run type-checkin app-shell (both tsconfigs): exit 0, andtsc -p tsconfig.test.json --listFilesshows the new test file in the checked set (nonzero, with a nonzero positive control on a sibling test file).turbo run lint --filter=@object-ui/app-shell:0 errors(pre-existing package-wide warnings only), lock VERDICTcommand-exit 0.check-control-bytes: OK,check-vi-mock-specifiers: OK(newvi.mockcall sites), i18n call-site keys OK,designer-field-key-parity: OK, changeset presence✅ … declares 1 changeset(s), changeset no-major✅ No changeset declares a major bump.Scope
bulkSetObject+ tests + a patch changeset, per the dispatch fence.ObjectPermfrom the spec'sObjectPermissionwith narrowings named in anOmit, the PR Honour per-optionvisibleWhenin the metadata-admin renderer #6618 shape) was assessed and judged larger than this card:keyof ObjectPermis used editor-wide as the boolean-checkbox key type, and the spec type carries the non-booleanreadScope/writeScopeplus (in the installed spec 17.2.0) the still-declared retired lifecycle keys — deriving would force a boolean-keys split across the editor's props and, without also derivingOBJECT_ACTIONS, buys no compile-time guarantee. Detailed in the report on the card.PAYLOAD_SHAPES, so the retired-key class the gate exists to catch is invisible on the permission surface #6606 remains open in its own lane (check-designer-field-key-parity.mjsuntouched). The retiredallowRestore/allowPurgekeys are not part of this change; the existing metadata-admin permission matrix still authors the retiredallowRestore/allowPurgebits — spec now rejects them at publish #6595 pins stay green.Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Generated by Claude Code
Generated by Claude Code