Skip to content

test(core,runtime): authorization ObjectQL doubles enforce limit - #11521

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-10978-doubles-honour-limit
Aug 24, 2026
Merged

test(core,runtime): authorization ObjectQL doubles enforce limit#11521
os-steve merged 1 commit into
mainfrom
claude/issue-10978-doubles-honour-limit

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Part of #10978

⚠️ Deliberately Part of, not a closing keyword — the dispatch asked for a closing
keyword, but the census below changed the shape of the work: this PR converts 9 of the
49
limit-blind doubles. Closing the card would retire the remaining 40 silently. The
card should stay open and be closed by hand once the PM has triaged the remainder.

What the card asked, and what the census found

The card proposed "teach the shared doubles to honour limit (a one-line slice)". The
dispatch flagged the word shared as an unmeasured guess. It is: there is no shared
helper.
The doubles are N hand-rolled copies.

Measured on this tree (919f3f6e):

count
find implementations in test files 83
... that take a query and read where (query-honouring doubles) 62
... of those, already reading limit 13
... of those, limit-blind 49, across 43 files in 10 packages

Only one exported helper exists anywhere — makeRecordingQl, the probe #10825 had to
build for itself, which is precisely the rediscovery cost the card is about.

The measurement — "let whatever breaks be the measurement"

Two instrumented runs over the 43 files, each a trap-guarded mutation restored on every
exit path, each with the mutation proven on disk before the run was believed.

Run 1 — receipts (behaviour-neutral). Records what each double is actually handed.
Without this, "nothing broke" is uninterpretable: it cannot tell no test was vacuous
from no limit ever arrives.

  • 49 of 49 doubles are reached at runtime.
  • 44 of 49 are handed a real bound — values 1, 2, 3, 4, 5, 6, 7, 10, 12, 20, 40, 50, 100, 200, 500, 1000, 5000, 10000.
  • Baseline: all 43 files green before any change.

Run 2 — all 49 doubles bounded. Every one taught to honour the bound at once:

0 of 1062 tests broke.

So the answer the card said "may well be zero today" is zero. The class was
unobservable, not wrong — no test in the tree is currently vacuous because of
limit-blindness, and nothing here is a suite being repaired.

What actually changed

Nine doubles across four files — the authorization-resolution seam, where the card's
worked example lives and where a dropped bound is a privilege bug rather than a cosmetic
one (org_user_ids is an RLS input):

  • packages/core/src/security/resolve-authz-context.test.ts (2 doubles)
  • packages/core/src/security/admin-standing-surface.test.ts
  • packages/core/src/security/api-key.test.ts
  • packages/runtime/src/security/resolve-execution-context.test.ts (5 doubles)

Semantics were taken from the real read path, not invented — driver-memory applies
filter, sort, offset, then limit:

  • After the filter, never before it. Bounding first returns rows the where excludes:
    a double that is silently wrong rather than merely unbounded.
  • By presence, not truthiness. limit: 0 means "return no records" and 0 is falsy,
    so opts.limit ? … answers a request for nothing with the whole table. That is a
    measured door in this repo, which driver-memory carries as query.limit !== undefined.
  • No ordering is implemented and no read on these paths asks for one, so filter-then-slice
    matches production here rather than diverging from it.

Four cases pin the instrument itself, including the card's worked example (two sys_member
reads at 200 and 1000). Without them the bound is unverified and deleting it fails nothing.

Reverse verification (predicted direction stated first: red). Ablating the bound to the
identity, from the committed state, with the anchor proven present before (1) and absent
after (0): 4 failed, 63 passed — exactly the four instrument cases, nothing else. No
rebuild is involved and none is owed: the ablated subject is the test file's own file-local
function, which vitest resolves from source, not through a package exports into dist/.

What is deliberately NOT here

Verification

All at the final commit 919f3f6e; each gate quoted by its own printed verdict line, with
exit codes captured before any pipe.

  • pnpm --filter @objectstack/core --filter @objectstack/runtime test — core 936 passed
    (936)
    , runtime 2724 passed (2724), 0 failures.
  • pnpm --filter @objectstack/runtime typecheck — clean (script name echoed, so not a
    zero-match). @objectstack/core declares no typecheck script; it is one of the 13
    DEBT-ledger packages, confirmed by the gate rather than assumed.
  • check:where-matcher — "288 matcher(s) discovered, 288 answer the combinator battery
    correctly or refuse it loudly (176 refuse)."
  • check:engine-double-contract — "OK — 390 pinned, 133 in the DEBT ledger, 2 exempt."
  • check:type-check-debt — "32 ledger entr(ies) re-measured in 515.3s, 1897 raw tsc
    error(s) total, none above its recorded number." (First attempt refused with 27 deps
    unbuilt; that refusal was treated as NOT MEASURED, the closure was built, and it was
    re-run.)
  • check:cross-package-test-inputs, check:kernel-hook-pairs, check:published-files,
    check:slot-lookup, check:test-source-alias, check:type-source-resolution,
    check:query-options-erasure, check:type-check-coverage, check:nul-bytes,
    check-ci-filter-parity, check-plugin-teardown-shape, check-affected-docs — all OK.

Declared narrowing. The two instrumented runs covered the 43 files carrying the doubles
rather than all ten packages' suites. The narrowing is sound by construction, not by
sampling: every edit is inside a test file, and each patched find is a file-local
function that nothing imports, so no behaviour outside its own file can change. The two
packages this PR actually edits were then run in full anyway (3660 tests). The gate set was
derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, which
computes its own change set (4 paths vs merge base 63da5867e) and confirmed the repo
assertion against this checkout's remote.


Generated by Claude Code

The in-memory `ql` doubles on the authorization-resolution path matched
`where` and handed back every matched row, dropping `opts.limit`. A double
that ignores the bound cannot tell a read bounded at 200 from the same read
bounded at 1000, or from one carrying no bound at all — so raising a limit,
lowering it, or folding two reads that carry different ones was green by
construction, and the production symptom is a silently truncated result set
rather than an error.

Nine doubles across four files now enforce the bound the way the real read
path does: AFTER the filter, and by PRESENCE rather than truthiness so
`limit: 0` returns nothing rather than the whole table — the door
`driver-memory` already carries for itself as `query.limit !== undefined`.

Four new cases pin the instrument itself, so the blindness cannot come back
unnoticed and deleting the bound fails something. They include the worked
example from issue #10978: two `sys_member` reads at 200 and 1000, where
folding them truncates `org_user_ids`, an RLS input.

Measurement over the whole population, reported on the card: 49 limit-blind
query-honouring doubles across 43 files, all 49 reached at runtime and 44
handed a real bound (values 1 … 10000). Teaching all 49 to honour the bound
broke 0 of 1062 tests — the class was unobservable, not wrong.

No shipped behaviour changes; test instruments only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

@github-actions github-actions Bot added the tests label Aug 24, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 24, 2026
@os-steve
os-steve marked this pull request as ready for review August 24, 2026 02:36
@os-steve
os-steve added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit 4c9780c Aug 24, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-10978-doubles-honour-limit branch August 24, 2026 02:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants