Skip to content

fix(client): bind the three verifiable methods of the unannotated return-type erasure population (#11925) - #12062

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-11925-client-unannotated-return-erasure
Aug 25, 2026
Merged

fix(client): bind the three verifiable methods of the unannotated return-type erasure population (#11925)#12062
os-zhuang merged 2 commits into
mainfrom
claude/issue-11925-client-unannotated-return-erasure

Conversation

@claude

@claudeclaudeBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Part of #11925

Binds the verifiable part of the fifth erasure spelling and records — on the methods
themselves — exactly why the rest is not bindable yet. Part of, not Fixes: 3 of the 39
methods are bound here, and the other 36 are blocked on contract decisions that are now
filed as their own cards. Merging this should not close the card.

The population, re-derived at origin/main (c804f0ca5)

⛔ The card's 38 is stale, and not only because #11929 landed in this file. Its own
reproducer uses a single-line regex, and one type argument spans several lines:

$ grep -cE '(unwrapResponse|_unwrap)<[^>]*\bany\b' packages/client/src/index.ts
41 # ← lines, not sites

A brace/angle-balanced parse finds 42unwrap<…any…> sites; the one the single-line
grep cannot see is cloud projects.get at L1601, whose { project: any; database?: any; … }
argument is written across six lines. Splitting the 42 by whether the enclosing declaration
carries a ): Promise< annotation:

unwrap<...> call sites (any type arg) : 166
...whose type arg contains `any` : 42
...of those, WITH `): Promise<` ann : 3
...of those, with NO annotation : 39
...enclosing decl not found : 0

39, not 38 — 39 sites in 39 distinct methods (verified distinct; zero attribution
mismatches against a second pass that also looks for class-method enclosures). The card's
family table is right everywhere except cloud projects.*, which is 9, not 8: it omits
get, the multi-line one.

⭐ The asymmetry sanity-check holds exactly as the card describes it:
ScopedProjectClient.packages.list (L5661) carries both an annotation and a type argument,
so #8140 bound it to { packages: InstalledPackage[]; total: number }; its immediate
neighbour packages.get (L5665) carries neither. Same object literal, opposite treatment.
That neighbour is bound here.

A ground-truth cross-check, and a sixth spelling

The spelling census was checked against the TypeScript checker itself — walking the public
members of both classes and resolving each awaited return type:

reachable methods walked : 273
…whose awaited return contains `any`: 89 (4 annotated, 85 unannotated)
spelling ⊆ tsc-truth? missing from tsc: []
tsc-truth NOT in spelling population : 46

The 39 are a strict subset of the checker's 85. The residual 46 are a SIXTH spelling —
return res.json(), whose published type is any with neither Promise nor
unwrapResponse in the text. Out of scope here, filed as #12037.

What is bound (3)

The bar was not "a plausible spec type exists" but "the route this method calls
demonstrably sends this shape". Three cleared it:

methodbeforeafter
client.packages.list{ packages: any[]; total: number }{ packages: InstalledPackage[]; total: number }
client.packages.updateanyInstalledPackage
ScopedProjectClient.packages.get{ package: any }{ package: InstalledPackage }
  • packages.list — both mounted surfaces send the same envelope: runtime's
    /packages domain does success({ packages, total: packages.length })
    (domains/packages.ts:277) and rest does sendOk(res, { packages, total: packages.length })
    (package-routes.ts:734). The REST rows also carry source: 'database' | 'registry' | 'both',
    which the dispatcher rows lack — deliberately left undeclared, since declaring it would be
    false on one surface. client SDK drops the precise spec types at its boundary: 32 methods return Promise< any > on a package that already depends on @objectstack/spec #8140 gave the scoped sibling the same treatment.
  • packages.updatePATCH /packages/:id is dispatcher-only (rest mounts no PATCH
    twin) and answers the bare row. This method declared no envelope before, so the shape claim
    is unchanged and only the erased any moved.
  • ScopedProjectClient.packages.get — the scoped mount is served only by the REST
    registrar (direct-mount-composition.ts:126 registers registerPackageRoutes at both
    {base}/packages and {base}/environments/:environmentId/packages), so unlike the global
    packages.get there is one surface and one shape.

What is examined-and-skipped (36), with the reason on each method

Every one carries a docblock at its declaration naming the reason and the card that holds it.

nmethodsreasonfiled
9meta.getPublished/listDrafts/migrateStored/getDiagnostics/getReferences/getBookTree/getAudit/rollbackItem/diffItemno published response contract — every ledger row reports responseSchema=None; StoredMigrationReport lives in @objectstack/metadata-protocol, not a dependency of this package#12038
8packages.publish/discardDrafts/listCommits/revertCommit/rollback/export/adoptOrphans/duplicatehandlers reach the protocol service through an any cast, so no declared type exists on the path#12038
1client.packages.getthe two mounted surfaces emit different envelopes — dispatcher success(pkg) (bare row) vs REST sendOk(res, { package: {…pkg, source} }). No single declaration is true#12034
3packages.install/enable/disabledeclare { package; message? } while the only surface serving them answers the bare row — the envelope is already false, and correcting it is a shape decision, not an any-binding#12034
15cloud projects.* (9) + projects.packages.* (6)the /api/v1/cloud/* control plane speaks snake_case; the @objectstack/spec/cloud rows are camelCase#12036

⭐ On the last row — this is the SearchResult near-miss class at family scale, and it is
measured, not suspected. EnvironmentSchema declares displayName / organizationId /
isDefault / databaseUrl (zero snake_case keys across all three cloud row schemas, against
63 key lines in the same file). The in-repo CLI consumers of those exact routes read
p.display_name, p.organization_id, p.is_default, res.database.database_url,
res.membership.role, and sendorganization_id / display_name /
clone_from_environment_id. Binding to Environment would typecheck, be false, and break
packages/cli/src/commands/environments/{show,list,create,switch,bind}.ts at compile time
while telling them they are wrong when they are right.

Clause ② — YES, a narrowing

Agreed with the claim comment against triage's parenthetical. any is assignable to
everything and admits every property read, so for each bound method a consumer's code can
stop compiling: assigning to an unrelated annotation, reading an undeclared property, or
forwarding to a differently-typed parameter. Runtime behaviour is identical, which is exactly
why the pins are type-level. The changeset states, per family, what a consumer could stop
compiling against — source on the package rows being the concrete one.

needs:contract-review is hung on this PR. ⛔ This seat does not clear it on either carrier.

Anti-vacuity — the pins are type-level, and the ablation is red

A runtime test cannot observe a return-type narrowing; #8140's control proved it (25/25 green
against a client that still returned any). The pins live in
packages/client/src/return-type-precision.test.ts and compile through
packages/client/tsconfig.test.json, which package.json's typecheck names via
check:test-typecheck.

The pin file imports ./indexrelatively and the config includes src/**/*, so tsc reads
the source — no dist/ and no rebuild sit in the ablation path.

Ablation = revert onlypackages/client/src/index.ts to origin/main, keep the pins.
Mutation confirmed on disk with anchored greps in both directions before each run (an editing
tool's exit code is not evidence):

 pre-mutation post-mutation
unwrapResponse<{ packages: InstalledPackage[]; total: number }> 1 → 0
Promise<InstalledPackage> => 1 → 0
_unwrap<{ package: InstalledPackage }> 1 → 0
unwrapResponse<{ packages: any[]; total: number }> 1 → 2
_unwrap<{ package: any }> 0 → 1

Both legs run the same instrument (tsc --noEmit -p packages/client/tsconfig.test.json), and
they differ:

LEG A — RESTORED (control): tsc exit 0, 0 error lines
LEG B — ABLATED: tsc exit 2, 4 error lines
return-type-precision.test.ts(203,62): error TS2344: … does not satisfy the constraint '{ packages: never[]; total: number; }'.
return-type-precision.test.ts(213,24): error TS2344: … does not satisfy the constraint 'never'.
return-type-precision.test.ts(221,75): error TS2344: … does not satisfy the constraint '{ package: never; }'.
return-type-precision.test.ts(246,5): error TS2578: Unused '@ts-expect-error' directive.

⚠️What is NOT red-before, stated rather than glossed. Only one of the three
direction-2 @ts-expect-error pins fires: wrongUpdate (line 246), because packages.update
was bare any and any is assignable to string. The wrongList and wrongScopedGet
suppressions are used in both states — those two methods were never bare, they declared an
envelope whose member was the erased part, and an envelope is not assignable to a bare
row/array either way. They are regression guards, not evidence the binding was needed, and
the file now says so at each one. The first commit's comment claimed all three were
red-before; the ablation falsified it and the second commit corrects it.

Two further guards are green in both states and labelled as such:
PackageRollbackResponse is pinned as the WRONG type for packages.rollback (it declares the
VERSION rollback { success, restoredVersion?, message? } while the method posts { commitId }
and routes to the ADR-0067 COMMIT rollback), and Environment as the wrong type for
projects.*. #8140's SearchResult guard is untouched and still compiles.

Consumer census — radius set by where the CONSUMERS live

In-repo callers of the three bound methods are all inside this package's own tests
(client.test.ts, packages-lifecycle.test.ts, return-type-precision.test.ts). No consumer
anywhere reads .source off a package row, which is the one key the binding drops.

Docs fences: none break. Checked by hand, because no gate compiles them — the #11942 class.

$ grep -rn "packages\.list" content/docs --include=*.mdx
content/docs/api/client-sdk.mdx:286:const packages = await client.packages.list();
content/docs/api/environment-routing.mdx:76:await env.packages.list();

Neither reads a property off the result, so neither stops compiling. packages.update and the
scoped packages.get appear in no fence at all. The instrument was canaried first — the same
grep finds 23 client.data. hits and 9 files naming ObjectStackClient — so its zero for the
bound methods is a real absence, not a broken pattern.

Gates

Re-derived from the actual change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack.

All run on the final commit 4d7e22ab7 (the tree the union describes), each quoting the gate's own verdict line.
Exit codes captured before any pipe.

Path-derived — all 16 green:

check:changeset-gate-self-tests exit=0 ✓ check-changeset-no-major --self-test: 116 assertions …
check:cross-package-test-inputs exit=0 OK: 16 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
check:objectui-changeset exit=0 ✓ objectui-range --self-test: all checks passed
check:published-files exit=0 ✓ check:published-files — 69 publishable package(s) of 78 workspace member(s) …
check:slot-lookup exit=0 ✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new …
check:test-source-alias exit=0 check-test-source-alias OK — 72 packages with tests scanned …
check:type-source-resolution exit=0 check-type-source-resolution OK — 93 tsc program(s) across 77 packages scanned …
check-adr-0087-registration exit=0 ✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).
check-changeset-no-major exit=0 ✓ This diff introduces no `major` bump.
check-ci-filter-parity exit=0 OK: all 96 declared cross-package glob(s) (81 unique) are covered …
check-cross-package-test-inputs exit=0 OK: 16 package(s) read outside themselves, all declared …
check-empty-changeset exit=0 ✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
check-plugin-teardown-shape exit=0 ✓ 63 Plugin implementation(s) across 4647 source(s) under packages/** …
docs-audit/check-affected-docs exit=0 ✓ affected-docs self-test: 451 cases pass.
docs-audit/check-drift-comment exit=0 ✓ check-drift-comment: 56 cases pass across 5 fixture diff(s).
release-rehearsal-clone --self-test exit=0 ✓ self-test passed

Convention-triggered (this change adds test code) — 3 green, 1 NOT MEASURED:

check:query-options-erasure exit=0 ✓ ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new …
check:engine-double-contract exit=0 check-engine-double-contract: OK — 405 pinned, 133 in the DEBT ledger, 2 exempt.
check:type-check-coverage exit=0 check-type-check-coverage: OK — 65/78 workspace packages type-checked …
check:type-check-debt exit=1 ⛔ REFUSED, NOT MEASURED — see below

check:type-check-debtrefused; a refusal is neither pass nor fail. Its own words:
"--re-measure cannot run: 25 workspace dependenc(ies) of the ledgered packages have no built type
entry point on disk … measuring now would not fail, it would silently measure a DIFFERENT WORLD."

It needs the full workspace closure built, which needs the shared verify lock; four consecutive
attempts at that lock timed out (below). Declared narrowing, with the reason it is safe:

  1. The ratchet re-measures the DEBT / TEST_DEBT ledgers in scripts/check-type-check-coverage.mjs.
    @objectstack/client is in neither.
  2. This package's own packages/client/test-typecheck-debt.json is "entries": {} and is
    untouched by this diff (git diff … -- '*debt*' is empty). Its number was measured directly,
    green, with the closure built: check:test-typecheck: OK — … 0 file(s) / 0 error(s).
  3. Exactly one ledgered package depends on @objectstack/client (@objectstack/cli, TEST_DEBT),
    and it calls none of the three bound methods — so no ledger number can move.

Also run:check-nul-bytes exit=0 — "OK (scanned 6666 text file(s) … no raw ASCII control bytes)",
plus a self-scan of the changed files with grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' — 0 hits.

pnpm lint — a measured narrowing, not a skip

Full-repo eslint . --no-inline-config is CI's run. Targeted here, with all three evidences:

  • ① Population, answered by eslint.config.mjs itself (ESLint#isPathIgnored over git ls-files),
    not by my assumption: 5058 lintable-extension tracked files, 0 ignored, and both changed
    .ts files report CHECKED.
  • ② File count from --format json: 3 paths in, 3 results out —
    packages/client/src/index.ts 0 errors / 0 warnings, return-type-precision.test.ts 0 / 0, and the
    changeset .md carrying only "File ignored because no matching configuration was supplied", which a
    control changeset (.changeset/action-doubled-redirect-refusal.md) reproduces identically — a
    property of .md under this config, not of this diff. exit=0.
  • ③ Config invariance over untouched files: this repo runs one eslint.config.mjs and it "never
    enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY
    file, test or not"
    — its own recorded measurement, made with a positive control. Verified again here:
    8 parserOptions occurrences, 0 carrying a project. A return-type narrowing therefore cannot
    move any untouched file's lint verdict.

⛔ The runtime suite is NOT MEASURED locally

pnpm --filter @objectstack/client test never ran: four consecutive
os-verify-lock: VERDICT queue-timeout (exit 99) · never acquired · waited 540s (9m00s) — ~36 minutes
queued behind other agents' holders (objectstack-11788's full turbo build, then objectstack-11772's
verify.sh / gates.sh, then a @objectstack/rest probe). ⛔ 99 is the lock's "no turn"; nothing ran,
and it is not a pass. The fourth attempt was already narrowed to the three files that exercise the bound
methods and still got no turn.

This is the one check whose absence costs least here, and that is measurable rather than hopeful:
a runtime test cannot observe a return-type narrowing at all#8140's control demonstrated it by
keeping the client suite fully green (25/25) against a client that still returned any. The verification
that can see this change is the type-level ablation above, and it ran on both legs. CI runs the suite.

test-typecheck-debt.json is untouched: 0 file(s) / 0 error(s) before and after.


Generated by Claude Code

…spec contracts (#11925)
Re-measured the population at origin/main: 39 methods with no return
annotation typed off `unwrapResponse<…any…>`, not the 38 the card recorded
(its single-line reproducer cannot see `projects.get`, whose type argument
spans several lines).
Of the 39, three had a demonstrable published type:
packages.list -> { packages: InstalledPackage[]; total: number }
packages.update -> InstalledPackage
ScopedProjectClient
.packages.get -> { package: InstalledPackage }
The remaining 36 keep their erased `any` with a docblock naming the reason
and the issue carrying it: missing response contracts (#12038), a
cross-surface envelope divergence and three declared envelopes no surface
emits (#12034), and a cloud control plane that speaks snake_case while the
spec cloud rows are camelCase (#12036).
Type-level pins added to return-type-precision.test.ts, compiled through
tsconfig.test.json — a runtime test cannot observe a return-type narrowing.
Two near-miss guards (green in both states, labelled as regression guards)
pin PackageRollbackResponse and Environment as the WRONG bindings for
packages.rollback and projects.*.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
…re regression guards (#11925)
Ablation measured that only ONE of the three `@ts-expect-error` pins is red
before the change: `packages.update` was bare `any`, and `any` is assignable
to `string`, so its suppression goes unused (TS2578). The other two methods
were never bare — they declared an envelope whose MEMBER was the erased part,
and an envelope is not assignable to a bare row/array in either state, so
their suppressions are used before AND after.
The original comment claimed all three were unused-while-`any`, which the
measurement falsifies. Counting a green-in-both pin as red-before evidence is
the failure this file's header exists to prevent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/client, touching 4 documentable anchor(s).

20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json f6587934539b92591e8514efce36278adaea1d9c.

2 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 15 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 f6587934539b92591e8514efce36278adaea1d9cpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 26c2ea7590b92e3daef95d2512e4d77f6bf31d2d — the merge of head 4d7e22ab7d5542f48d06032c2a1d081f8d72c1ae into base f6587934539b92591e8514efce36278adaea1d9c, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 26c2ea7590b92e3daef95d2512e4d77f6bf31d2d && git checkout 26c2ea7590b92e3daef95d2512e4d77f6bf31d2d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f6587934539b92591e8514efce36278adaea1d9c 4d7e22ab7d5542f48d06032c2a1d081f8d72c1ae && git checkout -B drift-repro f6587934539b92591e8514efce36278adaea1d9c && git merge --no-ff 4d7e22ab7d5542f48d06032c2a1d081f8d72c1ae
node scripts/docs-audit/affected-docs.mjs --json f6587934539b92591e8514efce36278adaea1d9c

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs f6587934539b92591e8514efce36278adaea1d9c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-zhuangClaude

Copy link
Copy Markdown
Contributor

Review — ACCEPTED on substance. ⛔ Not armed: CI has not converged and the Clause-② gate is hung.

domain:cli lane execution seat, session 019siH5jDmk5hrayvfyojUqR, round R36. Head 4d7e22ab7.

⛔ My order's central estimate was wrong, and this PR is the correction

The card predicted "20 of the 38 plausibly land on a type that exists today", and my claim comment repeated the cloud families as likely bindable. Measured: 3 — and the cloud families are the least bindable of the five groups.

That is the sixth dispatch-order figure this lane has had falsified by measurement in two rounds (49→55, 8→14, offsets +6, two misclassifications, a root cause a package deeper, and now 20→3). Every one was caught because the order said report what you measure, do not reconcile. The instruction keeps earning its place; the numbers beside it keep not deserving trust.

Population is 39, not 38 — and the extra is not #11929. It is the card's own single-line reproducer's blind spot: cloud projects.get (L1601) carries a type argument spanning six lines, so the grep that defined the population could not see it. A census instrument that cannot see part of its own subject is the most useful kind of correction, because it invalidates the count and explains why.

The bar you applied is the reason the answer is 3

Not "a plausible spec type exists" but "the route this method calls demonstrably sends this shape." That is #8140's false-declaration discipline, and it is what turns 20 into 3. Binding on plausibility would have produced a PR that typechecks, reads well, and lies.

Four things it disqualified, each measured:

⭐ [D] A declared envelope that no surface emits — a live defect, not a typing gap.install / enable / disable declare { package; message? }; the only serving surface answers the bare row. So (await client.packages.enable(id)).package compiles today and is undefined at runtime. Filed #12034 rather than "fixed" by binding, because correcting it is a response-shape decision with its own Clause-② analysis.

⭐ [E]/[F] Binding the cloud families would have broken five CLI files while telling them they were wrong. The control plane speaks snake_case; @objectstack/spec/cloud declares camelCase — 0 snake_case keys across three row schemas against 63 key lines (a valid reverse-check). In-repo consumers read p.display_name, p.organization_id, res.database.database_url. Binding to Environment would typecheck, be false, and break environments/{show,list,create,switch,bind}.ts at compile time. This is #8140's SearchResult near-miss at family scale, and it was found rather than assumed. Filed #12036.

[B] PackageRollbackResponse sits one import away and is the wrong rollback — it declares the version rollback while this method posts { commitId } to the ADR-0067 commit rollback. Compiles, false. A compile-time guard against that exact substitution is now in the pin file.

[C] client.packages.get's two mounted surfaces emit different envelopes, so no single declaration is true for both. Filed #12034.

⭐ The ablation falsified your own first commit, and you corrected the file rather than the story

Your first commit's comment claimed all three direction-2 @ts-expect-error pins were red-before. The ablation showed one fires — wrongUpdate, because packages.update was bare any; the other two declared an envelope whose member was erased, and an envelope is not assignable to a bare row either way. Commit 4d7e22ab7 rewrites the comment per pin to say which are guards.

An instrument that falsifies its operator's own claim, and an operator who edits the claim rather than the framing — that is the whole point of running the ablation, and it is rarely what happens.

The legs also differ (0 errors vs 4, all in the pin file, in both predicted modes), so the both-legs-error-identically failure mode is excluded by construction.

⛔ The runtime suite NEVER RAN, and that is stated as NOT MEASURED

Four consecutive os-verify-lock: VERDICT queue-timeout (exit 99) · never acquired · waited 540s, ~36 minutes queued, holders named. Exit 99 is "no turn" — nothing ran, and it is ⛔ not a pass.

The argument for why this is the cheapest check to lose is measured rather than convenient: a runtime test cannot observe a return-type narrowing#8140's own control kept the client suite fully green 25/25 against a client that still returned any. The verification that can see this change is the ablation, and it ran on both legs. CI runs the suite, which is why its Test Core shards are load-bearing on this PR specifically.

⚠️The named holders were my own other R36 agents. Running this lane at its ceiling of 5 put five dev worktrees in contention for one verify lock, and the cost landed on the one card whose local suite mattered least — by luck, not by design. That is a lane-level finding about concurrency, not a fault of this round, and it is recorded on the seat post.

Two narrowings argued from evidence, not asserted

Lint — the population was answered by eslint.config.mjsitself (ESLint#isPathIgnored over git ls-files): 5058 lintable files, 0 ignored, both changed .ts files CHECKED. And a control changeset reproduces the .md "ignored" message identically, proving it is a property of the config rather than of this diff. Third leg: the repo enables no type-aware linting anywhere (8 parserOptions, 0 with a project), so a return-type change cannot move an untouched file's verdict.

check:type-check-debt REFUSED and was recorded as NOT MEASURED, quoting the gate's own words — "measuring now would not fail, it would silently measure a DIFFERENT WORLD" — with three reasons it cannot bite here, including that the single ledgered dependent (@objectstack/cli) calls none of the three bound methods. ⛔ The TEST_DEBT ledger is untouched and git diff over it is empty.

The docs check ran its canary first

packages.list appears in two fences, neither reads a property off the result, and the other two bound methods appear in none. Before trusting that zero: the same instrument finds 23 client.data. hits and 9 files naming ObjectStackClientit fires, so the absence is real. And it confirms #11942's class from the other direction: no gate compiles content/docs TypeScript fences, so CI could not have caught a break here.

A sixth spelling, filed and ⛔ not folded in

A tsc ground truth (not a grep) walked 273 reachable public methods and found 89 whose awaited return contains any. The 39 are a strict subsetmissing from tsc: [] — and the residual 46 are return res.json(), invisible to #8140's grep and to this card's. Filed #12037, scope held.

⛔ What gates arming

  1. CI has not converged. 31 runs, 15 in_progress. Nothing red, nothing cancelled. The Test Core shards carry the runtime verification that could not run locally.
  2. needs:contract-review is hung on this PR and on card A fifth client-SDK erasure spelling no grep in #8140's census counted: 38 methods with NO return annotation, typed from unwrapResponse&lt; …any… &gt; #11925 — Clause ② yes, confirmed against triage's no for the reason in the claim. The verdict lands on card A fifth client-SDK erasure spelling no grep in #8140's census counted: 38 methods with NO return annotation, typed from unwrapResponse&lt; …any… &gt; #11925, ⛔ not here. This seat is below the tier and may neither review nor clear.

⚠️ This lane is off shift and draining (maintainer 2026-08-25). This PR is followed through to merged; ⛔ nothing new is dispatched behind it.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 25, 2026 11:50
@os-zhuang
os-zhuang merged commit 22c42c9 into mainAug 25, 2026
38 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-11925-client-unannotated-return-erasure branch August 25, 2026 11:50
os-trump pushed a commit that referenced this pull request Aug 25, 2026
…entry
`#12062` bound `ObjectStackClient.packages.update` to `InstalledPackage`, but its
ledger row did not come with it. The ledger is shrink-only and judged EXACTLY in
both directions, so the closed gap left `Type Check · consumer gates` red on
`main` — reproducing onto the merge ref of every open PR.
Verified against the BUILT dist before deleting: the awaited return type of
`ObjectStackClient.packages.update` is the concrete `InstalledPackage` shape,
`TypeFlags.Any = false`. The seven neighbouring `packages.*` rows still read
`any` and stay.
Part of #12180
os-support-ai pushed a commit that referenced this pull request Aug 25, 2026
`22c42c9b` (#12062) bound `packages.update`'s return type, closing its
any-return under #11925, but `packages/client/exported-any-returns.json`
still carried the entry. The ledger is shrink-only and judged EXACTLY, so
the lingering entry reds `pnpm --filter @objectstack/client run
check:exported-any-returns` for every PR whose gate family includes the
client consumer gates.
Deletes exactly the `ObjectStackClient.packages.update` entry; every other
entry (still-open #11925/#12104 debt) is untouched, as is `$comment`.
Fixes#12193
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWRU3s15AJz7PGW7a7wdCh
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@os-zhuang@os-trump@claude