feat: implement variable-bound FOL domain resolution for descendants/children

- Extend DomainResolver.resolve() signature to accept optional BindingEnv
  so that parentVar domains can be resolved with runtime variable bindings
- Pass BindingEnv through evaluateForAll/evaluateExists to resolver calls
- Add buildAncestorIndex() to precompute DOM ancestor sets from CDP data
- SelectorDomainResolver now filters descendant domains by the bound parent
  when domain.parentVar is present and ancestor index is available
- Return undefined for parentVar domains when no ancestor index or env
  (prevents silent fallback to global domain resolution)
- Update all test DomainResolver mocks for new resolve interface
- Add 10 unit tests covering ancestor index construction, backward compat,
  descendant filtering, exclusion of non-descendants, empty descendants,
  missing parentVar/env, and no-ancestor-index safety
This commit is contained in:
John Dvorak
2026-05-21 17:05:35 -07:00
parent b7ac0e8f31
commit 19559b658b
9 changed files with 465 additions and 270 deletions
@@ -31,6 +31,7 @@ import {
specStore,
extractWorld,
SelectorDomainResolver,
buildAncestorIndex,
compileCanonicalClauseToFormula,
getSelectorsFromAssertion,
getSelectorsFromFormula,
@@ -552,6 +553,10 @@ export function makeCheckAll(deps: CheckAllDeps): ImhotepUi['checkAll'] {
resolver.register(selector, [])
}
}
const ancestorIndex = buildAncestorIndex(world)
if (ancestorIndex.size > 0) {
resolver.setAncestorIndex(ancestorIndex)
}
for (let i = 0; i < validChecks.length; i++) {
const check = validChecks[i]
@@ -642,6 +647,10 @@ export function makeCheckAll(deps: CheckAllDeps): ImhotepUi['checkAll'] {
for (const [selector, ids] of extracted.selectorToIds) {
resolver.register(selector, ids)
}
const stateAncestorIndex = buildAncestorIndex(extracted.world)
if (stateAncestorIndex.size > 0) {
resolver.setAncestorIndex(stateAncestorIndex)
}
const folResult = evaluateLogic({
formula: check.formula,