Validation source — hypothesis resolved
Companion to 03-mform-json-analysis.md (gap A14c) and 04-mform-to-frappe-skill.md (§10). Resolves where mForm validations actually live.
Conclusion (one line)
Validations come from per-question keys (pattern, min, max, minRange, maxRange, restrictions, editable, isToBeEncrypted) plus input_type-implicit rules. The validation[] array with null condition is unused / placeholder. A14c is resolved — no mForm-team escalation needed.
Evidence
Top-level python3 + collections.Counter over every question in form 1000 (Member Profile, 56 questions) and form 1003 (Scheme Followup, 37 questions). Question-level keys we missed in 03-mform-json-analysis.md:
| Key | 1000 (n=56) | 1003 (n=37) | What it is |
|---|---|---|---|
order | 56 | 37 | universal join key (already documented) |
label | 56 | 37 | display label |
shortKey | 56 | 37 | short identifier — likely the basis for Frappe fieldname derivation |
viewSequence | 56 | 37 | render order |
input_type | 56 | 37 | mForm widget code → drives Frappe fieldtype |
validation | 56 | 37 | array; condition always null — placeholder, ignore |
restrictions | 56 | 37 | container for restriction rules — present on every question |
child | 56 | 37 | child-question reference (skip logic / conditional show) |
parent | 56 | 37 | parent-question reference |
editable | 56 | 37 | read/write flag → Frappe read_only |
weightage | 56 | 37 | scoring weight |
isToBeEncrypted | 54 | 28 | encrypt-at-rest flag (mostly true) |
pattern | 21 | 7 | regex pattern (where applicable) → Frappe field-level regex / JS validation |
min / max | 21 | 7 | length or numeric bound |
minRange / maxRange | 11 | 5 | range bound (numeric questions) |
valueHolder | 6 | 2 | placeholder text |
Bolded keys are validation-bearing. They fully explain the rules.
What this means for the import pipeline
For each question, the mapper must read:
input_type→ Frappefieldtype(Data / Int / Float / Select / Link / Date / Table …) — implicit type validation comes free.editable === false→read_only: 1.pattern→ field-level regex / JS client-script validator.min/max→ if the field is text, treat as length bounds; if numeric, treat as min/max value. Disambiguate frominput_type.minRange/maxRange→ numeric range — likely on slider/range input types specifically.restrictions→ open the array shape on at least one form before mapping (we know it’s there, but its keys weren’t sampled in the Counter pass; treat as TODO for the skill author to enumerate).isToBeEncrypted === true→ set the Frappe field’s encryption flag (or post-process via a hook — depends on Frappe version).
validation[].condition === null everywhere → do not consume.
Skill architecture confirms this layered model
The skill’s own documentation (raw/mform-to-frappe-skill/mform_to_frappe/PLAN-corrections.md:115) names a reference file validation-layer-matrix.md, and :258 mentions _validate_conditional_mandatory() — i.e., the skill is already designed for the Frappe validation layers (DocType field flags + JS client script + Python _validate_*() server hooks + child-table rules). That matrix is the canonical lookup; my Counter pass above tells you which mForm keys feed each layer.
Recipes that already encode validation patterns:
recipes/did-range-filter.md—Int+Selectwith min/max rangerecipes/geography-cascade.md— cascadingLinkfields (validation = “must exist in master at level N”)recipes/fetch-from-read-only.md—Link+read_only: 1(editable: falsemapping)recipes/form-with-loops.md—Tableforinput_type 20/21recipes/conditional-visibility.md—depends_onfor skip logicrecipes/scoring-computation.md— usesweightage
Action item update
- A14c — RESOLVED locally. Strike from “things to ask mForm team”. Replace with a one-line note in §10 of
01-kickoff-brief.md: validations come from per-question keys +input_type; the skill’svalidation-layer-matrix.mdis the canonical reference. - A14a (translations) and A14b (master data values) remain open and DO need mForm-team answers.
- New micro-task for the dev: when picking up A16, dump the actual
restrictionsarray shape from one form to confirm its sub-keys. The Counter pass shows it’s present but didn’t enumerate its contents.
Surfaced anti-patterns (skill-author warnings)
The skill repo has TODO/FIXME-style notes worth surfacing to whoever picks up A16:
mform_to_frappe/PLAN-corrections.md:986— “Map every question → Frappe fieldtype” is the third step in their plan, not the first. Step 1 is the(formId, order) → fieldnameregistry (matches our Job-1 finding).mform_to_frappe/PLAN-corrections.md:534— references a “verified 22-widget list” — there’s a known finite widget set; rareinput_typecodes 13/29/30 (from03-mform-json-analysis.md) need a sample before mapping or pipeline must hard-fail.