Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Pass the engine facade a bare filter, not a query envelope - #87

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape
Sep 1, 2026
Merged

Pass the engine facade a bare filter, not a query envelope#87
os-warren merged 2 commits into
mainfrom
claude/issue-79-engine-find-filter-shape

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#79

ctx.engine.find(object, query) takes a bare filter. The runtime's buildActionEngineFacade (@objectstack/runtime 17.2.0) adds the ObjectQL envelope itself:

asyncfind(object,query){constwhere=query&&Object.keys(query).length ? {where: query} : {};constrows=awaitql.find(object,{ ...where, context });

Every filtered read in src/actions/catalog.handlers.ts passed an envelope, so through the real dispatcher the query arrived as { where: { where: { … } } }. No row has a field called where, so each read came back empty with no error: duly_catalog_apply reported a successful run of zero, duly_catalog_sync scanned nothing and called every duty unchanged, and resolveBusinessUnit created unanchored duties. Only the one unfiltered read survived, because Object.keys({}).length === 0 skips the wrapping entirely — which is what made the handler look partially alive.

What changed

  • All five engine.find calls pass the filter flat — the apply catalog read and its duly_duty idempotency probe, the sync catalog read (both the narrowed and the unnarrowed branch) and its { source: 'catalog' } duty read, and resolveBusinessUnit's sys_user_position read.
  • FakeEngine.find in test/catalog-instantiate.test.ts reads the flat filter. It used to read query.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.
  • The #79 tripwire in test/catalog-apply-cadence.test.ts is deleted, not adjusted — it was written to go red when this landed. Its two facades collapse into one: handlerFacade now reproduces buildActionEngineFacade line for line, so there is a single convention in the file.
  • New test/catalog-engine-facade.test.ts dispatches through the real action route (HttpDispatcher.handleActions — the function createActionsDomain's route delegates to) and lets the runtime build ctx.engine itself. 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 ?? query rung was deliberately not added anywhere — a consumer that accepts both shapes is what let the wrong one ship green. The contract half (ActionEngineFacade.find types query as 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

From the issueWhere it is pinned
apply, dispatched with the facade the runtime builds, creates the duties for a matching position_codecatalog-engine-facade.test.ts — "creates the duties for a matching position_code"
duly_catalog_sync scans the catalog-sourced duties it is supposed to scansame file — "scans the catalog-sourced duties and replays a cadence edit" (scanned: 2, updated: 2)
the tripwire is deleted, not adjustedcatalog-apply-cadence.test.ts — last describe and runtimeFacade removed

Verification

All four gates on 0c510fd, each read from its own verdict output (exit codes captured before any pipe):

GATE validate EXIT=0 # one expected warning: hierarchy-security provider absent (AGENTS.md)
GATE typecheck EXIT=0
GATE test EXIT=0 # Test Files 22 passed (22) · Tests 559 passed (559)
GATE build EXIT=0 # Build complete (597ms) · dist/objectstack.json

Reverse verification. The pre-fix src/actions/catalog.handlers.ts was restored from origin/main over 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:

❯ test/catalog-apply-cadence.test.ts (3 tests | 2 failed)
❯ test/catalog-engine-facade.test.ts (8 tests | 7 failed)
❯ test/catalog-instantiate.test.ts (31 tests | 14 failed)
vitest exit: 1

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 TERM restore, and the tree was confirmed clean against HEAD afterwards. No rebuild leg was needed: these suites import ../src/… directly, so nothing resolves through dist/.

No changeset: this repo has no changeset mechanism (no .changeset/ in the tree or anywhere in git log --all, no @changesets/* dependency, no script, no mention in AGENTS.md). The four gates in .github/workflows/ci.yml are the whole contract.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 10:10
`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_01SqkTcrxUFci7nqXdbBSe2p
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. Two things I checked beyond the report.

1. Gates on the PR head, re-run by me on 0c510fd:validate 0, typecheck 0, test 0 (Test Files 22 passed, Tests 559 passed), build 0.

2. Gates on the head merged with current main, which is the state that actually lands. This branch was cut from dbe1537 and is two commits behind — it does not contain #83 (the duly_duty timezone write guard) or #85 (grouped-view projection). #83 matters here specifically: it puts a beforeInsert/beforeUpdate hook on duly_duty, and applyCatalogHandler writes duties. A fix that makes the catalog action start creating duties for the first time, landing next to a new guard that can refuse a duty write, is exactly the combination worth checking rather than assuming.

Merged origin/main into the branch locally and re-ran everything: Test Files 23 passed, Tests 589 passed, all four gates 0, no conflicts. The two changes are independent in practice. I have pushed that update to the branch so CI runs on the combination rather than on the older base.

On the work itself

  • Five call sites, not four. My card said four; the fifth is resolveBusinessUnit's sys_user_position read, which my own blast-radius section named and my count missed. That one was the nastiest of the set — "no position row" is a legitimate day-one state the handler tolerates, so its failure produced unanchored duties with no error anywhere.
  • The test dispatches through HttpDispatcher.handleActions, so the runtime builds ctx.engine. No double is written in that file. This is the whole point of the card: the previous suite was green because a hand-written FakeEngine encoded the author's belief about the contract, so the fake and the handler were wrong in the same direction by construction. A test that builds its own facade could not have caught this and cannot catch the next one.
  • No tolerant query.where ?? query rung anywhere. Correct, and worth stating why: accepting both shapes would have made the app work while leaving objectstack#14175 invisible, and the next application to read that type would make the same choice with nothing to warn it.
  • The ablation is the real evidence. Restoring the pre-fix handler over the fixed tree turns three suites red — 23 failures across catalog-apply-cadence (2), catalog-engine-facade (7) and catalog-instantiate (14) — where before the fix the tripwire was the only thing that could see the defect at all. That number is the answer to "did the tests actually gain coverage", and it is a much better answer than a passing run.
  • The tripwire was deleted rather than adjusted, as the card asked.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 10:24
@os-warren
os-warren merged commit 1243cef into mainSep 1, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

duly_catalog_apply finds no catalog items through the real action dispatcher — it reports a successful run of zero

1 participant

@os-warren