fix: propagate operand metrics through boolean connectives

- evaluateAnd: carry left metrics on left-fail short-circuit,
  carry right metrics on right fail/indeterminate
- evaluateOr: carry left metrics on left-pass short-circuit,
  carry right metrics on right pass, merge both on double-fail
- evaluateNot: carry operand metrics when operand passes
  (so not fails with diagnostic context), carry on indeterminate
- evaluateImplies: carry consequent metrics on fail/indeterminate

This ensures compound formula failures preserve measured geometry
context (gap, dimensions, direction) rather than presenting empty
diagnostics to users investigating layout contract violations.
This commit is contained in:
John Dvorak
2026-05-21 17:14:00 -07:00
parent 5830d5861e
commit 16dcf42762
@@ -548,6 +548,7 @@ function evaluateAnd(
formulaId,
outcome: 'fail',
truth: 'determinate',
metrics: leftResult.metrics,
};
state.proofs.push(makeProof(state, formulaId, 'fail', 'determinate'));
addTrace(state, 'evaluate-and-short-circuit', formulaId, { side: 'left', reason: 'fail' });
@@ -557,6 +558,7 @@ function evaluateAnd(
const rightResult = evaluateFormula(formula.right, env, state);
let outcome: 'pass' | 'fail' | 'indeterminate';
let truth: 'determinate' | 'indeterminate';
let metrics: Record<string, number> | undefined;
if (rightResult.outcome === 'pass') {
outcome = 'pass';
@@ -564,15 +566,18 @@ function evaluateAnd(
} else if (rightResult.outcome === 'indeterminate') {
outcome = 'indeterminate';
truth = 'indeterminate';
metrics = rightResult.metrics;
} else {
outcome = 'fail';
truth = 'determinate';
metrics = rightResult.metrics;
}
const result: FormulaResult = {
formulaId,
outcome,
truth,
metrics,
};
state.proofs.push(makeProof(state, formulaId, result.outcome, result.truth));
addTrace(state, 'evaluate-and-end', formulaId, { outcome });
@@ -593,6 +598,7 @@ function evaluateOr(
formulaId,
outcome: 'pass',
truth: 'determinate',
metrics: leftResult.metrics,
};
state.proofs.push(makeProof(state, formulaId, 'pass', 'determinate'));
addTrace(state, 'evaluate-or-short-circuit', formulaId, { side: 'left', reason: 'pass' });
@@ -602,22 +608,27 @@ function evaluateOr(
const rightResult = evaluateFormula(formula.right, env, state);
let outcome: 'pass' | 'fail' | 'indeterminate';
let truth: 'determinate' | 'indeterminate';
let metrics: Record<string, number> | undefined;
if (rightResult.outcome === 'pass') {
outcome = 'pass';
truth = 'determinate';
metrics = rightResult.metrics;
} else if (leftResult.outcome === 'indeterminate' || rightResult.outcome === 'indeterminate') {
outcome = 'indeterminate';
truth = 'indeterminate';
metrics = rightResult.metrics ?? leftResult.metrics;
} else {
outcome = 'fail';
truth = 'determinate';
metrics = { ...leftResult.metrics, ...rightResult.metrics };
}
const result: FormulaResult = {
formulaId,
outcome,
truth,
metrics,
};
state.proofs.push(makeProof(state, formulaId, result.outcome, result.truth));
addTrace(state, 'evaluate-or-end', formulaId, { outcome });
@@ -639,10 +650,12 @@ function evaluateNot(
const operandResult = evaluateFormula(formula.operand, env, state);
let outcome: 'pass' | 'fail' | 'indeterminate';
let truth: 'determinate' | 'indeterminate';
let metrics: Record<string, number> | undefined;
if (operandResult.outcome === 'pass') {
outcome = 'fail';
truth = 'determinate';
metrics = operandResult.metrics;
} else if (operandResult.outcome === 'fail') {
outcome = 'pass';
truth = 'determinate';
@@ -653,12 +666,14 @@ function evaluateNot(
} else {
outcome = 'indeterminate';
truth = 'indeterminate';
metrics = operandResult.metrics;
}
const result: FormulaResult = {
formulaId,
outcome,
truth,
metrics,
};
state.proofs.push(makeProof(state, formulaId, result.outcome, result.truth));
@@ -701,6 +716,7 @@ function evaluateImplies(
const consequentResult = evaluateFormula(formula.consequent, env, state);
let outcome: 'pass' | 'fail' | 'indeterminate';
let truth: 'determinate' | 'indeterminate';
let metrics: Record<string, number> | undefined;
if (consequentResult.outcome === 'pass') {
outcome = 'pass';
@@ -708,15 +724,18 @@ function evaluateImplies(
} else if (consequentResult.outcome === 'indeterminate') {
outcome = 'indeterminate';
truth = 'indeterminate';
metrics = consequentResult.metrics;
} else {
outcome = 'fail';
truth = 'determinate';
metrics = consequentResult.metrics;
}
const result: FormulaResult = {
formulaId,
outcome,
truth,
metrics,
};
state.proofs.push(makeProof(state, formulaId, result.outcome, result.truth));
addTrace(state, 'evaluate-implies-end', formulaId, { outcome });