fix: remove design-debt shims — falsy ID bug, selector normalization, concurrency, exception swallowing
pipeline.ts: || undefined → ?? undefined (9 occurrences) - || converts valid subject ID 0 to undefined because 0 is falsy in JS. This broke clause witnesses and topology references for the first subject. domain-index.ts: remove .toLowerCase() on CSS selectors - CSS selectors are case-sensitive (IDs, class names, attribute values). Lowercasing on lookup but not on storage (selectorIndex) meant case- sensitive selectors never matched — returning empty arrays silently. canonical.ts: add warning when visualBoxes falls back to layout boxes - visualBoxes ?? boxes silently substituted layout coordinates for visual space, producing incorrect results for transform-dependent assertions. Now emits console.warn so silent data corruption is visible. extraction.ts: serialize materializeSemanticSelector calls (3 sites) - Changed Promise.all over page.evaluate() to sequential for..of. While Playwright serializes CDP calls internally, concurrent DOM-modifying evaluate() calls create undefined execution order. Sequential resolution eliminates theoretical race conditions for semantic selector injection. engine.ts: include stack trace in evaluator exception diagnostics - Catch-all converted ALL exceptions (including TypeError from programming bugs) to IMH_EVALUATOR_EXCEPTION with just err.message. Now includes stack trace and logs to console.warn for visibility. Distinguishes TypeError (programming bug) from other evaluation errors. 648 SDK tests + 57 E2E hard tests pass, zero regressions.
This commit is contained in:
@@ -778,7 +778,9 @@ export function adaptSolverWorldToCanonical(
|
|||||||
matrices: solverWorld.matrices ?? { values: [] },
|
matrices: solverWorld.matrices ?? { values: [] },
|
||||||
rects: solverWorld.rects,
|
rects: solverWorld.rects,
|
||||||
boxes: solverWorld.boxes,
|
boxes: solverWorld.boxes,
|
||||||
visualBoxes: solverWorld.visualBoxes ?? solverWorld.boxes,
|
visualBoxes: solverWorld.visualBoxes && solverWorld.visualBoxes.boxId.length > 0
|
||||||
|
? solverWorld.visualBoxes
|
||||||
|
: (console.warn('[imhotep-core] adaptSolverWorldToCanonical: visualBoxes missing, falling back to layout boxes. Visual-space assertions may produce incorrect results.'), solverWorld.boxes),
|
||||||
fragments: {
|
fragments: {
|
||||||
fragmentId: [],
|
fragmentId: [],
|
||||||
subjectId: [],
|
subjectId: [],
|
||||||
|
|||||||
@@ -802,8 +802,8 @@ export function executionIrToClauseDescriptors(context: ExecutionContext): Claus
|
|||||||
clauseKind: 'unknown',
|
clauseKind: 'unknown',
|
||||||
version: 1,
|
version: 1,
|
||||||
subjectRef: executionIr.clauseSubject[i],
|
subjectRef: executionIr.clauseSubject[i],
|
||||||
referenceRef: executionIr.clauseReference[i] || undefined,
|
referenceRef: executionIr.clauseReference[i] ?? undefined,
|
||||||
frameRef: executionIr.clauseFrame[i] || undefined,
|
frameRef: executionIr.clauseFrame[i] ?? undefined,
|
||||||
options: { unsupported: true, rawType: clauseType },
|
options: { unsupported: true, rawType: clauseType },
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
@@ -837,13 +837,13 @@ export function executionIrToClauseDescriptors(context: ExecutionContext): Claus
|
|||||||
clauseId: `clause_${i}`,
|
clauseId: `clause_${i}`,
|
||||||
clauseKind,
|
clauseKind,
|
||||||
version: 1,
|
version: 1,
|
||||||
subjectRef: subjectRef || undefined,
|
subjectRef: subjectRef ?? undefined,
|
||||||
referenceRef: referenceRef || undefined,
|
referenceRef: referenceRef ?? undefined,
|
||||||
frameRef: executionIr.clauseFrame[i] || undefined,
|
frameRef: executionIr.clauseFrame[i] ?? undefined,
|
||||||
stateRef: context.stateIds[executionIr.clauseState[i]] || undefined,
|
stateRef: context.stateIds[executionIr.clauseState[i]] ?? undefined,
|
||||||
timelineRef: context.timelineIds[executionIr.clauseTimeline[i]] || undefined,
|
timelineRef: context.timelineIds[executionIr.clauseTimeline[i]] ?? undefined,
|
||||||
envGuardRef: context.envGuardIds[executionIr.clauseEnvGuard[i]] || undefined,
|
envGuardRef: context.envGuardIds[executionIr.clauseEnvGuard[i]] ?? undefined,
|
||||||
toleranceRef: context.toleranceIds[executionIr.clauseTolerance[i]] || undefined,
|
toleranceRef: context.toleranceIds[executionIr.clauseTolerance[i]] ?? undefined,
|
||||||
bounds: Object.keys(bounds).length > 0 ? bounds : undefined,
|
bounds: Object.keys(bounds).length > 0 ? bounds : undefined,
|
||||||
options: Object.keys(options).length > 0 ? options : undefined,
|
options: Object.keys(options).length > 0 ? options : undefined,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { GeometryWorld } from './world.js'
|
|||||||
* If the selector is not indexed, returns an empty array.
|
* If the selector is not indexed, returns an empty array.
|
||||||
*/
|
*/
|
||||||
export function getElementsBySelector(world: GeometryWorld, selector: string): number[] {
|
export function getElementsBySelector(world: GeometryWorld, selector: string): number[] {
|
||||||
const normalized = selector.trim().toLowerCase()
|
const normalized = selector.trim()
|
||||||
return world.selectorIndex.get(normalized) ?? []
|
return world.selectorIndex.get(normalized) ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,12 +409,11 @@ export async function extractWorldFastGeometry(
|
|||||||
selectorToIds: Array<[string, number[]]>
|
selectorToIds: Array<[string, number[]]>
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectorPlans: SelectorPlan[] = await Promise.all(
|
const selectorPlans: SelectorPlan[] = []
|
||||||
selectors.map(async (key, i) => {
|
for (let i = 0; i < selectors.length; i++) {
|
||||||
const queries = await materializeSemanticSelector(playwrightPage, key, i)
|
const queries = await materializeSemanticSelector(playwrightPage, selectors[i], i)
|
||||||
return { key, queries }
|
selectorPlans.push({ key: selectors[i], queries })
|
||||||
}),
|
}
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const extracted = await playwrightPage.evaluate(({ plans, needs }: any) => {
|
const extracted = await playwrightPage.evaluate(({ plans, needs }: any) => {
|
||||||
@@ -800,12 +799,11 @@ export async function extractWorldCdp(
|
|||||||
const errors: ImhotepDiagnostic[] = []
|
const errors: ImhotepDiagnostic[] = []
|
||||||
const selectorToNodeIds = new Map<string, number[]>()
|
const selectorToNodeIds = new Map<string, number[]>()
|
||||||
|
|
||||||
const selectorPlans: SelectorPlan[] = await Promise.all(
|
const selectorPlans: SelectorPlan[] = []
|
||||||
selectors.map(async (key, i) => {
|
for (let i = 0; i < selectors.length; i++) {
|
||||||
const queries = await materializeSemanticSelector(playwrightPage, key, i)
|
const queries = await materializeSemanticSelector(playwrightPage, selectors[i], i)
|
||||||
return { key, queries }
|
selectorPlans.push({ key: selectors[i], queries })
|
||||||
}),
|
}
|
||||||
)
|
|
||||||
|
|
||||||
const sessionManager = createSessionManager(playwrightPage)
|
const sessionManager = createSessionManager(playwrightPage)
|
||||||
try {
|
try {
|
||||||
@@ -993,12 +991,11 @@ export async function extractWorld(
|
|||||||
|
|
||||||
if (requiredFacts?.styles) {
|
if (requiredFacts?.styles) {
|
||||||
try {
|
try {
|
||||||
const plans: SelectorPlan[] = await Promise.all(
|
const plans: SelectorPlan[] = []
|
||||||
filteredSelectors.map(async (key, i) => {
|
for (let i = 0; i < filteredSelectors.length; i++) {
|
||||||
const queries = await materializeSemanticSelector(playwrightPage, key, i)
|
const queries = await materializeSemanticSelector(playwrightPage, filteredSelectors[i], i)
|
||||||
return { key, queries }
|
plans.push({ key: filteredSelectors[i], queries })
|
||||||
}),
|
}
|
||||||
)
|
|
||||||
const chWidthsBySelector = await measureChWidthsByPlan(playwrightPage, plans)
|
const chWidthsBySelector = await measureChWidthsByPlan(playwrightPage, plans)
|
||||||
attachMeasuredChWidths(result.world, result.selectorToIds, chWidthsBySelector)
|
attachMeasuredChWidths(result.world, result.selectorToIds, chWidthsBySelector)
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -272,7 +272,13 @@ export function evaluate(
|
|||||||
diagnostics.push(...result.diagnostics);
|
diagnostics.push(...result.diagnostics);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
const stack = err instanceof Error ? err.stack : undefined
|
||||||
|
console.warn(`[imhotep-solver] clause evaluator exception for kind "${clause.clauseKind}" (id ${clause.clauseId}): ${message}`)
|
||||||
|
if (stack) console.warn(stack)
|
||||||
|
const detail = err instanceof Error && err.name === 'TypeError'
|
||||||
|
? `UNCAUGHT PROGRAMMING BUG: ${message}\n${stack ?? ''}`
|
||||||
|
: `Evaluator error: ${message}`
|
||||||
const result: ClauseResult = {
|
const result: ClauseResult = {
|
||||||
clauseId: clause.clauseId,
|
clauseId: clause.clauseId,
|
||||||
status: 'error',
|
status: 'error',
|
||||||
@@ -282,7 +288,7 @@ export function evaluate(
|
|||||||
code: 'IMH_EVALUATOR_EXCEPTION',
|
code: 'IMH_EVALUATOR_EXCEPTION',
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
category: 'internal-error',
|
category: 'internal-error',
|
||||||
message,
|
message: detail,
|
||||||
clauseId: clause.clauseId,
|
clauseId: clause.clauseId,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user