Bug Report
A choice block is a oneOf or an anyOf array. A prefixItems slot can hold one. An items catalog is one.
A block can declare both keywords. JSON Schema then requires an element to satisfy both. CALM alternatives pin distinct unique-id values, so the two sets are normally disjoint and no element can satisfy them. The pattern is unsatisfiable. Where the sets overlap, the wider keyword declares alternatives that nothing can select.
Nothing reports this. calm validate accepts the pattern, and calm generate returns an empty selection without a message.
#3060 adds a rule for an items catalog. This issue covers the same fault in a prefixItems slot, which #3060 leaves alone because a slot is an existing construct and the rule is a behaviour change.
Steps to Reproduce:
- Write a pattern with a slot that declares both keywords:
"nodes": {
"type": "array",
"prefixItems": [
{
"oneOf": [ { "properties": { "unique-id": { "const": "redis" } } } ],
"anyOf": [ { "properties": { "unique-id": { "const": "memcached" } } } ]
}
]
}
- Run
calm validate.
- Run
calm generate and choose memcached.
Expected Result:
calm validate reports an error. The block must declare one keyword.
Actual Result:
Step 2 reports nothing. Step 3 returns an empty selection:
askedFor: "memcached" selected: []
The two shapes
JSON Schema combines the keywords with AND. An element must satisfy oneOf and anyOf. It is not a union of alternatives. Each CALM alternative pins a distinct unique-id, so an element matches at most one alternative in each keyword. That gives two shapes, both confirmed against Ajv.
Disjoint. The keywords name different alternatives.
oneOf |
anyOf |
Result |
redis |
memcached |
No element validates. redis fails the anyOf, memcached fails the oneOf. The pattern is unsatisfiable. |
Overlapping. The keywords share alternatives.
oneOf |
anyOf |
Result |
A, B, C |
A, B, C, D, E |
Only A, B and C validate. The narrower keyword binds. D and E are declared and can never be selected. |
Severity
Both shapes are errors.
The disjoint shape is plainly an error, because no architecture can ever match the pattern.
The overlapping shape does generate a valid architecture, so warn is arguable. It is still an error, because the author has declared alternatives that no answer can reach, and the pattern does not say what it appears to say. A rule that reports the shape needs no satisfiability test, which keeps it simple and keeps the message the same in both cases.
Note on the tooling
flattenOneOfAndAnyOf in options.ts resolves item.oneOf ?? item.anyOf, and patternTransformer.ts resolves hasOneOf ? item['oneOf'] : item['anyOf']. Making them read both keywords would not fix this. The two keywords are combined with AND, not OR, so their union is not a list of alternatives. Rejecting the shape is the correct response.
Environment:
shared (@finos/calm-shared). Reproduced on main at 0034591f. No pattern in this repository declares both keywords, so the rule regresses nothing here.
Bug Report
A choice block is a
oneOfor ananyOfarray. AprefixItemsslot can hold one. Anitemscatalog is one.A block can declare both keywords. JSON Schema then requires an element to satisfy both. CALM alternatives pin distinct
unique-idvalues, so the two sets are normally disjoint and no element can satisfy them. The pattern is unsatisfiable. Where the sets overlap, the wider keyword declares alternatives that nothing can select.Nothing reports this.
calm validateaccepts the pattern, andcalm generatereturns an empty selection without a message.#3060 adds a rule for an
itemscatalog. This issue covers the same fault in aprefixItemsslot, which #3060 leaves alone because a slot is an existing construct and the rule is a behaviour change.Steps to Reproduce:
calm validate.calm generateand choosememcached.Expected Result:
calm validatereports an error. The block must declare one keyword.Actual Result:
Step 2 reports nothing. Step 3 returns an empty selection:
The two shapes
JSON Schema combines the keywords with AND. An element must satisfy
oneOfandanyOf. It is not a union of alternatives. Each CALM alternative pins a distinctunique-id, so an element matches at most one alternative in each keyword. That gives two shapes, both confirmed against Ajv.Disjoint. The keywords name different alternatives.
oneOfanyOfredismemcachedredisfails theanyOf,memcachedfails theoneOf. The pattern is unsatisfiable.Overlapping. The keywords share alternatives.
oneOfanyOfA, B, CA, B, C, D, EA,BandCvalidate. The narrower keyword binds.DandEare declared and can never be selected.Severity
Both shapes are errors.
The disjoint shape is plainly an error, because no architecture can ever match the pattern.
The overlapping shape does generate a valid architecture, so
warnis arguable. It is still an error, because the author has declared alternatives that no answer can reach, and the pattern does not say what it appears to say. A rule that reports the shape needs no satisfiability test, which keeps it simple and keeps the message the same in both cases.Note on the tooling
flattenOneOfAndAnyOfinoptions.tsresolvesitem.oneOf ?? item.anyOf, andpatternTransformer.tsresolveshasOneOf ? item['oneOf'] : item['anyOf']. Making them read both keywords would not fix this. The two keywords are combined with AND, not OR, so their union is not a list of alternatives. Rejecting the shape is the correct response.Environment:
shared(@finos/calm-shared). Reproduced onmainat0034591f. No pattern in this repository declares both keywords, so the rule regresses nothing here.