Found while implementing #14175 (typing ActionEngineFacade.find's filter parameter) — the same declared-vs-produced family, on the neighbouring member. Reported, not fixed: #14175's scope is find only and every other member of the facade is untouched there.
What the two ends say
- Spec —
packages/spec/src/ui/action-params.zod.ts, ActionEngineFacade (on origin/main369da918): delete(object: string, id: string): Promise<void> — one id. - Runtime —
packages/runtime/src/action-execution.ts, buildActionEngineFacade, the delete arm (:1174-1181 on 369da918): async delete(object: string, idOrIds: string | string[]), commented "Tolerant of both the single-id and array conventions handler suites use (CRM handlers pass one id; todo handlers pass an id array)", iterating ql.delete once per id. - A shipped example depends on the undeclared half —
examples/app-todo/src/actions/task.handlers.ts declares its OWN ActionContext (:18-31) with delete(object: string, ids: string[]) and calls engine.delete('todo_task', ids) with an array (deleteCompletedTasks, :81-89). It compiles only because the example never imports ActionHandlerContext / ActionHandler from @objectstack/spec/ui; annotating the handler with the published type — which is what the ActionHandlerContext docblock asks authors to do — would be a compile error on a call the runtime serves correctly today.
Why it matters
The published type under-declares what the runtime delivers, and the one first-party handler suite that uses the array form had to copy the facade into a local interface to reach it — the hand-written-fake pattern #14175 describes, one member over. A handler author reading the contract cannot tell whether delete(object, ids) is supported, and the example's copy drifts from the spec whenever either side moves (its find copy already carries the pre-#14175query: Record... spelling).
Direction (for triage — not decided here)
Either declare the runtime's shape on the contract (delete(object: string, idOrIds: string | string[])) and retire the example's local ActionContext in favour of ActionHandler from @objectstack/spec/ui, or narrow the runtime to the declared single-id form and move the example to a loop. Contract-first says the spec decides which convention is the contract; today neither end says, and the runtime comment records the tolerance as an accident of two handler suites rather than a decision.
Dedup at filing time: open-domain REST enumeration (506 open issues, reconciled against open_issues_count 530 minus 24 open PRs), keyword scan over title + body for ActionEngineFacade / idOrIds / engine.delete / ctx.engine / task.handlers — only #14175 and the seat post #6017 hit. Closed domain not scanned (declared exception).
Related: #14175 — find only; this member is not addressed there.
Generated by Claude Code
Generated by Claude Code
Found while implementing #14175 (typing
ActionEngineFacade.find's filter parameter) — the same declared-vs-produced family, on the neighbouring member. Reported, not fixed: #14175's scope isfindonly and every other member of the facade is untouched there.What the two ends say
packages/spec/src/ui/action-params.zod.ts,ActionEngineFacade(onorigin/main369da918):delete(object: string, id: string): Promise<void>— one id.packages/runtime/src/action-execution.ts,buildActionEngineFacade, thedeletearm (:1174-1181on369da918):async delete(object: string, idOrIds: string | string[]), commented "Tolerant of both the single-id and array conventions handler suites use (CRM handlers pass one id; todo handlers pass an id array)", iteratingql.deleteonce per id.examples/app-todo/src/actions/task.handlers.tsdeclares its OWNActionContext(:18-31) withdelete(object: string, ids: string[])and callsengine.delete('todo_task', ids)with an array (deleteCompletedTasks,:81-89). It compiles only because the example never importsActionHandlerContext/ActionHandlerfrom@objectstack/spec/ui; annotating the handler with the published type — which is what theActionHandlerContextdocblock asks authors to do — would be a compile error on a call the runtime serves correctly today.Why it matters
The published type under-declares what the runtime delivers, and the one first-party handler suite that uses the array form had to copy the facade into a local interface to reach it — the hand-written-fake pattern #14175 describes, one member over. A handler author reading the contract cannot tell whether
delete(object, ids)is supported, and the example's copy drifts from the spec whenever either side moves (itsfindcopy already carries the pre-#14175query: Record...spelling).Direction (for triage — not decided here)
Either declare the runtime's shape on the contract (
delete(object: string, idOrIds: string | string[])) and retire the example's localActionContextin favour ofActionHandlerfrom@objectstack/spec/ui, or narrow the runtime to the declared single-id form and move the example to a loop. Contract-first says the spec decides which convention is the contract; today neither end says, and the runtime comment records the tolerance as an accident of two handler suites rather than a decision.Dedup at filing time: open-domain REST enumeration (506 open issues, reconciled against
open_issues_count530 minus 24 open PRs), keyword scan over title + body forActionEngineFacade/idOrIds/engine.delete/ctx.engine/task.handlers— only #14175 and the seat post #6017 hit. Closed domain not scanned (declared exception).Related: #14175 —
findonly; this member is not addressed there.Generated by Claude Code
Generated by Claude Code