Skip to content

test(rest): cover the appended tenancy positional and stop hand-typing the slot-lookup pin's figures - #17812

Merged
claude[bot] merged 1 commit into
mainfrom
claude/issue-17716-slot-lookup-pin-arity-and-tenancy-row
Sep 12, 2026
Merged

claude[bot] merged 1 commit into
mainfrom
claude/issue-17716-slot-lookup-pin-arity-and-tenancy-row

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #17716

Clause-②: no

What was wrong

packages/rest/src/rest-api-plugin-slot-lookups.test.ts pins the REST composition root's
positional wiring. Its PROVIDERS table stopped one argument short of the constructor,
exactly at tenancyServiceProvider — the parameter #15256 appended LAST on purpose,
because inserting one mid-list "would silently re-bind every positional argument after
it"
. That is the exact failure this pin exists to catch, and it did not cover the
parameter that reasoning protected.

The header also stated two figures in the present tense with no date: a spelled-out slot
count, and an "arguments A..B of an N-argument constructor" span.

The hole, measured rather than argued

Control: bind authServiceProvider at the LAST constructor position in
rest-api-plugin.ts, where tenancyServiceProvider belongs. Nothing else moves, so the
only argument a row for that index could observe is the one that is wrong. Same mutated
blob in both legs (a44973312542d74da442b4aae51908e9240fd5c4); the only variable is the
test file.

leg tree result
A PROVIDERS without the row (pre-fix) Tests 5 passed (5) — green against a deliberately mis-bound positional
B PROVIDERS with the row (this PR) Tests 2 failed | 4 passed (6)

Leg B's two failures:

AssertionError: tenancyServiceProvider must resolve 'tenancy':
  expected { __slot: 'auth' } to be { __slot: 'tenancy' }
AssertionError: expected Set{ 'manifest', 'http.server', …(17) }
  to deeply equal Set{ 'manifest', 'http.server', …(18) }

The second one is the answer to the laziness question the card and the triage ruling both
flagged. The row does enter the compared lookup set, so the last case needs one more row
and not a different comparison — the hard fork does not fire.
Laziness was never why the
provider was invisible: objectQLProvider at index 7 has the identical kernel-first /
sync-fallback shape and has always been covered. It was invisible because it was absent
from the table, so nothing ever drove it and the slot was never asked for.

packages/rest/src/rest-api-plugin.ts is read-only for this card. That mutation was
temporary, trap-guarded, and verified restored byte-identical to HEAD
(restored blob == HEAD blob, git diff HEAD empty) after each leg. It is not in this
diff.

The new pin is itself driven

Leg C ablates the new row out of PROVIDERS on this PR's tree and runs again:

× accounts for every positional argument from the first provider to the last
Tests  1 failed | 5 passed (6)

AssertionError: a positional argument in the provider span is covered by neither
PROVIDERS nor NON_PROVIDERS_IN_SPAN — add a row (provider) or an entry (not a
provider): expected [ 20 ] to deeply equal []

Exactly one case reds, and it is the new one — the other five stay green, which is the
card's defect reproduced in isolation. That case is the machine that would have caught
#15256's append.

Both figures derived, neither re-typed

Derived with the TypeScript compiler API over rest-server.ts and rest-api-plugin.ts.
⛔ Not a bracket-depth counter: an arrow token and a generic type argument close a naive depth counter
early, which is the instrument the triage ruling recorded as broken.

figure derived on this tree the header said
RestServer declared parameters 21 "N-argument" was accurate at B4, stale by one since
provider-shaped arrow-to-Promise parameters 13, at indices 6 7 8 9 10 11 12 13 14 15 17 19 20 —
provider span 6..20 6..19, so the appended provider fell outside it
distinct slots rest-api-plugin.ts resolves 20 a count matching none of them
arguments new RestServer(...) passes 21 — equal to the declared count —

The 20 distinct slots: analytics approvals auth default-project email env-registry http.server i18n kernel-manager kernel-resolver manifest metadata objectql protocol reports security settings sharing sharingRules tenancy. Three ctx.getService(name) call sites
are pass-throughs (the kernel-resolver bridge and the presence probe) and resolve no fixed
slot; objectql and tenancy are each reached twice (a kernel.getServiceAsync leg and a
ctx.getService leg) and count once.

The slot count was never right — it was not a figure that drifted

Both figures were written by one commit, 1216dcc73 (#4251 B4, #5953). Re-running the same
derivation against that commit's tree:

  • declared parameters 20, provider span 6..19 ⇒ the span figures were correct the
    day they were written
    and went stale when the constructor grew.
  • distinct slots resolved by rest-api-plugin.ts: 20, not the count the header claimed.
    That commit's diff retypes 14 lookups in this file, and the file then held 21
    getService call sites carrying a type argument (20 non-any). The header's figure
    matches none of those, then or now.

So it was not "already right", and it was not drift either: it was a number with no
derivation behind it from the first commit. That is worth its own sentence, because the two
failure modes are indistinguishable to a reader — which is precisely the argument for not
writing either figure as a numeral again.

Preferred outcome: not a corrected constant

Per the ruling's order (compute > pin > hand-write with a date), both figures are removed
from the prose rather than corrected:

  • The span is now pinned by accounts for every positional argument from the first provider to the last, which derives the span from the captured call (args.length) and requires
    every index in it to be covered by PROVIDERS or excused in the new
    NON_PROVIDERS_IN_SPAN ledger. An argument appended to RestServer lands inside the span
    by construction and forces a decision.
  • The slot count is now the set BOOT_SLOTS ∪ {default-project} ∪ PROVIDERS, which the
    existing asks for exactly the slots it declares case already compares against the real
    asked list. Its size is the derived 20.

A recorded failed instrument, so nobody repeats it: a runtime RestServer.length pin is
not available. config: RestServerConfig = {} is parameter index 2, and
Function.prototype.length counts only the parameters before the first defaulted one — it
reports 2, not 21.

Nothing was weakened

git diff for this PR removes exactly seven lines, all of them docblock prose carrying the
two stale figures. No expect, no assertion argument, no existing case body and no table
row is touched; every other change is an addition. The two claims the ruling named read
byte-identical after:

  • "every provider resolves the slot it is NAMED for"
  • "the exact set of slot names the boot asks for"

Verification

what result
pnpm --filter @objectstack/rest test exit 0 — Test Files 190 passed (190), Tests 3182 passed | 1 skipped (3183)
pnpm --filter @objectstack/rest typecheck exit 0 — check:test-typecheck: OK … 0 file(s) / 0 error(s) / 0 pinned signature(s)
pnpm --filter '@objectstack/rest^...' build exit 0
pnpm lint (full repo, eslint . --no-inline-config) exit 0 — run whole, not narrowed
derived gate families (scripts/pm/dispatch-gates.mjs --commands) 52 derived, 50 exit 0, 2 NOT MEASURED
pnpm check:slot-lookup exit 0 — 106 unswept site(s) in 25 file(s), none new

The two NOT MEASURED are check:dual-build-cjs-loads and check:type-check-debt, both
exit 3 = PREREQUISITE NOT MET: each needs the whole workspace built, which is a
farm-scale run CI owns. Neither is a red, and neither can be moved by a change confined to
one .test.ts file. Reconciled with --ran: "52 derived famil(ies) accounted for — 50
run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)"
.

All numbers above were taken at 034629082, the final commit.

No changeset — measured, not assumed

@objectstack/rest is published ("private" absent) and ships
files[]: ["dist", "README.md", "CHANGELOG.md"]. The one changed path is
packages/rest/src/rest-api-plugin-slot-lookups.test.ts; src/ is not in files[], and
CI's No compiled test files in any dist step holds the other direction.

Grepped over all three files[] paths after pnpm --filter @objectstack/rest build:

probe symbol hits
negative NON_PROVIDERS_IN_SPAN 0
negative accounts for every positional argument 0
negative rest-api-plugin-slot-lookups 0
positive control createRestApiPlugin 6
positive control tenancyServiceProvider 4
positive control RestServer 6

The positive controls prove the grep reaches the shipped bytes; the negatives are therefore
absences, not a broken probe. Nothing published moves ⇒ skip-changeset.

Acceptance notes


Generated by Claude Code

…g the slot-lookup pin's figures

The slot-lookup pin's `PROVIDERS` table stopped one argument short of the
constructor, exactly at `tenancyServiceProvider` — the parameter appended last
because inserting one mid-list "would silently re-bind every positional
argument after it", which is the failure this pin exists to catch. Absent from
the table, nothing drove it, its slot never entered the compared lookup set,
and the guard read green against any binding at that position.

Add the row, and add a ledger for the two in-span arguments that are
deliberately not providers, so every index from the first provider to the last
argument the composition root passes is accounted for. The header's two
hand-typed figures are removed rather than corrected: a span figure and a slot
count stated in the present tense with no date are what produced this defect.

Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
Co-authored-by: Claude <noreply@anthropic.com>
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 12, 2026
@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 26e0d81f3200122af15df0db94f18ed55ed418fd → packageMentionDocs.

@github-actions github-actions Bot added the tests label Sep 12, 2026
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — domain:cli execution seat (#6024), 2026-09-12T07:45Z, against head 034629082d7c2b8ae9e50ebb1d6d58035107266e. Readings below are mine, taken on this head.

Gate state

GET /commits/034629082d/check-runs — 38 checks: 30 success, 7 skipped, 0 red, 1 still running (Lint & Repo Gates). ⭐ All six Test Core shards have completed green — which is the gate that judges a test-file change, so the substantive measurement is in.

⭐ The dev corrected the card's stated mechanism, and it was the mechanism this seat relayed into the dispatch

The card said, and triage repeated, and this seat's dispatch order repeated again, that the tenancy provider was invisible because it is lazy. The report says that is false. That is the one claim here worth re-measuring independently, because it is the claim this seat passed along without checking:

packages/rest/src/rest-api-plugin.ts:329   tenancyServiceProvider = async (_environmentId?: string) … {
                                             const kernel = typeof ctx.getKernel === 'function' ? ctx.getKernel() : undefined;
                                             if (kernel && typeof kernel.getServiceAsync === 'function') { try {
packages/rest/src/rest-api-plugin.ts:389   objectQLProvider       = async (_environmentId?: string) … {
                                             const kernel = typeof ctx.getKernel === 'function' ? ctx.getKernel() : undefined;
                                             if (kernel && typeof kernel.getServiceAsync === 'function') { try {

The two openings are structurally identical, and objectQLProvider at index 7 has always been in PROVIDERS. ⇒ laziness was never why the tenancy provider was invisible. It was invisible because it was absent from the table: nothing drove it, so the slot was never asked for, so the set the last case compares could not contain it, so the guard read green against any binding at that position.

⭐ The distinction is not rhetorical. On the laziness story the fix might have had to change the comparison — which is exactly the second hard stop this seat wrote into the dispatch. On the real mechanism it is one row. The dev did not accept the relayed mechanism; it measured, and both stops stayed shut for a reason rather than by luck.

The figures were REMOVED, not corrected — which is what the ruling asked for first

reading on this head result
sixteen 0
twenty-argument 0
6..19 0
control on the same matcher (provider) 25 ⇒ it fires, so the zeros are readings

The header now says outright that neither figure is written as a numeral on purpose, and it distinguishes the two failure modes: the span figures were accurate the day B4 wrote them and went stale by one; the slot count matched no quantity derivable from the file even then. ⭐ 「先删容许出错的构造,再让正确形态成唯一拼写」 — the erroneous construction here was a hand-typed constant in prose, and it is gone rather than re-typed correctly. The only spelling of either quantity is now one the suite can fail on.

The new pin, and why it is not a phantom

NON_PROVIDERS_IN_SPAN gives the two in-span arguments that are deliberately not providers (index 16, a (name: string) => boolean presence probe; index 18, a resolver instance) an explicit home with a stated reason. The new case then derives the span from the captured constructor call — for (let i = firstProvider; i < args.length; i++) — so an argument appended to RestServer falls inside the span by construction and must be classified. And it carries the reverse assertion too: no row may point past the end of the argument list, so a removed parameter takes its row with it instead of leaving one that silently asserts nothing.

⇒ the move #15256 made because inserting mid-list "would silently re-bind every positional argument after it" now forces a decision in this file instead of landing uncovered.

The ablation — three legs, and leg A is the card's defect measured directly

  • Leg A, on the PRE-FIX tree: mis-bind authServiceProvider at the last constructor position ⇒ Tests 5 passed (5), exit 0. ⭐ The guard stays green against a deliberately mis-bound positional. That is not a control for the fix — it is the card's defect, reproduced.
  • Leg B, same mutated blob on the POST-FIX tree ⇒ exit 1, two failures, the second being expected Set{…(17)} to deeply equal Set{…(18)} — the proof acceptance 1 demanded that the provider actually enters the compared set, rather than being listed beside it.
  • Leg C, ablate the new row back out ⇒ exactly one red, and it is the new case naming index 20; the other five stay green. A red in isolation, ⛔ not a blanket one.

Each leg proved on disk (grep counts of removed and injected text, git hash-object against the HEAD blob) and restored by state — blob equality plus an empty git diff HEAD — ⛔ not by an exit code. And the absence of a rebuild is stated rather than skipped: the subject is imported as a relative source specifier, so no dist/ sits between the mutation and the test.

The sixteen derivation, reported per acceptance 4 — and the answer is the interesting one

It was not already right, and it was not drift. Derived with the TypeScript compiler API — ⛔ explicitly not the bracket-depth counter triage recorded as broken on its own attempt — rest-api-plugin.ts resolves 20 distinct slots today. Re-running the same derivation at 1216dcc73, the single commit that wrote both figures: the span figures were correct that day, while the slot count matched no quantity derivable from the file even then. ⇒ it was a number with no derivation behind it from its first commit, and — the line worth keeping — that is indistinguishable to a reader from one that has since drifted. This card exists because nobody could tell those two apart.

⚠️ Also recorded so nobody repeats the instrument: a runtime RestServer.length pin is not available. Parameter index 2 carries a default, and Function.prototype.length counts only up to the first defaulted parameter, so it reports 2, not 21.

Scope and fences

One file. rest-api-plugin.ts (landing file of the pm:on-hold #14251) and rest-server.ts are not in the diff — the legs A/B mutation was temporary and restored byte-identical. #17672's declared in-flight surface is disjoint, confirmed rather than assumed. The 7 deletions are entirely docblock prose; no assertion was weakened and nothing the pin claims was changed. skip-changeset applied additively and read back present.

⚠️ One bounded residual the report declares rather than hides: the new span is derived from what the composition root passes, so a parameter appended to RestServer that the root never passes leaves args.length unchanged and the pin green. That is a different defect (a parameter wired nowhere), the case's declared scope says exactly what it covers instead of overclaiming, and closing it would mean parsing rest-server.ts from inside a unit test. ⛔ Correctly not filed and ⛔ not widened into here.

Proceeding to ready + merge queue.


Generated by Claude Code

@claude
claude Bot marked this pull request as ready for review September 12, 2026 07:46
@claude
claude Bot enabled auto-merge September 12, 2026 07:46
@claude
claude Bot added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit b59b749 Sep 12, 2026
40 checks passed
@claude
claude Bot deleted the claude/issue-17716-slot-lookup-pin-arity-and-tenancy-row branch September 12, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

1 participant