refactor: missing-fact discipline in proof witness generation
- Remove defensive ?? 0 fallbacks in buildFailedPredicate that could silently report false geometry in proof witnesses - Require all 4 rect fields present before building subjectRect/referenceRect (was only checking subjectLeft) - Add explicit undefined guards for observed gap, measured values, delta, and aspect ratio — return undefined (no failedPredicate) when critical metrics are missing instead of fabricating 0 defaults - Generic synthesizeGenericFailedPredicate checks value presence instead of defaulting metrics[keys] to 0 - Option defaults (minGap, maxGap, tolerance, min, max bounds) retain ?? 0/?? Infinity as correct neutral values for user parameters
This commit is contained in:
@@ -59,9 +59,20 @@ function buildFailedPredicate(
|
||||
|
||||
// --- Directional gap (leftOf / rightOf / above / below) ---
|
||||
if (hasGap) {
|
||||
const gap = metrics.observedGap ?? metrics.gap ?? 0;
|
||||
const gap = metrics.observedGap ?? metrics.gap;
|
||||
if (gap === undefined) return undefined;
|
||||
const min = metrics.minGap ?? 0;
|
||||
const max = metrics.maxGap ?? Infinity;
|
||||
const hasSubjectRect =
|
||||
metrics.subjectLeft !== undefined &&
|
||||
metrics.subjectTop !== undefined &&
|
||||
metrics.subjectRight !== undefined &&
|
||||
metrics.subjectBottom !== undefined;
|
||||
const hasRefRect =
|
||||
metrics.refLeft !== undefined &&
|
||||
metrics.refTop !== undefined &&
|
||||
metrics.refRight !== undefined &&
|
||||
metrics.refBottom !== undefined;
|
||||
return {
|
||||
...base,
|
||||
op: gap < min ? '<' : '>',
|
||||
@@ -70,22 +81,20 @@ function buildFailedPredicate(
|
||||
measuredGap: gap,
|
||||
expectedMinGap: Number.isFinite(min) ? min : undefined,
|
||||
expectedMaxGap: Number.isFinite(max) ? max : undefined,
|
||||
subjectRect:
|
||||
metrics.subjectLeft !== undefined
|
||||
subjectRect: hasSubjectRect
|
||||
? {
|
||||
left: metrics.subjectLeft,
|
||||
top: metrics.subjectTop ?? 0,
|
||||
right: metrics.subjectRight ?? 0,
|
||||
bottom: metrics.subjectBottom ?? 0,
|
||||
top: metrics.subjectTop,
|
||||
right: metrics.subjectRight,
|
||||
bottom: metrics.subjectBottom,
|
||||
}
|
||||
: undefined,
|
||||
referenceRect:
|
||||
metrics.refLeft !== undefined
|
||||
referenceRect: hasRefRect
|
||||
? {
|
||||
left: metrics.refLeft,
|
||||
top: metrics.refTop ?? 0,
|
||||
right: metrics.refRight ?? 0,
|
||||
bottom: metrics.refBottom ?? 0,
|
||||
top: metrics.refTop,
|
||||
right: metrics.refRight,
|
||||
bottom: metrics.refBottom,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
@@ -93,37 +102,41 @@ function buildFailedPredicate(
|
||||
|
||||
// --- Containment / inside (unique overflow metrics) ---
|
||||
if (kind === 'inside') {
|
||||
const overflowLeft = metrics.overflowLeft ?? 0;
|
||||
const overflowTop = metrics.overflowTop ?? 0;
|
||||
const overflowRight = metrics.overflowRight ?? 0;
|
||||
const overflowBottom = metrics.overflowBottom ?? 0;
|
||||
const hasSubjectRect =
|
||||
metrics.subjectLeft !== undefined &&
|
||||
metrics.subjectTop !== undefined &&
|
||||
metrics.subjectRight !== undefined &&
|
||||
metrics.subjectBottom !== undefined;
|
||||
const hasRefRect =
|
||||
metrics.refLeft !== undefined &&
|
||||
metrics.refTop !== undefined &&
|
||||
metrics.refRight !== undefined &&
|
||||
metrics.refBottom !== undefined;
|
||||
return {
|
||||
...base,
|
||||
op: 'not-contained',
|
||||
left: 0,
|
||||
right: 0,
|
||||
overflowEdges: {
|
||||
left: overflowLeft,
|
||||
top: overflowTop,
|
||||
right: overflowRight,
|
||||
bottom: overflowBottom,
|
||||
left: metrics.overflowLeft ?? 0,
|
||||
top: metrics.overflowTop ?? 0,
|
||||
right: metrics.overflowRight ?? 0,
|
||||
bottom: metrics.overflowBottom ?? 0,
|
||||
},
|
||||
subjectRect:
|
||||
metrics.subjectLeft !== undefined
|
||||
subjectRect: hasSubjectRect
|
||||
? {
|
||||
left: metrics.subjectLeft,
|
||||
top: metrics.subjectTop ?? 0,
|
||||
right: metrics.subjectRight ?? 0,
|
||||
bottom: metrics.subjectBottom ?? 0,
|
||||
top: metrics.subjectTop,
|
||||
right: metrics.subjectRight,
|
||||
bottom: metrics.subjectBottom,
|
||||
}
|
||||
: undefined,
|
||||
referenceRect:
|
||||
metrics.refLeft !== undefined
|
||||
referenceRect: hasRefRect
|
||||
? {
|
||||
left: metrics.refLeft,
|
||||
top: metrics.refTop ?? 0,
|
||||
right: metrics.refRight ?? 0,
|
||||
bottom: metrics.refBottom ?? 0,
|
||||
top: metrics.refTop,
|
||||
right: metrics.refRight,
|
||||
bottom: metrics.refBottom,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
@@ -131,7 +144,8 @@ function buildFailedPredicate(
|
||||
|
||||
// --- Size threshold (atLeast / atMost / between) ---
|
||||
if (isSize && kind !== 'aspectRatio') {
|
||||
const observed = metrics.observed ?? metrics.value ?? 0;
|
||||
const observed = metrics.observed ?? metrics.value;
|
||||
if (observed === undefined) return undefined;
|
||||
const min = metrics.min ?? -Infinity;
|
||||
const max = metrics.max ?? Infinity;
|
||||
return {
|
||||
@@ -147,7 +161,8 @@ function buildFailedPredicate(
|
||||
|
||||
// --- Aspect ratio ---
|
||||
if (kind === 'aspectRatio') {
|
||||
const observed = metrics.observed ?? 0;
|
||||
const observed = metrics.observed;
|
||||
if (observed === undefined) return undefined;
|
||||
const minRatio = metrics.minRatio ?? -Infinity;
|
||||
const maxRatio = metrics.maxRatio ?? Infinity;
|
||||
return {
|
||||
@@ -163,7 +178,8 @@ function buildFailedPredicate(
|
||||
|
||||
// --- Alignment (alignedWith / centeredWithin) ---
|
||||
if (hasAxis || kind === 'centeredWithin') {
|
||||
const delta = metrics.delta ?? metrics.deltaX ?? metrics.deltaY ?? 0;
|
||||
const delta = metrics.delta ?? metrics.deltaX ?? metrics.deltaY;
|
||||
if (delta === undefined) return undefined;
|
||||
const tolerance = metrics.tolerance ?? 0;
|
||||
return {
|
||||
...base,
|
||||
@@ -188,16 +204,21 @@ function synthesizeGenericFailedPredicate(
|
||||
): Proof['failedPredicate'] {
|
||||
const keys = Object.keys(metrics);
|
||||
if (keys.length >= 2) {
|
||||
const left = metrics[keys[0]];
|
||||
const right = metrics[keys[1]];
|
||||
if (left === undefined || right === undefined) return undefined;
|
||||
return {
|
||||
op: '<',
|
||||
left: metrics[keys[0]] ?? 0,
|
||||
right: metrics[keys[1]] ?? 0,
|
||||
left,
|
||||
right,
|
||||
};
|
||||
}
|
||||
if (keys.length === 1) {
|
||||
const val = metrics[keys[0]];
|
||||
if (val === undefined) return undefined;
|
||||
return {
|
||||
op: '<',
|
||||
left: metrics[keys[0]] ?? 0,
|
||||
left: val,
|
||||
right: 0,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user