refactor: remove global registry fallbacks — factory pattern for test isolation
predicates.ts: Add populateDefaultPredicates(registry) accepting any PredicateRegistry. Add createDefaultPredicateRegistry() factory. registerDefaultPredicates() now delegates to populateDefaultPredicates on the global (backward compatible). logic-engine.ts: Replace globalPredicateRegistry fallback with createDefaultPredicateRegistry() factory. Each evaluateLogic() call creates a fresh self-populated registry unless one is explicitly injected. No shared mutable state between evaluations. engine.ts: Same pattern for clauses — add populateDefaultClauses(registry), createDefaultClauseRegistry() factory. registerDefaultClauses() now delegates to populateDefaultClauses on the global. evaluate() replaces globalClauseRegistry fallback with createDefaultClauseRegistry(). registry.ts: @deprecated tag on registerClause with migration note. Both global registries remain for backward compatibility via the deprecated registerDefault*() functions, but the evaluation engines no longer depend on them. Every evaluation gets its own registry by default, so custom predicates registered by one test cannot leak into another. Tests using explicit registry injection are unaffected. 662 tests pass (315 DSL + 141 core + 149 solver + 57 E2E).
This commit is contained in:
@@ -1200,45 +1200,56 @@ export const hasGapPredicate: PredicateEvaluator = {
|
||||
/** Sentinel registered to detect if defaults were already installed. */
|
||||
const DEFAULT_SENTINEL = '__imhotep_defaults_registered__'
|
||||
|
||||
export function registerDefaultPredicates(): void {
|
||||
if (globalPredicateRegistry.get(DEFAULT_SENTINEL)) return
|
||||
// Register sentinel first so partial failures don't cause infinite loops.
|
||||
globalPredicateRegistry.register({
|
||||
/** Populate a PredicateRegistry with all 33 built-in predicates (idempotent). */
|
||||
export function populateDefaultPredicates(registry: PredicateRegistry): void {
|
||||
if (registry.get(DEFAULT_SENTINEL)) return
|
||||
registry.register({
|
||||
descriptor: { name: DEFAULT_SENTINEL, arity: 0, domains: [], requiredFacts: [] },
|
||||
evaluateTuple: () => ({ truth: 'indeterminate' }),
|
||||
})
|
||||
registerPredicate(widthPredicate);
|
||||
registerPredicate(heightPredicate);
|
||||
registerPredicate(abovePredicate);
|
||||
registerPredicate(belowPredicate);
|
||||
registerPredicate(leftOfPredicate);
|
||||
registerPredicate(rightOfPredicate);
|
||||
registerPredicate(insidePredicate);
|
||||
registerPredicate(containsPredicate);
|
||||
registerPredicate(overlapsPredicate);
|
||||
registerPredicate(alignedWithPredicate);
|
||||
registerPredicate(centeredWithinPredicate);
|
||||
registerPredicate(atLeastPredicate);
|
||||
registerPredicate(atMostPredicate);
|
||||
registerPredicate(betweenPredicate);
|
||||
registerPredicate(clippedByPredicate);
|
||||
registerPredicate(attachedToScrollContainerPredicate);
|
||||
registerPredicate(escapeClippingChainOfPredicate);
|
||||
registerPredicate(aspectRatioPredicate);
|
||||
registerPredicate(inStackingContextPredicate);
|
||||
registerPredicate(separatedFromPredicate);
|
||||
registerPredicate(leftAlignedWithPredicate);
|
||||
registerPredicate(rightAlignedWithPredicate);
|
||||
registerPredicate(topAlignedWithPredicate);
|
||||
registerPredicate(bottomAlignedWithPredicate);
|
||||
registerPredicate(intersectsPredicate);
|
||||
registerPredicate(touchesPredicate);
|
||||
registerPredicate(hasGapPredicate);
|
||||
registerPredicate(besidePredicate);
|
||||
registerPredicate(nextToPredicate);
|
||||
registerPredicate(adjacentPredicate);
|
||||
registerPredicate(touchingPredicate);
|
||||
registerPredicate(nearPredicate);
|
||||
registerPredicate(underPredicate);
|
||||
registerPredicate(withinPredicate);
|
||||
registry.register(widthPredicate)
|
||||
registry.register(heightPredicate)
|
||||
registry.register(abovePredicate)
|
||||
registry.register(belowPredicate)
|
||||
registry.register(leftOfPredicate)
|
||||
registry.register(rightOfPredicate)
|
||||
registry.register(insidePredicate)
|
||||
registry.register(containsPredicate)
|
||||
registry.register(overlapsPredicate)
|
||||
registry.register(alignedWithPredicate)
|
||||
registry.register(centeredWithinPredicate)
|
||||
registry.register(atLeastPredicate)
|
||||
registry.register(atMostPredicate)
|
||||
registry.register(betweenPredicate)
|
||||
registry.register(clippedByPredicate)
|
||||
registry.register(attachedToScrollContainerPredicate)
|
||||
registry.register(escapeClippingChainOfPredicate)
|
||||
registry.register(aspectRatioPredicate)
|
||||
registry.register(inStackingContextPredicate)
|
||||
registry.register(separatedFromPredicate)
|
||||
registry.register(leftAlignedWithPredicate)
|
||||
registry.register(rightAlignedWithPredicate)
|
||||
registry.register(topAlignedWithPredicate)
|
||||
registry.register(bottomAlignedWithPredicate)
|
||||
registry.register(intersectsPredicate)
|
||||
registry.register(touchesPredicate)
|
||||
registry.register(hasGapPredicate)
|
||||
registry.register(besidePredicate)
|
||||
registry.register(nextToPredicate)
|
||||
registry.register(adjacentPredicate)
|
||||
registry.register(touchingPredicate)
|
||||
registry.register(nearPredicate)
|
||||
registry.register(underPredicate)
|
||||
registry.register(withinPredicate)
|
||||
}
|
||||
|
||||
/** Create a fresh PredicateRegistry with all 33 built-in predicates pre-registered. */
|
||||
export function createDefaultPredicateRegistry(): PredicateRegistry {
|
||||
const registry = new PredicateRegistry()
|
||||
populateDefaultPredicates(registry)
|
||||
return registry
|
||||
}
|
||||
|
||||
export function registerDefaultPredicates(): void {
|
||||
populateDefaultPredicates(globalPredicateRegistry)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user