Uh oh!
There was an error while loading. Please reload this page.
feat(client): bind erased SDK return types to their spec contracts - #11929
Conversation
`packages/client/src/index.ts` dropped the precise contract types at the SDK
boundary on a package that already depends on `@objectstack/spec`, so the types
were reachable and the `any` was forced by nothing.
Four spellings of the same erasure, all measured at head and all bound here:
32 `Promise<any>`, 5 `Promise<any[]>`, 4 `Promise<{ …any[]… }>`, and 14
fixed-shape `<T = any>` methods (8 on `ObjectStackClient`, 6 mirrored on
`ScopedProjectClient`) — 55 sites, of which 51 are bound and 4 are deliberate.
Each binding is the DECLARED return of the service method the route calls,
verified per method against the handler's emit rather than swept: four
federation methods are envelope-wrapped (`{ tables }`, not `RemoteTable[]`),
`security.explain` takes the `z.input` form its contract declares because no
parse runs on that path, and `search` has a same-named wrong type
(`SearchResult`) sitting one import away.
The fixed-shape generics become `<T extends X = X>`, not `<T = X>`: the default
alone closes the erasure only for an unannotated call, because TypeScript infers
`T` from the assignment's contextual type. Measured on the pin file.
`automation.create` / `automation.update` / `search` / `data.clone` keep
`Promise<any>` with a docblock each — they are missing CONTRACTS, not missing
annotations, and authoring one lands in `packages/spec`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 259b803588eb1c22d8229a130840f3f3f13f7476 && git checkout 259b803588eb1c22d8229a130840f3f3f13f7476
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ac5974490cf84cf49858446bb4022ffb8c3a6739 65c2b2a432aacf48c62d8a82878039c69ace5727 && git checkout -B drift-repro ac5974490cf84cf49858446bb4022ffb8c3a6739 && git merge --no-ff 65c2b2a432aacf48c62d8a82878039c69ace5727
node scripts/docs-audit/affected-docs.mjs --json ac5974490cf84cf49858446bb4022ffb8c3a6739
|
os-zhuang
commented
Aug 25, 2026
Review — ACCEPTED on substance. ⛔ NOT armed: CI has not converged and the Clause-② gate is hung.
⛔ First: my dispatch order was wrong on two counts, and this PR is the correctionI ordered the work at 49 sites with the fixed-shape group at 8, taken from the census. Measured here: 55 and 14. The census's own prose said the eight are "mirrored again on Second: I listed Both were caught because the order said to report what you measure and not reconcile to the census. That instruction earned its keep; the numbers in it did not. The ablation's control is the part that makes this PR credibleThe runtime suite is fully green against a client that still returns Mutation confirmed on disk in both directions before the run, with a reverse-check term ( Honest accounting kept in both directions: Three more things that were found rather than assumed
The open question is correctly routed, and this seat does not answer itWhether the 12 fixed-shape For the chain's benefit, the seat's read, ⛔ explicitly not a ruling: the shipped option A is the smaller break and it is the one the measurement forced — a bare ⛔ Two things gate arming
One review item, sent as a follow-upThe docs-drift advisory lists 9 hand-written pages naming Generated by Claude Code |
`content/docs/api/client-sdk.mdx` carries an UNMARKED ```typescript fence (no `<!-- os:check -->`), so `check:skill-examples` never compiled it and CI could not have caught this. Measured by extracting the fence verbatim into the package's own test tsc program and diffing diagnostics with `packages/client/src/index.ts` at this branch vs at origin/main: exactly two diagnostics are INTRODUCED by the narrowing, both TS2345. 1. `run.runId` -> `resume(flowName: string, runId: string)`. `AutomationResult.runId` is optional — a completed run carries none — so the argument is `string | undefined`. Narrowed with `&& run.runId`. 2. `suggestions[0].id` -> `confirm(id: string)`. `AudienceBindingSuggestion` is `Record<string, unknown>` by contract, so the property reads as `unknown`. Wrapped in `String(...)`. Both edits carry a one-line explanation, because each is the migration an external consumer has to make and the page's job is to teach it. Re-measured after the fix: 0 introduced diagnostics. Five diagnostics remain in BOTH states (four `err is of type unknown` in the catch block, one unused local) — pre-existing artifacts of compiling a doc snippet under strict settings it was never written for, identical before and after, and not touched here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
os-zhuang
commented
Aug 25, 2026
Follow-up round — ACCEPTED. ⛔ Not armed yet: CI still converging on the new head.
The docs check found real breakage, and the narrowing of the question was yoursI asked you to check one class across nine pages. You narrowed it correctly first: only Then the two diagnostics, both The first is ⭐ Four refusals discarded rather than reported — this is the part worth keeping
Every one of those would have produced a number. Three would have produced a clean-looking number — the third especially, where both legs error identically and the diff comes out empty, which reads exactly like "no breakage". ⛔ And you ran the instrument controls before trusting it: baseline without the probe → Honest accounting held on both sides: two diagnostics introduced, zero after the fix; five present in both states left alone and named as artifacts of compiling a doc snippet under strict settings it was never written for. And the The gate question, answered — and it produced the findingI asked whether a gate already covers this. The answer is the sharp kind: a gate exists and does cover #11942 is well-formed for the same reason its subject is uncomfortable: 137 pages carry a
⛔ What gates arming
The verdict also ruled your open question: A — Generated by Claude Code |
os-zhuang
commented
Aug 25, 2026
ACCEPT — armed (ready + auto-merge SQUASH)Card #8140. Head Contract-review gate — PASS, card #8140 comment CI on Head/verdict discrepancy, as flagged in advance at Arming was delayed ~25 minutes by a GitHub rate limit, not by anything about this PR. Two write calls failed with This round produced more measurement than the card asked for, and it is worth naming what it changed: The dispatch order was wrong three times and this PR is the correction. 49 sites → 55; the fixed-shape group 8 → 14 (the census named the The ablation ran the negative. The runtime suite stayed fully green (25/25) against a client that still returns Four refusals were discarded rather than reported, including one where both legs errored identically and the diff came out empty, which reads exactly like "no breakage". The instrument was then proved with a deliberate canary before its negative was trusted. And the docs check found real breakage that CI structurally could not: two Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8140
Binds the client SDK's erased return types to the
@objectstack/speccontracts the package alreadydepends on. 51 of 55 measured erasure sites are bound; the remaining 4 are deliberate and
documented in place. Types only — no request, response, unwrapping or error path changes.
All measurements at
origin/main=1f6d04703; all gates below ran at65c2b2a43, thisbranch's final commit (the ratchet family and repo-wide
pnpm lintwere re-run on it afterthe docs commit).
1. Premise re-verification — the counts I actually measured
The census (comment
5357091577, corrected by5357118695) was taken at04096f17eon2026-08-20, and PR #11714 landed in this exact file at 2026-08-25T00:20:59Z. Every line number in
the census has drifted (
+181lines). The four population counts have not moved:Verified equal at the census commit and at head (line counts differ, populations do not):
⛔ One population HAS moved, and it moved up. The dispatch order and the census both give the
< T = any >group as 8, and the true erasure surface as 49. Measured, it is 14 and55. The census's own prose already said the eight are "mirrored again on
ScopedProjectClient" — the mirrors were described but never added to the count:Those 28 split cleanly: 14 caller-supplied (
data.query/find/get/create/createMany/update/ updateManyonObjectStackClient, and the same 7 onScopedProjectClient) and 14 fixed-shape(6
automation.*on each class, plusactions.invoke/invokeGlobal). See §4 for why the lasttwo of those move back out.
Working population: 32 + 5 + 4 + 14 = 55.
premise_still_valid: true— the card's premise holdsand the census's classification (A=28 · B=0 · C=4 over the 32) reproduces exactly.
2. Clause ② — yes, and it is a NARROWING
Every binding narrows a published return type. None is purely additive, because
anyis assignableto everything and admits every property read: assigning the result to an unrelated annotation,
reading an undeclared property, or forwarding it to a differently-typed parameter all compile today
and stop compiling after. No runtime behaviour changes — no route, body, envelope, unwrap or
error path is touched.
exactly the class external consumers will hit. Both are in
packages/client/src/client.test.ts,which the census's sweep of
client-react/cli/app-tododid not cover:client.test.ts:1354—result.screen.nodeIdafterautomation.resume.AutomationResult.screenis optional (a completed run carries no screen), so this is now
result.screen?.nodeIdwitha
toBeDefined()beside it. This is the migration an external caller makes, and it is in thediff as the worked example.
client.test.ts:352—reports.save({ name, object }). See §5: this one made me revert achange rather than adapt the fixture.
The rest of the risk is external SDK consumers, unmeasurable from here. The changeset states the
break per family.
needs:contract-reviewis hung on this PR as well as the card; ⛔ this seat doesnot clear it.
3. ⭐ One design decision the review chain should ratify —
< T = X >is only HALF a fixThe obvious minimal fix for the fixed-shape
< T = any >methods is to give the parameter a precisedefault:
< T = FlowParsed >. Measured on this PR's own pin file, that is not enough.TypeScript infers
Tfrom the call's contextual type, so this still compiled:It surfaced as
TS2578: Unused '@ts-expect-error' directiveon the pin that asserts the wrong shapeis rejected — i.e. the guard caught the half-fix rather than my reading it off the code.
The spelling shipped here is
< T extends X = X >:await getFlow(n)wasany, is nowFlowParsed;A legitimate narrowing still works (
getFlow< FlowParsed & { name: 'onboarding' } >(…)); anunrelated type is now refused. Both directions are pinned. The stricter alternative — dropping the
type parameter entirely, so
getFlow< X >(…)becomesExpected 0 type arguments— is a largerbreak and is not taken here; it is the call worth making explicitly, which is why it is written
down rather than decided quietly.
4. The itemized list — the WHOLE population, including everything skipped
Bound — 51 sites
P1 ·
): Promise< any > =>→ 28 bound of 32 (4 are class C, listed below)email.sendPromise< any >Promise< SendEmailResult >datasources.external.listTablesPromise< any >Promise<{ tables: RemoteTable[] }>datasources.external.draftPromise< any >Promise<{ draft: ObjectDraft }>datasources.external.importPromise< any >Promise<{ object: ImportObjectResult }>datasources.external.refreshCatalogPromise< any >Promise<{ catalog: ExternalCatalog }>datasources.external.validatePromise< any >Promise< SchemaValidationReport >automation.getPromise< any >Promise< FlowParsed >automation.runs.getPromise< any >Promise< ExecutionLog >shareLinks.createPromise< any >Promise< ShareLink >security.explainPromise< any >Promise< ExplainDecision >security.describeDelegableScopePromise< any >Promise< DelegableScope >security.suggestedBindings.listPromise< any >Promise<{ suggestions: AudienceBindingSuggestion[]; synced: AudienceBindingSuggestionSync }>security.suggestedBindings.confirmPromise< any >Promise<{ suggestion: AudienceBindingSuggestion; bindingCreated: boolean }>security.suggestedBindings.dismissPromise< any >Promise<{ suggestion: AudienceBindingSuggestion }>approvals.recallPromise< any >Promise< ApprovalRecallResult >approvals.revisePromise< any >Promise< ApprovalSendBackResult >approvals.resubmitPromise< any >Promise< ApprovalResubmitResult >approvals.remindPromise< any >Promise<{ request: ApprovalRequestRow; notified: number }>approvals.requestInfoPromise< any >Promise<{ request: ApprovalRequestRow }>approvals.commentPromise< any >Promise<{ request: ApprovalRequestRow }>shares.grantPromise< any >Promise< RecordShare >shares.rules.savePromise< any >Promise< SharingRuleRow >shares.rules.getPromise< any >Promise< SharingRuleRow >shares.rules.evaluatePromise< any >Promise< SharingRuleEvaluationResult >reports.savePromise< any >Promise< SavedReport >reports.getPromise< any >Promise< SavedReport >reports.runPromise< any >Promise< ReportRunResult >reports.schedulePromise< any >Promise< ReportSchedule >P2 ·
): Promise< any[] > =>→ 5 bound of 5shareLinks.listPromise< any[] >Promise< ShareLink[] >shares.listPromise< any[] >Promise< RecordShare[] >shares.rules.listPromise< any[] >Promise< SharingRuleRow[] >reports.listPromise< any[] >Promise< SavedReport[] >reports.listSchedulesPromise< any[] >Promise< ReportSchedule[] >P3 ·
): Promise<{ …any[]… }> =>→ 4 bound of 4automation.listActions{ actions: any[]; total }{ actions: ActionDescriptor[]; total: number }automation.listConnectors{ connectors: any[]; total }{ connectors: ConnectorDescriptor[]; total: number }automation.runs.list{ runs: any[]; hasMore }{ runs: ExecutionLog[]; hasMore: boolean }ScopedProjectClient.packages.list{ packages: any[]; total }{ packages: InstalledPackage[]; total: number }Fixed-shape generics → 12 bound of 14 (2 skipped, below). Each appears twice — once on
ObjectStackClient.automation, once onScopedProjectClient.automation.automation.getFlow< T = any >< T extends FlowParsed = FlowParsed >automation.execute< T = any >< T extends AutomationResult = AutomationResult >automation.listRuns< T = any >< T extends { runs: ExecutionLog[]; hasMore: boolean } = … >automation.getRun< T = any >< T extends ExecutionLog = ExecutionLog >automation.resume< T = any >< T extends AutomationResult = AutomationResult >automation.getScreen< T = any >< T extends { runId: string; screen: ScreenSpec } = … >⭐
automation.getFlowis an explicit alias forautomation.get— the same route wore two erasurespellings in one file (#7 and #38). They now agree by construction.
Examined and SKIPPED — 18 sites, each with its reason
Class C — no type exists anywhere (4). These are a missing contract, not a missing
annotation.
packages/specwas read-only on this card, so nothing was authored there; each keepsPromise< any >with a docblock naming the reason, and all four are filed as #11924.automation.createdeps.success(body)(runtime/src/domains/automation.ts:982)IAutomationService.registerFlow(name, definition: unknown): voidreturns nothing; the body is never parsed throughFlowSchema.Flowwould be a claim about the REQUEST that no validation backs.automation.updatedeps.success(definition)(same file,:1586)search{ query, hits: Array<{ object, id, title, snippet?, record }>, totalObjects, totalHits, truncated }metadata-protocol/src/protocol.ts:9845-9863, not in@objectstack/spec;metadata-protocolis not a client dependency.SearchResult(contracts/search-service.ts:53) is a near-miss trap — it contracts the per-objectISearchService.search(hitscarryscore/document). Binding it would typecheck and be false; a compile-time guard against exactly that is in the pin file.data.clone{ object, id, sourceId, record }(protocol.ts:9488-9493)CreateDataResult< T >plussourceId— writing that equivalence in a consumer would mint an undeclared contract.Caller-supplied generics —
< T = any >is CORRECT (14). The record type genuinely belongs tothe caller. Untouched, and deliberately not constrained.
data.query,data.find,data.get,data.create,data.createMany,data.update,data.updateMany— onObjectStackClientand mirrored onScopedProjectClient(7 × 2).⛔ Two more move OUT of the fixed-shape group — a correction to the dispatch order and the
census. Both name
actions.invokeandactions.invokeGlobalamong the fixed-shape platformmethods. Measured, they are not:
The envelope is already precise.
Tis the return value of the app author's own handler,registered server-side with
engine.registerAction(objectName, actionName, handler)— caller-suppliedin exactly the sense
data.get< T >is. Constraining it would be wrong, and the surrounding shape wasnever erased.
invokeGlobaldelegates toinvoke. Left alone, with a docblock recording why.Not in scope — a FIFTH erasure spelling, filed as #11925. 38 methods carry no return
annotation at all; their public type is inferred from
unwrapResponse< …any… >(
meta.*history 9,packages.*14, cloudprojects.*8, env packages 6, scopedpackages.get1).Invisible to every
Promise< … >grep this card and its census used. ⭐ The asymmetry is visible inone object literal:
ScopedProjectClient.packages.listcarried both an annotation and the typeargument, so it is bound above as #37, while its neighbour
packages.getis not — purely because itlacks the annotation.
5. What I reverted, and why it is evidence
I briefly bound
reports.save's parameter toSaveReportInput.tscfailed on this repo's ownfixture at
client.test.ts:352:SaveReportInput.queryis required (contracts/report-service.ts:93) andPOST /reportsforwardsreq.body ?? {}unchecked (rest-server.ts:10293). So the SDK accepts an input its own servicecontract refuses. Parameter narrowing is not this card's scope (return types, and clause ② was
answered for those), so I reverted it and left the fixture alone rather than editing away a signal.
Filed as #11926.
6. Anti-vacuity — the ablation
Pins are type-level by necessity: a runtime test cannot observe a return-type narrowing, because the
value is identical either way. They are compiled —
packages/client/tsconfig.test.jsonincludessrc/**/*andpackage.json'stypechecknames it throughcheck:test-typecheck, which holdsevery unledgered file at zero errors.
Method: revert ONLY
packages/client/src/index.tstoorigin/main, keep the pins, run bothchecks. A
trap … EXIT INT TERMrestored the file on every exit path.Mutation confirmed on disk in both directions before the run:
Result — the pins go red, the runtime suite does not:
⭐ That control is the point: the runtime suite is fully green against a client that still returns
any. A pin that only calls the method and checks the value would have proved nothing.Restore verified byte-clean afterwards (
git status --porcelainempty; residual counts back to4 / 1 / 2).
Honest accounting — one guard is green in BOTH states and is not counted above.
searchResultIsNotTheGlobalSearchShapepins theSearchResultnear-miss trap in@objectstack/spec,not an annotation in this file, so reverting
index.tsdoes not move it. It exists so the nextsweep cannot "finish"
searchby binding the same-named neighbour.7. Verification — each gate's own printed verdict line
Gate family re-derived from the actual change set, not from the dispatch order's list:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack. Re-derived again after thedocs commit, which added
content/docs/api/client-sdk.mdxand with it 13 further doc-familygates — all run and all green (
check:doc-anchors,check:doc-authoring,check:doc-formula-expressions,check:doc-security-posture,check:docs-audit-scope,check:docs-redirects,check:published-readme-links,check:react-page-adapter-contract,check:role-word,check-doc-frontmatter,check-doc-route-spelling,check-docs-section-name,check-section-landing-index, plus the four@objectstack/specliveness gates). All run at65c2b2a43.Package checks
pnpm --filter @objectstack/client typecheck→check:test-typecheck: OK — @objectstack/client's test layer compiles under packages/client/tsconfig.test.json; 0 file(s) / 0 error(s) held in test-typecheck-debt.jsonpnpm --filter @objectstack/client test→Test Files 25 passed (25) · Tests 332 passed (332)pnpm lint(repo-wideeslint . --no-inline-config, not narrowed) →VERDICT command-exit 0Path-matched
check:changeset-gate-self-tests→✓ check-empty-changeset --self-test: 118 assertions over real temp git repos (real scan() path)check:cross-package-test-inputs→OK: 16 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:objectui-changeset→✓ objectui-range --self-test: all checks passedcheck:published-files→✓ check:published-files — 69 publishable package(s) of 78 workspace member(s) declare a 'files' whitelist …check:slot-lookup→✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new … baseline key set verified against 1f6d047: no files added.check:test-source-alias→check-test-source-alias OK — 72 packages with tests scanned; 61 registered …check:type-source-resolution→check-type-source-resolution OK — 77 packages with a tsconfig.json scanned; 51 registered …check-adr-0087-registration.mjs→✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).check-changeset-no-major.mjs→✓ This diff introduces no 'major' bump.check-ci-filter-parity.mjs→OK: all 96 declared cross-package glob(s) (81 unique) are covered by 'core' or 'crosspkg' …check-empty-changeset.mjs→✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).check-plugin-teardown-shape.mjs→✓ check:plugin-teardown-shape: 63 Plugin implementation(s) across 4639 source(s) …release-rehearsal-clone.mjs --self-test→✓ self-test passeddocs-audit/check-affected-docs.mjs→ exit 0check-nul-bytes.mjs→check-nul-bytes: OK (scanned 6638 text file(s) … no raw ASCII control bytes).Convention-triggered (adds/edits test files)
check:query-options-erasure→✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new … baseline key set verified against 1f6d047: no files added.check:engine-double-contract→check-engine-double-contract: OK — 403 pinned, 133 in the DEBT ledger, 2 exempt.check:where-matcher→✓ where-matcher conformance holds: 296 matcher(s) discovered, 296 answer the combinator battery correctly or refuse it loudly … none new.check:type-check-coverage→check-type-check-coverage: OK — 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger …check:type-check-debt --re-measure(over the built closure) →check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 249.0s, 1898 raw tsc error(s) total, none above its recorded number.-1surplus in@objectstack/plugin-approvals(TEST_DEBT 348, tsc now 347), unrelated to this diff — no approvals-plugin file is touched. Left for its owner rather than lowered here.8. Filed out of scope — all unassigned
Blocked-by: #8140unwrapResponse< …any… >with no annotationreports.saveaccepts an inputSaveReportInputrefuses; the route does not check it eitherfindingcheck:exported-anyequivalent — filed separately as triage directed, ⛔ not built here9. Docs-drift advisory — checked, and one page was genuinely falsified
The advisory listed 9 hand-written pages naming
ObjectStackClient/shareLinks. Types cannotfalsify prose about what a method does, so the only class that can break is a TS fence that
assigns an SDK result to an annotation or reads a property the now-precise type does not declare.
Where the call sites actually are. Only one of the 9 pages contains any:
The other 8 name the SDK only in prose or in
fetch-based examples. Of the 12os:check-markedblocks across those 8 pages, none calls a bound method — they use raw
fetch.⛔ No gate covers this fence, and that is the load-bearing finding. A fence-compiling gate DOES
exist —
pnpm --filter @objectstack/spec check:skill-examples, which compiles prose TypeScriptacross three surfaces including
content/docs/**. But it compiles only blocks marked with an<!-- os:check -->comment on the line above, and:The fence carrying all 15 call sites is unmarked, so CI was never the instrument here. Re-run on the
final commit for completeness:
✅ 256 prose examples type-check across 3 surface(s)— green, andgreen regardless of this fence, because it is not among the 256.
So I measured it directly rather than reading it. The fence was extracted verbatim into
packages/client/src/(a throwaway probe, sincetsconfig.test.jsonis the program already provento resolve
@objectstack/spec/*), then compiled withpackages/client/src/index.tsat this branchand at
origin/main, diffing the diagnostics. Two instrument controls first, because a check thatrefuses to run is not a measurement:
exit=0 errors=0;const probeCanary: number = "not a number"was reported onthe probe file, proving the fence really is compiled.
result's clothes:
TS5112(config conflict — tsc never ran),TS2688(@types/nodeunresolvable),and
TS2307on every@objectstack/spec/*import (the closure was unbuilt, so every SDK type wasan error in both states and the delta was meaningless). A fourth run measured the wrong fence
entirely — an ordinal
sed -n '3p'picked a different ````typescript` block — and was redoneanchored on content.
Result — exactly two diagnostics introduced by this diff, both real:
run.runIdpassed toresume(flowName: string, runId: string).AutomationResult.runIdisoptional — a completed run carries none. Fixed by narrowing:
if (run.status === 'paused' && run.runId).suggestions[0].idpassed toconfirm(id: string).AudienceBindingSuggestionisRecord< string, unknown >by contract, so the property reads asunknown. Fixed withString(...).Both fixes carry a one-line explanation in the fence, because each is precisely the migration an
external consumer must make and teaching it is the page's job. Re-measured after the fix: 0
introduced diagnostics.
Honest residue: five diagnostics are present in both states and were not touched — four
'err' is of type 'unknown'in thecatch (err)block and one unused local. They are artifacts ofcompiling a doc snippet under strict settings it was never written for, identical before and after,
and not falsified by this diff.
⭐ The
origin/mainleg of this second compile independently re-showed all 21 pin-file errors from§6, so the main ablation is confirmed twice by two different runs.
content/docs/releases/**was not touched. The advisory flaggedimplementation-status.mdxandv17.mdx; neither contains an SDK call site (the grep above covers them), so there is nothing toreport as wrong and nothing to file.
⛔ Draft, and staying that way: arming is the PM seat's step.
needs:contract-reviewhangs on thisPR and on #8140; ⛔ this seat does not clear it — the review chain does, and it records its verdict
on the card.
Generated by Claude Code
Generated by Claude Code