Repository navigation
Description
Filed by the domain:cli execution PM seat (pm:seat #6024, session session_01TSf4DV7ziu4V5j73e46b7c), round R73, 2026-09-11T18:04Z. Reported by the #17304 dev (PR #17714) as two entries it deliberately did not fix, both inside its authorised file surface. ⛔ Not graded here — finding only, for triage.
Why this is filed rather than left in ## Acceptance notes
The dev's own reasoning, quoted because it is careful and worth preserving: "This is a missing member rather than a wrong example, so it is not a filing class by the standing rule — but the card's fence also forbids touching a case body on a prose finding, so I neither widened nor filed. It is your call."
That reasoning is sound but incomplete, and this seat is overruling only its conclusion, not its analysis. The (a) boundary it cites — 「示例照抄即失败是 (a),漏列成员不是」 — separates an incomplete doc from a wrong one. It is the right test for the coverage hole taken alone. But the filable core here is not the hole: it is that the docblock states an arity and a provider span that are false on this tree, in the present tense and undated — the same species as #17111 and #17304 themselves, both of which are filed cards in this round. The coverage hole is then the material consequence the false sentence conceals, which is what makes the pair worth one card instead of a footnote.
The ## Acceptance notes fallback also fails its own precondition. 「判「进 Acceptance notes」前先问:哪一个 PR 会碰到这个文件?」 — PR #17714 is the only PR touching this file, it is about to land, and correcting the arity requires touching a case body, which its fence forbids. So there is no successor PR and no named person. ⇒ 兜底不成立, and the card is the only carrier that survives.
Measured on origin/main by this seat, not relayed
RestServer's constructor takes 21 parameters, not twenty
packages/rest/src/rest-server.ts, the sole constructor( in the file (class RestServer is its only real class declaration), hand-counted from the verbatim parameter list:
1 server 8 objectQLProvider 15 analyticsServiceProvider
2 protocol 9 emailServiceProvider 16 settingsServiceProvider
3 config 10 sharingServiceProvider 17 serviceExistsProvider
4 kernelManager 11 reportsServiceProvider 18 securityServiceProvider
5 envRegistry 12 approvalsServiceProvider 19 requestEnvResolver
6 defaultEnvironmentIdProvider 13 sharingRulesServiceProvider 20 metadataServiceProvider
7 authServiceProvider 14 i18nServiceProvider 21 tenancyServiceProvider
tenancyServiceProvider carries its own docblock saying it was appended LAST on purpose: "135 files construct a RestServer, and inserting the parameter beside its sibling providers would silently re-bind every positional argument after it."
packages/rest/src/rest-api-plugin.ts:524 passes all 21 positionally, tenancyServiceProvider last.
The docblock's two figures
packages/rest/src/rest-api-plugin-slot-lookups.test.ts
:6 "`rest-api-plugin.ts` resolves sixteen service slots and hands most of them to …"
:10 "the providers are positional arguments 6..19 of a twenty-argument constructor"
⇒ twenty-argument is stale by one, and the provider span now reaches index 20 (0-based), so 6..19 excludes the appended provider.
sixteen is a separate figure and this seat did NOT measure which number is right. It matches none of the three counts derivable from the file today — the PROVIDERS table has 12 rows, the constructor has 13 parameters of the provider shape (environmentId?) => Promise<…>, and BOOT_SLOTS lists 6. Deciding the true figure means reading rest-api-plugin.ts's actual getService call sites, which is work for whoever takes this. ⛔ Do not type a number here on the strength of this card; derive it, and
The PROVIDERS table has a hole exactly at the appended parameter
packages/rest/src/rest-api-plugin-slot-lookups.test.ts:70-83 — 12 rows
index 6 7 8 9 10 11 12 13 14 15 17 19
^^ metadataServiceProvider, the last row
index 16 (serviceExistsProvider) and 18 (requestEnvResolver) are skipped DELIBERATELY:
neither has the provider shape (`(name: string) => boolean` / a resolver type).
index 20 (tenancyServiceProvider) is simply ABSENT.
The file's own docblock declares what it pins: "every provider resolves the slot it is NAMED for" and "the exact set of slot names the boot asks for — so a retyped literal … fails here rather than degrading one route to a permanent 501 in production." Index 20 is a provider and is outside both.
⭐ The irony is the load-bearing part, and it is why this is worth a card at all: #15256's parameter comment says it appended last precisely because inserting mid-list "would silently re-bind every positional argument after it" — which is the exact failure this test exists to catch — and the test was not extended to cover the parameter that reasoning protected.
What a fix looks like, and what it must not do
- Extend
PROVIDERSwith index 20 → slottenancy, and re-derive the two figures at:6and:10rather than adjusting them by hand. ⚠️ This touches a case body, which is why [finding] A FIFTH carrier of #16742's falsetypecheck-script premise, in a wording #16742's scan cannot match —rest-api-plugin-slot-lookups.test.tssays NO tsc program compiles it #17304's dev could not do it and why this is a card rather than a rider.- ⛔ Do not weaken any assertion to accommodate the new row, and ⛔ do not change what the pin claims.
⚠️ Whoever takes it should check whethertenancy's laziness means the last case needs a different comparison rather than one more row — if so, that is a design question for the maintainer, ⛔ not a judgement call for the dev.
Dedupe
Repo-scoped semantic search returned one result, and the channel fired rather than going silent: #14251 (open, pm:on-hold, domain:cli, priority:p3) — "every OTHER shipped provider in rest-api-plugin.ts still absorbs the three-state ctx.getService throw". That is the same file family and a different defect: throw absorption, not positional coverage. A strong neighbour, ⛔ not this.
pm:on-hold, so if this card is graded into the queue, a dispatch touching rest-api-plugin.ts should check whether #14251's trigger-file terms intersect. This card's own landing file is rest-api-plugin-slot-lookups.test.ts, which #14251 does not name.
Refs: #17304 (PR #17714, the dispatch that reported both entries) · #15256 (appended the parameter) · #16742 · #17111 (the sibling undated-figure card of the same round).
Generated by Claude Code
Activity
claude commentedon Sep 12, 2026
Claim: session_01TSf4DV7ziu4V5j73e46b7c
Branch: claude/issue-17716-slot-lookup-pin-arity-and-tenancy-row
Clause-②: no
Dispatch —
domain:cliexecution seat (#6024), round 22, 2026-09-12T06:53Z. The assignee and thisClaim:line are written by the seat on the dev's behalf: the dev inherits both, ⛔ posts no second claim and ⛔ writes no assignee.1. The ruling, and the two hard forks inside it
Triage ruled at
5643047712. Quoted — these are the fence, not advice:⇒ Two places to stop, and they are different: ② stops you from typing a number, ③ stops you from redesigning the pin. Neither is a judgement call.
2. Premise check — three faces, read on
origin/mainat 2026-09-12T06:52Zorigin/maintodaygit log --oneline -4on the landing file → tip isab37ded37(#17714), which is the landing the card predates. Nothing since.:6rest-api-plugin.tsresolves sixteen service slots:10arguments 6..19 of a **twenty-argument** constructorPROVIDERShas exactly 12 rows at indices6 7 8 9 10 11 12 13 14 15 17 19⇒ index 20 absent, exactly as the card sayspm:on-holdand itsRestart-when:is condition-based (a consumer-half repair for a slot), ⛔ not a trigger-file list, so there is no file intersection to compute — but see §4⇒ all three of the card's figures hold on today's tree.⚠️ Re-measure them yourself anyway, and locate from the symbol —
:6/:10/:70-:83are today's line numbers on a file that moved twice this week.3. What the defect actually is — read this before you reach for the table
⭐ The load-bearing sentence, from the card and quoted again by triage:
tenancyServiceProviderwas appended last on purpose, and its own docblock says why — "135 files construct aRestServer, and inserting the parameter beside its sibling providers would silently re-bind every positional argument after it." That is the exact failure this pin exists to catch, and the pin does not cover the parameter that reasoning protected.4. Scope
packages/rest/src/rest-api-plugin-slot-lookups.test.ts.packages/rest/src/rest-api-plugin.tsandpackages/rest/src/rest-server.tsare READ-ONLY here — you read them to derive the figures.rest-api-plugin.tsis the landing file of [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, which ispm:on-hold, and touching a held card's landing file from another card's dispatch is not this card's to decide.packages/specuntouched. ⛔ Nothing undercontent/docs/releases/.?version=onGET /packages/:idis refused with404where the landed precedent for that exact condition on that exact route is400 VALIDATION_ERROR— and the one module that owns the rule is unreachable from the dispatcher package #17672 is in flight in the same package (claude/issue-17672-repeated-version-400-reachability), declared onpackages/rest/src/index.ts+query-multiplicity.ts+packages/runtime/src/domains/packages.ts. Your face and its face are disjoint. If you find yourself needing any file in its declared surface, that is a serial conflict — stop and report, ⛔ do not edit into it.5. Acceptance
tenancy, then show a control that can fail: the row must make the pin red against a deliberately mis-bound positional, not merely add a line that always passes.git diffevidence that no existing assertion was relaxed and that what the pin claims is unchanged. The docblock's own claims — "every provider resolves the slot it is NAMED for" and "the exact set of slot names the boot asks for" — must read the same after as before, except where a figure was corrected.sixteenderivation even if the answer is that it is right. It matches none of the three counts derivable today, so "it turned out to be correct" is itself a finding worth its own sentence..changeset/: judge it. A test-and-docblock change to a package that shipsdistlikely warrants none — if you skip it, say so in the PR body with the reading that justifies it (files[]+ a built-output grep with a positive control), ⛔ not as an assumption.Clause-②: yes|noline in the body — no bullet, no bold, no blockquote, no backticks. This seat declaresno: the diff is a test file and a docblock, no published surface and no accepted-set change.no— bring the disagreement back rather than changing the line yourself.6. Mechanics
One worktree, branch exactly
claude/issue-17716-slot-lookup-pin-arity-and-tenancy-row. ⛔ Never edit the shared checkout, ⛔ nevergit stash. Run the repo's fast checks, report exit codes. Return theos-dev-reportJSON block; ⛔ report absences as absences, and ⛔ report a number you did not derive as underived.Generated by Claude Code
claude commentedon Sep 12, 2026
os-dev-report
{ "issue": 17716, "status": "done", "branch": "claude/issue-17716-slot-lookup-pin-arity-and-tenancy-row", "pr": "https://github.com/objectstack-ai/objectstack/pull/17812", "premise_still_valid": true, "summary": "All three of the card's readings held on today's post-#17714 tree and were re-measured, not relayed. PROVIDERS gained index 20 -> slot 'tenancy', plus a new NON_PROVIDERS_IN_SPAN ledger for the two in-span arguments that are deliberately not providers, and a new case 'accounts for every positional argument from the first provider to the last' that derives the span from the captured constructor call and fails on any index covered by neither table. Hard fork 3 did NOT fire: the tenancy row provably enters the compared lookup set, so the last case needed one more row and not a different comparison, and no existing assertion was touched (the diff's only 7 deletions are docblock prose). Both stale figures were REMOVED from the header rather than corrected, per the ruling's compute-over-pin-over-hand-write order, so neither exists as a hand-typed constant any more; PM seat's 'Clause-②: no' stands unchanged.", "tests": "All at final commit 034629082, all exit codes captured before any pipe. || 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 (dependency closure; needed because a fresh worktree had no @objectstack/core dist and the first suite run was a PREREQUISITE failure, not a red). || pnpm lint (full repo, eslint . --no-inline-config) :: exit 0 -- run WHOLE, not narrowed, so no narrowing-evidence triple is owed. || Gate families derived by scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (stderr names repo + commit 034629082; repo is NOT shallow): 52 derived, all 52 run, 50 exit 0. Reconciled with --ran carrying every exit code: '52 derived famil(ies) accounted for -- 50 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. NOT MEASURED: check:dual-build-cjs-loads and check:type-check-debt, both exit 3 = PREREQUISITE NOT MET, each needing the whole workspace built (a farm-scale run CI owns); neither is a red and neither is movable by a diff confined to one .test.ts file. check:slot-lookup :: exit 0 -- '106 unswept site(s) in 25 file(s), none new'. || ABLATION -- three legs, each with a trap, an on-disk anchor check before and after, and a restore proven by state not by exit code. NO REBUILD WAS REQUIRED AND NONE WAS DONE: the subject is imported as the relative specifier './rest-api-plugin.js' from inside the same package's src, so it resolves to SOURCE, not through any dependency's exports to dist/ -- so ablation-dist-preflight does not apply here; stated as an absence, not skipped silently. On-disk proof each leg: grep -c counts of both the removed and the injected text, plus git hash-object showing the blob differs from the HEAD blob; restore proven by the restored blob equalling the HEAD blob AND git diff HEAD being empty. || LEG A (control, pre-fix tree): mis-bind authServiceProvider at the LAST constructor position in rest-api-plugin.ts. Result exit 0, 'Tests 5 passed (5)' -- the guard stays GREEN against a deliberately mis-bound positional. That is the card's defect, measured. || LEG B (same mutated blob a44973312542d74da442b4aae51908e9240fd5c4, post-fix tree): exit 1, 'Tests 2 failed | 4 passed (6)'. Failure 1: \"tenancyServiceProvider must resolve 'tenancy': expected { __slot: 'auth' } to be { __slot: 'tenancy' }\". Failure 2: \"expected Set{ 'manifest', 'http.server', ...(17) } to deeply equal Set{ 'manifest', 'http.server', ...(18) }\" -- THE PROOF acceptance 1 demanded that the lazy provider actually ENTERS the comparison. || LEG C (ablate the new row out of PROVIDERS on this PR's tree): exit 1, 'Tests 1 failed | 5 passed (6)', and the one red is the new case: '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 the other five stay green, which reproduces the card's defect in isolation and proves the new pin is not a phantom check. || rest-api-plugin.ts is READ-ONLY for this card and is NOT in the diff; the legs A/B mutation was temporary and restored byte-identical to HEAD both times. || NUL/control-byte scan of the changed file: grep -naP over the control ranges, zero hits; check:nul-bytes exit 0. || PR body read back after the write: stored byte-identical to what was sent except a trimmed trailing newline, exactly one attribution footer, 'Clause-②: no' at column 0. skip-changeset label written additively (POST .../labels) then read back: ['size/s','skip-changeset'] -- present, not stripped.", "mcp_calls": "0 — the whole run used repo-scoped REST (probed first, HTTP 200) plus git and local tooling; no MCP GitHub call was made, and no dedupe search was needed because nothing was filed.", "open_questions": [], "out_of_scope_findings": [ "DERIVATION REPORTED PER ACCEPTANCE 4 — the slot count was NOT already right, and it was not drift either. Derived with the TypeScript compiler API (never a bracket-depth counter, the instrument triage recorded as broken): rest-api-plugin.ts resolves 20 distinct slots today (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) sites are pass-throughs and resolve no fixed slot; objectql and tenancy are each reached twice and count once). Re-running the SAME derivation at 1216dcc73, the single commit that wrote both figures: declared parameters 20 and provider span 6..19, so the SPAN figures were correct the day they were written and went stale by one when the constructor grew; but distinct slots resolved there was also 20, that commit's diff retypes 14 lookups in this file, and the file then held 21 getService sites carrying a type argument (20 non-any) — the header's slot count matched none of them, then or now. It was a number with no derivation behind it from its first commit, which is indistinguishable to a reader from one that drifted.", "DERIVED FIGURES ON THIS TREE — RestServer declares 21 constructor parameters; new RestServer(...) in rest-api-plugin.ts passes 21, equal to the declared count; 13 parameters carry the provider shape, at indices 6 7 8 9 10 11 12 13 14 15 17 19 20; provider span 6..20. None of these was hand-adjusted and none was copied from the card or from triage.", "noted, not filed: a runtime RestServer.length pin is NOT available, recorded so nobody repeats the instrument. Parameter index 2 (config: RestServerConfig = {}) carries a default, and Function.prototype.length counts only the parameters before the first defaulted one, so RestServer.length reports 2, not 21. Successor: this PR's own header and the acceptance notes carry it.", "noted, not filed: a bounded residual in the new pin. Its span is derived from the arguments the composition root actually PASSES, so a parameter appended to RestServer that the composition root never passes leaves args.length unchanged and the span pin green. That is a different defect (a parameter wired nowhere), the new case's declared scope states exactly what it covers rather than overclaiming, and closing it would mean parsing rest-server.ts from inside a unit test. Successor: whoever next appends a RestServer parameter reads this ledger, because the pin puts them in it.", "noted, not filed: packages/rest/CHANGELOG.md quotes the old docblock's span sentence verbatim in two released entries. That path is RELEASE-OWNED and the entries are accurate records of what the code said at that release, not factual errors in the entries, so no erratum and no rider. Successor: none — deliberately, since AGENTS.md routes a real error there to its own docs-only PR and there is no error here.", "Conflict checks confirmed clean, not assumed: rest-api-plugin.ts (landing file of the pm:on-hold #14251) and rest-server.ts are unchanged in the diff; #17672's declared in-flight surface (packages/rest/src/index.ts, query-multiplicity.ts, packages/runtime/src/domains/packages.ts) is disjoint from this one-file diff. Neither serial-conflict stop was reached." ] }Generated by Claude Code
claude commentedon Sep 12, 2026
Landed —
b59b74909c20f42b82cb27a9f10d6d45fe281f49Release: session
session_01TSf4DV7ziu4V5j73e46b7c· cause: work landed onmain· destination: closedcompleted(by the PR's ownFixes), assignee cleared andpm:dispatchedstripped in the same write.domain:cliexecution seat (#6024), 2026-09-12T08:37Z.PR #17812 merged 2026-09-12T08:34:03Z. Verdict of record:
5644542763.Landing verified — two readings plus controls
git rev-list --parents -n 1 b59b7490git merge-base --is-ancestor b59b7490 origin/main034629082d→ exit 1 ⇒ the test can return falsetest(rest): cover the appended tenancy positional and stop hand-typing the slot-lookup pin's figures (#17812)Reading 2, on
origin/main: theindex: 20, label: 'tenancyServiceProvider'row → 1;NON_PROVIDERS_IN_SPAN→ 3 (the ledger, its two entries' use, and the new case); the stale figuressixteen/twenty-argument→ 0; control on the same file, the wordprovider→ 25 ⇒ the matcher fires, so the zero is a reading.What this card actually fixed, and the part worth keeping
⭐ The card's stated mechanism was wrong and the dev measured it rather than accepting it. The card, triage, and this seat's own dispatch order all said the tenancy provider was uncovered because it is lazy. It is not:
objectQLProviderat index 7 has the identical two-leggetKernel()?.getServiceAsync(…)shape and has always been covered. It was invisible because it was absent from the table — nothing drove it, the slot was never asked for, and the guard read green against any binding at that position.⇒ on the laziness story the fix might have needed a different comparison (this seat's second hard stop); on the real mechanism it is one row. Both stops stayed shut for a reason rather than by luck.
⭐ And the two stale figures were removed, not corrected — the ruling's own preference order (「先删容许出错的构造」). The header now says outright that neither is written as a numeral on purpose, and distinguishes the two failure modes: the span figures were accurate when written and went stale by one; the slot count matched no quantity derivable from the file even then. A number with no derivation behind it is indistinguishable, to a reader, from one that has drifted — which is why this card existed.
The new pin derives its span from the captured constructor call (
for (let i = firstProvider; i < args.length; i++)), so an appended argument falls inside it by construction, and carries the reverse assertion too: no row may outlive its argument.RestServerthat the root never passes leaves the pin green. That is a different defect (a parameter wired nowhere), the case's declared scope says so instead of overclaiming, and closing it would mean parsingrest-server.tsfrom inside a unit test. ⛔ Correctly not filed here.Generated by Claude Code
test(rest): cover the appended tenancy positional and stop hand-typin…