fix(forms): gate value_indexed population on is_filterable

FormValueObserver: value_indexed is filter-driven per ARCH §4.4, not
hint-driven. Populating it for every string-hint field produced dead
weight in the partial index and made FilterQueryBuilder logic murkier.

Behaviour after fix:
  hint=string,  is_filterable=true  → populate value_indexed
  hint=string,  is_filterable=false → leave null
  hint=number/date/bool, any filterable → populate typed column (unchanged)
  hint=json, any filterable → leave typed columns null (unchanged)

value_number / value_date / value_bool remain hint-driven — they serve
display and sorting beyond filtering. Only value_indexed is gated.

VerifyFormsDataIntegrity: "value_indexed set on non-filterable field"
is now a FAIL (was WARN) — it means the observer didn't run correctly,
which is a real integrity issue.

Observer tests: split the old "string hint populates value_indexed"
case into filterable/non-filterable pair. Full suite 911/911.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 15:28:15 +02:00
parent 021a3cd079
commit ccdfd5b77b
3 changed files with 28 additions and 15 deletions

View File

@@ -259,19 +259,15 @@ final class VerifyFormsDataIntegrity extends Command
])
->count();
if ($orphanSub > 0 || $orphanField > 0 || $dup > 0 || $longIndexed > 0 || $multiValueIndexed > 0) {
if ($orphanSub > 0 || $orphanField > 0 || $dup > 0 || $longIndexed > 0 || $multiValueIndexed > 0 || $nonFilterableIndexed > 0) {
$this->recordFailure('Value coherence',
"{$orphanSub} orphan submission, {$orphanField} orphan field, {$dup} duplicate pairs, {$longIndexed} over-length value_indexed, {$multiValueIndexed} multi-value rows with value_indexed set"
"{$orphanSub} orphan submission, {$orphanField} orphan field, {$dup} duplicate pairs, {$longIndexed} over-length value_indexed, {$multiValueIndexed} multi-value rows with value_indexed set, {$nonFilterableIndexed} value_indexed set on non-filterable field",
'observer should only populate value_indexed when field.is_filterable=true — re-save affected rows to let FormValueObserver reconcile'
);
return;
}
// Warning only — doesn't fail the check.
if ($nonFilterableIndexed > 0) {
$this->warn(" [WARN] {$nonFilterableIndexed} form_values have value_indexed set for a non-filterable field");
}
$this->recordPass('Value coherence', "{$total} values verified");
}