Repository navigation
Commit 644e4b2
fix(core): the authz prelude's grant read projects the columns it decides on (#24941)
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 (`resolveUserAuthzGrants` in
`packages/core/src/security/resolve-authz-context.ts`) read
`sys_user_permission_set` with 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-private `tryFind`, which gains an optional
`fields` argument. No new export, no error code, and no other read in
the prelude changes.
### The columns, measured before the edit (base `74fcec1edb`)
| column | read by | via |
|---|---|---|
| `permission_set` | `grantSetNameOf` |
`anchorReadsDefaultOrganization`, `mayApply`, the ledger names,
`anchorsPlatformStanding`, the 6b set loop |
| `organization_id` (helper also accepts `organizationId`) |
`grantOrganizationOf` | `grantAppliesInTenant`,
`grantAnchorsPlatformStanding`, `mayApply`, `applyingGrants` |
| `valid_from` (also `validFrom`) | `isGrantActive`,
`nextGrantValidityBoundary` | the `upsRows` filter; the grants-cache
boundary over `upsRowsAll` |
| `valid_until` (also `validUntil`) | `isGrantActive`,
`nextGrantValidityBoundary` | same |
Nothing in the prelude reads a grant row's `id`, `user_id` (it is only
the filter), `granted_by`, `reason` or `display_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-empty `fields` list 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, `id` included
only when named, and the resolver reads no `id`.
### The counts, on the rig of `auth-prelude-query-count.pin.test.ts` (a
principal with one organization-owned direct grant)
| core | `display_title` | reads | round trips |
|---|---|---|---|
| `main` | as declared (own columns) | 8 | 4 |
| `main` | PR #24885's one-hop formula (applied in the test fixture
only) | 9 (`sys_user` 3) | 5 |
| this PR | PR #24885's one-hop formula | 8 | 4 |
## Pins
- **Equivalence**
(`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, no `id`), 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_id` null) with and without
an active organization, an `admin_full_access` grant owned by the
Default Organization (acting elsewhere, and with none active), an
expired `admin_full_access` grant, another organization's
`admin_full_access`, and all states at once. The batch-equivalence
`FIXTURES` matrix 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.
- **Count**
(`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
whose `display_title` reads one hop through `user_id`, with the same
expression as PR #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 computes `Prelude →
prelude_editor` from the user's name.
## Verification (head `41b479266`, after merging `origin/main` at
`71696121a`)
- **Ablation:** projection reverted (`--hold` with a dist marker,
preflight proved the marker in core's `dist/`). The count pin went red
only under the one-hop title (`sys_user` 3, expected 2). The equivalence
pin's projection case went red. Restore: `ablation-replace` reports blob
== HEAD (`6d3b1baf7150`) and an empty `git diff HEAD`. Rebuild and
`ablation-dist-preflight --absent` report the marker absent from all 14
built files and the tree clean.
- **Column sweep:** each of the four columns dropped from the projection
in turn. Envelope-equivalence fixtures went red every time:
`permission_set` 11 failed, `organization_id` 11, `valid_from` 3,
`valid_until` 6. Every leg restored blob == HEAD.
- **Suites (exit 0 each):**
- `@objectstack/core` test: 108 files / 2623 tests.
- `@objectstack/core` `test:repo`: 6 / 82.
- `@objectstack/plugin-security` test: 196 files / 4075 passed, 45
skipped.
- `@objectstack/plugin-auth` test: 171 files / 3283 passed, 10 skipped.
- **Typecheck:** `@objectstack/core` and `@objectstack/plugin-auth` both
exit 0. Both test layers compile with no new debt.
- **Build:** the full build (`turbo run build`, docs excluded) exits 0.
- **Gates:** `dispatch-gates.mjs --commands` derived 70 families, all
run with exit 0, and `--ran` reconciles: 70 derived, 70 run, 0
NOT-MEASURED, all exit codes recorded. `check:dual-build-cjs-loads`
first exited 3 (no `dist` yet). `check:lean-entry-closure` first exited
1, because it read `spec`'s `dist` while the full build was rewriting
it. Both exit 0 when re-run on the finished build.
- **Pin sweep:**
- The batch-equivalence and request-rounds goldens record `object`,
`where`, `limit` and `isSystem`, never `fields`, so none moves.
- No pin anywhere asserts the grant read's options exactly.
- The one pin that moves is the query-count pin, which gains its
grant-holder case.
## Cross-lane
`packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts`
is in `domain:services`' package. The claim declares this edit as
cross-lane, and the seat declares it to that lane before the queue.
## Acceptance notes
- `sys_member` and `sys_user_position`, the prelude's other grant reads,
are also unprojected. Each declares an own-column `display_title`
formula (`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).
- The grants cache default (`OS_AUTHZ_GRANTS_CACHE_TTL_MS=0`) is
untouched.
- PR #24885 can merge `main` after this lands: its formula then adds no
read or round trip to the prelude.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01BQSBMNgcScNf9TzF8W6YpU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent feb09db commit 644e4b2
4 files changed
Lines changed: 305 additions & 4 deletions
File tree
- .changeset
- packages
- core/src/security
- plugins/plugin-auth/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
364 | 371 | | |
365 | 372 | | |
366 | 373 | | |
367 | 374 | | |
368 | | - | |
| 375 | + | |
369 | 376 | | |
370 | 377 | | |
371 | 378 | | |
| |||
870 | 877 | | |
871 | 878 | | |
872 | 879 | | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
873 | 893 | | |
874 | 894 | | |
875 | 895 | | |
| |||
1463 | 1483 | | |
1464 | 1484 | | |
1465 | 1485 | | |
1466 | | - | |
| 1486 | + | |
1467 | 1487 | | |
1468 | 1488 | | |
1469 | 1489 | | |
| |||
0 commit comments