Skip to content

Gate: an ObjectQL find double in a test must apply the caller's limit, or refuse it loudly - #12030

Merged
yinlianghui merged 7 commits into
mainfrom
claude/issue-11525-objectql-double-limit-gate
Aug 25, 2026
Merged

Gate: an ObjectQL find double in a test must apply the caller's limit, or refuse it loudly#12030
yinlianghui merged 7 commits into
mainfrom
claude/issue-11525-objectql-double-limit-gate

Conversation

@os-steve

@os-steveos-steve commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#11525

A find(object, opts) double inside a test file that matches opts.where and hands back
every matched row cannot tell a read bounded at 200 from the same read bounded at 1000, or
from one carrying no bound at all. Every limit change on such a read is green by
construction
, and the production symptom is a silently truncated result set rather than an
error. The worked example is #10978's: resolveUserAuthzGrants reads sys_member twice,
{user_id} at 200 and {organization_id} at 1000; the obvious "same object, fold the two
reads" cleanup caps org_user_ids — an RLS input — at 200.

PR #11521 made nine such doubles right and measured the population. Nothing held them right.
This is the gate, in the shape #7620 / check:where-matcher established next door and for
its recorded reason: "the doubles were made right, and nothing held them right."

The criterion

A discovered find double must APPLY the caller's limit — by PRESENCE, AFTER the filter,
and BEFORE any stage that touches the rows — or REFUSE it by throwing. What it must never do
is silently ignore it.

Refusal counts as conforming on purpose: the defect class is silence, not incompleteness.
A double that throws the moment a bound arrives makes the suite RED, not quietly green.

The three shape rules from the landed nine, and how each is encoded

The gate is behavioural, not syntactic — it lifts each double out of its file and asks it a
bounded question, so a shared helper, a per-file helper and an inline slice all grade
identically.

ruleencoding
1. presence, not truthinessthe presence probe: limit: 0 must return zero rows (0 is falsy, so opts.limit ? … : rows answers a request for nothing with everything)
2. bound AFTER the filterthe probe corpus puts the non-matching rows first, so a double that slices before it filters cannot reach the requested count
3. bound BEFORE any row-touching stageper-row read counters: reads of a match inside the bound vs. a match outside it vs. a miss

Rule 3 is provable in one direction only — bounding before or after a pure wrap returns
identical values — and the script says exactly where the encoding stops, pins the two residuals
as self-test fixtures, and prints how many doubles landed in each wrap-order state on every run.

What this branch changes beyond the gate itself

This card was picked up as a rescue after the previous run was lost to a container restart with
its work uncommitted and nothing verified. Verification was re-established from scratch
rather than inherited, and that turned up a defect in the gate's own admission rule.

The control probe granted a seat whenever it had not been disproven, rather than when it had
been proven.
allCarry answers null when no returned row carries the probe's field, and
null !== false. So a double answering both control probes with rows of its own — constant
stubs like async find(o, q) { return [{ id: 'r1' }]; }, and schema-signature fixtures that
exist only to satisfy a parse() — was seated on evidence the control never obtained, and then
graded limit-blind. That is debt with no possible remedy: there is no corpus to bound, and a
shrink-only ledger carrying unfixable entries can never burn down.

Measured: 19 of 294 seated candidates were in that state. They now take the same fallthrough
every unseated candidate takes — 15 drop OUT OF SCOPE (the body never filters), 4 are DECLARED
unjudged (it does, but the lift cannot drive it). Two new --self-test fixtures pin both
directions.

The header's SHAPED invariant also claimed no shape violation was grandfathered while 32 were;
it now describes what the ledger actually holds.

The bare-root verdict this gate owed

scripts/pm/bare-root-worklist.mjs --self-test red on this branch: the gate declares
SCAN_ROOT = 'packages', a bare single-segment literal that extractWatchHints builds no
hint at all
from, so the gate is unnameable by any dispatch brief. Every gate newly joining
that species owes a recorded verdict.

The verdict is REFUSE-UNSPELLABLE, established from the gate's own walk rather than by
analogy — though it lands beside its two nearest neighbours, check:where-matcher SCAN_ROOT packages and check:examples-live-imports PACKAGES_ROOT packages, which are test-files-only
under the same root and refused alike.

testFilesUnder() recurses under packages/ and admits /\.test\.ts$/ and nothing else, so
the population is a file-KIND filter, not a subtree. Measured on this tree:

claimcoversverdict
the gate's real population2696 of 5161 tracked files under packages/ (52%)
packages/** — the only spellable claimall 2696 test files and all 2465 non-test filesfalse for 2465 files it never opens
packages/**/*.test.ts0 of 2696collapses to packages//.test.ts
packages/*/src/**/*.test.ts0 of 2696collapses to packages//src//.test.ts
packages/**/src/**0 of 2696collapses to packages//src

collapseHint strips globs, so every glob spelling of the real population reduces to a
malformed double-separator prefix that hintCovers matches against nothing — a narrow
declaration would not be a precise hint but a live hint covering zero files. That is the
check:i18n row's shape, re-measured here rather than assumed.

No narrower subtree exists either: the corpus is spread over 28 second-level directories,
and 274 of the 2696 sit outside any src segment, so even the check:runner-env-posture
src-segment shape would be false here as well as uncollapsible.

So a declaration is refused in both available directions, and REFUSE-WIDE would be the wrong
word for it: the population is not the whole root. Declaring packages/** is the costlier
error the self-test names by name — a find double can only ever land in a test file.

Merged with main, and the workflow arithmetic

The branch was 42 commits behind and a .github/workflows/lint.yml co-tenant with two commits
that landed under it — 607c870f4 (#12018) and 18dea75eb (#11918). Merged with merge commits,
both sides of every hunk kept; no rebase, no force-push. A second merge later took the branch up
to 0b048393f, which touched no workflow.

The lint job's - name: step count reads 186, and nothing was dropped:

treestepsdelta
original merge base 311e207f8183
origin/main (then and now)185main added Self-test wiring gate and Self-test workflow-command gate
this branch before the merge184this PR added ObjectQL double limit gate
after the merges186= main's 185 + this PR's 1

A name-by-name diff against origin/main shows exactly one line of difference — this PR's own
step. The earlier 183 → 184 measurement in this PR's history was correct at its merge base;
main moved underneath it.

Both gates main added see this gate's --self-test for the first time, and both are green
against it: check-self-test-wired (129 scripts CI runs that ship a --self-test, all wired —
this one through the package.json alias, which chains --self-test before the corpus scan)
and check-self-test-workflow-commands (14 self-tests naming a workflow-command token were run
and their real stdout+stderr scanned).

The ledger, and why it is not 40

before the seating fixafterre-measured on the merged tree
files263251253
limit-blind186167168
breaking a shape rule323232
unjudged (declared, never skipped)505455

The card was filed against a census of 49 blind doubles across 43 files, 9 converted, 40 left.
The two numbers are not the same instrument: the earlier one was scoped by hand, this one
seats every candidate by driving it. Seeding the ledger at 40 would have pinned the ratchet to a
number the corpus does not have, and it would have read green forever. The nine doubles PR
#11521 converted are the positive control on the re-derivation — all nine grade CONFORMING, so
all four of their files are absent from the ledger.

The re-measurement, and why those arrivals are not new debt

The initial measurement was taken at merge base 311e207f8. Merging main moved the corpus
underneath the ledger, and the gate reported it in both directions — which is the ratchet
working, not a defect. Every item is main's doing, none is this PR's:

filestateprovenancetreatment
plugin-security/src/packaged-permission-set-restore-leg.test.tsBLINDabsent at 311e207f8; landed by #12021seated
rest/src/meta-compound-save-mode-parity.test.tsBLINDabsent at 311e207f8; landed by #11933seated
service-storage/src/tombstone-hydration-download-agreement.test.tsUNJUDGEDabsent at 311e207f8; landed by #11844seated
plugin-security/src/bootstrap-declared-capabilities.test.tsratchet DOWN#11962 taught its double to honour the boundentry deleted
plugin-auth/src/audience-posture.test.tsWRONG — truthinesslanded by #11767, after the re-measurementfixed, not seated

The first three are not new debt this PR introduces. They already exist on main and would
have been in the initial measurement had this branch been cut today. "The baseline never grows"
governs the period after this gate lands; seating them in the initial measurement is the
same act already performed, against the correct tree. Fixing three unrelated test files across
plugin-security, rest and service-storage would widen this PR across three packages to
satisfy a rule that is not yet in force.

The fourth is the ratchet's other direction firing on its own: main fixed a double this branch
had recorded as debt, and the gate refused to let the stale entry stand.

The fifth was fixed rather than seated — one line in one file, and a real defect: the double
read the bound as if (q.limit), so limit: 0 returned every matched row. The ledger is
unchanged by it (253 / 168 / 32 / 55); the double now grades CONFORMING. plugin-auth's suite
is green over the change (77 files, 1586 tests), as is its typecheck.

On the UNJUDGED one specifically.tombstone-hydration-download-agreement.test.ts:329 is a
different state from BLIND, and it is worth being precise about because the double is
correct:

returntypeofoptions?.limit==='number' ? hit.slice(0,options.limit) : hit;

That is exactly the prescribed shape. It is unseatable because it filters on file_id and
answers from joinRows captured in its own closure, so the control probe — which deliberately
uses synthetic field names (__os_bound_field) precisely so no double can special-case them —
reads no evidence that it filtered on the probe's where at all. It is the
FIXTURE_FOREIGN_ROWS shape, pinned in the self-test in both directions. This is the seating
rule working as designed, not a residual gap: before the null !== false fix above, this
double would have been graded BLIND — a false accusation against correct code, and
unfixable debt. unjudged is the honest column for it: it records that a double is present
which the gate could not read, and says nothing about whether it is broken.

Standing exposure: arrivals after measurement

The gate seats candidates behaviourally across the whole packages/** test corpus, and this
repo merges to main many times a working day. So any merge that lands a test file carrying a
non-conforming find double will red this PR until the ledger is re-measured or the double is
fixed. That is the ratchet working on a moving corpus, not an unresolved defect in this branch —
and it resolves itself at landing time, because the merge queue builds against current main.

It is a shrink-only bidirectional ratchet, not a fixed exclusion set, and not a mute button:
a count that grew is an error, a count that fell is an error (ratchet it down in the same PR), a
file with nothing left must lose its entry, and the key set is checked against the merge base so
a newly-added file matching its own count cannot sail through. There is deliberately no
--update flag. All five directions are demonstrated below.

Evidence

Every leg ran under trap '' EXIT INT TERM, with the mutation confirmed on disk by
anchored grep -c on the injected and the removed text — never an editor's exit code, never
a bare git diff --stat.

These five legs were verified at 06074cf0a and have not been re-run: the merges changed
the corpus the gate reads, not the gate's behaviour or what any leg measures. The files changed
since are the ledger, whose contents every leg mutates for itself, and one unrelated test file.

Positive control — the gate fires on doubles that are not in the ledger. A new test file
carrying one blind double, one truthiness double and one conforming double:

GATE_EXIT=1
x ObjectQL double `limit` conformance (1 problem(s)):
- packages/objectql/src/__os-ablation-limit-probe.test.ts: NEW ObjectQL `find` double that
does not hold the caller's bound (1 blind, 1 breaking a shape rule, 0 unjudged).
line 8: BLIND
line 18: WRONG -- truthiness, not presence (`limit: 0` returns rows -- `0` is falsy)

The conforming double in the same file is not reported (conforming 76 → 77); candidates
420 → 423 and graded 275 → 278, so all three were seated and each graded on its own merits.
Restore leg: fixture absent, tree clean, GATE_EXIT=0, counts back to 420 / 275 / 167 / 32.

Non-vacuity of the seating fix, reverse-verified. Reverting the one condition back to
=== false — predicted RED in both legs, before running:

SELFTEST_EXIT=1 x check-objectql-double-limit --self-test (3 failure(s)):
- the control probe refuses a seat it could not read -- constants are OUT OF SCOPE, never BLIND
- a query-shaped double answering with rows of its own is not graded BLIND
- it is DECLARED unjudged rather than dropped -- absence must be loud
PROD_EXIT=1 x ObjectQL double `limit` conformance (21 problem(s)):

Exactly the three new assertions fail and no others — the pins bite, and nothing else depended
on the old behaviour.

The ratchet, in every direction.

legmutationresult
RECONCILED downledger says 3, corpus has 2count fell 3 -> 2 · exit 1
RECONCILED upledger says 1, corpus has 2blind double count grew 1 -> 2 · exit 1
RECONCILED gonekey with no findingsbaselined file is clean or gone · exit 1
MONOTONIC greenkey set unchangedbaseline key set verified against f7e038c: no files added. · exit 0
MONOTONIC redone key absent at the baseADDED to the baseline (not present at f7e038c). The grandfather list is not a mute button · exit 1

The two MONOTONIC legs needed a base that actually carries the ledger, so the ref list was
pointed at this branch's own first commit for the measurement and restored. On the branch as it
stands the gate prints NOT verified: could not read the baseline at the merge base with main
honest and expected, because the ledger is new on this branch and there is nothing at the
merge base to compare against
. The lint job checks out with fetch-depth: 0, so the check
reads it on every run once this lands.

The open design question the card asked, answered by measurement

The card asked whether one shared bounded() helper is right or per-file copies are, and
required a measurement rather than inheriting #7620's no-shared-substrate ruling.

The card's premise holds: the bound really does have a single lowest common denominator. Of the
files that read it by presence, 74 of 79 spell it typeof ?.limit === 'number'.

What varies is everything around it. The options bag is named six ways — query 21, options
18, ast 18, opts 14, q 6, o 1 — and the row source takes at least four shapes (a closure
array, tables[object], storeFor(o).values(), store.get(o)), which is why the gate's lift
needs a row stub that answers as an array, a map and a callable at once.

Measured recommendation: per-file copies — for a different reason than #7620's. Not because
capability varies (it does not; the card was right), but because the shared part is already a
single expression, so a per-file copy costs one line, while sharing it would add a
cross-package test-input dependency across ~250 files in 24 package directories to save that one
line each — the very thing check:cross-package-test-inputs exists to police.

The gate takes no side either way: FIXTURE_SHARED_HELPER pins that a shared helper and a
per-file copy grade identically, so this question stays open for the conversion lane in #10978
without blocking the gate.

Verification

Gate families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
with no hand-written path list (it reads its own change set — 6 paths vs merge base
0b048393f). 31 families, all green, at 7a10cfa5e — the final commit of this branch, tree
clean. Every exit code was captured before any pipe.

The 25 families the tooling paths pull in were run at 2bd0e5cdb; adding the one test file
pulls in 6 more, run at 7a10cfa5e along with both ratchet families and the edited package:

check:objectql-double-limit OK conformance holds: 278 graded, 78 apply the caller's
bound or refuse it loudly · ledger 253/168/32/55
check:objectql-double-limit OK --self-test: separates bounding, limit-blind, truthiness,
bound-before-filter and refusing doubles
pm/bare-root-worklist --self-test OK 42 live row(s), 36 unreachable as spelled,
36 recorded verdict(s) — none stale, none missing
check:published-files exit 0
check:slot-lookup exit 0
check:test-source-alias exit 0
check:type-source-resolution exit 0
check-plugin-teardown-shape exit 0
docs-audit/check-affected-docs exit 0
plugin-auth test Test Files 77 passed · Tests 1586 passed
plugin-auth typecheck exit 0

The 25 at 2bd0e5cdb:

check:agent-test-spelling · check:cross-package-test-inputs · check:entry-guard
check:node-version · check:parse-guard · check:pnpm-acquisition · check:pnpm-filter-targets
check:required-contexts · check:shard-attestation · check:workflow-status-functions
check:nul-bytes · check-aggregator-roster · check-ci-filter-parity
check-cross-package-test-inputs · check-required-contexts · check-shard-attestation
check-step-collectors · check-whole-set-label-write · docs-audit/check-drift-comment
pm/ci-failure --self-test all exit 0
check:type-check-coverage OK — 65/78 workspace packages type-checked
check:type-check-debt --re-measure: OK — 32 ledger entr(ies) re-measured,
1898 raw tsc error(s), none above its recorded number
check-self-test-wired ✓ every one of the 129 script(s) CI runs that ship a
`--self-test` has that self-test run by CI
check-self-test-workflow-commands ✓ no self-test CI runs prints a line the Actions runner
would parse as a workflow command
check:objectql-double-limit OK 277 graded (before the audience-posture fix)

check:type-check-debt first refused to run — it re-measures with tsc resolving workspace
imports through each dependency's built dist/*.d.ts, and this rescue worktree had never been
built. That refusal is the gate declining to measure a different world, not a failure. The
closure was built exactly as lint.yml does before that step
(turbo run build --filter='./packages/*' --filter='./packages/*/*', 70/70 tasks) under the
shared verify lock, and the gate then measured green.

No changeset

CI tooling plus one test-file fix: a gate script, its ledger, one workflow step, one script
alias in the private root package.json, and a test double in plugin-auth. Nothing
shipped from packages/** changes and nothing is published, so this carries the
skip-changeset label rather than a changeset.

Refs: #10978 (parent — remains open for the remaining 168) · PR #11521 (the nine converted,
with the census) · #7620 / check:where-matcher (the precedent) · #11190 / PR #11512 (the
ordering constraint, released — c96cbfc48 carries both numbers).


Generated by Claude Code

Rescue commit of in-place worktree contents after a container restart.
Verification has NOT been re-established at this point; a later commit
carries the verified state.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
…vidence
The control probe granted a seat whenever it had not been disproven, rather
than when it had been proven: `allCarry` answers `null` when NO returned row
carries the probe's field, and `null !== false`. So a double answering both
control probes with rows of its OWN -- constant stubs, and schema-signature
fixtures that exist only to satisfy a parse() -- was seated and then graded
limit-blind. That is debt with no possible remedy: there is no corpus to bound,
and a shrink-only ledger carrying unfixable entries can never burn down.
19 of 294 seated candidates were in that state. They now take the same
fallthrough every unseated candidate takes: 15 drop OUT OF SCOPE (the body
never filters), 4 are DECLARED unjudged (it does, but the lift cannot drive
it). Both directions are pinned by new --self-test fixtures.
Baseline re-derived from the corrected measurement: 263 files / 186 blind / 50
unjudged -> 251 files / 167 blind / 54 unjudged. Shape breakers are unchanged
at 32, and the header's SHAPED invariant -- which claimed no shape violation
was grandfathered while 32 were -- now describes what the ledger holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 25, 2026
@github-actionsgithub-actionsBot added size/xl ci/cd dependencies Pull requests that update a dependency file labels Aug 25, 2026
@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM handover — this PR is intentionally left as a draft, unarmed. Two things stand between it and landing, and neither is a defect in the work.

1. A ruling is owed on the ledger size — I am not taking it unilaterally

The dispatch pinned "Shrink-only baseline of 40" as BINDING. The delivered ledger is 251 files / 167 blind / 32 wrong / 54 unjudged. The author named the conflict rather than quietly choosing, which is the right call, and recommends landing as measured.

I agree with that recommendation, and record why so the ruling can be checked rather than trusted:

  • The two numbers come from different instruments. The 40 came from test(core,runtime): authorization ObjectQL doubles enforce limit #11521's hand-scoped census; this gate seats every candidate by driving it, so membership is behavioural rather than by name or location. Asking a behavioural instrument to reproduce a hand-scoped count is asking it to stop measuring.
  • The constraint's real content is the ratchet direction, not the literal 40 — and every part of that direction is demonstrated: fails when the count falls, fails when it grows, key set checked against the merge base, and no --update / --fix flag exists (verified independently: the only argv flags the script accepts are --census, --self-test, --why).
  • A ledger sized to 40 over a corpus of 251 would have read green forever — the failure mode a shrink-only ratchet exists to prevent.
  • The re-derivation carries its own positive control: all nine doubles test(core,runtime): authorization ObjectQL doubles enforce limit #11521 converted grade CONFORMING, so all four of their files are absent from the ledger.

⚠️ But a 6× deviation from a constraint marked BINDING is a scope call, not a review call. Maintainer to confirm route A before this lands.

2. The branch is 42 commits stale, and it is a lint.yml co-tenant with a PR that landed tonight

merge-base 311e207f8 → origin/main 1e79aa4f8 (42 commits behind)
commits touching .github/workflows/lint.yml since the merge base:
607c870f4 #12018 ← landed tonight
18dea75eb #11918

⚠️ The - name: step count reads 184 on this branch against 185 on main. That is not a dropped step in this PR — the author's own 183 → 184 measurement was correct at its merge base. Main simply moved underneath it.

Fix before landing: merge origin/main in and keep BOTH sides, then re-count - name: to prove nothing was lost. ⛔ Never rebase or force-push this branch.

What I verified and what I did not

Verified by content: the shrink-only property has no escape hatch (flag list above); the four-file diff is exactly lint.yml +22 · package.json +1 · the gate 1270 · the baseline 838.

Not re-verified by me: the gate's own self-test and production runs, and the five ablations. Those are the author's readings, and this is a rescue branch — a previous agent built it over hours, was killed by a container restart without ever committing, and nothing it did was verified. The rescue run re-established verification from scratch rather than inheriting it, and in doing so found a real defect in what the gate ADMITS: the control probe granted a seat whenever it had not been disproven rather than when it had been proven (allCarry answers null when no returned row carries the probe's field, and null !== false), seating 19 of 294 candidates on evidence never obtained and then grading them limit-blind — unfixable debt in a ledger that can only shrink. That defect is fixed here and pinned in both directions.

⚠️ Whoever picks this up: the same discipline applies to the first commit (f7e038cda). Inherit no claim from it.


Generated by Claude Code

…mit gate
REFUSE-UNSPELLABLE: the gate walks packages/ but admits *.test.ts only
(2696 of 5161, 52%). packages/** is the only spellable claim and covers
2465 non-test files the gate never opens; every narrower glob collapses
to packages//.test.ts and covers 0 of 2696.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
The initial measurement was taken at merge base 311e207. Merging 42 commits
of main moved the corpus underneath it: three new test files arrived carrying
find doubles, and main's #11962 independently taught the
bootstrap-declared-capabilities double to honour the bound.
Re-measured: 251 -> 253 files, 167 -> 168 blind, 54 -> 55 unjudged, wrong
unchanged at 32. Ratchet DOWN applied, three arrivals seated as pre-existing
debt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
…, not truthiness
The double read the caller's bound as `if (q.limit)`, so `limit: 0` -- a
request for NOTHING -- returned every matched row. Arrived with #11767 after
this branch's ledger was measured; fixed rather than seated, since it is one
line in one file.
Ledger unchanged: 253 files, 168 blind, 32 wrong, 55 unjudged.
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.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 3def551026963aed4c333b80fa5d82190ba35893packageMentionDocs.

@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

Status update — correcting my own handover comment above, which is now half stale.

That comment listed two blockers. Blocker 2 is resolved. It said the branch was 42 commits behind main and that the - name: count read 184 vs 185. Both are fixed: main has been merged twice (42 commits, then 4 more), both sides of every lint.yml hunk kept, and the count is now 186 = main's 185 + this PR's 1 step, which I verified independently.

CI is fully green on 7a10cfa5e — 35 checks, all success or skipped, none in flight, Lint & Repo Gates included.

Three CI reds were worked through on the way here, and none was a defect in the change:

  1. bare-root-worklist --self-test — the new gate owed a verdict for its SCAN_ROOT = 'packages'. Recorded as REFUSE-UNSPELLABLE, established by measurement in both directions rather than picked to go green: the walk admits *.test.ts and nothing else (2696 of 5161 tracked files, 52%), packages/** would name this gate for 2465 files it never opens, and every narrower glob collapses to a malformed packages//.test.ts that covers 0 of 2696. It sits beside check:where-matcher and check:examples-live-imports, which carry the same shape and the same verdict.
  2. Three find doubles arrived with the merge, plus one baselined file that main's fix(security): measure the unscoped existence page cap instead of trusting it #11962 independently cleaned (a ratchet-DOWN). Seated as pre-existing debt / deleted respectively, with provenance recorded in the PR body.
  3. One WRONG double from feat(spec,auth)!: one declared audience posture for self-registration — invite_only|email_domain|open, default invite_only #11767's audience-posture.test.tsfixed rather than seated, one line, one file. The ledger is therefore unchanged at 253 rather than grown.

⚠️The one remaining blocker is unchanged and is still yours: the ledger-size ruling (40 as dispatched vs 253 as measured). The PR stays draft with no auto-merge until that is answered. My recommendation and its reasoning are in the handover comment above; the author reached the same conclusion independently.

⚠️One thing the ruling now also decides. This gate seats candidates behaviourally across the whole packages/** test corpus, and this repo merges to main many times a day, so any merge landing a non-conforming find double reds this PR — that is what all three rounds above were, and the branch has drifted behind main again since. Re-deriving on every red is unbounded at ~27 minutes a round, so the loop was deliberately stopped rather than continued. The merge queue is the right instrument: it builds against current main and measures the corpus at landing time. ⛔ Narrowing discovery scope or making the ledger tolerant of new entries would destroy the property this gate exists to have, and neither was done.

So the practical shape of the decision: it is not only which number the ratchet is pinned to, it is also how long this PR keeps having to re-measure before it can land.


Generated by Claude Code

Merged via the queue into main with commit 57f54a0Aug 25, 2026
36 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-11525-objectql-double-limit-gate branch August 25, 2026 08:27
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/xlskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A gate over limit-blind ObjectQL test doubles — the population is now measured (40 blind, 44 live-bound), and nothing holds the converted ones right

3 participants

@os-steve@yinlianghui@claude