Filed by the domain:engine execution seat (session_01CqmCgU5RGDoJYhHUMVp2af) out of the #18545 round (PR #18781), from that dev's out_of_scope_findings where it was marked 「to file」, class (b). ⛔ Filed bare: finding only; domain:* / type / priority are triage's.
Dedupe words: PermissionEvaluator · checkObjectPermission · objectPermissionGrants · super-user fold · viewAllRecords.
The reading
One rule — 「does this effective object permission grant this verb?」 — now has three independent implementations, each with its own spelling:
| where |
what it is |
@objectstack/spec/security |
objectPermissionGrants — published by PR #18781, every cell pinned |
@objectstack/plugin-security |
PermissionEvaluator.checkObjectPermission — the enforcement door; the fold the server actually applies |
packages/lint |
buildAccessMatrix — folds the same bits a third time (read: allowRead || viewAllRecords || modifyAllRecords) |
The fold itself: read bypasses on viewAllRecords || modifyAllRecords; write bypasses on modifyAllRecords alone and never create; export requires the grant and read.
⚠️ All three agree today, and each is pinned on its own side. ⛔ Nothing is broken. The finding is that one rule has three implementations, and the repo's own Route-&-surface-ownership rule 1 says that forks every future invariant.
⭐ Why it is a card rather than a note
The third implementation was added on purpose and with a ruling behind it, so this is ⛔ not drift that can be tidied away by reverting something.
The #18545 dev asked whether can should answer the bare bit or apply the enforcement fold; the seat ruled it must fold, because a predicate answering false where the server answers 200 hides an action from the one administrator who can use it — the same defect class that card exists to prevent, pointed the other way.
⇒ ⭐ The fold is correct AND it is now stated three times. Those are both true, and the second is the cost of the first. The dev named that cost in its own acceptance notes rather than letting it pass, which is why it is written down here at all.
Shape (⛔ a proposal, not a prescription)
Converge PermissionEvaluator.checkObjectPermission and buildAccessMatrix onto the published objectPermissionGrants helper, so the spec's copy is the one definition and the other two ask it — the same 「one definition」 move #17469 made for isMultiValueField.
⚠️ It is outside #18545's file surface, and packages/plugins/plugin-security is a package that card never opened.
⭐ An affinity, ⛔ not a merge this seat is making
#18783 — the server-side wiring card from the same round — does open packages/plugins/plugin-security. ⇒ The two could sensibly be one card, and the #18545 dev suggested exactly that.
⚠️ The seat is deliberately ⛔ not merging them: that would widen #18783 before anyone has priced either half, and the two answer different questions (one is 「who populates the map」, the other is 「who owns the fold」). ⭐ The affinity is recorded on both cards so triage can merge them in one stroke if it judges that right — ⛔ but it is triage's call.
⛔ Not measured
- Whether the three folds agree on every input or only on the inputs each one's pins cover. ⇒ ⚠️ 「All three agree today」 is the dev's reading of three pinned implementations, ⛔ not an exhaustive differential over the input space. A taker should run that differential first — it is the cheap thing that would turn this from a structural finding into a reproducible one, or rule that out.
- Whether any consumer outside these three folds the same bits a fourth time.
Refs: #18545 / PR #18781 (which added the third and published the helper) · the seat's ruling on that card's open_questions[1] · #18783 (the affinity above) · #17469 (the 「one definition」 precedent, for isMultiValueField)
Generated by Claude Code
Filed by the
domain:engineexecution seat (session_01CqmCgU5RGDoJYhHUMVp2af) out of the #18545 round (PR #18781), from that dev'sout_of_scope_findingswhere it was marked 「to file」, class (b). ⛔ Filed bare:findingonly;domain:*/ type / priority are triage's.Dedupe words:
PermissionEvaluator·checkObjectPermission·objectPermissionGrants·super-user fold·viewAllRecords.The reading
One rule — 「does this effective object permission grant this verb?」 — now has three independent implementations, each with its own spelling:
@objectstack/spec/securityobjectPermissionGrants— published by PR #18781, every cell pinned@objectstack/plugin-securityPermissionEvaluator.checkObjectPermission— the enforcement door; the fold the server actually appliespackages/lintbuildAccessMatrix— folds the same bits a third time (read: allowRead || viewAllRecords || modifyAllRecords)The fold itself: read bypasses on
viewAllRecords || modifyAllRecords; write bypasses onmodifyAllRecordsalone and never create; export requires the grant and read.⭐ Why it is a card rather than a note
The third implementation was added on purpose and with a ruling behind it, so this is ⛔ not drift that can be tidied away by reverting something.
The #18545 dev asked whether
canshould answer the bare bit or apply the enforcement fold; the seat ruled it must fold, because a predicate answeringfalsewhere the server answers 200 hides an action from the one administrator who can use it — the same defect class that card exists to prevent, pointed the other way.⇒ ⭐ The fold is correct AND it is now stated three times. Those are both true, and the second is the cost of the first. The dev named that cost in its own acceptance notes rather than letting it pass, which is why it is written down here at all.
Shape (⛔ a proposal, not a prescription)
Converge
PermissionEvaluator.checkObjectPermissionandbuildAccessMatrixonto the publishedobjectPermissionGrantshelper, so the spec's copy is the one definition and the other two ask it — the same 「one definition」 move #17469 made forisMultiValueField.packages/plugins/plugin-securityis a package that card never opened.⭐ An affinity, ⛔ not a merge this seat is making
#18783 — the server-side wiring card from the same round — does open
packages/plugins/plugin-security. ⇒ The two could sensibly be one card, and the #18545 dev suggested exactly that.⛔ Not measured
Refs: #18545 / PR #18781 (which added the third and published the helper) · the seat's ruling on that card's
open_questions[1]· #18783 (the affinity above) · #17469 (the 「one definition」 precedent, forisMultiValueField)Generated by Claude Code