Repository navigation
Commit b3d6918
test(client,runtime): close 337 refused authz-resolver reads — the false-green class on this lane's nine sites (#18082)
Fixes #18070
The false-green class, closed on the nine sites this lane owns: seven
`@objectstack/client` fixtures where the refusals were **visible in the
log**,
and two `@objectstack/runtime` integration fixtures where the symptom
was
**pinned rather than closed**. Test-only: no product code is touched,
and no
dependency edge onto `plugin-auth` / `plugin-security` is added.
## What was wrong
`core/src/security/resolve-authz-context.ts` reaches five `sys_*` tables
through
`tryFind`, which classifies a **missing table** as "not provisioned" and
answers
`[]`. A fixture that boots a real `ObjectQL` over a real `SqlDriver` but
registers a narrower object set therefore gets a green it did not earn:
the
assertion passes because the read returned empty, not because the state
was
empty. A suite in that condition cannot turn red when grant resolution
breaks.
## The card's counts, RE-MEASURED (not transcribed)
Every number in the card was re-derived on `fb29f62ce`, classified by
the
`(table, filter, limit)` triple of the eight reads the resolver issues —
so
`sys_position ... where name in (...) limit 200` counts and `where name
= ?
limit 1` on the same table does not.
| file | card | measured | after |
|---|---|---|---|
| `client/src/client.metadata-prefix.test.ts` | 95 | **95** | **0** |
| `client/src/client.hono.test.ts` | 35 | **35** | **0** |
| `client/src/client.data-prefix.test.ts` | 25 | **25** | **0** |
| `client/src/client.batch-transaction.test.ts` | 25 | **25** | **0** |
| `client/src/auth-get-session-envelope.test.ts` | 21 | **21** | **0** |
| `client/src/client.environment-scoping.test.ts` | 20 | **20** | **0**
|
| `client/src/auth-login-register-envelope.test.ts` | 6 | **6** | **0**
|
| **sum** | **227** | **227** | **0** |
Whole-package control, same tree, same command shape:
`@objectstack/client`
emitted **264** refused reads of which **227** were resolver-class;
after, **37**
of which **0** are. The 37 that remain are `sys_metadata` 25,
`sys_setting` 7,
`sys_metadata_history` 3, `sys_organization` 2 — the
`probeInstallOrganizations`
and boot-metadata classes, a different card. The seven summing to the
whole-package 227 is what proves there is no eighth site.
## The instrument under-reads, and here is the proof
The two `@objectstack/runtime` sites route their refusals through
`captureExpectedReadRefusals` (#10629 / #11081), which **withholds the
driver
line**. Measured on `fb29f62ce`: `grep -c "refused a read on"` over a
full run of
either file reads a clean **0**, while the capture's own counter reads
```
notifications.hono.integration refusals {"sys_user":10,"sys_member":10,"sys_user_position":10,
"sys_user_permission_set":10,"sys_position":10,"sys_setting":2}
engineFrames identical → 52 total, 50 resolver-class
notification-schema-conformance refusals {"sys_user":12,"sys_member":12,"sys_user_position":12,
"sys_user_permission_set":12,"sys_position":12,"sys_setting":3}
engineFrames identical → 63 total, 60 resolver-class
```
After: `{"sys_setting":2}` and `{"sys_setting":3}` — **110
resolver-class
refusals closed**, and `sys_setting`, which is not resolver-class,
deliberately
left exactly as it was.
Total across this PR: **337** refused authz-resolver reads closed (227
visible +
110 withheld).
## The count falls because the read SUCCEEDS
Nothing is silenced, filtered or re-levelled. Proven positively by a
one-off
probe that seeded one row per table and printed what the read returns —
injected, run, then restored under a `trap` and verified byte-exact with
`git hash-object` against each path's HEAD blob:
```
[#18070 PROBE ROWS] sys_user [{"id":"probe-user",...,"email":"probe@example.com"}]
[#18070 PROBE ROWS] sys_member [{"id":"probe-mem",...,"user_id":"probe-user","role":"admin"}]
[#18070 PROBE ROWS] sys_user_position [{"id":"probe-up",...,"position":"everyone"}]
[#18070 PROBE ROWS] sys_user_permission_set [{"id":"probe-ups",...,"permission_set_id":"probe-ps"}]
[#18070 PROBE ROWS] sys_position [{"id":"probe-pos",...,"name":"everyone","active":true}]
RESTORED packages/runtime/src/notifications.hono.integration.test.ts blob=9f7158bc9... == HEAD-BLOB-of-the-same-path
RESTORED packages/runtime/src/notification-schema-conformance.integration.test.ts blob=299cd3b37... == HEAD-BLOB-of-the-same-path
RESTORED packages/client/src/client.environment-scoping.test.ts blob=2aa383b08... == HEAD-BLOB-of-the-same-path
RESTORE VERIFIED: git diff HEAD empty over all three probed paths
```
A by-product worth naming: seeding those rows made the new runtime
assertion go
RED (`expected [ { id: 'probe-user', …(6) } ] to deeply equal []`). That
is the
assertion reading real state rather than a stub.
## ⭐ The `runtime` half: a passing assertion had to move, deliberately
This is the review's sticking point, and it should be.
Both runtime fixtures declared `ABSENT_AUTHZ_TABLES` and asserted
`noise.silentChannels(ALWAYS_READ_AUTHZ_TABLES)` — an assertion that
each of the
five reads was **still being refused**. That pins the symptom. Closing
the read
necessarily falsifies it, and leaving it in place would leave a pin
asserting a
number that no longer describes reality.
It is **replaced, not deleted**. What it asserted about behaviour —
"these five
reads really happen on this path" — is now asserted in the direction the
fix
runs, by `expectResolverAuthzReadsSucceed()`: each read SUCCEEDS and
answers
`[]` because the state is empty rather than because the table is
missing. Same
call sites, same `-t`-safety (per authed test in the hono file; in
`afterAll`
for the conformance file, with the shutdown moved into a `finally` so
the old
invariant — a failure here can never leave the kernel running — survives
the
reordering the live-engine read forces).
`ABSENT_AUTHZ_TABLES` shrinks to `['sys_setting']`, which is the shared
capture
module's own prescribed repair: *"a table that started resolving means
the
fixture now provisions it"*. ⭐ Shrinking that list is also what keeps
the
capture from becoming a mute: `captureDriver` forwards an
**unrecognised**
refusal straight to `console.warn`, so a regression that stops
provisioning one
of the five is now LOUD as well as red — where, while the five were
declared,
the same regression would have been withheld and merely counted.
## Ablation — the new pin can fail
Registration deleted, on-disk landing proven by anchor counts
before/after
(`rt2=1 cl1=1` → `rt2=0 cl1=0`, one `ABLATED` marker each), restored
under a
`trap` and verified byte-exact against HEAD:
| ablated | result |
|---|---|
| `notification-schema-conformance.integration.test.ts` | **Test Files 1
failed** — while all 8 `Tests` pass. The failure is the new `afterAll`
assertion, i.e. the pin catches exactly the regression it exists for.
The five driver refusal lines are also **visible in the output**,
confirming the loudness claim above. |
| `client.environment-scoping.test.ts` | refusals back to **20
resolver-class** — and the suite still **passes (5/5)**. That is the
false green itself, reproduced on demand. |
## Scope and fences
- `resolve-authz-context.ts` lives in `packages/core/src/security/`
(`domain:engine`) and is **not touched**. Nothing needed it.
- `packages/spec` is **not touched**. The only new dependency on it is a
`import type { ServiceObject }` — a type already re-exported from
`@objectstack/spec/data`, which both packages already depend on.
- Objects are registered **locally**, with only the columns the reading
path touches (`id` is not declared anywhere — the registry supplies the
primary key). `sys_position_permission_set` and `sys_permission_set` are
deliberately absent: the resolver reaches them only after a
`sys_position` row resolves, and measurement confirms neither appears in
any of these files' refusals before or after.
- Changeset: **`skip-changeset`**, measured rather than assumed. Both
packages publish `files: ["dist","README.md","CHANGELOG.md"]`; `grep
-rl` for the new symbols (`AUTHZ_RESOLVER_OBJECTS`,
`expectResolverAuthzReadsSucceed`) over every one of those paths returns
**zero hits**, against a positive control (`ObjectStackClient`,
`createRestApiPlugin`) that hits `dist/`. No published artefact moves.
## Acceptance notes
Out of scope, observed while measuring, filed as nothing:
- **The non-resolver refusal classes are still there and are
deliberately untouched.** After this PR `@objectstack/client` still
emits 37 refused reads (`sys_metadata` 25, `sys_setting` 7,
`sys_metadata_history` 3, `sys_organization` 2) and the two `runtime`
fixtures still withhold `sys_setting` (2 and 3). None is resolver-class
by the `(table, filter, limit)` classifier — they are the
`probeInstallOrganizations` / boot-metadata-load / localization-settings
classes, which #18070's body and PR #18067's own notes already separate
out. Noted, not filed: the carrier is whoever picks up that class, which
lands in these same files.
- **The shared capture has no affordance for the positive direction.**
`expected-read-refusal-noise.ts` prescribes the repair for "a table that
started resolving" (drop it from the declared list) but offers nothing
to assert the success that replaces the refusal, so each consumer
hand-rolls it — this PR writes `expectResolverAuthzReadsSucceed()`
twice. A design observation about a test helper, not a defect in it, and
not a reproducible failure: noted, not filed, carrier none.
## Verification
See the report comment on #18070 for the full gate table and exit codes.
Authored by Claude Code in session `session_01TbSMtGzMrtPwh925wDEZd5` —
kept as prose
because this body was EDITED after creation, and the edit channel
appends its own
footer block: a session-URL footer sent on an edit ends up with the
platform's bare
one beneath it, two footers where the form allows one.
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 57343f7 commit b3d6918
9 files changed
Lines changed: 1047 additions & 99 deletions
File tree
- packages
- client/src
- runtime/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
76 | 77 | | |
77 | 78 | | |
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 | + | |
79 | 157 | | |
80 | 158 | | |
81 | 159 | | |
| |||
86 | 164 | | |
87 | 165 | | |
88 | 166 | | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
89 | 172 | | |
90 | 173 | | |
91 | 174 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
| |||
79 | 80 | | |
80 | 81 | | |
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 | + | |
82 | 160 | | |
83 | 161 | | |
84 | 162 | | |
| |||
89 | 167 | | |
90 | 168 | | |
91 | 169 | | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
92 | 175 | | |
93 | 176 | | |
94 | 177 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 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 | + | |
37 | 135 | | |
38 | 136 | | |
39 | 137 | | |
| |||
106 | 204 | | |
107 | 205 | | |
108 | 206 | | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
109 | 213 | | |
110 | 214 | | |
111 | 215 | | |
| |||
0 commit comments