Uh oh!
There was an error while loading. Please reload this page.
Pass the engine facade a bare filter, not a query envelope - #87
Conversation
`ctx.engine.find(object, query)` takes a FILTER: the runtime's
`buildActionEngineFacade` adds the `where` envelope itself. Every filtered
read in `catalog.handlers.ts` passed an envelope, so through the real
dispatcher the query arrived as `{ where: { where: … } }`, matched nothing,
and came back empty with no error — `duly_catalog_apply` reported a
successful run of zero, `duly_catalog_sync` scanned nothing, and
`resolveBusinessUnit` silently created unanchored duties.
- flatten all five `engine.find` calls
- `FakeEngine.find` in catalog-instantiate.test.ts reads the flat filter, so
the fake stops honouring a shape production never produces
- delete the #79 tripwire in catalog-apply-cadence.test.ts and fold its two
facades into one
- new test/catalog-engine-facade.test.ts dispatches through the real action
route with the facade the RUNTIME builds — no double
Fixes#79. Contract half filed upstream as objectstack-ai/objectstack#14175.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2pos-warren
commented
Sep 1, 2026
Reviewed — merging. Two things I checked beyond the report.1. Gates on the PR head, re-run by me on 2. Gates on the head merged with current Merged On the work itself
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#79
ctx.engine.find(object, query)takes a bare filter. The runtime'sbuildActionEngineFacade(@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:Every filtered read in
src/actions/catalog.handlers.tspassed an envelope, so through the real dispatcher the query arrived as{ where: { where: { … } } }. No row has a field calledwhere, so each read came back empty with no error:duly_catalog_applyreported a successful run of zero,duly_catalog_syncscanned nothing and called every duty unchanged, andresolveBusinessUnitcreated unanchored duties. Only the one unfiltered read survived, becauseObject.keys({}).length === 0skips the wrapping entirely — which is what made the handler look partially alive.What changed
engine.findcalls pass the filter flat — the apply catalog read and itsduly_dutyidempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its{ source: 'catalog' }duty read, andresolveBusinessUnit'ssys_user_positionread.FakeEngine.findintest/catalog-instantiate.test.tsreads the flat filter. It used to readquery.where, honouring the handler's convention rather than the runtime's, which is exactly how 78 duty assertions stayed green against a shape production never produced. Left as-is, the fix would have been invisible to the suite.#79tripwire intest/catalog-apply-cadence.test.tsis deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one:handlerFacadenow reproducesbuildActionEngineFacadeline for line, so there is a single convention in the file.test/catalog-engine-facade.test.tsdispatches through the real action route (HttpDispatcher.handleActions— the functioncreateActionsDomain's route delegates to) and lets the runtime buildctx.engineitself. No facade is written in that file; that is the point. It covers apply, the business-unit anchor, idempotency across two dispatched runs, the object-bound twin's own route, sync's scan/replay, the narrowed sweep, and the retired report, plus one control asserting the route really is the capability-gated platform route.A tolerant
query.where ?? queryrung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.findtypesqueryas a plain record and says nothing about which shape it is) is filed upstream as objectstack-ai/objectstack#14175 and referenced from a code comment; this PR does not wait on it.Acceptance
position_codecatalog-engine-facade.test.ts— "creates the duties for a matching position_code"duly_catalog_syncscans the catalog-sourced duties it is supposed to scanscanned: 2,updated: 2)catalog-apply-cadence.test.ts— lastdescribeandruntimeFacaderemovedVerification
All four gates on
0c510fd, each read from its own verdict output (exit codes captured before any pipe):Reverse verification. The pre-fix
src/actions/catalog.handlers.tswas restored fromorigin/mainover the fixed tree, the mutation was confirmed on disk by grep before anything ran (1 envelope read present, 0 flat reads — an editor's exit code proves nothing), and the three catalog suites were re-run:23 failures, in the predicted direction and across all three suites — before this PR only the tripwire could see the defect. The one new test that stays green under the mutation is the capability control, which does not depend on the query shape. The mutation script carried a
trap … EXIT INT TERMrestore, and the tree was confirmed clean againstHEADafterwards. No rebuild leg was needed: these suites import../src/…directly, so nothing resolves throughdist/.No changeset: this repo has no changeset mechanism (no
.changeset/in the tree or anywhere ingit log --all, no@changesets/*dependency, no script, no mention inAGENTS.md). The four gates in.github/workflows/ci.ymlare the whole contract.Generated by Claude Code