ProjectCascade.java
package network.ike.workspace.cascade;
import java.util.List;
/**
* One project's own {@code src/main/cascade/release-cascade.yaml} —
* its edges in the IKE release cascade (IKE-Network/ike-issues#420).
*
* <p>The cascade is modelled as a loosely-coupled distributed system:
* every project version-controls this file in its own git tree and
* declares <em>only its own</em> {@code upstream} and {@code downstream}
* edges. No project authors the global ordering; the full graph is
* assembled by {@link CascadeAssembler} traversing these files.
*
* <p>The {@code head} and {@code terminal} markers are asserted, not
* inferred. A project at the head of the cascade has no
* {@code upstream} edge, and one at the end has no {@code downstream}
* edge — but an <em>omitted</em> edge looks identical to a genuine
* endpoint, so each endpoint must positively declare itself. The
* canonical constructor rejects a marker that disagrees with the
* corresponding edge list, turning a forgotten edge into a manifest
* error rather than an invisible omission.
*
* @param schema the manifest schema version (currently {@code 1})
* @param head {@code true} iff this project declares no
* {@code upstream} edge — the cascade head
* @param upstream edges to the projects this one consumes; each
* carries a {@code versionProperty}; never
* {@code null}
* @param terminal {@code true} iff this project declares no
* {@code downstream} edge — the cascade terminus
* @param downstream edges to the projects that consume this one;
* never {@code null}
*/
public record ProjectCascade(int schema, boolean head,
List<CascadeEdge> upstream,
boolean terminal,
List<CascadeEdge> downstream) {
/**
* Canonical constructor — defensively copies the edge lists,
* substitutes empty lists for {@code null}, verifies the
* {@code head}/{@code terminal} markers agree with the edge
* lists, and requires a {@code versionProperty} on every
* {@code upstream} edge.
*/
public ProjectCascade {
upstream = upstream == null ? List.of() : List.copyOf(upstream);
downstream = downstream == null ? List.of()
: List.copyOf(downstream);
if (head != upstream.isEmpty()) {
throw new IllegalArgumentException(head
? "release-cascade.yaml declares 'head: true' but"
+ " also lists upstream edges"
: "release-cascade.yaml has no upstream edges and"
+ " must declare 'head: true' — a missing"
+ " upstream edge must not be silently omitted");
}
if (terminal != downstream.isEmpty()) {
throw new IllegalArgumentException(terminal
? "release-cascade.yaml declares 'terminal: true'"
+ " but also lists downstream edges"
: "release-cascade.yaml has no downstream edges and"
+ " must declare 'terminal: true' — a missing"
+ " downstream edge must not be silently omitted");
}
for (CascadeEdge edge : upstream) {
if (edge.versionProperty() == null
|| edge.versionProperty().isBlank()) {
throw new IllegalArgumentException(
"upstream edge " + edge.ga() + " must declare a"
+ " version-property");
}
}
}
}