Skip to content

Checking a catalog: solvi check

solvi check lints a catalog for mistakes that can sit in it for a long time before a decision shows them:

solvi check myapp.decisions:system            # exit 0: no errors; 1: errors; 2: usage errors
solvi check myapp.decisions:system --strict   # warnings fail too;  --json for data
solvi check gallery/01_support_triage         # a task file or a directory with task.py, as for solvi test
from solvi.check import lint
rep = lint(system)                            # or lint(catalog): the checks that need questions are skipped
print(rep); rep.ok; rep.errors; rep.warnings  # each finding: level, code, where, message

Flows are planned with every given fact present. Errors: a hard check whose then= sets an answer for a question whose flow never runs it (then_not_in_flow: the question's rule does not read it through any fact and the question does not list it in requires, so when the check fails the question is answered as if it had passed — the fix is requires=[...]); then= naming no question or an answer outside the question's options; facts that need each other (cycle; facts derived from each other, each with a producer outside the loop, are only a note, mutual_producers, for a System with strategist= — the flows are planned by the system's own strategist); a question no input can answer (a fact nothing can compute, a missing required part, a span / rank / estimate question without a rule); a producer's type its consumer cannot read, or a System(input_model=...) field its typed reader cannot read (type_conflict); a producer's validate that requires an argument no producer of its fact takes as an input (validate_reads_unknown: it cannot run, so every output of that producer would be rejected — System(...) refuses such a catalog, and a part that is not an alternative producer is refused when it is declared); constraints between answers that no combination satisfies — one alone or all together, tried by brute force over the answers' finite domains (yes/no, choice, ordinal, multi-label up to 10 options; up to --max-combos combinations per group of constraints that share questions) — and a constraint reading a name that is not a question (it never applies); a rule with a return <literal> that is not one of its question's options (rule_returns_non_option: return "aprove" — on that path the question abstains); a hard check without -> bool with a return that is plainly not True or False (hard_check_untyped: return 0, return None, a bare return — on that path the check is rejected and the questions it governs abstain). Both read the function's source: only literals in return statements (also in a if c else b) are judged, a returned variable or call is not. Warnings: a rule registered for a question the system does not ask (unused_rule); with System(input_model=Model), an argument that no part computes and the model does not declare (input_not_declared: amout for amount — it could only arrive as an extra key, and never when the model forbids extra keys), and a uses hint naming neither a part nor a field of the model (uses_unknown; without an input model any such name is taken for a given fact, so a typo in uses cannot be told there); a part no question's flow uses (a question without a rule, fit or uses counts as using everything computable: its future head's candidate features); then= on a soft check (ignored); a rule reading a question's name (answers are not facts); typed readers of a given fact, or alternative producers, whose types no value satisfies together; an option the constraints always rule out (dead_option); a constraint that raises on some answers; and silent defaults: in a function that reads the input (a given fact), x or <literal> and d.get(k, <literal>) on that input (amount or 0, order.get("total", 0), order["tax"] or 0) turn a missing, empty or null input into a value nobody gave — the answer looks decided while it rests on a guess. A lookup in a constant table ({...}.get(kind, 1)) or a default on a computed value is not flagged. Say what a missing input means (check for None and abstain, or declare the default in System(input_model=...)), or mark the line # solvi: ok. Notes never fail: a question without a rule abstains until an answer head is fitted.