finos / finos/architecture-as-code

A prefixItems slot that declares both oneOf and anyOf is unsatisfiable and passes validation

Closed
#3,062 0 comments 0 reactions 1 assignee Claimed by @YoofiTT96 View on GitHub
Dominant language
TypeScript
Stars
399
Forks
138
Avg merge
2d 14h
Merged PRs (30d)
37

Description

## 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:
1. Write a pattern with a slot that declares both keywords:

```json
"nodes": {
"type": "array",
"prefixItems": [
{
"oneOf": [ { "properties": { "unique-id": { "const": "redis" } } } ],
"anyOf": [ { "properties": { "unique-id": { "const": "memcached" } } } ]
}
]
}
```

2. Run `calm validate`.
3. 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.