Skip to content

test(rest): the objectQLProvider consumer census, made mechanical — and its third consumer, driven - #18556

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-18546-engine-slot-consumer-census
Sep 17, 2026
Merged

os-support-ai merged 1 commit into
mainfrom
claude/issue-18546-engine-slot-consumer-census

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Part of #18546 — deliverable 1 is delivered and pinned here; deliverable 2's disposition is the seat's, for the reason in the second section. #18546 remains open on purpose.

Deliverable 1 — the consumer census, and it disagrees with the record

The engine slot has THREE consumers, not two. Both prior enumerations counted two, in the same direction, and the third was already on the tree each of them was taken against.

enumeration said measured here at 79a046f8c
#14251 phase-1 census table (comment 5542086636 accepts it) 2 — computeExecCtx, GET /meta/object/:name/state/:field 3
the #15405 repair's own docblock (landed as PR #17077) — "objectQLProvider has two consumers in rest-server.ts" 2 3

The third is POST /batch (the cross-object transactional batch door), which reads the field directly. It was present at the census sha: git show a56baa2bdf:packages/rest/src/rest-server.ts | grep -n 'this.objectQLProvider' prints 2362, 2363, 7452, 7453 and 12251. It is not hidden either — the provider's own docblock in rest-api-plugin.ts names it in as many words, "the transaction probe behind the batch routes". Neither enumeration could fail, because both were prose.

The pathspec bound of this enumeration, not only the count

Bound: every read of RestServer's private field objectQLProvider, in packages/rest/src/rest-server.ts. That is the whole reachable set, and two facts make it so — both now asserted in the new pin rather than argued here: the shipped closure is handed to exactly one new RestServer(...) construction site in packages/rest/src/rest-api-plugin.ts, and the field is read only as this.objectQLProvider (no bracket access, no as any read) in any non-test source.

Control that can fail, carried per the dispatch's instruction. The scanner is run against a symbol known to be present — it must find sites, and it does — and against one known to be absent, which must read zero. So a future zero means "gone" rather than "the regex stopped matching". That is the failure mode that produced the two-consumer sentence in the first place: a consumer that cannot be parsed and a consumer that does not exist look identical, and the correct actions are opposite.

What this search cannot see, stated rather than rounded away:

  • A consumer reaching the objectql service through a different producer. Two exist and neither is this slot: the deliberately-absorbing KERNEL branch at the same computeExecCtx seam (kernel.getServiceAsync('objectql') via seamOrUndefined, the RESIDUE wiredEngineOrLoud documents and [finding] every OTHER shipped provider in rest-api-plugin.ts still absorbs the three-state ctx.getService throw — the #13904 shape, waiting one seam over #14251 leaves in place), and roughly ninety direct ctx.getService('objectql') sites in other packages, each its own seam.
  • A consumer landing in a sibling branch not yet on origin/main at 79a046f8c.
  • A runtime-only dynamic read by an embedder outside this repo — TypeScript private is compile-time only. No such spelling exists in this repo's non-test sources, which is what the pin asserts; it cannot speak for code that is not here.

The verdict on the decidable test — driven, not inferred

The tightened test from comment 5542086636: a slot is ripe only when NO consumer converts a rejection into exactly the value its undefined path produces.

consumer rejection (wired and failed to build) resolved undefined (absence) re-collapses?
computeExecCtx, via wiredEngineOrLoud 503 AuthzStoreUnavailableError quiet undefined no
GET /meta/object/:name/state/:field, via wiredEngineOrLoud since #17077 503 404 NOT_FOUND no
POST /batch, direct await into the handler's own catch 500 INTERNAL_ERROR 501 NOT_IMPLEMENTED no

⇒ No consumer re-collapses. The slot is ripe. The card's STOP branch is therefore not triggered: there is no re-collapsing consumer to file.

⚠️ The third consumer distinguishes incidentally, through handleRouteError, not through the seam helper its two siblings use — and it answers 500 where they answer 503 for the identical fact. ⛔ That is recorded, not ruled, and ⛔ nothing here moves it: a public door changing its answer is the per-consumer wire ruling #14251 reserves. It is handed back as a finding.

Deliverable 2 — ⛔ the premise is falsified: there is no provider half left to repair

The card says deliverable 2 is "the provider-half repair in rest-api-plugin.ts, in the shape #13904 landed for objectQLProvider". Read at origin/main 79a046f8c, objectQLProvider already is that shape — #13904 landed it on this very closure: the async leg absorbs only isServiceNotRegisteredError and re-raises everything else, the sync leg is the documented KernelBase leg the census explicitly fences off from any phase-2 roster, and its pin rest-api-plugin-objectql-provider-three-state.test.ts is on the tree.

The engine slot is the census's worked example, not one of the twelve unrepaired slots (those are auth, security, email, sharing, reports, approvals, sharingRules, analytics, i18n, settings, metadata, default-project). Its consumer half landed first (#13476), its provider half second (#13904), and its second consumer third (#15405 via PR #17077). Both halves are landed. So the only half of deliverable 2 with a target left is "plus the pin" — which is this PR.

The two stop-gates — reported, not decided

1. Clause-②: no

Measured from this diff (79a046f8c...HEAD), 2 files changed, both *.test.ts, 384 insertions / 3 deletions:

reading measurement
new exports 0 — git diff … | grep -cE '^\+\s*export ' = 0; the new file exports nothing
ERROR_CODE_LEDGER / StandardErrorCode 0 occurrences anywhere in the diff
accept-set changes 0 — no schema, allowlist, refuseUnknownQueryParams set or .strict() surface is touched; no non-test file is touched at all

check-clause2-carriers --pair exit code is recorded in the os-dev-report comment on #18546, run against this PR number once it existed.

⇒ Not a clause-② PR, so it does not park outside the merge queue the way #18428 / #18429 do.

2. Runtime permission/security behaviour change: NO

The diff touches no runtime file — two test files, nothing else — so there is no before/after to have. The dispatch asked for the wire consequence to be DRIVEN rather than inferred, so it was driven anyway, on a real RestServer over a real ObjectKernel, and the answer is that a registered-but-FAILING engine is already told apart from an absent one at all three doors (the table above). What changed it was #13904 plus #17077, both landed; this PR moves none of it.

For completeness, the third door's four facts as measured on POST /api/v1/batch this run — no provider wired 501, provider resolving undefined 501, provider rejecting 500, provider throwing synchronously 500 — beside a served 200 and a declared 400, so no fault status above comes from an instrument that can only report faults.

Verification

  • Ablation, two legs, each proved on disk before the run and restored from HEAD after it (blob hash equal to HEAD:packages/rest/src/rest-server.ts and git diff HEAD empty on both restore legs; the mutation script carries a trap … EXIT INT TERM):
    • a fourth consumer injected → the census pin goes red (exit 1, naming "THREE consumers, not two"); restored → green.
    • the third consumer re-collapsed to the retired .catch(() = > undefined) spelling → the behaviour pin goes red (exit 1, naming the decidable-test assertion); restored → green.
    • Baseline and both restore legs: 11 passed (11).
    • ⭐ The first ablation attempt asserted the wrong injected-text count and the script refused to run, rather than measuring nothing and reading green. The expectation was corrected and the leg re-run; recording this because the guard is the point.
  • pnpm --filter @objectstack/rest test — 193 files, 3229 passed, 1 skipped.
  • pnpm --filter @objectstack/rest typecheck — clean. pnpm --filter @objectstack/rest check:test-typecheck — 0 file(s) / 0 error(s) / 0 pinned signature(s).
  • pnpm --filter '@objectstack/rest^...' build — green (dependency closure, so nothing above read a stale dist).
  • Gate families derived in this worktree, cut fresh from origin/main — ⛔ not reused from the dispatch, whose checkout the tool reported stale: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 53 commands, all 53 run, reconciled with --ran: "53 derived famil(ies) accounted for — 51 run, 2 NOT-MEASURED".
  • pnpm lint repo-wide (the lane blind spot dispatch-gates does not name) — exit 0, whole tree, no narrowing claimed.
  • NOT MEASURED, both PREREQUISITE NOT MET (exit 3, a gate refusing its own prerequisite — ⛔ not a finding and ⛔ not a pass): check:dual-build-cjs-loads and check:type-check-debt, each needing every package's dist/. CI builds first and runs both.
  • ⚠️ One recorded exit 1 is pre-existing and not this diff's: check:cross-package-test-inputs reds on packages/cli/test/init-created-files-summary.e2e.test.ts walking packages/spec/dist/. Controlled: a detached worktree at 79a046f8c with this diff absent exits 0 with no packages/spec/dist/ on disk and exits 1 with the identical finding once that directory exists. ⇒ the verdict follows local build state, not the change set. Filed back as a finding.

skip-changeset measured rather than assumed: packages/rest ships files: ["dist", "README.md", "CHANGELOG.md"]; after pnpm --filter @objectstack/rest build, every symbol unique to this diff reads 0 hits across all three, while the positive control wiredEngineOrLoud is found in dist/index.cjs, dist/index.js and CHANGELOG.md. ⇒ nothing published moves.

Acceptance notes

Observed while measuring, ⛔ not filed and ⛔ not fixed here:

  • enumerateAbsorbSites() in execctx-authz-input-seam-reachability.test.ts is the same instrument as this PR's §1 scanner but scoped to the body of computeExecCtx alone. The two could share one helper; they do not, and this PR does not merge them — a whole-file enumerator is a wider verification surface than this card's. Successor: whichever card next widens that seam census.
  • [finding] every OTHER shipped provider in rest-api-plugin.ts still absorbs the three-state ctx.getService throw — the #13904 shape, waiting one seam over #14251's body still promises "one bounded edit per slot", which its own phase-1 acceptance refuted ("ruling + consumer edit + provider edit + pin"). Triage flagged the drift in comment 5706358804 and declined to correct another card's body; this PR is not that card either. Successor: the card's next implementer, who reads the body top-down.

Generated by Claude Code

… drive its third consumer

The engine slot's consumer set has been counted as two, twice, by two seats:
#14251's phase-1 census table, and the docblock of the #15405 repair. There
are three -- `POST /batch` reads `this.objectQLProvider` directly, and it was
already on the tree both readings were taken against. Neither enumeration
could fail, because both were prose.

This adds the enumeration as a test: the call sites are read out of
`rest-server.ts` source, the count is pinned at three, and the scanner carries
a positive and a negative control so a future zero means "gone" rather than
"stopped matching". The third consumer is then driven for the first time --
absent, unwired, rejecting and synchronously-throwing engines, plus a served
200 and a declared 400 so the instrument can say something other than a fault.

Measured: a rejection reaches 500 INTERNAL_ERROR where the `undefined` path
reaches 501 NOT_IMPLEMENTED, so this consumer does NOT re-collapse. The 500 is
recorded, not ruled -- the slot's two other consumers answer 503 for the same
fact, and whether this door joins them is a per-consumer wire ruling.

Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Co-authored-by: Claude <noreply@anthropic.com>
@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 79a046f8cdf085d95200826ee9bb2fa6584bc3d5 → packageMentionDocs.

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