You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Validation rules cannot depend on field values. Two shapes are missing, and between them they account for every constraint I could not encode while modelling the SEEK definition format as a profile (seek-template-model).
1. A conditional rule tests presence, not value.
ConditionalRule parses its condition by splitting on whitespace and discarding the tokens AND, OR, NOT; whatever remains is treated as a field name and checked for presence (validators/rules.py:446-453). So "A OR B" and "(NOT A) OR B" work, but there is no way to write "required when this other field holds a particular value".
The SEEK case: an attribute whose data_type is Controlled Vocabulary must carry cv_terms. With any other data type, cv_terms is meaningless. Not expressible.
2. Cardinality and uniqueness cannot be scoped to a subset.
cardinality constrains the length of a list (min_items/max_items). uniqueness constrains all values of a field within a scope. Neither takes a predicate, so a rule about some of the children cannot be written.
The SEEK cases, all constraints the server enforces and rejects uploads for:
Exactly one attribute per sample type sets is_display_column. It is the row's identity, so zero or two is a defect. Needs a count over the children that match a predicate, not a count of the children.
At most one attribute per sample type has data_type: Registered Sample List (the Input attribute).
The tags source, protocol, sample, data_file and other_material may each be used at most once per sample type — but attributes named Input are exempt from that count. So it is a uniqueness constraint scoped both by value set and by an exclusion.
Evidence this matters
I round-tripped 11 sample types, 8 extended metadata types, 146 sample attributes, 51 extended metadata attributes and 456 vocabulary terms through seek-template-model 0.2. The enum and required-field constraints work well — validate_extracted correctly rejected a bad data_type, a bad isa_tag and a missing isa_tag, the last being an error that previously only surfaced as a 422 from SEEK at upload time.
But the four constraints above had to be checked by a Python script sitting outside metaseed. That script immediately found a real defect the profile reported as valid:
A template with no display column passes validate_extracted cleanly today. The whole point of moving this model into metaseed was to stop maintaining a separate checker, and these four rules are what keeps it alive.
Proposal
Add a where predicate to cardinality and uniqueness, and when/require to conditional.
A deliberately small expression grammar is enough for all of these: field op literal, with op in == != in "not in" > >= < <=, combined with and / or / not and parentheses. No function calls, no arithmetic, no traversal into other entities.
Definition of done
where is accepted on rules of type cardinality and uniqueness; when absent, behaviour is byte-for-byte what it is today.
when and require are accepted on rules of type conditional; the existing presence-only condition keeps working unchanged, so no existing profile changes behaviour.
ValidationRuleSpec gains the three fields, keeping extra="forbid".
The expression grammar is implemented for the operators listed above and documented in the profile-spec reference with one worked example per rule type.
An expression naming a field that the entity does not declare is a spec error, raised at load, in the same loud style as _VALID_RULE_TYPES in validators/engine.py:138-151 — not silently skipped, since a rule that never runs lets invalid data pass.
An expression that fails to parse is likewise a spec error at load, quoting the offending expression.
Predicated rules run in both validate_dataset and validate_extracted, and report the offending entity plus the predicate that selected it.
spec_add_rule in the Hub MCP takes where, when and require.
spec_validate reports these rules in its issue list like any other.
Tests, each proved red before green:
a Controlled Vocabulary attribute with no cv_terms fails; the same attribute with data_type: String and no cv_terms passes;
a sample type with zero display columns fails, one passes, two fails;
a sample type with two Registered Sample List attributes fails, one passes;
two attributes tagged protocol fail, but a second attribute tagged input and named Input passes;
a rule whose where names an unknown field raises at load;
an existing rule with no where/when produces identical results before and after the change.
seek-template-model is updated to carry all four SEEK rules, and re-running the round-trip over the CropXR definitions reproduces the 0 display columns finding from metaseed rather than from an external script.
Context
Found while building seek-template-model 0.1/0.2, a profile whose instances are SEEK template definitions rather than research metadata. Related to #210, which is about identity being inferred rather than declared; this one is about constraints being inexpressible rather than misinferred.
Problem
Validation rules cannot depend on field values. Two shapes are missing, and between them they account for every constraint I could not encode while modelling the SEEK definition format as a profile (
seek-template-model).1. A conditional rule tests presence, not value.
ConditionalRuleparses its condition by splitting on whitespace and discarding the tokensAND,OR,NOT; whatever remains is treated as a field name and checked for presence (validators/rules.py:446-453). So"A OR B"and"(NOT A) OR B"work, but there is no way to write "required when this other field holds a particular value".The SEEK case: an attribute whose
data_typeisControlled Vocabularymust carrycv_terms. With any other data type,cv_termsis meaningless. Not expressible.2. Cardinality and uniqueness cannot be scoped to a subset.
cardinalityconstrains the length of a list (min_items/max_items).uniquenessconstrains all values of a field within a scope. Neither takes a predicate, so a rule about some of the children cannot be written.The SEEK cases, all constraints the server enforces and rejects uploads for:
is_display_column. It is the row's identity, so zero or two is a defect. Needs a count over the children that match a predicate, not a count of the children.data_type: Registered Sample List(theInputattribute).source,protocol,sample,data_fileandother_materialmay each be used at most once per sample type — but attributes namedInputare exempt from that count. So it is a uniqueness constraint scoped both by value set and by an exclusion.Evidence this matters
I round-tripped 11 sample types, 8 extended metadata types, 146 sample attributes, 51 extended metadata attributes and 456 vocabulary terms through
seek-template-model0.2. The enum and required-field constraints work well —validate_extractedcorrectly rejected a baddata_type, a badisa_tagand a missingisa_tag, the last being an error that previously only surfaced as a422from SEEK at upload time.But the four constraints above had to be checked by a Python script sitting outside metaseed. That script immediately found a real defect the profile reported as valid:
A template with no display column passes
validate_extractedcleanly today. The whole point of moving this model into metaseed was to stop maintaining a separate checker, and these four rules are what keeps it alive.Proposal
Add a
wherepredicate tocardinalityanduniqueness, andwhen/requiretoconditional.A deliberately small expression grammar is enough for all of these:
field op literal, withopin== != in "not in" > >= < <=, combined withand/or/notand parentheses. No function calls, no arithmetic, no traversal into other entities.Definition of done
whereis accepted on rules of typecardinalityanduniqueness; when absent, behaviour is byte-for-byte what it is today.whenandrequireare accepted on rules of typeconditional; the existing presence-onlyconditionkeeps working unchanged, so no existing profile changes behaviour.ValidationRuleSpecgains the three fields, keepingextra="forbid"._VALID_RULE_TYPESinvalidators/engine.py:138-151— not silently skipped, since a rule that never runs lets invalid data pass.validate_datasetandvalidate_extracted, and report the offending entity plus the predicate that selected it.spec_add_rulein the Hub MCP takeswhere,whenandrequire.spec_validatereports these rules in its issue list like any other.Controlled Vocabularyattribute with nocv_termsfails; the same attribute withdata_type: Stringand nocv_termspasses;Registered Sample Listattributes fails, one passes;protocolfail, but a second attribute taggedinputand namedInputpasses;wherenames an unknown field raises at load;where/whenproduces identical results before and after the change.seek-template-modelis updated to carry all four SEEK rules, and re-running the round-trip over the CropXR definitions reproduces the0 display columnsfinding from metaseed rather than from an external script.Context
Found while building
seek-template-model0.1/0.2, a profile whose instances are SEEK template definitions rather than research metadata. Related to #210, which is about identity being inferred rather than declared; this one is about constraints being inexpressible rather than misinferred.