v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)

This commit is contained in:
John Dvorak
2025-08-15 10:00:00 -07:00
commit 92deb689cd
321 changed files with 79170 additions and 0 deletions
@@ -0,0 +1,257 @@
/**
* Canonical formula adapter for the Imhotep solver.
*
* Bridges canonical formula nodes (from Stream 0 contracts) to the solver's
* internal logic-ast shape, and adapts solver evaluation results back to
* canonical result shapes.
*
* All cross-package handoffs go through this file — no implicit casts.
*/
import type {
// Canonical contracts
CanonicalFormulaNode,
CanonicalTupleBinding,
CanonicalDomainRef,
CanonicalVariableRef,
CanonicalAccessorTerm,
CanonicalTermNode,
CanonicalDeterministicSceneResult,
CanonicalDiagnostic,
// Solver-facing contracts from logic-ast
FormulaNode,
ForAllFormula,
ExistsFormula,
AndFormula,
OrFormula,
NotFormula,
ImpliesFormula,
PredicateCall,
VariableRef,
DomainRef,
AccessorTerm,
TupleBinding,
} from 'imhotep-core'
import type {
DeterministicSceneEvaluation,
FormulaResult,
} from './logic-engine.js'
// ---------------------------------------------------------------------------
// Solver Formula Node (explicit alias for the shape the engine consumes)
// ---------------------------------------------------------------------------
/** The solver engine accepts FormulaNode from imhotep-core logic-ast. */
export type SolverFormulaNode = FormulaNode
/** The solver engine accepts DomainValue from imhotep-core domains. */
export interface SolverResult extends DeterministicSceneEvaluation {}
/** Canonical clause result produced by adapting a solver evaluation. */
export interface CanonicalClauseResult extends CanonicalDeterministicSceneResult {}
// ---------------------------------------------------------------------------
// Canonical → Solver
// ---------------------------------------------------------------------------
/**
* Adapt a canonical formula node to the solver's FormulaNode shape.
*
* The canonical shape lacks the `type` discriminator on formula nodes and
* term nodes; this adapter adds the required discriminators so the solver
* engine can consume the formula without modification.
*/
export function adaptCanonicalFormulaToSolver(
formula: CanonicalFormulaNode,
): SolverFormulaNode {
switch (formula.kind) {
case 'forall':
return adaptForAll(formula)
case 'exists':
return adaptExists(formula)
case 'and':
return adaptAnd(formula)
case 'or':
return adaptOr(formula)
case 'not':
return adaptNot(formula)
case 'implies':
return adaptImplies(formula)
case 'predicate':
return adaptPredicate(formula)
default:
// Exhaustiveness check — if we hit this, canonical added a new kind.
throw new Error(
`Cannot adapt unknown canonical formula kind: ${(formula as CanonicalFormulaNode).kind}`,
)
}
}
function adaptForAll(formula: Extract<CanonicalFormulaNode, { kind: 'forall' }>): ForAllFormula {
return {
type: 'FormulaNode',
kind: 'forall',
bindings: formula.bindings.map(adaptTupleBinding),
body: adaptCanonicalFormulaToSolver(formula.body),
}
}
function adaptExists(formula: Extract<CanonicalFormulaNode, { kind: 'exists' }>): ExistsFormula {
return {
type: 'FormulaNode',
kind: 'exists',
bindings: formula.bindings.map(adaptTupleBinding),
body: adaptCanonicalFormulaToSolver(formula.body),
}
}
function adaptAnd(formula: Extract<CanonicalFormulaNode, { kind: 'and' }>): AndFormula {
return {
type: 'FormulaNode',
kind: 'and',
left: adaptCanonicalFormulaToSolver(formula.left),
right: adaptCanonicalFormulaToSolver(formula.right),
}
}
function adaptOr(formula: Extract<CanonicalFormulaNode, { kind: 'or' }>): OrFormula {
return {
type: 'FormulaNode',
kind: 'or',
left: adaptCanonicalFormulaToSolver(formula.left),
right: adaptCanonicalFormulaToSolver(formula.right),
}
}
function adaptNot(formula: Extract<CanonicalFormulaNode, { kind: 'not' }>): NotFormula {
return {
type: 'FormulaNode',
kind: 'not',
operand: adaptCanonicalFormulaToSolver(formula.operand),
}
}
function adaptImplies(
formula: Extract<CanonicalFormulaNode, { kind: 'implies' }>,
): ImpliesFormula {
return {
type: 'FormulaNode',
kind: 'implies',
antecedent: adaptCanonicalFormulaToSolver(formula.antecedent),
consequent: adaptCanonicalFormulaToSolver(formula.consequent),
}
}
function adaptPredicate(
formula: Extract<CanonicalFormulaNode, { kind: 'predicate' }>,
): PredicateCall {
return {
type: 'FormulaNode',
kind: 'predicate',
predicate: formula.predicate,
args: formula.args.map(adaptTerm),
}
}
function adaptTupleBinding(binding: CanonicalTupleBinding): TupleBinding {
return {
type: 'TupleBinding',
variables: binding.variables,
domain: adaptDomainRef(binding.domain),
}
}
function adaptDomainRef(ref: CanonicalDomainRef): DomainRef {
return {
type: 'DomainRef',
domain: ref.domain,
selector: ref.selector,
parentVar: ref.parentVar,
}
}
function adaptTerm(term: CanonicalTermNode): VariableRef | DomainRef | AccessorTerm {
if ('name' in term && !('variable' in term) && !('domain' in term)) {
return adaptVariableRef(term as CanonicalVariableRef)
}
if ('domain' in term) {
return adaptDomainRef(term as CanonicalDomainRef)
}
if ('variable' in term && 'property' in term) {
return adaptAccessorTerm(term as CanonicalAccessorTerm)
}
// Fallback for malformed term — treat as empty variable ref.
return { type: 'VariableRef', name: '' }
}
function adaptVariableRef(ref: CanonicalVariableRef): VariableRef {
return {
type: 'VariableRef',
name: ref.name,
}
}
function adaptAccessorTerm(term: CanonicalAccessorTerm): AccessorTerm {
return {
type: 'AccessorTerm',
variable: term.variable,
property: term.property,
}
}
// ---------------------------------------------------------------------------
// Solver → Canonical
// ---------------------------------------------------------------------------
/**
* Adapt a solver deterministic scene evaluation to the canonical result shape.
*
* Maps solver-specific diagnostics to canonical diagnostics, preserves all
* formula results, proofs, and trace events.
*/
export function adaptSolverResultToCanonical(
result: SolverResult,
): CanonicalClauseResult {
return {
mode: result.mode,
sceneId: result.sceneId,
results: result.formulaResults.map(adaptFormulaResult),
proofs: result.proofs,
diagnostics: result.diagnostics.map(adaptSolverDiagnosticToCanonical),
}
}
function adaptFormulaResult(result: FormulaResult): Record<string, unknown> {
return {
formulaId: result.formulaId,
outcome: result.outcome,
truth: result.truth,
witness: result.witness,
metrics: result.metrics,
}
}
function adaptSolverDiagnosticToCanonical(
d: {
code: string
severity: 'error' | 'warning' | 'info'
category?: string
message: string
position?: {
start: { line: number; column: number; offset: number }
end: { line: number; column: number; offset: number }
}
clauseId?: string
},
): CanonicalDiagnostic {
return {
code: d.code,
severity: d.severity,
category: (d.category ?? 'internal-error') as CanonicalDiagnostic['category'],
message: d.message,
source: 'imhotep-solver',
clauseId: d.clauseId,
position: d.position,
}
}