Repository navigation
fix(core): the authz prelude's grant read projects the columns it decides on - #24941
Conversation
…ides on The prelude read sys_user_permission_set with no projection, so the engine computed every formula field of the grant table in every request a grant holder made. A record title that reads one hop through user_id would add a sys_user read and a round trip to each such request. The read now names permission_set, organization_id, valid_from and valid_until - every column the resolver reads off a grant row - through the module-private tryFind. Pinned: the resolved envelope is identical over projected and full rows for every grant state, and the prelude's query-count pin gains a grant-holder case that stays at 8 reads in 4 round trips under a one-hop title. Claude-Session: https://claude.ai/code/session_01BQSBMNgcScNf9TzF8W6YpU Co-authored-by: Claude <noreply@anthropic.com>
…d projection Claude-Session: https://claude.ai/code/session_01BQSBMNgcScNf9TzF8W6YpU Co-authored-by: Claude <noreply@anthropic.com>
…elude-grant-projection
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 27 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 520ea21c16ddaa77b7841261daea1353082a143b && git checkout 520ea21c16ddaa77b7841261daea1353082a143b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 71696121a64d8bd057fac969a72a84295890ed37 41b47926643dec979624d5b7a7e6df3d3c5747bf && git checkout -B drift-repro 71696121a64d8bd057fac969a72a84295890ed37 && git merge --no-ff 41b47926643dec979624d5b7a7e6df3d3c5747bf
node scripts/docs-audit/affected-docs.mjs --json 71696121a64d8bd057fac969a72a84295890ed37
|
Contract reviewServed-tier: Scope: the net diff against the merge base
Also the card #24896 with the claim 6295406119 and the dispatch 6295459203, the dev's ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 ·
|
| Item | Reading |
|---|---|
| Shape | Draft PR → main. The body opens Fixes #24896. 4 files, +305/−4. Not governed: check-governed-merges matched 0 of 4 paths; 309 changed lines. |
| Scope | The authz prelude's sys_user_permission_set read projects permission_set, organization_id, valid_from and valid_until, through the module-private tryFind. No formula on the grant table is computed there, and a grant holder stays at 8 reads in 4 round trips under a one-hop title. The other prelude reads are unchanged. |
| Seat's own read | Every reader of a grant row traced at this head: grantSetNameOf, grantOrganizationOf (with grantAppliesInTenant, grantAnchorsPlatformStanding, mayApply, applyingGrants), isGrantActive and nextGrantValidityBoundary. They read those four columns and no other. All four are declared on the object. git merge-tree against today's origin/main is clean. |
| Review | TIER_DEFAULT, in-seat. Record 6296487539 is PASS on this head. |
| Declaration | One changeset, @objectstack/core patch, Clause-②: no (a read's projection: …) at a line start, as on the claim and the PR body. No accept set, served shape, export or error code moves. |
| Cross-lane | plugin-auth's auth-prelude-query-count.pin.test.ts (test-only, +63/−2), declared to domain:services on #6021 at 6296470031. No objection on the card, the PR or the seat posts at this read. |
| Gates | Dev at 41b479266: 70 derived, 70 run, all exit 0. core 2623 passed, core test:repo 82 passed, plugin-security 4075 passed, plugin-auth 3283 passed. Ablation red on both pins, and a column sweep red on each of the four columns; each leg restored to HEAD. |
| CI | At this read on 41b4792664: ci-failure GREEN, all 41 check-runs completed, none failed, the six Test Core shards included. |
Disposition:
open_questions: all three as the dev settled them (record ③): a positional optionalfields, snake_case columns only, and the recording double plus the real-engine count pin.deviations: all accepted (record ③).out_of_scope_findings: the unprojectedsys_member/sys_user_positionreads cost no read today, and the camelCase alternates have no producer. Neither meets a filing gate.- For PR fix(plugin-security): a permission-set assignment is titled by the person's name, not the raw user id #24885: once this lands, fix(plugin-security): a permission-set assignment is titled by the person's name, not the raw user id #24885's one-hop title adds no read to the prelude; it merges
mainafter.
Landing: ordinary queue (pr_ready + automerge_enable through the relay). Fixes #24896 then closes the card.
Generated by Claude Code
Brings #24941 (644e4b2): the authz prelude's sys_user_permission_set grant read projects the columns it decides on, so this branch's one-hop display_title is no longer computed on that read. Clean merge, no conflicts, no os-regen deferral. Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
…erson-named one Semantic merge conflict with #24941's pin, resolved in one line. Its direct-grant case "as declared" read the declared sys_user_permission_set title as the raw user id, a precondition asserted before the count. On this branch the declared title IS the one-hop person title, so both cases of the pair now read 'Prelude → prelude_editor'. The counts the pin holds (8 reads in 4 round trips) are unchanged and green. Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
…ected prelude read Since #24941 the authz prelude's grant read names the columns it decides on, so it computes no display_title and the prelude stays 8 reads in 4 round trips for a principal holding a direct grant. The changeset said 9 in 5, a figure measured on the pre-#24941 base. Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
Fixes #24896
Clause-②: no (a read's projection: every resolved authz answer is unchanged and pinned so; no accept set, served shape or export moves)
What changed
The authorization prelude (
resolveUserAuthzGrantsinpackages/core/src/security/resolve-authz-context.ts) readsys_user_permission_setwith no projection, so the engine computed every formula field of the grant table on every request a grant holder made. That read now names exactly the four columns the resolver decides on, through the module-privatetryFind, which gains an optionalfieldsargument. No new export, no error code, and no other read in the prelude changes.The columns, measured before the edit (base
74fcec1edb)permission_setgrantSetNameOfanchorReadsDefaultOrganization,mayApply, the ledger names,anchorsPlatformStanding, the 6b set looporganization_id(helper also acceptsorganizationId)grantOrganizationOfgrantAppliesInTenant,grantAnchorsPlatformStanding,mayApply,applyingGrantsvalid_from(alsovalidFrom)isGrantActive,nextGrantValidityBoundaryupsRowsfilter; the grants-cache boundary overupsRowsAllvalid_until(alsovalidUntil)isGrantActive,nextGrantValidityBoundaryNothing in the prelude reads a grant row's
id,user_id(it is only the filter),granted_by,reasonordisplay_title. The camelCase spellings are not declared fields of the object, so the engine never returns them under those keys; only the declared names are projected.What a projection does in objectql
planFormulaProjection(packages/objectql/src/engine.ts) evaluates only the formula fields a non-emptyfieldslist names. A list of stored columns yields an empty plan, so no formula is computed and the hop a formula would read is never issued. (#22300's widening applies only when a formula field is IN the projection.) No system column has to be named: driver-sql answers exactly the named columns,idincluded only when named, and the resolver reads noid.The counts, on the rig of
auth-prelude-query-count.pin.test.ts(a principal with one organization-owned direct grant)display_titlemainmainsys_user3)Pins
packages/core/src/security/resolve-authz-context.grant-projection.test.ts, new). Each fixture is resolved twice on the batch-equivalence recording double: over full stored rows plus a computed title, as the unprojected read answered, and over projected rows (named columns only, noid), as a driver answers a projection. The whole envelope must be identical. Fixtures: an active grant in the active organization, one owned by another organization, an expired one, one not yet valid, one inside its window, a deployment-wide one (organization_idnull) with and without an active organization, anadmin_full_accessgrant owned by the Default Organization (acting elsewhere, and with none active), an expiredadmin_full_accessgrant, another organization'sadmin_full_access, and all states at once. The batch-equivalenceFIXTURESmatrix runs through the same comparison. A further case pins that the grant read names exactly the four columns and that no other prelude read names any.packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts). The rig gains an optional direct grant, and a variant of the grant table whosedisplay_titlereads one hop throughuser_id, with the same expression as PR fix(plugin-security): a permission-set assignment is titled by the person's name, not the raw user id #24885. A new grant-holder case runs under both titles and holds 8 reads in 4 round trips. The case first proves the hop is live on that engine: an unprojected read computesPrelude → prelude_editorfrom the user's name.Verification (head
41b479266, after mergingorigin/mainat71696121a)--holdwith a dist marker, preflight proved the marker in core'sdist/). The count pin went red only under the one-hop title (sys_user3, expected 2). The equivalence pin's projection case went red. Restore:ablation-replacereports blob == HEAD (6d3b1baf7150) and an emptygit diff HEAD. Rebuild andablation-dist-preflight --absentreport the marker absent from all 14 built files and the tree clean.permission_set11 failed,organization_id11,valid_from3,valid_until6. Every leg restored blob == HEAD.@objectstack/coretest: 108 files / 2623 tests.@objectstack/coretest:repo: 6 / 82.@objectstack/plugin-securitytest: 196 files / 4075 passed, 45 skipped.@objectstack/plugin-authtest: 171 files / 3283 passed, 10 skipped.@objectstack/coreand@objectstack/plugin-authboth exit 0. Both test layers compile with no new debt.turbo run build, docs excluded) exits 0.dispatch-gates.mjs --commandsderived 70 families, all run with exit 0, and--ranreconciles: 70 derived, 70 run, 0 NOT-MEASURED, all exit codes recorded.check:dual-build-cjs-loadsfirst exited 3 (nodistyet).check:lean-entry-closurefirst exited 1, because it readspec'sdistwhile the full build was rewriting it. Both exit 0 when re-run on the finished build.object,where,limitandisSystem, neverfields, so none moves.Cross-lane
packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.tsis indomain:services' package. The claim declares this edit as cross-lane, and the seat declares it to that lane before the queue.Acceptance notes
sys_memberandsys_user_position, the prelude's other grant reads, are also unprojected. Each declares an own-columndisplay_titleformula (sys_member: user and role;sys_user_position: user and position), so today they cost one CEL evaluation per row and no read. They would carry the same read cost if either title gained a hop. Measured and named only; not built (out of scope per the claim).OS_AUTHZ_GRANTS_CACHE_TTL_MS=0) is untouched.mainafter this lands: its formula then adds no read or round trip to the prelude.Generated by Claude Code