You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[finding] Ruling A on #16231 narrows IScopedObjectRepository, but ctx.api.object(name) resolves through the CLASS ObjectRepository — the hook-facing door keeps Promise<any>, and updateById keeps it too #16786
Filed by the domain:engine execution PM seat, from the #16231 round (PR #16783). The implementer reported it and ⛔ deliberately did not fold it in; the measurements below are the seat's own, re-taken on origin/main.
⛔ Filed unassigned and unlabelled, as an observation for triage. Grade deliberately not asserted.
Why this is not just "one more any"
Maintainer ruling A on #16231 (5572153316, 「同意」) extends #15823's find() rule to findOne / update / delete, naming "packages/spec/src/contracts/data-engine.ts:276-279and the scoped-context mirrors at scoped-context.ts:148/164". PR #16783 narrows exactly those.
⚠️The door a hook author actually touches is not among them.ScopedContextisHookContext.api, so the hook-facing call is ctx.api.object(name).findOne(…) — and that call does not resolve through the narrowed interface.
Measured on origin/mained7243d52b
where
reading
packages/spec/src/contracts/scoped-context.ts:181
object(name: string): IScopedObjectRepository; — the interface does return the interface
packages/objectql/src/engine.ts:14662
object(name: string): ObjectRepository { — the implementation returns the CLASS
packages/objectql/src/engine.ts:14479-14480
the file's own docblock confirms this is "what ScopedContext.object(name) returns, and what a hook reaches as ctx.api.object(name)"
⇒ TypeScript's implements accepts a wider declared return, so ObjectRepository's own Promise<any> members satisfy the narrowed interface while the call site keeps seeing any. Narrowing the interface therefore does not reach ctx.api.object(name).findOne(…).
⚠️How I nearly filed this wrong, recorded because it is the more useful half: my first measurement grepped packages/spec/src/contracts/scoped-context.ts, found object(name): IScopedObjectRepository, and looked like a falsification of the report. It was the wrong file — the claim is about the implementation class, not the spec interface. ⇒ ⛔ a reading that refutes a claim about implementation by measuring the declaration has measured a different thing.
Second, smaller gap in the same family: IScopedObjectRepository.updateById (scoped-context.ts:167) stays updateById(id: string | number, data: any): Promise<any> while its update sibling narrows under ruling A. A by-id update has the same record-or-null answer as the update by-id exit. The ruling names :148 / :164 and not :167.
What this costs, in the ruling's own terms
Ruling A exists because "every consumer that has to decide whether a normalizer limb over one of these is dead pays a reachability argument instead of reading a type" — the #15094 census counted 104 array-or-envelope normalizer blocks. ⇒ for hook authors, who reach these verbs through ctx.api, that cost is not paid down by PR #16783: the type they read is still any.
⭐ And the class is live, not theoretical. The same round measured seven test doubles answering shapes their own IDataDriver contract does not have — { deleted: n }, an array from a Promise<number> door, undefined from a bare vi.fn() — every one invisible while the door said any. That is the drift a declaration stops, and the hook-facing door is still undeclared.
Suggested shape, not asserted
The implementer's own reading: "a one-line annotation change plus its own consumer census." ⇒ annotate ScopedContext.object with IScopedObjectRepository, and narrow updateById alongside update.
⚠️ Two things a taker must not skip:
The census is the cost, not the annotation. Ruling A's precondition was a measured consumer census posted before any declaration moved, and it found 18 files / 92 compile errors for the three verbs. This annotation newly type-checks every ctx.api.object(…) call in the repo against the narrowed members; that number is unmeasured and could be larger.
Filed by the
domain:engineexecution PM seat, from the #16231 round (PR #16783). The implementer reported it and ⛔ deliberately did not fold it in; the measurements below are the seat's own, re-taken onorigin/main.⛔ Filed unassigned and unlabelled, as an observation for triage. Grade deliberately not asserted.
Why this is not just "one more
any"Maintainer ruling A on #16231 (
5572153316, 「同意」) extends #15823'sfind()rule tofindOne/update/delete, naming "packages/spec/src/contracts/data-engine.ts:276-279and the scoped-context mirrors atscoped-context.ts:148/164". PR #16783 narrows exactly those.ScopedContextisHookContext.api, so the hook-facing call isctx.api.object(name).findOne(…)— and that call does not resolve through the narrowed interface.Measured on
origin/mained7243d52bpackages/spec/src/contracts/scoped-context.ts:181object(name: string): IScopedObjectRepository;— the interface does return the interfacepackages/objectql/src/engine.ts:14662object(name: string): ObjectRepository {— the implementation returns the CLASSpackages/objectql/src/engine.ts:14479-14480ScopedContext.object(name)returns, and what a hook reaches asctx.api.object(name)"⇒ TypeScript's
implementsaccepts a wider declared return, soObjectRepository's ownPromise<any>members satisfy the narrowed interface while the call site keeps seeingany. Narrowing the interface therefore does not reachctx.api.object(name).findOne(…).packages/spec/src/contracts/scoped-context.ts, foundobject(name): IScopedObjectRepository, and looked like a falsification of the report. It was the wrong file — the claim is about the implementation class, not the spec interface. ⇒ ⛔ a reading that refutes a claim about implementation by measuring the declaration has measured a different thing.Second, smaller gap in the same family:
IScopedObjectRepository.updateById(scoped-context.ts:167) staysupdateById(id: string | number, data: any): Promise<any>while itsupdatesibling narrows under ruling A. A by-id update has the same record-or-nullanswer as theupdateby-id exit. The ruling names:148/:164and not:167.What this costs, in the ruling's own terms
Ruling A exists because "every consumer that has to decide whether a normalizer limb over one of these is dead pays a reachability argument instead of reading a type" — the #15094 census counted 104 array-or-envelope normalizer blocks. ⇒ for hook authors, who reach these verbs through
ctx.api, that cost is not paid down by PR #16783: the type they read is stillany.⭐ And the class is live, not theoretical. The same round measured seven test doubles answering shapes their own
IDataDrivercontract does not have —{ deleted: n }, an array from aPromise<number>door,undefinedfrom a barevi.fn()— every one invisible while the door saidany. That is the drift a declaration stops, and the hook-facing door is still undeclared.Suggested shape, not asserted
The implementer's own reading: "a one-line annotation change plus its own consumer census." ⇒ annotate
ScopedContext.objectwithIScopedObjectRepository, and narrowupdateByIdalongsideupdate.ctx.api.object(…)call in the repo against the narrowed members; that number is unmeasured and could be larger.ObjectQL.findOne/update/deletereturnhookContext.resultunder aPromise[any]declaration — nothing to guard, because nothing is declared #16231's ruling. The maintainer named three anchors and these are not among them — whether the same answer extends here is a question to put, ⛔ not an inference to make. This seat learned that the expensive way tonight on [finding] The InMemory driver silently ignores the engine's tenant scope for objects that OMIT atenancyblock — its guard only refuses an explicitenabled: true, so memory-driver runs show cross-organization rows a SQL driver refuses #16589, where a direction settled by a standing criterion turned out to have been ruled the other way a month earlier.Boundaries
findOne,updateanddeletedeclare what they answer, and their hook seams are guarded (#16231) #16783 — that PR delivers exactly the ruling's named anchors and is under contract review.Promise[any]on fiveIDataDriverdoors after #14438 —findOne,create,bulkCreate,execute,explainonSqlDriver, andTursoDriver.create()'s own override #15267 (the driver-layerPromise<any>twin) — different layer, its own card.origin/mained7243d52band will drift — ⛔ 按符号重新推导.