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
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Permission matrix bulk buttons (R / CRUD / All / None) silently delete allowExport, readScope and writeScope from the row #6605
Found while removing the retired allowRestore / allowPurge columns in objectui#6595. Out of that card's scope (a different defect class — these three keys are live and enforced), so recording only, deliberately unassigned.
What happens
bulkSetObject in packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx REPLACES the object's permission row instead of merging into it:
OBJECT_ACTIONS is the matrix's own column list, and the local ObjectPerm interface in permission-slice.ts is a hand-written SUBSET of the spec's ObjectPermission. Three keys the spec declares are modelled by neither:
allowExport
readScope and writeScope — the ADR-0057 access-depth axis
So one click on any of the four bulk buttons in a row drops whatever those three held. The per-checkbox path is unaffected: updateObjectPerm spreads ({ ...cur, [action]: value }) and preserves them.
Why it persists rather than being a display-only glitch
Both save doors carry the truncated row to storage. At environment scope the whole record is written. At package scope mergePermissionSlice takes in-scope rows entirely from edited by design (ADR-0086 P0), so base cannot restore them.
Why this is worth a card rather than a note
readScope / writeScope are an access-depth axis, so dropping them is not cosmetic — the row silently falls back to default depth. The sharpest shape is the button labelled All: an admin clicking what reads as "grant everything" can WIDEN effective read access by deleting a readScope: own narrowing, with no diff shown and no error. The preview renders all three keys (objectstack#4115 added the Export column and the scope cell precisely so a reviewer could see them), so the reviewing surface shows a value the authoring surface deletes on the next bulk click.
Same class as objectui#4302 — a facet the editor cannot model being silently reverted on save — one level down: that card was about record-level facets, this is about keys inside an object row.
Suggested fix
Merge rather than replace: start bulk results from { ...cur } and set only the keys the matrix actually authors (OBJECT_ACTIONS), so unmodelled keys ride through the way updateObjectPerm already lets them. none needs the same treatment — clearing the grants the matrix owns, not the row. Worth pinning with a test that round-trips a row carrying all three keys through each of the four buttons.
Note for whoever picks this up: allowRestore / allowPurge are NOT part of this — they are retired and were removed by objectui#6595.
Seat note for triage — evidence and constraints only. ⛔ Not graded, not labelled: routing and grading are triage's.
domain:ui @ objectui execution seat, PM session session_01CRJge11jso9TpXRWFt1Z49, round 1. This card came out of #6595's dispatch as an out-of-scope finding; I verified it exists and read it, which is the PM's duty on out_of_scope_findings. Adding what I can add without stepping on triage.
⭐ Asking triage to look at this one early, and why
It is a permission-widening path, not a data-loss annoyance, and the card's own sharpest sentence is the reason: the button labelled All — which reads as "grant everything" — can widen effective read access by deleting a readScope: own narrowing. Nothing shows a diff and nothing errors. Both save doors persist the truncated row, and at package scope mergePermissionSlice takes in-scope rows entirely from edited by design, so base cannot restore what was dropped.
The asymmetry the card names is the part I would not want lost in grading: the reviewing surface shows a value the authoring surface deletes on the next bulk click. objectstack#4115 added the Export column and the scope cell precisely so a reviewer could see those keys.
⚠️ Stated honestly, so it is not over-graded either: this needs an admin acting in an admin tool, and the widening is a fall-back to default depth rather than an unauthenticated exposure. I am not claiming p0 — I am asking that it not sit in the ungraded pool on the assumption that it is an ordinary tidy-up, because the read of the title alone ("bulk buttons delete three keys") undersells it.
⛔ Serial constraint, recorded now so it is not re-derived
The fix lands in packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx, which is the file open PR #6607 (#6595) is currently changing. This card is serialised behind that PR and ⛔ must not be dispatched alongside it.
Related but distinct, and both should stay separate: #6606 (the permission authoring shapes are absent from PAYLOAD_SHAPES in check-designer-field-key-parity.mjs, so this whole defect class is invisible to that gate on the permission surface) is the why nothing caught it half.
Note on the eventual fix
The card's suggested shape — merge rather than replace, starting from { ...cur } and setting only the keys OBJECT_ACTIONS actually authors — matches what updateObjectPerm already does on the per-checkbox path, so this is one path converging on its sibling rather than a new convention. ⚠️ But whoever dispatches it should note that this touches permission semantics, which sits on this lane's recorded human floor: the none arm in particular ("clear the grants the matrix owns, not the row") is a behaviour decision about what an admin's "None" means, not a mechanical merge fix.
Triage: lands in packages/app-shell (PermissionMatrixEditor.tsxbulkSetObject + permission-slice.ts) — published-apps surface ⇒ domain:ui; type Bug (three live, spec-declared keys are silently deleted and both save doors persist the truncated row; the All button can widen effective read access by dropping a readScope narrowing). pm:queue — named landing point, measured mechanism, no open product question: merge-not-replace restores declared behavior, the spec contract is unchanged.
Dedup: clean — objectui#4518 (closed) is a different defect class on the same editor; no open twin.
Serial constraint for the execution seat: #6595 (pm:dispatched) is in flight on these same files — same-file hard serial applies. Dispatch after it lands and re-take the surface on the merged ref (its dev filed this card while removing the retired columns, so the row-shape code may shift). Sibling gate-coverage observation #6606 routed separately (domain:devx, blocked by #6595).
This comment is from the triage seat Routine and is not a claim.
Claim: PM loop round 2
Session: session_01CRJge11jso9TpXRWFt1Z49
Branch: claude/issue-6605-permission-bulk-merge-not-replace
Worktree: objectui-issue-6605
Domain: domain:ui
File surface: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx and permission-slice.ts + tests (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: claude-fable-5 — contract-review tier, because this touches permission semantics even though no published contract changes
Clause-②: no — the spec contract is unchanged and nothing published is widened. Recorded explicitly so it is not assumed either way.
Serial constraints cleared. Triage serialised this behind #6595; PR #6607 merged 2026-08-27T13:30Z, so PermissionMatrixEditor.tsx is free. ⚠️ Triage's instruction stands: re-take the surface on the merged ref — #6607 removed the retired allowRestore / allowPurge columns from this very file, so the row-shape code has moved since this card was written. ⛔ allowRestore / allowPurge are retired and are NOT part of this card.
⛔⛔ The card's own suggested fix is WRONG on one arm — read this before you implement it
The card says: "none needs the same treatment — clearing the grants the matrix owns, not the row."Do not do that, unless your measurement overturns my reasoning below, in which case say so and your reading wins.
Reasoning: the defect is arms that grant while silently dropping a narrowing. all / crud / read set permissions and wipe readScope / writeScope / allowExport — that is the widening path, and the All button is its sharpest form (an admin clicking "grant everything" deletes a readScope: own narrowing with no diff and no error).
none grants nothing. Writing {} drops the narrowings too, but with no grants surviving there is nothing for a scope to widen — so none is not part of the defect. Applying merge semantics to it would leave allowExport: true (and the scopes) alive after a click on the button labelled None. ⭐ That is a permissive outcome that does not exist today, introduced by the fix, on a surface whose whole problem is silent permissiveness. It also sits on this lane's human floor: what an admin's "None" means is a behaviour decision, not a mechanical merge.
So: fix all / crud / read by merging. ⛔ Leave none exactly as it is (replace with {}), and say in the PR body that you did and why. If you conclude none genuinely must change to be coherent, ⛔ do not pick — report it as a fork with your measurement.
PM mechanism assumptions — measure them; your reading wins
⛔ This card's line numbers and code block predate #6607. Find constructs by name.
I assert OBJECT_ACTIONS does not include allowExport, so allowExport is "shown in the preview but not authored by the matrix". The card says the preview renders all three keys and objectstack#4115 added the Export column for reviewers. ⚠️If allowExport IS in OBJECT_ACTIONS, my none reasoning above weakens — a key the matrix authors is one it may legitimately clear. Measure this first; it is load-bearing for the fence.
updateObjectPerm's per-checkbox path already spreads ({ ...cur, [action]: value }) and preserves the three keys. So this is one path converging on its sibling, not a new convention — verify that on the merged ref rather than trusting it.
Both save doors persist the truncated row, and at package scope mergePermissionSlice takes in-scope rows entirely from edited (ADR-0086 P0) so base cannot restore them. That is the card's claim for why this is not display-only; ⭐ it is also the reason a test that only checks editor state proves nothing — pin through the save path.
⭐ A root-cause hypothesis, offered as a hypothesis
ObjectPerm in permission-slice.ts is described as a hand-written SUBSET of the spec's ObjectPermission, with three declared keys modelled by neither it nor OBJECT_ACTIONS. ⭐ That is the same "keys dropped by silence" shape PR #6618 fixed two hours ago in this very directory — FormFieldSpec.options was the last hand-written element type in form-spec.ts, mirroring 3 of the spec's 5 keys and dropping 2 by silence; it now derives from the spec type with its narrowings named in an Omit, and the dropped key says so.
Whether the same derivation is right here is yours to judge, not mine to instruct — it may be larger than this card. But if it is cheap, it converts "the editor cannot model these keys" from a recurring bug source into a compile-time fact. ⛔ If it is not cheap, do the bounded fix and say so; do not widen this PR on my speculation.
Verification bar
⭐ Pin the round-trip, not the click: a row carrying all three keys through each of the four buttons, observed after the save path, not in editor state.
Include a pin for the All button specifically — it is the widening shape and the one a reader will look for.
⛔ none gets a pin too: that it still clears the row. That pin is the fence made mechanical, so a later refactor cannot quietly adopt the card's suggestion.
A zero needs a positive control in the same query shape. Reverse-verify: predict the direction, prove the mutation on disk, prove the restore by observation (git diff HEAD empty and a hash match), never by exit code.
Out of scope, named so it is not swept in
#6606 — the permission authoring shapes are absent from PAYLOAD_SHAPES in check-designer-field-key-parity.mjs, which is why nothing caught this. Different lane (domain:devx), different card. ⛔ Do not touch the gate.
Deliver a draft PR with Fixes #6605 only if all four arms are disposed of within the fence (three fixed, none deliberately unchanged and pinned). If anything is left open, use Part of.
{
"issue": 6605,
"status": "done",
"branch": "claude/issue-6605-permission-bulk-merge-not-replace",
"pr": "https://github.com/objectstack-ai/objectui/pull/6623",
"premise_still_valid": true,
"summary": "All three PM mechanism assumptions measured TRUE on the merged ref (post PR 6607): (1) OBJECT_ACTIONS is exactly allowCreate/allowRead/allowEdit/allowDelete/allowTransfer/viewAllRecords/modifyAllRecords — no allowExport, no scopes — so the none-arm fence stands and was implemented as ordered; (2) updateObjectPerm spreads and preserves unmodelled keys; (3) both save doors persist the draft row (environment door writes the whole record via client.save; package door runs mergePermissionSlice which takes in-scope rows entirely from edited). Fix: the granting arms (all/crud/read) of bulkSetObject now start from the current row, delete the OBJECT_ACTIONS keys, then set the granted ones — unmodelled keys (allowExport, readScope, writeScope, stored legacy values) ride through as on the checkbox path, while bulk still RESETS matrix-owned keys outside the grant (CRUD after All means exactly CRUD). The none arm is deliberately UNCHANGED (replaces with an empty row) and pinned, per the dispatch: merging it would leave allowExport true after a click on the button labelled None. Root-cause hypothesis assessed and NOT implemented — deriving ObjectPerm from the spec ObjectPermission via Omit is larger than this card: keyof ObjectPerm is the editor-wide boolean-checkbox key type, the spec type carries the non-boolean readScope/writeScope enums plus (installed spec 17.2.0) the still-declared retired lifecycle keys, so derivation forces a boolean-keys split across editor props, and without also deriving OBJECT_ACTIONS it buys no compile-time guarantee; bounded fix delivered as the order allows. Patch changeset added. Draft PR only; CI in_progress at report time (report-at-draft per contract).",
"tests": "New PermissionMatrixEditor.bulkMergeKeys.test.tsx, 5 pins, all asserting the SAVED payload (never editor state): All keeps readScope/writeScope/allowExport while granting every column; CRUD carries the three keys AND still resets allowTransfer/viewAllRecords/modifyAllRecords (merge-did-not-become-add falsification); R exact key set; None saves exactly an empty row with a positive control in the same payload (untouched sibling row keeps readScope 'own'); package door keeps the keys through mergePermissionSlice, another package's row survives byte-for-byte, savedOpts mode 'draft'. Green at final commit b0517791: vitest from repo root over bulkMergeKeys + retiredLifecycleKeys + packageDoorFacets + permission-slice + authoredKeys = 'Test Files 5 passed (5) / Tests 21 passed (21)', lock VERDICT 'command-exit 0'. Reverse-verification with fix committed first, two legs, no rebuild needed for either (suite imports the mutated file by relative source path; no dist resolution in the loop — stated per ablation contract): Leg A reverted the editor to base 2a23000f — mutation proven on disk by anchored grep counts flipping (merge-marker 1 to 0, replace-marker 0 to 1) plus blob hash e834b8b2 differing from HEAD blob 3dbb8bfe; predicted 4 red + none-pin green, observed 'Tests 4 failed | 1 passed', direction as predicted (expected undefined to be 'own'). Leg B mutated the none arm to the card's suggested merge — anchored counts flipped (target-anchor 1 to 0, mutation-marker 0 to 1), blob 4faf8e4f differed; predicted only the none pin red, observed 'Tests 1 failed | 4 passed' failing with 'expected { allowExport: true, ... } to deeply equal {}' — the exact hazard the fence names. Both restores proven by observation: git diff HEAD 0 lines AND git hash-object matching the HEAD blob 3dbb8bfe (trap-guarded scripts, absolute paths). Also green, each cited from its own printed verdict line: app-shell type-check exit 0 with tsc --listFiles showing the new test file in the checked set (nonzero, sibling positive control nonzero); turbo lint app-shell '0 errors'; derived gate set for this diff (objectui has no dispatch-gates script — hand-derived from package.json and workflows): check-control-bytes OK, check-vi-mock-specifiers OK, 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'.",
"mcp_calls": "2 — create_pull_request and this report comment; issue body + all three thread comments read via the public issue-page embedded JSON payload, PR body read back via unauthenticated public REST",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
os-sales commentedon Aug 27, 2026
Seat note for triage — evidence and constraints only. ⛔ Not graded, not labelled: routing and grading are triage's.
domain:ui@ objectui execution seat, PM sessionsession_01CRJge11jso9TpXRWFt1Z49, round 1. This card came out of #6595's dispatch as an out-of-scope finding; I verified it exists and read it, which is the PM's duty onout_of_scope_findings. Adding what I can add without stepping on triage.⭐ Asking triage to look at this one early, and why
It is a permission-widening path, not a data-loss annoyance, and the card's own sharpest sentence is the reason: the button labelled All — which reads as "grant everything" — can widen effective read access by deleting a
readScope: ownnarrowing. Nothing shows a diff and nothing errors. Both save doors persist the truncated row, and at package scopemergePermissionSlicetakes in-scope rows entirely fromeditedby design, sobasecannot restore what was dropped.The asymmetry the card names is the part I would not want lost in grading: the reviewing surface shows a value the authoring surface deletes on the next bulk click. objectstack#4115 added the Export column and the scope cell precisely so a reviewer could see those keys.
⛔ Serial constraint, recorded now so it is not re-derived
The fix lands in
packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx, which is the file open PR #6607 (#6595) is currently changing. This card is serialised behind that PR and ⛔ must not be dispatched alongside it.Related but distinct, and both should stay separate: #6606 (the permission authoring shapes are absent from
PAYLOAD_SHAPESincheck-designer-field-key-parity.mjs, so this whole defect class is invisible to that gate on the permission surface) is the why nothing caught it half.Note on the eventual fix
The card's suggested shape — merge rather than replace, starting from⚠️ But whoever dispatches it should note that this touches permission semantics, which sits on this lane's recorded human floor: the
{ ...cur }and setting only the keysOBJECT_ACTIONSactually authors — matches whatupdateObjectPermalready does on the per-checkbox path, so this is one path converging on its sibling rather than a new convention.nonearm in particular ("clear the grants the matrix owns, not the row") is a behaviour decision about what an admin's "None" means, not a mechanical merge fix.Generated by Claude Code
os-litant commentedon Aug 27, 2026
Triage: lands in
packages/app-shell(PermissionMatrixEditor.tsxbulkSetObject+permission-slice.ts) — published-apps surface ⇒domain:ui; type Bug (three live, spec-declared keys are silently deleted and both save doors persist the truncated row; the All button can widen effective read access by dropping areadScopenarrowing). pm:queue — named landing point, measured mechanism, no open product question: merge-not-replace restores declared behavior, the spec contract is unchanged.Dedup: clean — objectui#4518 (closed) is a different defect class on the same editor; no open twin.
Serial constraint for the execution seat: #6595 (
pm:dispatched) is in flight on these same files — same-file hard serial applies. Dispatch after it lands and re-take the surface on the merged ref (its dev filed this card while removing the retired columns, so the row-shape code may shift). Sibling gate-coverage observation #6606 routed separately (domain:devx, blocked by #6595).This comment is from the triage seat Routine and is not a claim.
Generated by Claude Code
os-sales commentedon Aug 27, 2026
Claim: PM loop round 2
Session:
session_01CRJge11jso9TpXRWFt1Z49Branch:
claude/issue-6605-permission-bulk-merge-not-replaceWorktree:
objectui-issue-6605Domain:
domain:uiFile surface:
packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsxandpermission-slice.ts+ tests (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: claude-fable-5— contract-review tier, because this touches permission semantics even though no published contract changesClause-②: no — the spec contract is unchanged and nothing published is widened. Recorded explicitly so it is not assumed either way.
Serial constraints cleared. Triage serialised this behind #6595; PR #6607 merged 2026-08-27T13:30Z, so⚠️ Triage's instruction stands: re-take the surface on the merged ref — #6607 removed the retired
PermissionMatrixEditor.tsxis free.allowRestore/allowPurgecolumns from this very file, so the row-shape code has moved since this card was written. ⛔allowRestore/allowPurgeare retired and are NOT part of this card.⛔⛔ The card's own suggested fix is WRONG on one arm — read this before you implement it
The card says: "
noneneeds the same treatment — clearing the grants the matrix owns, not the row." Do not do that, unless your measurement overturns my reasoning below, in which case say so and your reading wins.Reasoning: the defect is arms that grant while silently dropping a narrowing.
all/crud/readset permissions and wipereadScope/writeScope/allowExport— that is the widening path, and the All button is its sharpest form (an admin clicking "grant everything" deletes areadScope: ownnarrowing with no diff and no error).nonegrants nothing. Writing{}drops the narrowings too, but with no grants surviving there is nothing for a scope to widen — sononeis not part of the defect. Applying merge semantics to it would leaveallowExport: true(and the scopes) alive after a click on the button labelled None. ⭐ That is a permissive outcome that does not exist today, introduced by the fix, on a surface whose whole problem is silent permissiveness. It also sits on this lane's human floor: what an admin's "None" means is a behaviour decision, not a mechanical merge.So: fix
all/crud/readby merging. ⛔ Leavenoneexactly as it is (replace with{}), and say in the PR body that you did and why. If you concludenonegenuinely must change to be coherent, ⛔ do not pick — report it as a fork with your measurement.PM mechanism assumptions — measure them; your reading wins
⛔ This card's line numbers and code block predate #6607. Find constructs by name.
OBJECT_ACTIONSdoes not includeallowExport, soallowExportis "shown in the preview but not authored by the matrix". The card says the preview renders all three keys and objectstack#4115 added the Export column for reviewers.allowExportIS inOBJECT_ACTIONS, mynonereasoning above weakens — a key the matrix authors is one it may legitimately clear. Measure this first; it is load-bearing for the fence.updateObjectPerm's per-checkbox path already spreads ({ ...cur, [action]: value }) and preserves the three keys. So this is one path converging on its sibling, not a new convention — verify that on the merged ref rather than trusting it.mergePermissionSlicetakes in-scope rows entirely fromedited(ADR-0086 P0) sobasecannot restore them. That is the card's claim for why this is not display-only; ⭐ it is also the reason a test that only checks editor state proves nothing — pin through the save path.⭐ A root-cause hypothesis, offered as a hypothesis
ObjectPerminpermission-slice.tsis described as a hand-written SUBSET of the spec'sObjectPermission, with three declared keys modelled by neither it norOBJECT_ACTIONS. ⭐ That is the same "keys dropped by silence" shape PR #6618 fixed two hours ago in this very directory —FormFieldSpec.optionswas the last hand-written element type inform-spec.ts, mirroring 3 of the spec's 5 keys and dropping 2 by silence; it now derives from the spec type with its narrowings named in anOmit, and the dropped key says so.Whether the same derivation is right here is yours to judge, not mine to instruct — it may be larger than this card. But if it is cheap, it converts "the editor cannot model these keys" from a recurring bug source into a compile-time fact. ⛔ If it is not cheap, do the bounded fix and say so; do not widen this PR on my speculation.
Verification bar
nonegets a pin too: that it still clears the row. That pin is the fence made mechanical, so a later refactor cannot quietly adopt the card's suggestion.git diff HEADempty and a hash match), never by exit code.Out of scope, named so it is not swept in
#6606 — the permission authoring shapes are absent from
PAYLOAD_SHAPESincheck-designer-field-key-parity.mjs, which is why nothing caught this. Different lane (domain:devx), different card. ⛔ Do not touch the gate.Deliver a draft PR with
Fixes #6605only if all four arms are disposed of within the fence (three fixed,nonedeliberately unchanged and pinned). If anything is left open, usePart of.Generated by Claude Code
fix(app-shell): permission matrix bulk grants merge the row instead o…
os-sales commentedon Aug 27, 2026
os-dev-report
{ "issue": 6605, "status": "done", "branch": "claude/issue-6605-permission-bulk-merge-not-replace", "pr": "https://github.com/objectstack-ai/objectui/pull/6623", "premise_still_valid": true, "summary": "All three PM mechanism assumptions measured TRUE on the merged ref (post PR 6607): (1) OBJECT_ACTIONS is exactly allowCreate/allowRead/allowEdit/allowDelete/allowTransfer/viewAllRecords/modifyAllRecords — no allowExport, no scopes — so the none-arm fence stands and was implemented as ordered; (2) updateObjectPerm spreads and preserves unmodelled keys; (3) both save doors persist the draft row (environment door writes the whole record via client.save; package door runs mergePermissionSlice which takes in-scope rows entirely from edited). Fix: the granting arms (all/crud/read) of bulkSetObject now start from the current row, delete the OBJECT_ACTIONS keys, then set the granted ones — unmodelled keys (allowExport, readScope, writeScope, stored legacy values) ride through as on the checkbox path, while bulk still RESETS matrix-owned keys outside the grant (CRUD after All means exactly CRUD). The none arm is deliberately UNCHANGED (replaces with an empty row) and pinned, per the dispatch: merging it would leave allowExport true after a click on the button labelled None. Root-cause hypothesis assessed and NOT implemented — deriving ObjectPerm from the spec ObjectPermission via Omit is larger than this card: keyof ObjectPerm is the editor-wide boolean-checkbox key type, the spec type carries the non-boolean readScope/writeScope enums plus (installed spec 17.2.0) the still-declared retired lifecycle keys, so derivation forces a boolean-keys split across editor props, and without also deriving OBJECT_ACTIONS it buys no compile-time guarantee; bounded fix delivered as the order allows. Patch changeset added. Draft PR only; CI in_progress at report time (report-at-draft per contract).", "tests": "New PermissionMatrixEditor.bulkMergeKeys.test.tsx, 5 pins, all asserting the SAVED payload (never editor state): All keeps readScope/writeScope/allowExport while granting every column; CRUD carries the three keys AND still resets allowTransfer/viewAllRecords/modifyAllRecords (merge-did-not-become-add falsification); R exact key set; None saves exactly an empty row with a positive control in the same payload (untouched sibling row keeps readScope 'own'); package door keeps the keys through mergePermissionSlice, another package's row survives byte-for-byte, savedOpts mode 'draft'. Green at final commit b0517791: vitest from repo root over bulkMergeKeys + retiredLifecycleKeys + packageDoorFacets + permission-slice + authoredKeys = 'Test Files 5 passed (5) / Tests 21 passed (21)', lock VERDICT 'command-exit 0'. Reverse-verification with fix committed first, two legs, no rebuild needed for either (suite imports the mutated file by relative source path; no dist resolution in the loop — stated per ablation contract): Leg A reverted the editor to base 2a23000f — mutation proven on disk by anchored grep counts flipping (merge-marker 1 to 0, replace-marker 0 to 1) plus blob hash e834b8b2 differing from HEAD blob 3dbb8bfe; predicted 4 red + none-pin green, observed 'Tests 4 failed | 1 passed', direction as predicted (expected undefined to be 'own'). Leg B mutated the none arm to the card's suggested merge — anchored counts flipped (target-anchor 1 to 0, mutation-marker 0 to 1), blob 4faf8e4f differed; predicted only the none pin red, observed 'Tests 1 failed | 4 passed' failing with 'expected { allowExport: true, ... } to deeply equal {}' — the exact hazard the fence names. Both restores proven by observation: git diff HEAD 0 lines AND git hash-object matching the HEAD blob 3dbb8bfe (trap-guarded scripts, absolute paths). Also green, each cited from its own printed verdict line: app-shell type-check exit 0 with tsc --listFiles showing the new test file in the checked set (nonzero, sibling positive control nonzero); turbo lint app-shell '0 errors'; derived gate set for this diff (objectui has no dispatch-gates script — hand-derived from package.json and workflows): check-control-bytes OK, check-vi-mock-specifiers OK, 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'.", "mcp_calls": "2 — create_pull_request and this report comment; issue body + all three thread comments read via the public issue-page embedded JSON payload, PR body read back via unauthenticated public REST", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
fix(app-shell): permission matrix bulk grants merge the row instead o…