Bead: mf-mhsi
Plan spec: docs/project/specs/active/plan-2026-02-14-tiered-validation-model.md
Problem
markform set silently accepts semantically invalid values (e.g., lowercase ticker against a pattern constraint), printing only "Form updated" with no indication anything is wrong. The invalid value enters the form and is only caught later by markform validate. This surprises both agents and users, who reasonably expect "set succeeded" to mean "value is valid."
More broadly, the spec groups all semantic checks together regardless of cost, and the CLI surfaces issues inconsistently across commands.
Root cause
The spec (Layer 4) defines two-phase patch validation:
- Structural (pre-apply): field exists, type matches, option ID valid — rejects on failure
- Semantic (post-apply): pattern, range, required, selection counts — accepts value, returns issues
All semantic checks are grouped together and none block writes. But this conflates two very different cost profiles:
- Fast deterministic checks (~0ms): pattern regex, min/max, integer, date format, minLength/maxLength, minItems, uniqueItems
- Expensive checks (ms-seconds): code validators via jiti, LLM validators (MF/0.2), cross-field validation, external API calls
The fast checks have no reason to be deferred. The expensive checks genuinely must be.
Proposed changes
Two-tier validation model (instant vs deferred)
Replace the current two-phase model with two tiers based on cost:
| Tier |
Cost |
When |
Behavior |
| Instant |
~0ms |
During applyPatches() |
Always runs; structural failures reject, constraint failures surface as issues |
| Deferred |
ms-seconds |
During validate() / inspect() |
Must be explicitly triggered |
Within instant validation, there are two failure modes (not separate tiers):
- Structural failures (field missing, type wrong) → reject the patch batch
- Constraint failures (pattern, min/max, required) → accept the patch, surface issues in
ApplyResult.issues
Consistent issue surfacing across CLI and API
Principle: Same InspectIssue[] data model, channel-appropriate presentation.
Specific changes:
- CLI
set: Remove validation_error filter — surface ALL issue reasons, not just validation errors
FillResult.remainingIssues: Align type with InspectIssue[] (currently a subset missing scope, reason, blockedBy)
InspectIssue: Add optional deferred?: boolean flag so callers can distinguish instant vs deferred issues
Open questions
- Should
set return a non-zero exit code when issues exist (even though patches were applied)?
- Is the
FillResult.remainingIssues type change acceptable as a minor breaking change?
Implementation phases
- Spec + types: Update spec Layer 4, add
deferred flag to InspectIssue, align FillResult type
- CLI consistency: Fix
set command issue surfacing, add tests
- Architecture docs: Update design docs and QA playbooks
See full plan spec: docs/project/specs/active/plan-2026-02-14-tiered-validation-model.md
Bead: mf-mhsi
Plan spec:
docs/project/specs/active/plan-2026-02-14-tiered-validation-model.mdProblem
markform setsilently accepts semantically invalid values (e.g., lowercase ticker against a pattern constraint), printing only "Form updated" with no indication anything is wrong. The invalid value enters the form and is only caught later bymarkform validate. This surprises both agents and users, who reasonably expect "set succeeded" to mean "value is valid."More broadly, the spec groups all semantic checks together regardless of cost, and the CLI surfaces issues inconsistently across commands.
Root cause
The spec (Layer 4) defines two-phase patch validation:
All semantic checks are grouped together and none block writes. But this conflates two very different cost profiles:
The fast checks have no reason to be deferred. The expensive checks genuinely must be.
Proposed changes
Two-tier validation model (instant vs deferred)
Replace the current two-phase model with two tiers based on cost:
applyPatches()validate()/inspect()Within instant validation, there are two failure modes (not separate tiers):
ApplyResult.issuesConsistent issue surfacing across CLI and API
Principle: Same
InspectIssue[]data model, channel-appropriate presentation.Specific changes:
set: Removevalidation_errorfilter — surface ALL issue reasons, not just validation errorsFillResult.remainingIssues: Align type withInspectIssue[](currently a subset missingscope,reason,blockedBy)InspectIssue: Add optionaldeferred?: booleanflag so callers can distinguish instant vs deferred issuesOpen questions
setreturn a non-zero exit code when issues exist (even though patches were applied)?FillResult.remainingIssuestype change acceptable as a minor breaking change?Implementation phases
deferredflag toInspectIssue, alignFillResulttypesetcommand issue surfacing, add testsSee full plan spec:
docs/project/specs/active/plan-2026-02-14-tiered-validation-model.md