Describe GROQ boolean expressions — filters, predicates, gates — without an LLM:
analyzeCondition — static analysis from the groq-js AST: which
$params the expression reads, and its boolean structure as an and/or
clause tree (negation pushed to the leaves).explainCondition — evaluate against an in-memory dataset + params:
three-valued per-atom verdicts (GROQ's Kleene logic), the unmet frontier
(blockedBy), single-fix pivotality, and solvable requirements
("$fields.approved must be true").whatIfCondition — counterfactual re-evaluation with one param value
replaced.describeCondition / checklistLines — deterministic English
phrasing as a checklist; every phrase is an InsightPhrase
({id, params, text}) so i18n consumers translate by message id. Hosts
wire their own vocabulary through DescribeContext seams (renderRead,
renderRequirement, atomPatterns, nowParam); anything unphrasable
falls back to its GROQ and marks fullyDescribed: false.import {explainCondition, describeCondition, checklistLines} from '@sanity/groq-condition-describe'
const insight = await explainCondition({
condition: '$fields.legalApproved == true && $allActivitiesDone',
dataset: [],
params: {fields: {}, allActivitiesDone: true},
})
const description = describeCondition(insight, {})
description.summary // 'Needs: $fields (legalApproved) must be true'
checklistLines(description) // ['All of:', ' ✗ …', ' ✓ …']
Extracted from @sanity/workflow-engine, which layers its workflow
vocabulary (field titles, role/claim desugar shapes, $now) onto these
seams — see its insight surface for the fully-worded result.