feat(contract): support "at least one of" material choke groups

Merged
JI
4mo ago
+491-56

Add new group field to contract materials to denote oneOf behaviour and validation

Ungrouped materials (group == ""): unchanged — required unless optional.
Grouped materials: partition by group value. For each group, at least one member
must be present in the crafted materials; otherwise the group is reported missing
(e.g. at least one material from group "sbom-source" is required: cyclonedx-sbom, spdx-sbom).
A member’s individual optional flag is ignored for grouped materials (the group rule governs).

Closes https://github.com/chainloop-dev/chainloop/issues/3234

Example:

apiVersion: chainloop.dev/v1
kind: Contract
metadata:
  name: $C
spec:
  materials:
    - name: build-ref
      type: STRING
    - name: sbom-cyclonedx
      type: SBOM_CYCLONEDX_JSON
      group: sbom
    - name: sbom-spdx
      type: SBOM_SPDX_JSON
      group: sbom
EOF
> go run app/cli/main.go --insecure attestation status 2>&1
...

┌────────────────────────────────────────┐
│ Materials                              │
├───────────┬────────────────────────────┤
│ Name      │ build-ref                  │
│ Type      │ STRING                     │
│ Set       │ No                         │
│ Required  │ Yes                        │
├───────────┼────────────────────────────┤
│ Group     │ sbom                       │
│ Rule      │ at least one of 2 required │
│ Satisfied │ No                         │
├───────────┼────────────────────────────┤
│ Name      │ ↳ sbom-cyclonedx           │
│ Type      │ SBOM_CYCLONEDX_JSON        │
│ Set       │ No                         │
├───────────┼────────────────────────────┤
│ Name      │ ↳ sbom-spdx                │
│ Type      │ SBOM_SPDX_JSON             │
│ Set       │ No                         │
└───────────┴────────────────────────────┘

AI assistance: this contribution was produced with assistance from Claude Code (disclosed via the Assisted-by trailer on the commits).