From fd02f1c7e99375f00db1c139201af95c50274b55 Mon Sep 17 00:00:00 2001 From: ontave Date: Thu, 7 May 2026 09:01:31 +0200 Subject: [PATCH 01/13] fix: stackUpgradeHandler constructs OCI image ref from talosVersion talosImage was being set to the raw version string from the UpgradePolicy (e.g., "v1.12.7") and passed directly to TalosClient.Upgrade, which then tried to pull "docker.io/library/v1.12.7:latest". talosUpgradeHandler correctly builds "ghcr.io/siderolabs/installer:"; stack handler now follows the same pattern. Rename talosImage to talosVersion when reading from the UpgradePolicy, then compute talosImage := "ghcr.io/siderolabs/installer:" + talosVersion. Discovered during live ccs-dev stack upgrade (session/25d). --- internal/capability/platform_upgrade.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/capability/platform_upgrade.go b/internal/capability/platform_upgrade.go index 9a0dc0b..b4c5061 100644 --- a/internal/capability/platform_upgrade.go +++ b/internal/capability/platform_upgrade.go @@ -302,21 +302,22 @@ func (h *stackUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) fmt.Sprintf("list UpgradePolicy in %s: %v", ns, err)), nil } - var talosImage, kubeVersion string + var talosVersion, kubeVersion string for _, item := range crList.Items { ut, _, _ := unstructuredString(item.Object, "spec", "upgradeType") if ut != "stack" { continue } - talosImage, _, _ = unstructuredString(item.Object, "spec", "targetTalosVersion") + talosVersion, _, _ = unstructuredString(item.Object, "spec", "targetTalosVersion") kubeVersion, _, _ = unstructuredString(item.Object, "spec", "targetKubernetesVersion") break } - if talosImage == "" || kubeVersion == "" { + if talosVersion == "" || kubeVersion == "" { return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ValidationFailure, fmt.Sprintf("UpgradePolicy with upgradeType=stack must specify both targetTalosVersion and targetKubernetesVersion in %s", ns)), nil } + talosImage := "ghcr.io/siderolabs/installer:" + talosVersion var steps []runnerlib.StepResult From fe0c6fb208dbe4db7aea02b8abbae41e123a8e5b Mon Sep 17 00:00:00 2001 From: ontave Date: Thu, 7 May 2026 09:15:15 +0200 Subject: [PATCH 02/13] fix: stackUpgradeHandler uses per-node rolling reboot, not staged-only Stage=true left both Talos and kubelet changes sitting on disk indefinitely; nodes required manual reboots to apply them. New behaviour mirrors talosUpgradeHandler: per node, stage the kubelet image (staged mode so it co-applies on the Talos reboot), then trigger Talos upgrade with stage=false (immediate reboot), then wait for recovery before moving to the next node. Drop talosconfig-path node enumeration in favour of TalosClient.Nodes() (same source; cleaner and already tested via the stub). Require at least one node (validation failure otherwise). Tests: rename TestStackUpgrade_RunsBothUpgradeSteps to two tests -- TestStackUpgrade_NoNodesReturnsValidationFailure TestStackUpgrade_RollingUpgrade_AllNodes (verifies per-node loop, upgradeCallCount == node count, all ApplyConfiguration calls use staged mode) --- internal/capability/platform_upgrade.go | 102 +++++++++++++----------- test/unit/capability/platform_test.go | 71 +++++++++++++---- 2 files changed, 111 insertions(+), 62 deletions(-) diff --git a/internal/capability/platform_upgrade.go b/internal/capability/platform_upgrade.go index b4c5061..8c15c13 100644 --- a/internal/capability/platform_upgrade.go +++ b/internal/capability/platform_upgrade.go @@ -282,8 +282,11 @@ func (h *kubeUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) } // stackUpgradeHandler implements the stack-upgrade named capability. -// Combines talos-upgrade and kube-upgrade into a sequenced upgrade of both -// the Talos OS and Kubernetes components. platform-schema.md §5. +// Combines talos-upgrade and kube-upgrade into a sequenced rolling upgrade of +// both the Talos OS and Kubernetes kubelet. Per node: stage the kubelet image +// change, then trigger the Talos upgrade with immediate reboot (stage=false). +// The node reboots once and applies both changes together. Wait for each node +// to recover before proceeding to the next. platform-schema.md §5. type stackUpgradeHandler struct{} func (h *stackUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) (runnerlib.OperationResultSpec, error) { @@ -294,6 +297,12 @@ func (h *stackUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) "stack-upgrade requires TalosClient and DynamicClient"), nil } + nodes := params.TalosClient.Nodes() + if len(nodes) == 0 { + return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ValidationFailure, + "stack-upgrade: no nodes available from talosconfig"), nil + } + ns := tenantNamespace(params.ClusterRef) crList, err := params.DynamicClient.Resource(upgradePolicyGVR).Namespace(ns). List(ctx, metav1.ListOptions{}) @@ -319,66 +328,63 @@ func (h *stackUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) } talosImage := "ghcr.io/siderolabs/installer:" + talosVersion - var steps []runnerlib.StepResult - - // Step 1 — Stage Talos upgrade. - step1Start := time.Now().UTC() - if err := params.TalosClient.Upgrade(ctx, talosImage, true); err != nil { - return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, - fmt.Sprintf("Upgrade (Talos) to %s: %v", talosImage, err)), nil - } - steps = append(steps, runnerlib.StepResult{ - Name: "talos-upgrade", Status: runnerlib.ResultSucceeded, - StartedAt: step1Start, CompletedAt: time.Now().UTC(), - Message: fmt.Sprintf("staged Talos upgrade to %s", talosImage), - }) - - // Step 2 — Apply Kubernetes kubelet upgrade per node. The Talos OS upgrade is - // already staged (stage=true above), so the kubelet change is also staged here - // to co-apply on the next reboot that the Talos upgrade triggers. kubeletImagePatch := []byte(fmt.Sprintf( `{"machine":{"kubelet":{"image":"ghcr.io/siderolabs/kubelet:v%s"}}}`, kubeVersion, )) - var stackNodeIPs []string - if params.TalosconfigPath != "" { - ips, epErr := EndpointsFromTalosconfig(params.TalosconfigPath) - if epErr != nil { - return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, - fmt.Sprintf("read endpoints from talosconfig: %v", epErr)), nil - } - stackNodeIPs = ips - } - applyStackKubelet := func(nodeCtx context.Context, nodeID string) error { + + var steps []runnerlib.StepResult + + // Rolling per-node upgrade: stage kubelet then trigger immediate Talos reboot. + // The node reboots once and co-applies both the Talos installer and the kubelet + // image change in the same reboot cycle. + for i, nodeIP := range nodes { + stepStart := time.Now().UTC() + nodeCtx := NodeContext(ctx, nodeIP) + + slog.Info("stack-upgrade: staging kubelet image change", + slog.Int("node_index", i+1), slog.Int("node_total", len(nodes)), + slog.String("node", nodeIP), slog.String("kubeVersion", kubeVersion)) + existing, err := params.TalosClient.GetMachineConfig(nodeCtx) if err != nil { - return fmt.Errorf("GetMachineConfig on %s: %w", nodeID, err) + return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, + fmt.Sprintf("GetMachineConfig on %s: %v", nodeIP, err)), nil } merged, err := mergeYAMLPatch(existing, kubeletImagePatch) if err != nil { - return fmt.Errorf("merge kubelet patch on %s: %w", nodeID, err) + return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, + fmt.Sprintf("merge kubelet patch on %s: %v", nodeIP, err)), nil } - return params.TalosClient.ApplyConfiguration(nodeCtx, merged, "staged") - } - step2Start := time.Now().UTC() - if len(stackNodeIPs) > 0 { - for _, nodeIP := range stackNodeIPs { - if err := applyStackKubelet(NodeContext(ctx, nodeIP), nodeIP); err != nil { - return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, - fmt.Sprintf("ApplyConfiguration (Kubernetes) to %s on %s: %v", kubeVersion, nodeIP, err)), nil - } + if err := params.TalosClient.ApplyConfiguration(nodeCtx, merged, "staged"); err != nil { + return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, + fmt.Sprintf("ApplyConfiguration (Kubernetes %s) on %s: %v", kubeVersion, nodeIP, err)), nil } - } else { - if err := applyStackKubelet(ctx, "node"); err != nil { + + slog.Info("stack-upgrade: triggering Talos upgrade with immediate reboot", + slog.String("node", nodeIP), slog.String("image", talosImage)) + + if uErr := params.TalosClient.Upgrade(nodeCtx, talosImage, false); uErr != nil { return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, - fmt.Sprintf("ApplyConfiguration (Kubernetes) to %s: %v", kubeVersion, err)), nil + fmt.Sprintf("Upgrade (Talos %s) on %s: %v", talosImage, nodeIP, uErr)), nil } + + if wErr := waitForNodeReboot(ctx, params.TalosClient, nodeIP); wErr != nil { + return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ExecutionFailure, + fmt.Sprintf("node %s did not recover after stack upgrade: %v", nodeIP, wErr)), nil + } + + slog.Info("stack-upgrade: node ready after reboot", + slog.String("node", nodeIP), slog.String("talos", talosImage), slog.String("kube", kubeVersion)) + + steps = append(steps, runnerlib.StepResult{ + Name: "stack-upgrade-" + nodeIP, + Status: runnerlib.ResultSucceeded, + StartedAt: stepStart, + CompletedAt: time.Now().UTC(), + Message: fmt.Sprintf("upgraded node %s to Talos %s + Kubernetes %s", nodeIP, talosVersion, kubeVersion), + }) } - steps = append(steps, runnerlib.StepResult{ - Name: "kube-upgrade", Status: runnerlib.ResultSucceeded, - StartedAt: step2Start, CompletedAt: time.Now().UTC(), - Message: fmt.Sprintf("staged Kubernetes upgrade to %s", kubeVersion), - }) return runnerlib.OperationResultSpec{ Capability: runnerlib.CapabilityStackUpgrade, diff --git a/test/unit/capability/platform_test.go b/test/unit/capability/platform_test.go index eda02ba..af1581f 100644 --- a/test/unit/capability/platform_test.go +++ b/test/unit/capability/platform_test.go @@ -443,14 +443,49 @@ func TestStackUpgrade_NilClientsReturnsValidationFailure(t *testing.T) { assertValidationFailure(t, result, "stack-upgrade nil clients") } -func TestStackUpgrade_RunsBothUpgradeSteps(t *testing.T) { +func TestStackUpgrade_NoNodesReturnsValidationFailure(t *testing.T) { reg := capability.NewRegistry() capability.RegisterAll(reg) h, _ := reg.Resolve(runnerlib.CapabilityStackUpgrade) - // targetKubernetesVersion without "v" prefix; handler must prepend "v" for the - // kubelet image. targetTalosVersion carries the "v" as Talos convention. - talos := &stubTalosClient{} + talos := &stubTalosClient{nodes: []string{}} + result, err := h.Execute(context.Background(), capability.ExecuteParams{ + Capability: runnerlib.CapabilityStackUpgrade, + ClusterRef: "ccs-test", + ExecuteClients: capability.ExecuteClients{ + TalosClient: talos, + DynamicClient: newPlatformDynClient(upgradePolicyCR("ccs-test", "stack", "v1.12.0", "1.32.0")), + }, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + assertValidationFailure(t, result, "stack-upgrade no nodes") +} + +// TestStackUpgrade_RollingUpgrade_AllNodes verifies the per-node rolling upgrade: +// kubelet staged, Talos immediate reboot, wait for recovery -- for each node. +func TestStackUpgrade_RollingUpgrade_AllNodes(t *testing.T) { + old := capability.NodeRebootPollInterval + capability.NodeRebootPollInterval = 0 + t.Cleanup(func() { capability.NodeRebootPollInterval = old }) + + reg := capability.NewRegistry() + capability.RegisterAll(reg) + h, _ := reg.Resolve(runnerlib.CapabilityStackUpgrade) + + // Three nodes. Per node: one health error (offline) then nil (back online). + nodes := []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"} + healthResponses := make([]error, 0, len(nodes)*2) + for range nodes { + healthResponses = append(healthResponses, fmt.Errorf("rebooting")) + healthResponses = append(healthResponses, nil) + } + + talos := &stubTalosClient{ + nodes: nodes, + healthResponses: healthResponses, + } result, err := h.Execute(context.Background(), capability.ExecuteParams{ Capability: runnerlib.CapabilityStackUpgrade, ClusterRef: "ccs-test", @@ -465,20 +500,28 @@ func TestStackUpgrade_RunsBothUpgradeSteps(t *testing.T) { if result.Status != runnerlib.ResultSucceeded { t.Errorf("expected ResultSucceeded; got %q (reason: %v)", result.Status, result.FailureReason) } - if !talos.upgradeCalled { - t.Error("expected Upgrade() to be called for talos step") + // One Upgrade() call per node. + if talos.upgradeCallCount != len(nodes) { + t.Errorf("expected Upgrade called %d times (once per node), got %d", len(nodes), talos.upgradeCallCount) } - if len(result.Steps) < 2 { - t.Errorf("expected ≥2 steps; got %d", len(result.Steps)) + // One step per node. + if len(result.Steps) != len(nodes) { + t.Errorf("expected %d step results, got %d", len(nodes), len(result.Steps)) } - // Verify the kubelet image in the applied config has the "v" prefix. - if len(talos.applyConfigCalls) == 0 { - t.Fatal("expected ApplyConfiguration to be called for kube step") + // One ApplyConfiguration per node (kubelet staged). + if len(talos.applyConfigCalls) != len(nodes) { + t.Fatalf("expected %d ApplyConfiguration calls (one per node); got %d", len(nodes), len(talos.applyConfigCalls)) } - applied := string(talos.applyConfigCalls[0].configBytes) + // Every ApplyConfiguration must use "staged" mode. + for i, call := range talos.applyConfigCalls { + if call.mode != "staged" { + t.Errorf("node %d: expected mode=staged; got %q", i, call.mode) + } + } + // Kubelet image must carry the "v" prefix. const wantImage = "ghcr.io/siderolabs/kubelet:v1.32.0" - if !strings.Contains(applied, wantImage) { - t.Errorf("applied config does not contain %q; got:\n%s", wantImage, applied) + if !strings.Contains(string(talos.applyConfigCalls[0].configBytes), wantImage) { + t.Errorf("applied config does not contain %q", wantImage) } } From 095ccb954ddabf400c7316757cf13f1713ad4ccb Mon Sep 17 00:00:00 2001 From: ontave Date: Thu, 7 May 2026 09:37:26 +0200 Subject: [PATCH 03/13] fix: use docker.io/siderolabs/installer and kubelet refs for lab registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ghcr.io is not accessible from lab nodes. The docker.io registry mirror (docker.io → 10.20.0.1:5000) is the only configured mirror. Using docker.io/ image references allows Talos to resolve installer and kubelet images through the local registry mirror during node upgrades. Affects talos-upgrade, kube-upgrade, and stack-upgrade capabilities. --- internal/capability/platform_upgrade.go | 8 ++++---- test/unit/capability/platform_test.go | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/capability/platform_upgrade.go b/internal/capability/platform_upgrade.go index 8c15c13..3b00f71 100644 --- a/internal/capability/platform_upgrade.go +++ b/internal/capability/platform_upgrade.go @@ -72,7 +72,7 @@ func (h *talosUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) return failureResult(runnerlib.CapabilityTalosUpgrade, now, runnerlib.ValidationFailure, fmt.Sprintf("no UpgradePolicy CR with upgradeType=talos and targetTalosVersion found in %s", ns)), nil } - upgradeImage := "ghcr.io/siderolabs/installer:" + targetVersion + upgradeImage := "docker.io/siderolabs/installer:" + targetVersion steps := make([]runnerlib.StepResult, 0, len(nodes)) for i, nodeIP := range nodes { @@ -210,7 +210,7 @@ func (h *kubeUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) } kubeletImagePatch := []byte(fmt.Sprintf( - `{"machine":{"kubelet":{"image":"ghcr.io/siderolabs/kubelet:v%s"}}}`, + `{"machine":{"kubelet":{"image":"docker.io/siderolabs/kubelet:v%s"}}}`, targetKubeVersion, )) @@ -326,10 +326,10 @@ func (h *stackUpgradeHandler) Execute(ctx context.Context, params ExecuteParams) return failureResult(runnerlib.CapabilityStackUpgrade, now, runnerlib.ValidationFailure, fmt.Sprintf("UpgradePolicy with upgradeType=stack must specify both targetTalosVersion and targetKubernetesVersion in %s", ns)), nil } - talosImage := "ghcr.io/siderolabs/installer:" + talosVersion + talosImage := "docker.io/siderolabs/installer:" + talosVersion kubeletImagePatch := []byte(fmt.Sprintf( - `{"machine":{"kubelet":{"image":"ghcr.io/siderolabs/kubelet:v%s"}}}`, + `{"machine":{"kubelet":{"image":"docker.io/siderolabs/kubelet:v%s"}}}`, kubeVersion, )) diff --git a/test/unit/capability/platform_test.go b/test/unit/capability/platform_test.go index af1581f..b944714 100644 --- a/test/unit/capability/platform_test.go +++ b/test/unit/capability/platform_test.go @@ -376,7 +376,8 @@ func TestKubeUpgrade_AppliesKubeletConfig(t *testing.T) { // targetKubernetesVersion is stored without the "v" prefix in UpgradePolicy spec // (mirrors TalosCluster.spec.kubernetesVersion). The handler must prepend "v" when - // building the kubelet image reference so the image is pullable from ghcr.io. + // building the kubelet image reference. docker.io prefix is used so lab registry + // mirrors (docker.io → 10.20.0.1:5000) resolve the image without ghcr.io access. talos := &stubTalosClient{} result, err := h.Execute(context.Background(), capability.ExecuteParams{ Capability: runnerlib.CapabilityKubeUpgrade, @@ -396,7 +397,7 @@ func TestKubeUpgrade_AppliesKubeletConfig(t *testing.T) { t.Fatal("expected ApplyConfiguration to be called") } applied := string(talos.applyConfigCalls[0].configBytes) - const wantImage = "ghcr.io/siderolabs/kubelet:v1.32.0" + const wantImage = "docker.io/siderolabs/kubelet:v1.32.0" if !strings.Contains(applied, wantImage) { t.Errorf("applied config does not contain %q; got:\n%s", wantImage, applied) } @@ -519,7 +520,7 @@ func TestStackUpgrade_RollingUpgrade_AllNodes(t *testing.T) { } } // Kubelet image must carry the "v" prefix. - const wantImage = "ghcr.io/siderolabs/kubelet:v1.32.0" + const wantImage = "docker.io/siderolabs/kubelet:v1.32.0" if !strings.Contains(string(talos.applyConfigCalls[0].configBytes), wantImage) { t.Errorf("applied config does not contain %q", wantImage) } From c81114a1dd276b8edf5328a6fc1fb670b3457e57 Mon Sep 17 00:00:00 2001 From: ontave Date: Tue, 12 May 2026 11:35:23 +0200 Subject: [PATCH 04/13] feat(phase-1): migrate runnerlib to conductor-sdk external module All imports of github.com/ontai-dev/conductor/pkg/runnerlib updated to github.com/ontai-dev/conductor-sdk/runnerlib across 37 files. Internal pkg/runnerlib deleted. go.mod updated with replace directive pointing to ../conductor-sdk and require entry. go mod tidy completed. All unit tests pass: go build ./... and go test ./test/unit/... green before deletion. --- cmd/conductor/main.go | 2 +- go.mod | 2 + internal/agent/capability_publisher.go | 2 +- internal/capability/guardian.go | 2 +- internal/capability/platform_cluster.go | 2 +- internal/capability/platform_etcd.go | 2 +- internal/capability/platform_machineconfig.go | 2 +- .../capability/platform_machineconfig_test.go | 2 +- internal/capability/platform_node.go | 2 +- internal/capability/platform_security.go | 2 +- internal/capability/platform_upgrade.go | 2 +- internal/capability/registry.go | 2 +- internal/capability/stubs.go | 2 +- internal/capability/wrapper.go | 2 +- internal/kernel/agent.go | 2 +- internal/kernel/mode.go | 2 +- internal/persistence/configmap_writer.go | 2 +- .../persistence/operationresult_writer.go | 2 +- .../operationresult_writer_test.go | 2 +- internal/persistence/tcor_writer.go | 2 +- pkg/runnerlib/.gitkeep | 0 pkg/runnerlib/capability.go | 87 ------ pkg/runnerlib/constants.go | 110 ------- pkg/runnerlib/doc.go | 27 -- pkg/runnerlib/generators.go | 139 --------- pkg/runnerlib/jobspec.go | 271 ------------------ pkg/runnerlib/operationresult.go | 190 ------------ pkg/runnerlib/packreceipt.go | 1 - pkg/runnerlib/packreceipt_test.go | 81 ------ pkg/runnerlib/runnerconfig.go | 1 - test/unit/agent/capability_publisher_test.go | 2 +- test/unit/capability/dispatcher_test.go | 2 +- test/unit/capability/guardian_test.go | 2 +- .../unit/capability/pack_deploy_split_test.go | 2 +- test/unit/capability/platform_test.go | 2 +- test/unit/capability/registry_test.go | 2 +- test/unit/capability/wrapper_test.go | 2 +- test/unit/kernel/execute_capability_test.go | 2 +- test/unit/kernel/execute_sequencer_test.go | 2 +- .../unit/persistence/configmap_writer_test.go | 2 +- .../operationresult_writer_test.go | 2 +- test/unit/persistence/tcor_writer_test.go | 2 +- test/unit/runnerlib/capability_test.go | 2 +- test/unit/runnerlib/constants_test.go | 2 +- test/unit/runnerlib/generators_test.go | 2 +- test/unit/runnerlib/jobspec_test.go | 2 +- test/unit/runnerlib/operationresult_test.go | 2 +- test/unit/runnerlib/runnerconfig_test.go | 2 +- 48 files changed, 39 insertions(+), 944 deletions(-) delete mode 100644 pkg/runnerlib/.gitkeep delete mode 100644 pkg/runnerlib/capability.go delete mode 100644 pkg/runnerlib/constants.go delete mode 100644 pkg/runnerlib/doc.go delete mode 100644 pkg/runnerlib/generators.go delete mode 100644 pkg/runnerlib/jobspec.go delete mode 100644 pkg/runnerlib/operationresult.go delete mode 100644 pkg/runnerlib/packreceipt.go delete mode 100644 pkg/runnerlib/packreceipt_test.go delete mode 100644 pkg/runnerlib/runnerconfig.go diff --git a/cmd/conductor/main.go b/cmd/conductor/main.go index 9ed20c6..dbaf554 100644 --- a/cmd/conductor/main.go +++ b/cmd/conductor/main.go @@ -37,7 +37,7 @@ import ( "github.com/ontai-dev/conductor/internal/config" "github.com/ontai-dev/conductor/internal/kernel" "github.com/ontai-dev/conductor/internal/persistence" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) var seamScheme = runtime.NewScheme() diff --git a/go.mod b/go.mod index 0ad3ae1..9596558 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ontai-dev/conductor go 1.25.3 replace ( + github.com/ontai-dev/conductor-sdk => ../conductor-sdk github.com/ontai-dev/guardian => ../guardian github.com/ontai-dev/platform => ../platform github.com/ontai-dev/seam-core => ../seam-core @@ -15,6 +16,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.98.0 github.com/onsi/ginkgo/v2 v2.27.2 github.com/onsi/gomega v1.38.2 + github.com/ontai-dev/conductor-sdk v0.0.0-00010101000000-000000000000 github.com/ontai-dev/guardian v0.0.0-00010101000000-000000000000 github.com/ontai-dev/platform v0.0.0-00010101000000-000000000000 github.com/ontai-dev/seam-core v0.1.0-alpha.0.20260426085946-e3630ad7b38f diff --git a/internal/agent/capability_publisher.go b/internal/agent/capability_publisher.go index b673d01..6d3e903 100644 --- a/internal/agent/capability_publisher.go +++ b/internal/agent/capability_publisher.go @@ -15,7 +15,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // capabilityPublishRetryInterval is the interval between retry attempts when diff --git a/internal/capability/guardian.go b/internal/capability/guardian.go index d4fddcf..8c26139 100644 --- a/internal/capability/guardian.go +++ b/internal/capability/guardian.go @@ -16,7 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // managementSignatureAnnotation is the annotation key used by the management diff --git a/internal/capability/platform_cluster.go b/internal/capability/platform_cluster.go index 59c364c..fa839b1 100644 --- a/internal/capability/platform_cluster.go +++ b/internal/capability/platform_cluster.go @@ -10,7 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // talosClusterGVR is the GroupVersionResource for InfrastructureTalosCluster. diff --git a/internal/capability/platform_etcd.go b/internal/capability/platform_etcd.go index 3f10306..dc54e70 100644 --- a/internal/capability/platform_etcd.go +++ b/internal/capability/platform_etcd.go @@ -11,7 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // etcdMaintenanceGVR is the GroupVersionResource for EtcdMaintenance. diff --git a/internal/capability/platform_machineconfig.go b/internal/capability/platform_machineconfig.go index f7aa1de..0355a7a 100644 --- a/internal/capability/platform_machineconfig.go +++ b/internal/capability/platform_machineconfig.go @@ -14,7 +14,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" sigsyaml "sigs.k8s.io/yaml" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // machineConfigBackupGVR is the GroupVersionResource for TalosMachineConfigBackup. diff --git a/internal/capability/platform_machineconfig_test.go b/internal/capability/platform_machineconfig_test.go index 88256aa..e1aae2d 100644 --- a/internal/capability/platform_machineconfig_test.go +++ b/internal/capability/platform_machineconfig_test.go @@ -11,7 +11,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic/fake" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // stubTalosClientMC is a minimal TalosNodeClient for machineconfig-backup tests. diff --git a/internal/capability/platform_node.go b/internal/capability/platform_node.go index 9580a44..60f82ae 100644 --- a/internal/capability/platform_node.go +++ b/internal/capability/platform_node.go @@ -13,7 +13,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" sigsyaml "sigs.k8s.io/yaml" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // nodeMaintenanceGVR is the GroupVersionResource for NodeMaintenance. diff --git a/internal/capability/platform_security.go b/internal/capability/platform_security.go index d0b0040..ea47388 100644 --- a/internal/capability/platform_security.go +++ b/internal/capability/platform_security.go @@ -15,7 +15,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" sigsyaml "sigs.k8s.io/yaml" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // HardeningStablePollInterval is the interval between Health() checks while diff --git a/internal/capability/platform_upgrade.go b/internal/capability/platform_upgrade.go index 3b00f71..8ade528 100644 --- a/internal/capability/platform_upgrade.go +++ b/internal/capability/platform_upgrade.go @@ -13,7 +13,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // upgradePolicyGVR is the GroupVersionResource for UpgradePolicy. diff --git a/internal/capability/registry.go b/internal/capability/registry.go index 674a242..5face58 100644 --- a/internal/capability/registry.go +++ b/internal/capability/registry.go @@ -12,7 +12,7 @@ import ( "fmt" "log/slog" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // Handler is the interface every named capability implementation must satisfy. diff --git a/internal/capability/stubs.go b/internal/capability/stubs.go index f4315be..794f3b4 100644 --- a/internal/capability/stubs.go +++ b/internal/capability/stubs.go @@ -1,7 +1,7 @@ package capability import ( - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // RegisterAll populates the registry with the real capability handler diff --git a/internal/capability/wrapper.go b/internal/capability/wrapper.go index 981da16..d117487 100644 --- a/internal/capability/wrapper.go +++ b/internal/capability/wrapper.go @@ -25,7 +25,7 @@ import ( "k8s.io/client-go/dynamic" sigsyaml "sigs.k8s.io/yaml" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // namespaceGVR is the GroupVersionResource for Kubernetes Namespace resources. diff --git a/internal/kernel/agent.go b/internal/kernel/agent.go index a40989b..e3ca69c 100644 --- a/internal/kernel/agent.go +++ b/internal/kernel/agent.go @@ -16,7 +16,7 @@ import ( "github.com/ontai-dev/conductor/internal/federation" "github.com/ontai-dev/conductor/internal/permissionservice" "github.com/ontai-dev/conductor/internal/webhook" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // agentVersion is the version string stamped into the capability manifest. diff --git a/internal/kernel/mode.go b/internal/kernel/mode.go index bbc0f86..5fe6958 100644 --- a/internal/kernel/mode.go +++ b/internal/kernel/mode.go @@ -10,7 +10,7 @@ import ( "time" "github.com/ontai-dev/conductor/internal/config" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // invariantViolationExit is the structured JSON document written to stderr before diff --git a/internal/persistence/configmap_writer.go b/internal/persistence/configmap_writer.go index 0b36959..7610756 100644 --- a/internal/persistence/configmap_writer.go +++ b/internal/persistence/configmap_writer.go @@ -13,7 +13,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // ResultDataKey is the ConfigMap key under which the JSON-encoded diff --git a/internal/persistence/operationresult_writer.go b/internal/persistence/operationresult_writer.go index d72579a..5f456b7 100644 --- a/internal/persistence/operationresult_writer.go +++ b/internal/persistence/operationresult_writer.go @@ -13,7 +13,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // labelPackExecution is the label key used to group PackOperationResult CRs by diff --git a/internal/persistence/operationresult_writer_test.go b/internal/persistence/operationresult_writer_test.go index a9650c5..8a0b6d6 100644 --- a/internal/persistence/operationresult_writer_test.go +++ b/internal/persistence/operationresult_writer_test.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) func newTestScheme(t *testing.T) *runtime.Scheme { diff --git a/internal/persistence/tcor_writer.go b/internal/persistence/tcor_writer.go index e02cad6..4b0d084 100644 --- a/internal/persistence/tcor_writer.go +++ b/internal/persistence/tcor_writer.go @@ -16,7 +16,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // tenantNamespaceFor returns the seam-tenant-{clusterRef} namespace name. diff --git a/pkg/runnerlib/.gitkeep b/pkg/runnerlib/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/pkg/runnerlib/capability.go b/pkg/runnerlib/capability.go deleted file mode 100644 index 97b48c4..0000000 --- a/pkg/runnerlib/capability.go +++ /dev/null @@ -1,87 +0,0 @@ -package runnerlib - -import "time" - -// CapabilityManifest is the self-declared capability list published by the runner -// agent to RunnerConfig status on startup. Operators read this manifest before -// submitting any Job to confirm the named capability is available. CR-INV-005. -type CapabilityManifest struct { - // RunnerVersion is the version of the runner binary that published this manifest. - RunnerVersion string - - // PublishedAt is the time this manifest was written to RunnerConfig status. - PublishedAt time.Time - - // Entries is the list of named capabilities this runner image supports. - // Always initialized to a non-nil slice. An empty manifest is valid (agent - // starting up) but operators will raise CapabilityUnavailable until entries appear. - Entries []CapabilityEntry -} - -// CapabilityEntry declares one named capability supported by this runner image. -// The Name field is the authoritative identifier — it must match a named capability -// constant exactly. Names are permanent. Renaming is forbidden. CR-INV-004. -// -// JSON field names match the CRD status.capabilities array schema exactly: -// name, version, description, parameterSchema. Mode is a Go-only field not -// declared in the CRD and is excluded from serialization via json:"-". -type CapabilityEntry struct { - // Name is the globally unique, immutable capability identifier. - // Must match one of the Capability* constants in constants.go exactly. - Name string `json:"name"` - - // Version is the semantic version of this capability implementation. - // Tied to the runner version. No independent capability releases. CR-INV-004. - Version string `json:"version"` - - // Mode declares the execution mode this capability runs in. - // Named capabilities are always ExecutorMode. Not declared in the CRD schema — - // excluded from JSON serialization; used only for internal dispatch logic. - Mode CapabilityMode `json:"-"` - - // ParameterSchema declares the input parameters this capability accepts. - // Map key is the parameter name. Value is the parameter definition. - // An empty map is valid for capabilities with no parameters. - ParameterSchema map[string]ParameterDef `json:"parameterSchema,omitempty"` - - // Description is a human-readable summary of what this capability does. - Description string `json:"description,omitempty"` -} - -// CapabilityMode is a typed string declaring which runner mode a capability -// executes in. The three-mode boundary is absolute. INV-014, CR-INV-001. -type CapabilityMode string - -const ( - // ExecutorMode indicates the capability runs as a Kueue Job in executor mode. - // All named capabilities in the capability table are ExecutorMode. - ExecutorMode CapabilityMode = "executor" - - // AgentMode indicates the capability is an agent-internal control loop. - // Not externally invokable. Not submitted as a Job. - AgentMode CapabilityMode = "agent" - - // CompileMode indicates the capability runs in runner compile mode. - // Helm and Kustomize goClients are available. Talos goclient is forbidden. - // INV-014. - CompileMode CapabilityMode = "compile" -) - -// ParameterDef describes the schema for one input parameter of a named capability. -// Operators use this to validate Job specs before submission. -type ParameterDef struct { - // Type is the parameter value type. - // One of: string, int, bool, secretRef. - Type string - - // Required indicates whether this parameter must be present for the capability - // to execute. Zero value is false (optional). - Required bool - - // Description is a human-readable explanation of this parameter's purpose. - Description string - - // Default is the value used when the parameter is absent and Required is false. - // Empty string means no default — the capability handles absence explicitly. - Default string -} diff --git a/pkg/runnerlib/constants.go b/pkg/runnerlib/constants.go deleted file mode 100644 index e240dbf..0000000 --- a/pkg/runnerlib/constants.go +++ /dev/null @@ -1,110 +0,0 @@ -package runnerlib - -// Named capability string constants. These values are permanent and immutable. -// Renaming a capability is forbidden. Fundamental behavior changes require a new -// name. CR-INV-004. Values match conductor-schema.md Section 6 exactly. -// -// The capability name is stamped into the Job spec as the CAPABILITY environment -// variable. The runner reads this on executor startup to resolve the capability -// from the registry. - -// platform capabilities — cluster lifecycle and operations. -const ( - // CapabilityBootstrap performs full cluster bootstrap from seed nodes. - // Multi-step. Uses PVC protocol. Triggered by TalosCluster. - CapabilityBootstrap = "bootstrap" - - // CapabilityTalosUpgrade performs a rolling Talos OS version upgrade. - // Triggered by TalosUpgrade CR. - CapabilityTalosUpgrade = "talos-upgrade" - - // CapabilityKubeUpgrade performs a Kubernetes version upgrade. - // Triggered by TalosKubeUpgrade CR. - CapabilityKubeUpgrade = "kube-upgrade" - - // CapabilityStackUpgrade performs a coordinated Talos OS and Kubernetes upgrade. - // Multi-step. Uses PVC protocol. Triggered by TalosStackUpgrade CR. - CapabilityStackUpgrade = "stack-upgrade" - - // CapabilityNodePatch applies a machine config patch to one or more nodes. - // Triggered by TalosNodePatch CR. - CapabilityNodePatch = "node-patch" - - // CapabilityNodeScaleUp provisions and bootstraps additional nodes into a cluster. - // Triggered by TalosNodeScaleUp CR. - CapabilityNodeScaleUp = "node-scale-up" - - // CapabilityNodeDecommission cordons, drains, and removes a node. - // Triggered by TalosNodeDecommission CR. - CapabilityNodeDecommission = "node-decommission" - - // CapabilityNodeReboot reboots one or all cluster nodes. - // Triggered by TalosReboot CR. - CapabilityNodeReboot = "node-reboot" - - // CapabilityEtcdBackup takes an etcd snapshot and exports machine config to S3. - // Triggered by TalosBackup CR. - CapabilityEtcdBackup = "etcd-backup" - - // CapabilityEtcdDefrag performs etcd defragmentation on all members. - // Triggered by EtcdMaintenance CR with operation=defrag. conductor-schema.md §6. - CapabilityEtcdDefrag = "etcd-defrag" - - // CapabilityEtcdRestore performs disaster recovery from an S3 etcd snapshot. - // Triggered by TalosRecovery CR. - CapabilityEtcdRestore = "etcd-restore" - - // CapabilityPKIRotate rotates PKI certificates and updates talosconfig secret. - // Triggered by TalosPKIRotation CR. - CapabilityPKIRotate = "pki-rotate" - - // CapabilityCredentialRotate rotates service account signing keys and OIDC credentials. - // Triggered by TalosCredentialRotation CR. - CapabilityCredentialRotate = "credential-rotate" - - // CapabilityHardeningApply applies a TalosHardeningProfile to a running cluster. - // Triggered by TalosHardeningApply CR. - CapabilityHardeningApply = "hardening-apply" - - // CapabilityClusterReset performs a destructive factory reset with a human gate. - // Multi-step. Uses PVC protocol. Triggered by TalosClusterReset CR. - // Requires annotation ontai.dev/reset-approved=true before execution proceeds. - // INV-007, INV-015. - CapabilityClusterReset = "cluster-reset" - - // CapabilityMachineConfigBackup reads each node's running machine config via - // GetMachineConfig and uploads to S3 at {cluster}/machineconfigs/{TIMESTAMP}/{hostname}.yaml. - // Triggered by TalosMachineConfigBackup CR. platform-schema.md §11. - CapabilityMachineConfigBackup = "machineconfig-backup" - - // CapabilityMachineConfigRestore downloads a node machine config from S3 at - // {cluster}/machineconfigs/{backupTimestamp}/{hostname}.yaml and applies it - // via ApplyConfiguration. Non-fatal per node. platform-schema.md §11. - CapabilityMachineConfigRestore = "machineconfig-restore" -) - -// Compile mode capabilities — invoked by the conductor binary directly, not by conductor Jobs. -// These capabilities run on the operator's workstation or in a CI/CD pipeline and are never -// submitted to Kueue. They never run on any cluster. -const ( - // CapabilityPackCompile is an conductor compile mode invocation that renders PackBuild - // inputs (Helm charts, Kustomize overlays, raw manifests) into a ClusterPack OCI artifact. - // It is invoked by the human or CI/CD pipeline on the workstation — never as a Kueue Job, - // never via conductor, never on any cluster. The output is a ClusterPack CR YAML emitted - // for git commit and a ClusterPack OCI artifact pushed to the OCI registry. - CapabilityPackCompile = "pack-compile" -) - -// wrapper capabilities — pack delivery. Execute mode: Kueue Jobs on the management cluster. -const ( - // CapabilityPackDeploy applies a ClusterPack to a target cluster. - // Triggered by PackExecution CR via wrapper. - CapabilityPackDeploy = "pack-deploy" -) - -// guardian capabilities — RBAC plane. -const ( - // CapabilityRBACProvision provisions RBAC artifacts on a target cluster from - // the current PermissionSnapshot. Initiated by the security agent control loop. - CapabilityRBACProvision = "rbac-provision" -) diff --git a/pkg/runnerlib/doc.go b/pkg/runnerlib/doc.go deleted file mode 100644 index 3c9cf12..0000000 --- a/pkg/runnerlib/doc.go +++ /dev/null @@ -1,27 +0,0 @@ -// Package runnerlib is the shared library between the conductor binary and all -// ONT platform operators. -// -// All operators import this package. Breaking changes require a major version bump -// and simultaneous operator dependency updates before the runner release is cut. -// Non-breaking changes (new optional fields, new capability entries) require only -// a minor version bump and may precede operator dependency updates. -// -// The library defines: -// - RunnerConfig types: RunnerConfigSpec, RunnerConfigStatus, and all nested types. -// - CapabilityManifest types: CapabilityManifest, CapabilityEntry, CapabilityMode, -// ParameterDef. -// - OperationResult types: OperationResultSpec, ResultStatus, ArtifactRef, -// FailureReason, FailureCategory, StepResult. -// - Generator functions: GenerateFromTalosCluster, GenerateFromPackBuild. -// - JobSpecBuilder interface and concrete implementation. -// - Named capability string constants. -// -// Do not add logic to this package beyond what is declared in -// conductor-design.md Section 12. This package is an API contract, not an -// implementation. All execution logic lives in the runner binary's internal -// packages. -// -// INV-009: RunnerConfig is operator-generated at runtime using this package. -// INV-010: This package is the single source of RunnerConfig schema. -// CR-INV-003: Breaking changes to this package require a major version bump. -package runnerlib diff --git a/pkg/runnerlib/generators.go b/pkg/runnerlib/generators.go deleted file mode 100644 index 4201bec..0000000 --- a/pkg/runnerlib/generators.go +++ /dev/null @@ -1,139 +0,0 @@ -package runnerlib - -import seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" - -// TalosClusterSpec is the minimal representation of a TalosCluster CR spec used -// by GenerateFromTalosCluster to produce a RunnerConfigSpec. Fields derived from -// platform-schema.md Section 2 (TalosCluster key spec fields). -// -// This type exists in the shared library to decouple the generator from the -// platform CRD Go types. Operators populate this from their CRD types and -// pass it to the generator. -type TalosClusterSpec struct { - // ClusterEndpoint is the VIP or first control plane IP. Embedded in all - // generated configs. Used as the cluster identity in RunnerConfigSpec.ClusterRef. - // - // TODO: a formal cluster name field should be added to TalosCluster when the - // Schema Engineer defines the CRD API types. ClusterEndpoint is used as cluster - // identity here as an interim measure. platform-schema.md §2 TalosCluster. - ClusterEndpoint string - - // TalosVersion is the Talos OS version. Determines runner compatibility. - // INV-012: the runner image tag must be compatible with this version. - TalosVersion string - - // KubernetesVersion is the Kubernetes version to pin on this cluster. - KubernetesVersion string - - // InstallDisk is the block device path for Talos installation. - InstallDisk string - - // ControlPlaneNodes is the list of control plane node IPs. - ControlPlaneNodes []string - - // WorkerNodes is the list of worker node IPs. May be empty for control-plane-only - // clusters. - WorkerNodes []string - - // SeedNodes is the list of node IPs reachable on port 50000 before config is - // applied. Used by the bootstrap capability to apply initial machine configuration. - SeedNodes []string -} - -// PackBuildSpec is the minimal representation of a PackBuild CR spec used by -// GenerateFromPackBuild to produce a RunnerConfigSpec. Fields derived from -// wrapper-schema.md Section 3 (PackBuild key spec fields). -// -// This type exists in the shared library to decouple the generator from the -// wrapper CRD Go types. Operators populate this from their CRD types and pass -// it to the generator. -type PackBuildSpec struct { - // SourceHelm is the Helm chart source configuration. Nil if not a Helm-based pack. - SourceHelm *HelmSource - - // SourceKustomize is the Kustomize overlay source. Nil if not Kustomize-based. - SourceKustomize *KustomizeSource - - // SourceRaw is the list of raw manifest references. May be empty. - SourceRaw []RawManifestSource - - // TargetVersion is the version string to assign to the produced ClusterPack. - TargetVersion string -} - -// HelmSource describes a Helm chart source for a PackBuild. -// wrapper-schema.md §3 PackBuild source.helm fields. -type HelmSource struct { - // RepoURL is the Helm chart repository URL. - RepoURL string - - // ChartName is the name of the chart within the repository. - ChartName string - - // ChartVersion is the pinned chart version to render. - ChartVersion string - - // Values is the structured values map passed to Helm at render time. - // Corresponds to values.yaml overrides declared in the PackBuild spec. - Values map[string]interface{} -} - -// KustomizeSource describes a Kustomize overlay source for a PackBuild. -// wrapper-schema.md §3 PackBuild source.kustomize fields. -type KustomizeSource struct { - // OverlayPath is the path reference to the Kustomize overlay directory. - OverlayPath string -} - -// RawManifestSource describes a single raw manifest reference for a PackBuild. -// wrapper-schema.md §3 PackBuild source.raw fields. -type RawManifestSource struct { - // Path is the path reference to the raw Kubernetes manifest file. - Path string -} - -// GenerateFromTalosCluster produces a RunnerConfigSpec from a TalosClusterSpec. -// Called by platform when a TalosCluster CR lands on the management cluster. -// The returned RunnerConfigSpec has ClusterRef populated from the spec endpoint, -// RunnerImage left empty (the operator sets this from the cluster's desired runner -// version), and Phases initialized with a "launch" phase. -// -// INV-009: RunnerConfig is operator-generated at runtime. This function is the -// generation path. INV-010: this shared library is the single source. -func GenerateFromTalosCluster(spec TalosClusterSpec) (seamcorev1alpha1.InfrastructureRunnerConfigSpec, error) { - return seamcorev1alpha1.InfrastructureRunnerConfigSpec{ - // TODO: replace ClusterEndpoint-as-identity with a formal cluster name field - // once TalosCluster CRD API types are defined by the Schema Engineer. - // See platform-schema.md §2 TalosCluster. - ClusterRef: spec.ClusterEndpoint, - RunnerImage: "", // Caller must set this from the cluster's desired runner version. - Phases: []seamcorev1alpha1.RunnerPhaseConfig{ - { - Name: "launch", - Parameters: map[string]string{}, - }, - }, - OperationalHistory: []seamcorev1alpha1.RunnerOperationalHistoryEntry{}, - }, nil -} - -// GenerateFromPackBuild produces a RunnerConfigSpec from a PackBuildSpec. -// Called by wrapper when a PackBuild CR lands on the management cluster. -// Pack compilation is not cluster-specific — ClusterRef is empty. RunnerImage -// must be set by the caller. -// -// INV-009: RunnerConfig is operator-generated at runtime. INV-010: this shared -// library is the single source. -func GenerateFromPackBuild(spec PackBuildSpec) (seamcorev1alpha1.InfrastructureRunnerConfigSpec, error) { - return seamcorev1alpha1.InfrastructureRunnerConfigSpec{ - ClusterRef: "", // Pack compilation is not cluster-specific. - RunnerImage: "", // Caller must set this. - Phases: []seamcorev1alpha1.RunnerPhaseConfig{ - { - Name: "compile", - Parameters: map[string]string{}, - }, - }, - OperationalHistory: []seamcorev1alpha1.RunnerOperationalHistoryEntry{}, - }, nil -} diff --git a/pkg/runnerlib/jobspec.go b/pkg/runnerlib/jobspec.go deleted file mode 100644 index bcbeb59..0000000 --- a/pkg/runnerlib/jobspec.go +++ /dev/null @@ -1,271 +0,0 @@ -package runnerlib - -import ( - "errors" - - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" -) - -// ServiceAccountName is the default Kubernetes ServiceAccount used by runner Jobs. -// All runner Jobs run under this service account unless overridden. -const ServiceAccountName = "conductor" - -// DefaultTTLSecondsAfterFinished is the default TTL applied to completed Jobs -// when WithTTL is not called on the builder. 600 seconds = 10 minutes. -const DefaultTTLSecondsAfterFinished int32 = 600 - -// JobSpecBuilder is the interface for constructing a JobSpec for a runner executor -// Job. Each operator uses this builder (via the shared library) to produce the -// Job spec it submits to Kueue. The builder enforces invariants at Build time. -// -// Usage: -// -// spec, err := NewJobSpecBuilder(). -// WithCapability(CapabilityBootstrap). -// WithClusterRef("my-cluster"). -// WithRunnerImage("registry.ontai.dev/ontai-dev/conductor:v1.9.3-r1"). -// WithQueueName("platform-system-queue"). -// WithOperationResultConfigMap("bootstrap-result-my-cluster"). -// Build() -type JobSpecBuilder interface { - // WithCapability sets the named capability this Job will execute. - // Required. Build returns an error if not set. - WithCapability(name string) JobSpecBuilder - - // WithClusterRef sets the cluster identity passed to the runner as CLUSTER_REF. - WithClusterRef(clusterRef string) JobSpecBuilder - - // WithRunnerImage sets the fully qualified runner image including tag. - // Required. Build returns an error if not set. - WithRunnerImage(image string) JobSpecBuilder - - // WithQueueName sets the Kueue LocalQueue name this Job is submitted to. - WithQueueName(queueName string) JobSpecBuilder - - // WithOperationResultConfigMap sets the name of the ConfigMap the runner will - // write OperationResultSpec to before exit. - WithOperationResultConfigMap(name string) JobSpecBuilder - - // WithSecretVolume adds a Secret mount to the Job. The Secret is always mounted - // read-only. This method may be called multiple times to add multiple mounts. - WithSecretVolume(secretName string, mountPath string) JobSpecBuilder - - // WithTTL sets the TTL in seconds after Job completion before the Job is - // garbage collected. Default is DefaultTTLSecondsAfterFinished (600s). - WithTTL(seconds int32) JobSpecBuilder - - // WithNodeExclusions sets the list of node names that this Job must not be - // scheduled onto. Conductor execute mode calls this with the merged list of - // RunnerConfigSpec.MaintenanceTargetNodes and RunnerConfigSpec.OperatorLeaderNode - // when SelfOperation=true. When SelfOperation=false, callers must not set this. - // conductor-schema.md §13. - WithNodeExclusions(nodes []string) JobSpecBuilder - - // Build validates all required fields and produces a JobSpec. - // Returns an error if RunnerImage, Capability, or Namespace is empty. - Build() (JobSpec, error) -} - -// JobSpec is the value type produced by JobSpecBuilder.Build(). It contains all -// fields needed for the operator to construct a Kueue-compatible batch/v1 Job. -// Operators map this to the actual Kubernetes Job manifest — this type is the -// shared contract, not a Kubernetes API type. -type JobSpec struct { - // Name is the Job name. Derived from the capability and clusterRef by the builder. - Name string - - // Namespace is the Kubernetes namespace for this Job. - // Defaults to ont-system for management cluster operations. - Namespace string - - // Image is the fully qualified runner image reference. - Image string - - // Capability is the named capability this Job executes. - Capability string - - // ClusterRef is the cluster identity passed as CLUSTER_REF env var. - ClusterRef string - - // QueueName is the Kueue LocalQueue this Job targets. - QueueName string - - // OperationResultConfigMap is the name of the ConfigMap the runner writes - // OperationResultSpec to before exit. - OperationResultConfigMap string - - // SecretVolumes is the list of Secret mounts for this Job. - SecretVolumes []SecretVolume - - // TTLSecondsAfterFinished is the Job TTL after completion. - TTLSecondsAfterFinished int32 - - // ServiceAccountName is the ServiceAccount the Job pod runs under. - ServiceAccountName string - - // NodeExclusions is the list of node names this Job must not be scheduled - // onto. Populated by the operator (via ResolveNodeExclusionsFromRunnerConfig) - // when RunnerConfigSpec.SelfOperation=true. Empty for tenant-targeted operations. - // conductor-schema.md §13. - NodeExclusions []string -} - -// SecretVolume declares a Secret to be mounted into the runner Job pod. -// -// ReadOnly is always true by convention — runner Jobs must never modify -// mounted secrets. This invariant is enforced by the builder regardless of -// what the caller passes to WithSecretVolume. -type SecretVolume struct { - // SecretName is the name of the Kubernetes Secret to mount. - SecretName string - - // MountPath is the filesystem path where the Secret is mounted in the pod. - MountPath string - - // ReadOnly is always true. The builder enforces this invariant. - // Runner Jobs must never modify mounted secrets. - ReadOnly bool -} - -// jobSpecBuilder is the unexported concrete implementation of JobSpecBuilder. -// The builder uses value semantics — each With* method returns a new copy with -// the field set, never mutating the original. This makes the builder safe to -// branch and safe to pass between goroutines. -type jobSpecBuilder struct { - capability string - clusterRef string - runnerImage string - queueName string - operationResultConfigMap string - secretVolumes []SecretVolume - ttl *int32 - namespace string - nodeExclusions []string -} - -// NewJobSpecBuilder returns a new JobSpecBuilder with all fields at zero value. -// At minimum, WithCapability, WithRunnerImage must be called before Build. -func NewJobSpecBuilder() JobSpecBuilder { - return &jobSpecBuilder{} -} - -func (b *jobSpecBuilder) WithCapability(name string) JobSpecBuilder { - c := *b - c.capability = name - return &c -} - -func (b *jobSpecBuilder) WithClusterRef(clusterRef string) JobSpecBuilder { - c := *b - c.clusterRef = clusterRef - return &c -} - -func (b *jobSpecBuilder) WithRunnerImage(image string) JobSpecBuilder { - c := *b - c.runnerImage = image - return &c -} - -func (b *jobSpecBuilder) WithQueueName(queueName string) JobSpecBuilder { - c := *b - c.queueName = queueName - return &c -} - -func (b *jobSpecBuilder) WithOperationResultConfigMap(name string) JobSpecBuilder { - c := *b - c.operationResultConfigMap = name - return &c -} - -func (b *jobSpecBuilder) WithSecretVolume(secretName string, mountPath string) JobSpecBuilder { - c := *b - // Copy the slice to maintain value semantics — do not share backing array. - vols := make([]SecretVolume, len(b.secretVolumes), len(b.secretVolumes)+1) - copy(vols, b.secretVolumes) - // ReadOnly is always true — enforced here regardless of caller intent. - vols = append(vols, SecretVolume{ - SecretName: secretName, - MountPath: mountPath, - ReadOnly: true, - }) - c.secretVolumes = vols - return &c -} - -func (b *jobSpecBuilder) WithTTL(seconds int32) JobSpecBuilder { - c := *b - c.ttl = &seconds - return &c -} - -func (b *jobSpecBuilder) WithNodeExclusions(nodes []string) JobSpecBuilder { - c := *b - if len(nodes) == 0 { - c.nodeExclusions = nil - return &c - } - // Copy the slice to maintain value semantics. - excl := make([]string, len(nodes)) - copy(excl, nodes) - c.nodeExclusions = excl - return &c -} - -// ResolveNodeExclusionsFromRunnerConfig derives the NotIn node exclusion list -// from a RunnerConfigSpec. Returns a merged slice of MaintenanceTargetNodes and -// OperatorLeaderNode when SelfOperation is true. Returns nil when SelfOperation -// is false — tenant-targeted operations are exempt from exclusion logic. -// conductor-schema.md §13. -func ResolveNodeExclusionsFromRunnerConfig(spec seamcorev1alpha1.InfrastructureRunnerConfigSpec) []string { - if !spec.SelfOperation { - return nil - } - var nodes []string - nodes = append(nodes, spec.MaintenanceTargetNodes...) - if spec.OperatorLeaderNode != "" { - nodes = append(nodes, spec.OperatorLeaderNode) - } - return nodes -} - -// Build validates required fields and produces a JobSpec. -// Returns an error if RunnerImage or Capability is empty. -// Applies defaults: Namespace=ont-system if empty, TTL=600s, ServiceAccountName="conductor". -func (b *jobSpecBuilder) Build() (JobSpec, error) { - if b.runnerImage == "" { - return JobSpec{}, errors.New("runnerlib: JobSpecBuilder.Build: RunnerImage is required") - } - if b.capability == "" { - return JobSpec{}, errors.New("runnerlib: JobSpecBuilder.Build: Capability is required") - } - - ns := b.namespace - if ns == "" { - ns = "ont-system" - } - - ttl := DefaultTTLSecondsAfterFinished - if b.ttl != nil { - ttl = *b.ttl - } - - vols := b.secretVolumes - if vols == nil { - vols = []SecretVolume{} - } - - return JobSpec{ - Namespace: ns, - Image: b.runnerImage, - Capability: b.capability, - ClusterRef: b.clusterRef, - QueueName: b.queueName, - OperationResultConfigMap: b.operationResultConfigMap, - SecretVolumes: vols, - TTLSecondsAfterFinished: ttl, - ServiceAccountName: ServiceAccountName, - NodeExclusions: b.nodeExclusions, - }, nil -} diff --git a/pkg/runnerlib/operationresult.go b/pkg/runnerlib/operationresult.go deleted file mode 100644 index 021ce22..0000000 --- a/pkg/runnerlib/operationresult.go +++ /dev/null @@ -1,190 +0,0 @@ -package runnerlib - -import "time" - -// OperationResultSpec is the complete result document written to a Kubernetes -// ConfigMap by every runner executor Job before exit. The operator reads this -// ConfigMap to advance the CR status. No other communication channel exists -// between operator and runner. conductor-design.md Section 8, conductor-schema.md -// Section 8. -type OperationResultSpec struct { - // Phase identifies the RunnerConfig phase this result belongs to. - Phase string - - // Status is the overall result of the capability execution. - Status ResultStatus - - // Capability is the name of the named capability that produced this result. - // Matches one of the Capability* constants in constants.go. - Capability string - - // StartedAt is the time the capability execution began. - StartedAt time.Time - - // CompletedAt is the time the capability execution finished (success or failure). - CompletedAt time.Time - - // Artifacts is the list of artifacts produced by this execution. - // References only — never raw content, never secret values. Section 10 of - // conductor-design.md. - Artifacts []ArtifactRef - - // FailureReason is populated when Status is ResultFailed. Nil on success. - FailureReason *FailureReason - - // Steps contains individual step results for multi-step capabilities. - // Empty for single-step capabilities. - Steps []StepResult - - // DeployedResources is the list of Kubernetes resources applied during - // this execution. Populated by pack-deploy on success. Used by the wrapper - // PackInstanceReconciler to write PackInstance.Status.DeployedResources for - // deletion cleanup. wrapper-schema.md §3, Decision 11. - // +optional - DeployedResources []DeployedResource - - // ClusterPackRef is the name of the ClusterPack CR deployed by this operation. - // Populated by pack-deploy capability. Used to set the ontai.dev/cluster-pack - // POR label so the wrapper rollback handler can query PORs by ClusterPack. - ClusterPackRef string - - // ClusterPackVersion is the ClusterPack spec.version deployed. - // Populated by pack-deploy on success. Stored in POR as rollback anchor. - // seam-core-schema.md §7.8, wrapper-schema.md §6.2. - ClusterPackVersion string - - // RBACDigest is the OCI digest of the RBAC layer deployed. - // Populated by pack-deploy on success (split path). Stored in POR as rollback anchor. - RBACDigest string - - // WorkloadDigest is the OCI digest of the workload layer deployed. - // Populated by pack-deploy on success (split path). Stored in POR as rollback anchor. - WorkloadDigest string -} - -// ResultStatus is a typed string representing the terminal status of a capability -// execution or individual step. -type ResultStatus string - -const ( - // ResultSucceeded indicates the capability or step completed without error. - ResultSucceeded ResultStatus = "Succeeded" - - // ResultFailed indicates the capability or step encountered a failure. - // FailureReason is populated on the containing OperationResultSpec. - ResultFailed ResultStatus = "Failed" -) - -// ArtifactRef is a structured reference to an artifact produced by a capability. -// Never contains raw artifact content. Secrets are referenced, never embedded. -// conductor-design.md Section 10. -type ArtifactRef struct { - // Name is a logical identifier for this artifact within the OperationResult. - Name string - - // Kind declares the artifact type. - // One of: ConfigMap, Secret, OCIImage, S3Object. - Kind string - - // Reference is the fully qualified reference for the artifact kind. - // For ConfigMap/Secret: namespace/name. - // For OCIImage: registry/repository:tag@digest. - // For S3Object: s3://bucket/key. - Reference string - - // Checksum is the content-addressed checksum of the artifact. - // Format: sha256:. Empty if not applicable for the kind. - Checksum string -} - -// FailureReason is a structured failure description populated in OperationResultSpec -// when Status is ResultFailed. Every failure is classified into exactly one -// FailureCategory. conductor-design.md Section 6.1. -type FailureReason struct { - // Category classifies the failure. Never empty. - Category FailureCategory - - // Reason is a human-readable description of the specific failure. - Reason string - - // FailedStep is the name of the step that failed, for multi-step capabilities. - // Empty for single-step capabilities. - FailedStep string -} - -// FailureCategory is a typed string classifying the failure domain. -// Every failure is classified into exactly one category. -// conductor-design.md Section 6.1. -type FailureCategory string - -const ( - // ValidationFailure indicates input does not meet schema or invariant requirements. - // The failure occurred before any execution step began. - ValidationFailure FailureCategory = "ValidationFailure" - - // CapabilityUnavailable indicates the requested capability is not in the runner - // registry. The operator should raise CapabilityUnavailable on its operational CR - // and wait for a runner version that supports the capability. - CapabilityUnavailable FailureCategory = "CapabilityUnavailable" - - // ExecutionFailure indicates a step-level failure during execution. - // The capability began executing but a step did not complete successfully. - ExecutionFailure FailureCategory = "ExecutionFailure" - - // ExternalDependencyFailure indicates the Kubernetes API, Talos API, or OCI - // registry was unreachable during execution. - ExternalDependencyFailure FailureCategory = "ExternalDependencyFailure" - - // InvariantViolation indicates a programming error: a mode boundary was crossed, - // a forbidden client was invoked, or a contract was violated. These failures - // indicate runner implementation bugs and should never occur in production. - InvariantViolation FailureCategory = "InvariantViolation" - - // LicenseViolation indicates license constraints are not satisfied for this - // cluster. The runner agent enforces this at startup. CR-INV-007, CR-INV-008. - LicenseViolation FailureCategory = "LicenseViolation" - - // StorageUnavailable indicates PVC creation failed for a multi-step capability. - // The management cluster must have a storage class available. - // conductor-design.md Section 5.6, conductor-schema.md Section 7. - StorageUnavailable FailureCategory = "StorageUnavailable" -) - -// DeployedResource records a single Kubernetes resource applied by a pack-deploy -// capability. Stored in OperationResultSpec.DeployedResources so the wrapper -// PackInstanceReconciler can write the list to PackInstance.Status.DeployedResources -// for use by the deletion handler. wrapper-schema.md §3, Decision 11. -type DeployedResource struct { - // APIVersion is the Kubernetes apiVersion (e.g., apps/v1, v1). - APIVersion string `json:"apiVersion"` - - // Kind is the Kubernetes resource Kind (e.g., Deployment, Namespace). - Kind string `json:"kind"` - - // Namespace is the resource namespace. Empty for cluster-scoped resources. - Namespace string `json:"namespace,omitempty"` - - // Name is the resource name. - Name string `json:"name"` -} - -// StepResult is the execution result for one step within a multi-step capability. -// Aggregated into OperationResultSpec.Steps for multi-step capabilities. -// Empty for single-step capabilities. -type StepResult struct { - // Name is the step identifier within the capability. Unique within the capability. - Name string - - // Status is the terminal status of this step. - Status ResultStatus - - // StartedAt is the time this step began execution. - StartedAt time.Time - - // CompletedAt is the time this step finished execution. - CompletedAt time.Time - - // Message provides additional context about the step outcome. - // Populated on both success and failure. - Message string -} diff --git a/pkg/runnerlib/packreceipt.go b/pkg/runnerlib/packreceipt.go deleted file mode 100644 index a5ceba8..0000000 --- a/pkg/runnerlib/packreceipt.go +++ /dev/null @@ -1 +0,0 @@ -package runnerlib diff --git a/pkg/runnerlib/packreceipt_test.go b/pkg/runnerlib/packreceipt_test.go deleted file mode 100644 index 9b6eacd..0000000 --- a/pkg/runnerlib/packreceipt_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Package runnerlib -- T-08 unit tests for InfrastructurePackReceiptSpec fields. -// -// Tests cover: -// - InfrastructurePackReceiptSpec carries RBACDigest, WorkloadDigest, ChartVersion, -// ChartURL, ChartName, HelmVersion. -// - All six fields survive a JSON serialization round-trip. -// - Fields are zero-value (empty string) when not populated. -// -// T-08, Decision B, T-04 schema. Types migrated to seam-core in T-2B-6. -package runnerlib_test - -import ( - "encoding/json" - "testing" - - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" -) - -func TestPackReceiptSpec_NewFields_RoundTrip(t *testing.T) { - spec := seamcorev1alpha1.InfrastructurePackReceiptSpec{ - ClusterPackRef: "cert-manager-v1.13.3-r1", - TargetClusterRef: "ccs-mgmt", - RBACDigest: "sha256:aabbcc", - WorkloadDigest: "sha256:ddeeff", - ChartVersion: "v1.13.3", - ChartURL: "https://charts.jetstack.io", - ChartName: "cert-manager", - HelmVersion: "v3.14.0", - } - - data, err := json.Marshal(spec) - if err != nil { - t.Fatalf("marshal InfrastructurePackReceiptSpec: %v", err) - } - - var got seamcorev1alpha1.InfrastructurePackReceiptSpec - if err := json.Unmarshal(data, &got); err != nil { - t.Fatalf("unmarshal InfrastructurePackReceiptSpec: %v", err) - } - - checks := map[string][2]string{ - "RBACDigest": {got.RBACDigest, spec.RBACDigest}, - "WorkloadDigest": {got.WorkloadDigest, spec.WorkloadDigest}, - "ChartVersion": {got.ChartVersion, spec.ChartVersion}, - "ChartURL": {got.ChartURL, spec.ChartURL}, - "ChartName": {got.ChartName, spec.ChartName}, - "HelmVersion": {got.HelmVersion, spec.HelmVersion}, - } - for field, pair := range checks { - if pair[0] != pair[1] { - t.Errorf("%s: got %q, want %q", field, pair[0], pair[1]) - } - } -} - -func TestPackReceiptSpec_NewFields_ZeroWhenAbsent(t *testing.T) { - spec := seamcorev1alpha1.InfrastructurePackReceiptSpec{ - ClusterPackRef: "raw-pack-v1.0.0-r1", - TargetClusterRef: "ccs-mgmt", - // No digest or chart fields -- raw or kustomize pack. - } - - if spec.RBACDigest != "" { - t.Errorf("RBACDigest: expected empty, got %q", spec.RBACDigest) - } - if spec.WorkloadDigest != "" { - t.Errorf("WorkloadDigest: expected empty, got %q", spec.WorkloadDigest) - } - if spec.ChartVersion != "" { - t.Errorf("ChartVersion: expected empty, got %q", spec.ChartVersion) - } - if spec.ChartURL != "" { - t.Errorf("ChartURL: expected empty, got %q", spec.ChartURL) - } - if spec.ChartName != "" { - t.Errorf("ChartName: expected empty, got %q", spec.ChartName) - } - if spec.HelmVersion != "" { - t.Errorf("HelmVersion: expected empty, got %q", spec.HelmVersion) - } -} diff --git a/pkg/runnerlib/runnerconfig.go b/pkg/runnerlib/runnerconfig.go deleted file mode 100644 index a5ceba8..0000000 --- a/pkg/runnerlib/runnerconfig.go +++ /dev/null @@ -1 +0,0 @@ -package runnerlib diff --git a/test/unit/agent/capability_publisher_test.go b/test/unit/agent/capability_publisher_test.go index 0153610..4b2702e 100644 --- a/test/unit/agent/capability_publisher_test.go +++ b/test/unit/agent/capability_publisher_test.go @@ -14,7 +14,7 @@ import ( k8stesting "k8s.io/client-go/testing" "github.com/ontai-dev/conductor/internal/agent" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) var runnerConfigGVR = schema.GroupVersionResource{ diff --git a/test/unit/capability/dispatcher_test.go b/test/unit/capability/dispatcher_test.go index 90d3713..0cd87a7 100644 --- a/test/unit/capability/dispatcher_test.go +++ b/test/unit/capability/dispatcher_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/ontai-dev/conductor/internal/capability" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // TestDispatcher_ExecuteKnownCapabilityReturnsResult verifies that dispatching diff --git a/test/unit/capability/guardian_test.go b/test/unit/capability/guardian_test.go index fe9498f..e46b55c 100644 --- a/test/unit/capability/guardian_test.go +++ b/test/unit/capability/guardian_test.go @@ -17,7 +17,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "github.com/ontai-dev/conductor/internal/capability" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // permissionSnapshotGVR mirrors the GVR in guardian.go. diff --git a/test/unit/capability/pack_deploy_split_test.go b/test/unit/capability/pack_deploy_split_test.go index f58fd84..602a63e 100644 --- a/test/unit/capability/pack_deploy_split_test.go +++ b/test/unit/capability/pack_deploy_split_test.go @@ -25,7 +25,7 @@ import ( k8stesting "k8s.io/client-go/testing" "github.com/ontai-dev/conductor/internal/capability" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // --------------------------------------------------------------------------- diff --git a/test/unit/capability/platform_test.go b/test/unit/capability/platform_test.go index b944714..32cc861 100644 --- a/test/unit/capability/platform_test.go +++ b/test/unit/capability/platform_test.go @@ -19,7 +19,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "github.com/ontai-dev/conductor/internal/capability" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // --------------------------------------------------------------------------- diff --git a/test/unit/capability/registry_test.go b/test/unit/capability/registry_test.go index a292c02..d7c9f21 100644 --- a/test/unit/capability/registry_test.go +++ b/test/unit/capability/registry_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/ontai-dev/conductor/internal/capability" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // fixedHandler is a test Handler that returns a pre-set result. diff --git a/test/unit/capability/wrapper_test.go b/test/unit/capability/wrapper_test.go index cefd5ba..dcb3a16 100644 --- a/test/unit/capability/wrapper_test.go +++ b/test/unit/capability/wrapper_test.go @@ -18,7 +18,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "github.com/ontai-dev/conductor/internal/capability" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // makeTarGz builds an in-memory tar.gz archive from the provided file map. diff --git a/test/unit/kernel/execute_capability_test.go b/test/unit/kernel/execute_capability_test.go index 27cc1af..edfe609 100644 --- a/test/unit/kernel/execute_capability_test.go +++ b/test/unit/kernel/execute_capability_test.go @@ -6,7 +6,7 @@ import ( "github.com/ontai-dev/conductor/internal/config" "github.com/ontai-dev/conductor/internal/kernel" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" ) diff --git a/test/unit/kernel/execute_sequencer_test.go b/test/unit/kernel/execute_sequencer_test.go index ff6ac12..0b6105e 100644 --- a/test/unit/kernel/execute_sequencer_test.go +++ b/test/unit/kernel/execute_sequencer_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/ontai-dev/conductor/internal/kernel" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" ) diff --git a/test/unit/persistence/configmap_writer_test.go b/test/unit/persistence/configmap_writer_test.go index 602ac02..8109ffb 100644 --- a/test/unit/persistence/configmap_writer_test.go +++ b/test/unit/persistence/configmap_writer_test.go @@ -10,7 +10,7 @@ import ( "k8s.io/client-go/kubernetes/fake" "github.com/ontai-dev/conductor/internal/persistence" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // TestConfigMapWriter_CreatesConfigMapWithResultKey verifies that WriteResult diff --git a/test/unit/persistence/operationresult_writer_test.go b/test/unit/persistence/operationresult_writer_test.go index 276c5c9..242dcaf 100644 --- a/test/unit/persistence/operationresult_writer_test.go +++ b/test/unit/persistence/operationresult_writer_test.go @@ -12,7 +12,7 @@ import ( seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" "github.com/ontai-dev/conductor/internal/persistence" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) func buildTestScheme(t *testing.T) *runtime.Scheme { diff --git a/test/unit/persistence/tcor_writer_test.go b/test/unit/persistence/tcor_writer_test.go index 9447e28..121128c 100644 --- a/test/unit/persistence/tcor_writer_test.go +++ b/test/unit/persistence/tcor_writer_test.go @@ -13,7 +13,7 @@ import ( seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" "github.com/ontai-dev/conductor/internal/persistence" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) func buildTCORScheme(t *testing.T) *runtime.Scheme { diff --git a/test/unit/runnerlib/capability_test.go b/test/unit/runnerlib/capability_test.go index 8f9f298..029387d 100644 --- a/test/unit/runnerlib/capability_test.go +++ b/test/unit/runnerlib/capability_test.go @@ -3,7 +3,7 @@ package runnerlib_test import ( "testing" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // TestCapabilityManifestEntriesNonNil verifies that a CapabilityManifest with diff --git a/test/unit/runnerlib/constants_test.go b/test/unit/runnerlib/constants_test.go index fbe1a7c..928276a 100644 --- a/test/unit/runnerlib/constants_test.go +++ b/test/unit/runnerlib/constants_test.go @@ -3,7 +3,7 @@ package runnerlib_test import ( "testing" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // allCapabilityConstants returns all 18 named capability constants. diff --git a/test/unit/runnerlib/generators_test.go b/test/unit/runnerlib/generators_test.go index c5a36bc..6667a19 100644 --- a/test/unit/runnerlib/generators_test.go +++ b/test/unit/runnerlib/generators_test.go @@ -3,7 +3,7 @@ package runnerlib_test import ( "testing" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // TestGenerateFromTalosClusterZeroValueSpec verifies that GenerateFromTalosCluster diff --git a/test/unit/runnerlib/jobspec_test.go b/test/unit/runnerlib/jobspec_test.go index 4bcc0be..26a4ab0 100644 --- a/test/unit/runnerlib/jobspec_test.go +++ b/test/unit/runnerlib/jobspec_test.go @@ -3,7 +3,7 @@ package runnerlib_test import ( "testing" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // TestJobSpecZeroValueNoPanic verifies that the zero value of JobSpec is diff --git a/test/unit/runnerlib/operationresult_test.go b/test/unit/runnerlib/operationresult_test.go index 1709d65..0c890e2 100644 --- a/test/unit/runnerlib/operationresult_test.go +++ b/test/unit/runnerlib/operationresult_test.go @@ -3,7 +3,7 @@ package runnerlib_test import ( "testing" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" ) // TestResultStatusConstantsDistinct verifies that ResultSucceeded and ResultFailed diff --git a/test/unit/runnerlib/runnerconfig_test.go b/test/unit/runnerlib/runnerconfig_test.go index 2ddd519..1289e1c 100644 --- a/test/unit/runnerlib/runnerconfig_test.go +++ b/test/unit/runnerlib/runnerconfig_test.go @@ -3,7 +3,7 @@ package runnerlib_test import ( "testing" - "github.com/ontai-dev/conductor/pkg/runnerlib" + "github.com/ontai-dev/conductor-sdk/runnerlib" seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" ) From 2225b585bb1b452bf36432dcb3604fab0302ae9d Mon Sep 17 00:00:00 2001 From: ontave Date: Tue, 12 May 2026 12:36:04 +0200 Subject: [PATCH 05/13] feat(phase-3.3-3.7): update conductor consumers to seam.ontai.dev dispatcher types Update all GVR references, scheme registrations, and import paths in conductor to consume the migrated dispatcher types from wrapper/api/seam: PackDelivery (was InfrastructureClusterPack), PackExecution, PackInstalled (was InfrastructurePackInstance), PackReceipt, PackLog (was PackOperationResult). packDeliveryRef field replaces clusterPackRef in pack_receipt_drift_loop.go and all associated tests. compileLaunchBundle now embeds wrapper CRDs via wrappercrd.FS so agents receive the seam.ontai.dev CRD bundle at startup. --- cmd/compiler/compile.go | 12 +-- cmd/compiler/compile_launch.go | 10 +- cmd/compiler/compile_launch_test.go | 10 +- cmd/compiler/compile_packbuild_helm.go | 12 +-- cmd/compiler/compile_packbuild_kustomize.go | 12 +-- cmd/compiler/compile_packbuild_raw.go | 12 +-- cmd/compiler/compile_packbuild_test.go | 14 +-- cmd/conductor/main.go | 4 + go.mod | 1 + internal/agent/pack_receipt_drift_loop.go | 10 +- .../agent/pack_receipt_drift_loop_test.go | 22 ++--- internal/agent/receipt_reconciler.go | 6 +- internal/agent/signing_loop.go | 20 ++-- internal/capability/wrapper.go | 30 +++--- .../persistence/operationresult_writer.go | 95 +++++++++---------- .../operationresult_writer_test.go | 38 ++++---- .../signing/signing_integration_test.go | 23 +++-- .../unit/agent/packinstance_pull_loop_test.go | 8 +- test/unit/agent/receipt_reconciler_test.go | 4 +- test/unit/agent/signing_loop_test.go | 12 +-- .../unit/capability/pack_deploy_split_test.go | 20 ++-- test/unit/capability/wrapper_test.go | 20 ++-- .../operationresult_writer_test.go | 64 ++++++------- 23 files changed, 230 insertions(+), 229 deletions(-) diff --git a/cmd/compiler/compile.go b/cmd/compiler/compile.go index a885db2..b14054b 100644 --- a/cmd/compiler/compile.go +++ b/cmd/compiler/compile.go @@ -24,7 +24,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/config/generate/secrets" "github.com/siderolabs/talos/pkg/machinery/config/machine" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" platformv1alpha1 "github.com/ontai-dev/platform/api/v1alpha1" ) @@ -1394,18 +1394,18 @@ func compilePackBuild(input, output string) error { ns = "seam-system" } - cp := seamcorev1alpha1.InfrastructureClusterPack{ + cp := dispatcherv1alpha1.PackDelivery{ TypeMeta: metav1.TypeMeta{ - APIVersion: "infrastructure.ontai.dev/v1alpha1", - Kind: "InfrastructureClusterPack", + APIVersion: "seam.ontai.dev/v1alpha1", + Kind: "PackDelivery", }, ObjectMeta: metav1.ObjectMeta{ Name: in.Name, Namespace: ns, }, - Spec: seamcorev1alpha1.InfrastructureClusterPackSpec{ + Spec: dispatcherv1alpha1.PackDeliverySpec{ Version: in.Version, - RegistryRef: seamcorev1alpha1.InfrastructurePackRegistryRef{ + RegistryRef: dispatcherv1alpha1.PackRegistryRef{ URL: in.RegistryURL, Digest: in.Digest, }, diff --git a/cmd/compiler/compile_launch.go b/cmd/compiler/compile_launch.go index 4a08250..dea3dc9 100644 --- a/cmd/compiler/compile_launch.go +++ b/cmd/compiler/compile_launch.go @@ -19,6 +19,7 @@ import ( guardiancrd "github.com/ontai-dev/guardian/config/crd" platformcrd "github.com/ontai-dev/platform/config/crd" seamcorecrd "github.com/ontai-dev/seam-core/config/crd" + wrappercrd "github.com/ontai-dev/wrapper/config/crd" ) const launchHelp = `Usage: compiler launch --output [--kubeconfig ] @@ -72,8 +73,8 @@ func runLaunchSubcommand(args []string) { // CRD sources (all embedded at build time): // - platform.ontai.dev: TalosCluster, day-2 CRDs, SeamInfrastructureCluster/Machine // - security.ontai.dev: RBACPolicy, RBACProfile, IdentityBinding, IdentityProvider, PermissionSet -// - infrastructure.ontai.dev: InfrastructureClusterPack, InfrastructurePackExecution, -// InfrastructurePackInstance, InfrastructureRunnerConfig, InfrastructureLineageIndex (seam-core) +// - infrastructure.ontai.dev: InfrastructureRunnerConfig, InfrastructureLineageIndex (seam-core) +// - seam.ontai.dev: PackDelivery, PackExecution, PackInstalled, PackReceipt, PackLog (wrapper) // // Output is deterministic: CRD files within each operator are sorted by name. // conductor-schema.md §9 Step 2. @@ -83,9 +84,7 @@ func compileLaunchBundle(output string) error { } // Collect CRD YAML from all operator embedded filesystems. - // Order: platform, guardian, seam-core. - // wrapper and conductor no longer carry their own CRDs; all infrastructure.ontai.dev - // CRDs are declared in seam-core. + // Order: platform, guardian, seam-core, wrapper. sources := []struct { name string fsys fs.FS @@ -93,6 +92,7 @@ func compileLaunchBundle(output string) error { {"platform", platformcrd.FS}, {"guardian", guardiancrd.FS}, {"seam-core", seamcorecrd.FS}, + {"wrapper", wrappercrd.FS}, } var bundle bytes.Buffer diff --git a/cmd/compiler/compile_launch_test.go b/cmd/compiler/compile_launch_test.go index 7f72832..4f4508b 100644 --- a/cmd/compiler/compile_launch_test.go +++ b/cmd/compiler/compile_launch_test.go @@ -53,9 +53,9 @@ func TestLaunch_BundleContainsGuardianCRDs(t *testing.T) { assertContainsStr(t, content, "rbacprofiles") } -// TestLaunch_BundleContainsWrapperCRDs verifies that infrastructure.ontai.dev CRDs -// for wrapper types (InfrastructureClusterPack etc.) are present in the bundle. -// wrapper-schema.md. Wrapper CRDs migrated to seam-core (infrastructure.ontai.dev). +// TestLaunch_BundleContainsWrapperCRDs verifies that seam.ontai.dev CRDs +// for wrapper dispatcher types (PackDelivery etc.) are present in the bundle. +// wrapper-schema.md. Dispatcher types migrated to wrapper under seam.ontai.dev. func TestLaunch_BundleContainsWrapperCRDs(t *testing.T) { outDir := t.TempDir() if err := compileLaunchBundle(outDir); err != nil { @@ -64,8 +64,8 @@ func TestLaunch_BundleContainsWrapperCRDs(t *testing.T) { data, _ := os.ReadFile(filepath.Join(outDir, "crds.yaml")) content := string(data) - assertContainsStr(t, content, "infrastructure.ontai.dev") - assertContainsStr(t, content, "infrastructureclusterpacks") + assertContainsStr(t, content, "seam.ontai.dev") + assertContainsStr(t, content, "packdeliveries") } // TestLaunch_BundleContainsSeamCoreCRDs verifies that infrastructure.ontai.dev diff --git a/cmd/compiler/compile_packbuild_helm.go b/cmd/compiler/compile_packbuild_helm.go index 52f74dd..5f435fb 100644 --- a/cmd/compiler/compile_packbuild_helm.go +++ b/cmd/compiler/compile_packbuild_helm.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" ) // HelmSource describes a Helm chart source for automated packbuild. @@ -242,18 +242,18 @@ func helmCompilePackBuild(ctx context.Context, in PackBuildInput, inputDir, outp // Emit ClusterPack CR. ns := in.Namespace - cp := seamcorev1alpha1.InfrastructureClusterPack{ + cp := dispatcherv1alpha1.PackDelivery{ TypeMeta: metav1.TypeMeta{ - APIVersion: "infrastructure.ontai.dev/v1alpha1", - Kind: "InfrastructureClusterPack", + APIVersion: "seam.ontai.dev/v1alpha1", + Kind: "PackDelivery", }, ObjectMeta: metav1.ObjectMeta{ Name: in.Name, Namespace: ns, }, - Spec: seamcorev1alpha1.InfrastructureClusterPackSpec{ + Spec: dispatcherv1alpha1.PackDeliverySpec{ Version: in.Version, - RegistryRef: seamcorev1alpha1.InfrastructurePackRegistryRef{ + RegistryRef: dispatcherv1alpha1.PackRegistryRef{ URL: in.RegistryURL, Digest: workloadDigest, }, diff --git a/cmd/compiler/compile_packbuild_kustomize.go b/cmd/compiler/compile_packbuild_kustomize.go index 6e4f916..92a3d93 100644 --- a/cmd/compiler/compile_packbuild_kustomize.go +++ b/cmd/compiler/compile_packbuild_kustomize.go @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/kustomize/api/krusty" "sigs.k8s.io/kustomize/kyaml/filesys" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" ) // KustomizeSource describes the kustomize overlay directory for automated packbuild. @@ -94,18 +94,18 @@ func kustomizeCompilePackBuild(ctx context.Context, in PackBuildInput, inputDir, checksum := computeChecksum(rbacLayer + clusterScopedLayer + workloadLayer) - cp := seamcorev1alpha1.InfrastructureClusterPack{ + cp := dispatcherv1alpha1.PackDelivery{ TypeMeta: metav1.TypeMeta{ - APIVersion: "infrastructure.ontai.dev/v1alpha1", - Kind: "InfrastructureClusterPack", + APIVersion: "seam.ontai.dev/v1alpha1", + Kind: "PackDelivery", }, ObjectMeta: metav1.ObjectMeta{ Name: in.Name, Namespace: in.Namespace, }, - Spec: seamcorev1alpha1.InfrastructureClusterPackSpec{ + Spec: dispatcherv1alpha1.PackDeliverySpec{ Version: in.Version, - RegistryRef: seamcorev1alpha1.InfrastructurePackRegistryRef{ + RegistryRef: dispatcherv1alpha1.PackRegistryRef{ URL: in.RegistryURL, Digest: workloadDigest, }, diff --git a/cmd/compiler/compile_packbuild_raw.go b/cmd/compiler/compile_packbuild_raw.go index 7f5eaa7..797a496 100644 --- a/cmd/compiler/compile_packbuild_raw.go +++ b/cmd/compiler/compile_packbuild_raw.go @@ -14,7 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" ) // RawSource describes a directory of raw YAML manifest files for automated packbuild. @@ -102,18 +102,18 @@ func rawCompilePackBuild(ctx context.Context, in PackBuildInput, inputDir, outpu checksum := computeChecksum(rbacLayer + clusterScopedLayer + workloadLayer) - cp := seamcorev1alpha1.InfrastructureClusterPack{ + cp := dispatcherv1alpha1.PackDelivery{ TypeMeta: metav1.TypeMeta{ - APIVersion: "infrastructure.ontai.dev/v1alpha1", - Kind: "InfrastructureClusterPack", + APIVersion: "seam.ontai.dev/v1alpha1", + Kind: "PackDelivery", }, ObjectMeta: metav1.ObjectMeta{ Name: in.Name, Namespace: in.Namespace, }, - Spec: seamcorev1alpha1.InfrastructureClusterPackSpec{ + Spec: dispatcherv1alpha1.PackDeliverySpec{ Version: in.Version, - RegistryRef: seamcorev1alpha1.InfrastructurePackRegistryRef{ + RegistryRef: dispatcherv1alpha1.PackRegistryRef{ URL: in.RegistryURL, Digest: workloadDigest, }, diff --git a/cmd/compiler/compile_packbuild_test.go b/cmd/compiler/compile_packbuild_test.go index fd40142..3ce24e3 100644 --- a/cmd/compiler/compile_packbuild_test.go +++ b/cmd/compiler/compile_packbuild_test.go @@ -54,7 +54,7 @@ func TestPackBuild_ProducesClusterPackYAML(t *testing.T) { } // TestPackBuild_ClusterPackHasCorrectAPIVersionAndKind verifies the emitted -// ClusterPack CR carries apiVersion=infrastructure.ontai.dev/v1alpha1 and kind=InfrastructureClusterPack. +// ClusterPack CR carries apiVersion=seam.ontai.dev/v1alpha1 and kind=PackDelivery. // conductor-schema.md §9. func TestPackBuild_ClusterPackHasCorrectAPIVersionAndKind(t *testing.T) { inputPath := writePackBuildInput(t, validPackBuildInput) @@ -74,10 +74,10 @@ func TestPackBuild_ClusterPackHasCorrectAPIVersionAndKind(t *testing.T) { t.Fatalf("parse output YAML: %v", err) } - if cr["apiVersion"] != "infrastructure.ontai.dev/v1alpha1" { + if cr["apiVersion"] != "seam.ontai.dev/v1alpha1" { t.Errorf("apiVersion: got %q; want %q", cr["apiVersion"], "infrastructure.ontai.dev/v1alpha1") } - if cr["kind"] != "InfrastructureClusterPack" { + if cr["kind"] != "PackDelivery" { t.Errorf("kind: got %q; want %q", cr["kind"], "InfrastructureClusterPack") } } @@ -277,8 +277,8 @@ spec: } content := string(data) for _, want := range []string{ - "infrastructure.ontai.dev/v1alpha1", - "InfrastructureClusterPack", + "seam.ontai.dev/v1alpha1", + "PackDelivery", "rbacDigest", "workloadDigest", "v0.1.0-r1", @@ -573,8 +573,8 @@ func TestKustomizeCompilePackBuild_ProducesClusterPack(t *testing.T) { } content := string(data) for _, want := range []string{ - "infrastructure.ontai.dev/v1alpha1", - "InfrastructureClusterPack", + "seam.ontai.dev/v1alpha1", + "PackDelivery", "workloadDigest", "v0.1.0-r1", "seam-system", diff --git a/cmd/conductor/main.go b/cmd/conductor/main.go index dbaf554..22936f5 100644 --- a/cmd/conductor/main.go +++ b/cmd/conductor/main.go @@ -33,6 +33,7 @@ import ( ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" "github.com/ontai-dev/conductor/internal/capability" "github.com/ontai-dev/conductor/internal/config" "github.com/ontai-dev/conductor/internal/kernel" @@ -46,6 +47,9 @@ func init() { if err := seamv1alpha1.AddToScheme(seamScheme); err != nil { panic("conductor: failed to register seam-core scheme: " + err.Error()) } + if err := dispatcherv1alpha1.AddToScheme(seamScheme); err != nil { + panic("conductor: failed to register dispatcher scheme: " + err.Error()) + } } func main() { diff --git a/go.mod b/go.mod index 9596558..7f0d63b 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/ontai-dev/guardian v0.0.0-00010101000000-000000000000 github.com/ontai-dev/platform v0.0.0-00010101000000-000000000000 github.com/ontai-dev/seam-core v0.1.0-alpha.0.20260426085946-e3630ad7b38f + github.com/ontai-dev/wrapper v0.0.0-00010101000000-000000000000 github.com/prometheus/client_golang v1.23.2 github.com/siderolabs/talos/pkg/machinery v1.12.6 google.golang.org/grpc v1.79.3 diff --git a/internal/agent/pack_receipt_drift_loop.go b/internal/agent/pack_receipt_drift_loop.go index da065a2..5ba3dc9 100644 --- a/internal/agent/pack_receipt_drift_loop.go +++ b/internal/agent/pack_receipt_drift_loop.go @@ -24,13 +24,13 @@ var driftSignalGVR = schema.GroupVersionResource{ Resource: "driftsignals", } -// clusterPackMgmtGVR is the GroupVersionResource for ClusterPack CRs on the +// clusterPackMgmtGVR is the GroupVersionResource for PackDelivery CRs on the // management cluster. Used by the drift loop to reconstruct the signed message // for packSignature verification. conductor-schema.md §7.3. var clusterPackMgmtGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructureclusterpacks", + Resource: "packdeliveries", } // escalationThreshold is the maximum number of drift re-emit cycles before the @@ -105,7 +105,7 @@ func (l *PackReceiptDriftLoop) runOnce(ctx context.Context) { // cluster, tear down all deployed resources and delete this PackReceipt. The // conductor owns cleanup of its cluster's resources when the governance record is // revoked. Decision H, conductor-schema.md §7.9. - clusterPackRef, _ := spec["clusterPackRef"].(string) + clusterPackRef, _ := spec["packDeliveryRef"].(string) if clusterPackRef != "" { _, cpErr := l.mgmtClient.Resource(clusterPackMgmtGVR).Namespace(l.mgmtTenantNS).Get( ctx, clusterPackRef, metav1.GetOptions{}, @@ -240,7 +240,7 @@ func (l *PackReceiptDriftLoop) checkDrift(ctx context.Context, spec map[string]i return false } - clusterPackRef, _ := spec["clusterPackRef"].(string) + clusterPackRef, _ := spec["packDeliveryRef"].(string) for _, raw := range rawItems { item, ok := raw.(map[string]interface{}) diff --git a/internal/agent/pack_receipt_drift_loop_test.go b/internal/agent/pack_receipt_drift_loop_test.go index 6f84cc9..fd9a244 100644 --- a/internal/agent/pack_receipt_drift_loop_test.go +++ b/internal/agent/pack_receipt_drift_loop_test.go @@ -16,9 +16,9 @@ import ( // verified is nil for executor-created receipts (status.verified absent), // pointer-to-true for receipts confirmed by the packinstance pull loop, // pointer-to-false for receipts whose signature verification failed. -func fakePackReceipt(name, clusterPackRef string, verified *bool, resources []map[string]interface{}) *unstructured.Unstructured { +func fakePackReceipt(name, packDeliveryRef string, verified *bool, resources []map[string]interface{}) *unstructured.Unstructured { spec := map[string]interface{}{ - "clusterPackRef": clusterPackRef, + "packDeliveryRef": packDeliveryRef, "targetClusterRef": "ccs-dev", } if len(resources) > 0 { @@ -29,8 +29,8 @@ func fakePackReceipt(name, clusterPackRef string, verified *bool, resources []ma spec["deployedResources"] = items } obj := map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackReceipt", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackReceipt", "metadata": map[string]interface{}{ "name": name, "namespace": "ont-system", @@ -46,12 +46,12 @@ func fakePackReceipt(name, clusterPackRef string, verified *bool, resources []ma return &unstructured.Unstructured{Object: obj} } -// fakeClusterPack builds a fake InfrastructureClusterPack for the management cluster. +// fakeClusterPack builds a fake PackDelivery for the management cluster. func fakeClusterPack(name, ns string, specPayload map[string]interface{}) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureClusterPack", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackDelivery", "metadata": map[string]interface{}{ "name": name, "namespace": ns, @@ -64,16 +64,16 @@ func fakeClusterPack(name, ns string, specPayload map[string]interface{}) *unstr func setupDriftLoopScheme() *runtime.Scheme { s := runtime.NewScheme() s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructurePackReceipt", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "PackReceipt", }, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructurePackReceiptList", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "PackReceiptList", }, &unstructured.UnstructuredList{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureClusterPack", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "PackDelivery", }, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureClusterPackList", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "PackDeliveryList", }, &unstructured.UnstructuredList{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "DriftSignal", diff --git a/internal/agent/receipt_reconciler.go b/internal/agent/receipt_reconciler.go index beda9da..201fd6a 100644 --- a/internal/agent/receipt_reconciler.go +++ b/internal/agent/receipt_reconciler.go @@ -17,11 +17,11 @@ import ( ) // packReceiptGVR is the GroupVersionResource for PackReceipt CRs. -// Defined in infrastructure.ontai.dev (seam-core). conductor-schema.md §10, §15. +// Defined in seam.ontai.dev (dispatcher). conductor-schema.md §10, §15. var packReceiptGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurepackreceipts", + Resource: "packreceipts", } // permissionSnapshotReceiptGVR is the GroupVersionResource for PermissionSnapshotReceipt CRs. diff --git a/internal/agent/signing_loop.go b/internal/agent/signing_loop.go index b5f2f14..9d3ac28 100644 --- a/internal/agent/signing_loop.go +++ b/internal/agent/signing_loop.go @@ -21,22 +21,22 @@ import ( "k8s.io/client-go/dynamic" ) -// packInstanceGVR is the GroupVersionResource for PackInstance CRs. -// Defined in infrastructure.ontai.dev (seam-core). conductor-schema.md §10. +// packInstanceGVR is the GroupVersionResource for PackInstalled CRs. +// Defined in seam.ontai.dev (wrapper dispatcher). conductor-schema.md §10. var packInstanceGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurepackinstances", + Resource: "packinstalleds", } -// clusterPackGVR is the GroupVersionResource for ClusterPack CRs. -// Defined in infrastructure.ontai.dev (seam-core). conductor-schema.md §10. -// The management cluster Conductor signs ClusterPacks so the wrapper -// ClusterPackReconciler can transition Status.Signed=true and Available. +// clusterPackGVR is the GroupVersionResource for PackDelivery CRs. +// Defined in seam.ontai.dev (wrapper dispatcher). conductor-schema.md §10. +// The management cluster Conductor signs PackDeliveries so the wrapper +// PackDeliveryReconciler can transition Status.Signed=true and Available. var clusterPackGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructureclusterpacks", + Resource: "packdeliveries", } // clusterPackSignatureAnnotation is the annotation key read by the wrapper diff --git a/internal/capability/wrapper.go b/internal/capability/wrapper.go index d117487..cf2ee6b 100644 --- a/internal/capability/wrapper.go +++ b/internal/capability/wrapper.go @@ -33,28 +33,28 @@ import ( var namespaceGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"} // packExecutionGVR is the GroupVersionResource for PackExecution. -// infrastructure.ontai.dev/v1alpha1/infrastructurepackexecutions — wrapper-schema.md §4. +// seam.ontai.dev/v1alpha1/packexecutions — wrapper-schema.md §4. var packExecutionGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurepackexecutions", + Resource: "packexecutions", } -// clusterPackGVR is the GroupVersionResource for ClusterPack. -// infrastructure.ontai.dev/v1alpha1/infrastructureclusterpacks — wrapper-schema.md §4. +// clusterPackGVR is the GroupVersionResource for PackDelivery. +// seam.ontai.dev/v1alpha1/packdeliveries — wrapper-schema.md §4. var clusterPackGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructureclusterpacks", + Resource: "packdeliveries", } // packReceiptGVR is the GroupVersionResource for PackReceipt. // Written to ont-system on the tenant cluster after successful pack apply. // Sole local desired-state reference on tenant clusters. conductor-schema.md. var packReceiptGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurepackreceipts", + Resource: "packreceipts", } // Readiness-check GVRs. Used by isResourceReady to poll resource status. @@ -104,12 +104,12 @@ func (h *packDeployHandler) Execute(ctx context.Context, params ExecuteParams) ( fmt.Sprintf("get PackExecution %s/%s: %v", peTenantNS, peName, err)), nil } - clusterPackName, _, _ := unstructuredString(peObj.Object, "spec", "clusterPackRef", "name") - clusterPackVersion, _, _ := unstructuredString(peObj.Object, "spec", "clusterPackRef", "version") + clusterPackName, _, _ := unstructuredString(peObj.Object, "spec", "packDeliveryRef", "name") + clusterPackVersion, _, _ := unstructuredString(peObj.Object, "spec", "packDeliveryRef", "version") if clusterPackName == "" { return failureResult(runnerlib.CapabilityPackDeploy, now, runnerlib.ValidationFailure, - fmt.Sprintf("PackExecution %s/%s has no clusterPackRef.name", peTenantNS, peName)), nil + fmt.Sprintf("PackExecution %s/%s has no packDeliveryRef.name", peTenantNS, peName)), nil } // Read the ClusterPack to get OCI registry reference, checksum, and @@ -946,7 +946,7 @@ func writePackReceipt(ctx context.Context, tenantClient dynamic.Interface, clust } spec := map[string]interface{}{ - "clusterPackRef": clusterPackRef, + "packDeliveryRef": clusterPackRef, "targetClusterRef": targetCluster, "rbacDigest": rbacDigest, "workloadDigest": workloadDigest, @@ -956,8 +956,8 @@ func writePackReceipt(ctx context.Context, tenantClient dynamic.Interface, clust } receiptJSON, err := json.Marshal(map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackReceipt", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackReceipt", "metadata": map[string]interface{}{ "name": clusterPackRef, "namespace": "ont-system", diff --git a/internal/persistence/operationresult_writer.go b/internal/persistence/operationresult_writer.go index 5f456b7..66c1641 100644 --- a/internal/persistence/operationresult_writer.go +++ b/internal/persistence/operationresult_writer.go @@ -1,5 +1,5 @@ // Package persistence OperationResultWriter writes OperationResultSpec to a -// PackOperationResult CR in seam-core (infrastructure.ontai.dev/v1alpha1). +// PackLog CR in the dispatcher API (seam.ontai.dev/v1alpha1). // Replaces the ConfigMap output channel. seam-core-schema.md §8, Decision 11. package persistence @@ -12,38 +12,37 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" - seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" "github.com/ontai-dev/conductor-sdk/runnerlib" ) -// labelPackExecution is the label key used to group PackOperationResult CRs by +// labelPackExecution is the label key used to group PackLog CRs by // the PackExecution they belong to. The single-active-revision pattern relies on // this label for list queries. const labelPackExecution = "ontai.dev/pack-execution" -// labelClusterPack is the label key used to group PackOperationResult CRs by -// the ClusterPack they belong to. Used by the wrapper rollback handler to find -// any POR for a given ClusterPack (both active and superseded) for N-step rollback. -// seam-core-schema.md §7.8. +// labelClusterPack is the label key used to group PackLog CRs by +// the PackDelivery they belong to. Used by the wrapper rollback handler to find +// any PackLog for a given PackDelivery (both active and superseded) for N-step rollback. const labelClusterPack = "ontai.dev/cluster-pack" -// labelSuperseded marks a PackOperationResult as superseded (retained for rollback -// history). Value is "true". Superseded PORs are never deleted by the writer +// labelSuperseded marks a PackLog as superseded (retained for rollback +// history). Value is "true". Superseded PackLogs are never deleted by the writer // until the retention cap (maxRetainedSupersededPORs) is reached. const labelSuperseded = "ontai.dev/superseded" -// maxRetainedSupersededPORs is the maximum number of superseded PORs retained per -// ClusterPack. When exceeded, the oldest superseded POR (lowest revision) is pruned. +// maxRetainedSupersededPORs is the maximum number of superseded PackLogs retained per +// PackDelivery. When exceeded, the oldest superseded PackLog (lowest revision) is pruned. const maxRetainedSupersededPORs = 10 // OperationResultWriter writes an OperationResultSpec to a named -// PackOperationResult CR. This is the output channel between Conductor +// PackLog CR. This is the output channel between Conductor // execute-mode Jobs and the wrapper operator. type OperationResultWriter interface { // WriteResult implements the single-active-revision pattern: lists existing - // PackOperationResults for packExecutionRef, creates a new CR at revision N+1, + // PackLogs for packExecutionRef, creates a new CR at revision N+1, // logs the predecessor spec, then deletes the predecessor. After a successful - // call exactly one PackOperationResult labelled by packExecutionRef exists in + // call exactly one PackLog labelled by packExecutionRef exists in // the namespace. WriteResult(ctx context.Context, namespace, packExecutionRef string, result runnerlib.OperationResultSpec) error } @@ -62,10 +61,10 @@ func NewKubeOperationResultWriter(client ctrlclient.Client, clusterRef string) O return &kubeOperationResultWriter{client: client, clusterRef: clusterRef} } -// WriteResult implements the N-step rollback-aware revision pattern for PackOperationResult. +// WriteResult implements the N-step rollback-aware revision pattern for PackLog. // // Steps: -// 1. List all PackOperationResults in namespace labelled by packExecutionRef +// 1. List all PackLogs in namespace labelled by packExecutionRef // (includes both active and superseded). // 2. Select the one with the highest Revision as the predecessor (N). If none // exist, N=0 and there is no predecessor. @@ -73,17 +72,17 @@ func NewKubeOperationResultWriter(client ctrlclient.Client, clusterRef string) O // pack-deploy-result-{packExecutionRef}-r{N+1}. // 4. Log the predecessor spec at INFO level (GraphQuery DB stub). // 5. Label the predecessor ontai.dev/superseded=true (retained for N-step rollback). -// 6. Prune: if superseded POR count for this ClusterPack exceeds maxRetainedSupersededPORs, -// delete the oldest superseded POR (lowest revision). +// 6. Prune: if superseded PackLog count for this PackDelivery exceeds maxRetainedSupersededPORs, +// delete the oldest superseded PackLog (lowest revision). // -// Exactly one active (non-superseded) PackOperationResult exists per packExecutionRef -// at any time. Superseded PORs are retained up to maxRetainedSupersededPORs per ClusterPack. +// Exactly one active (non-superseded) PackLog exists per packExecutionRef +// at any time. Superseded PackLogs are retained up to maxRetainedSupersededPORs per PackDelivery. func (w *kubeOperationResultWriter) WriteResult( ctx context.Context, namespace, packExecutionRef string, result runnerlib.OperationResultSpec, ) error { - list := &seamv1alpha1.PackOperationResultList{} + list := &dispatcherv1alpha1.PackLogList{} if err := w.client.List(ctx, list, ctrlclient.InNamespace(namespace), ctrlclient.MatchingLabels{labelPackExecution: packExecutionRef}, @@ -91,7 +90,7 @@ func (w *kubeOperationResultWriter) WriteResult( return fmt.Errorf("operationresult writer: list %q in %q: %w", packExecutionRef, namespace, err) } - var prev *seamv1alpha1.PackOperationResult + var prev *dispatcherv1alpha1.PackLog var highestRevision int64 for i := range list.Items { item := &list.Items[i] @@ -109,20 +108,20 @@ func (w *kubeOperationResultWriter) WriteResult( prevRef = prev.Name } - spec := buildPackOperationResultSpec(result, packExecutionRef, w.clusterRef) + spec := buildPackLogSpec(result, packExecutionRef, w.clusterRef) spec.Revision = newRevision spec.PreviousRevisionRef = prevRef - // Set ownerReference to the PackExecution so Kubernetes GC deletes this POR - // when the PE is deleted, preventing stale PORs from surviving a redeploy. + // Set ownerReference to the PackExecution so Kubernetes GC deletes this PackLog + // when the PE is deleted, preventing stale PackLogs from surviving a redeploy. // If the PE is already gone (late-arriving result), skip the ownerRef. var ownerRefs []metav1.OwnerReference - pe := &seamv1alpha1.InfrastructurePackExecution{} + pe := &dispatcherv1alpha1.PackExecution{} if getErr := w.client.Get(ctx, ctrlclient.ObjectKey{Namespace: namespace, Name: packExecutionRef}, pe); getErr == nil { blockOwner := true ownerRefs = []metav1.OwnerReference{{ - APIVersion: "infrastructure.ontai.dev/v1alpha1", - Kind: "InfrastructurePackExecution", + APIVersion: "seam.ontai.dev/v1alpha1", + Kind: "PackExecution", Name: pe.Name, UID: pe.UID, BlockOwnerDeletion: &blockOwner, @@ -134,7 +133,7 @@ func (w *kubeOperationResultWriter) WriteResult( labels[labelClusterPack] = result.ClusterPackRef } - por := &seamv1alpha1.PackOperationResult{ + por := &dispatcherv1alpha1.PackLog{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: newName, @@ -165,10 +164,10 @@ func (w *kubeOperationResultWriter) WriteResult( return fmt.Errorf("operationresult writer: label predecessor superseded %q in %q: %w", prev.Name, namespace, err) } - // Prune oldest superseded POR for this ClusterPack if the cap is exceeded. + // Prune oldest superseded PackLog for this PackDelivery if the cap is exceeded. if result.ClusterPackRef != "" { if err := w.pruneSupersededPORs(ctx, namespace, result.ClusterPackRef); err != nil { - slog.WarnContext(ctx, "operationresult writer: superseded POR pruning failed (non-fatal)", + slog.WarnContext(ctx, "operationresult writer: superseded PackLog pruning failed (non-fatal)", "clusterPack", result.ClusterPackRef, "namespace", namespace, "err", err) } } @@ -177,10 +176,10 @@ func (w *kubeOperationResultWriter) WriteResult( return nil } -// pruneSupersededPORs deletes the oldest superseded PORs for a ClusterPack when +// pruneSupersededPORs deletes the oldest superseded PackLogs for a PackDelivery when // the retained count exceeds maxRetainedSupersededPORs. Oldest = lowest revision. func (w *kubeOperationResultWriter) pruneSupersededPORs(ctx context.Context, namespace, clusterPackRef string) error { - superseded := &seamv1alpha1.PackOperationResultList{} + superseded := &dispatcherv1alpha1.PackLogList{} if err := w.client.List(ctx, superseded, ctrlclient.InNamespace(namespace), ctrlclient.MatchingLabels{ @@ -188,7 +187,7 @@ func (w *kubeOperationResultWriter) pruneSupersededPORs(ctx context.Context, nam labelSuperseded: "true", }, ); err != nil { - return fmt.Errorf("list superseded PORs for %s: %w", clusterPackRef, err) + return fmt.Errorf("list superseded PackLogs for %s: %w", clusterPackRef, err) } if len(superseded.Items) <= maxRetainedSupersededPORs { @@ -206,27 +205,27 @@ func (w *kubeOperationResultWriter) pruneSupersededPORs(ctx context.Context, nam toDelete := len(items) - maxRetainedSupersededPORs for i := 0; i < toDelete; i++ { if err := w.client.Delete(ctx, &items[i]); err != nil && !apierrors.IsNotFound(err) { - return fmt.Errorf("delete oldest superseded POR %s: %w", items[i].Name, err) + return fmt.Errorf("delete oldest superseded PackLog %s: %w", items[i].Name, err) } } return nil } -// buildPackOperationResultSpec maps OperationResultSpec fields to -// PackOperationResultSpec. Revision and PreviousRevisionRef are set by +// buildPackLogSpec maps OperationResultSpec fields to +// PackLogSpec. Revision and PreviousRevisionRef are set by // WriteResult after this function returns. -func buildPackOperationResultSpec( +func buildPackLogSpec( result runnerlib.OperationResultSpec, packExecutionRef, clusterRef string, -) seamv1alpha1.PackOperationResultSpec { - spec := seamv1alpha1.PackOperationResultSpec{ +) dispatcherv1alpha1.PackLogSpec { + spec := dispatcherv1alpha1.PackLogSpec{ PackExecutionRef: packExecutionRef, - ClusterPackRef: result.ClusterPackRef, + PackDeliveryRef: result.ClusterPackRef, TargetClusterRef: clusterRef, Capability: result.Capability, Phase: result.Phase, - Status: seamv1alpha1.PackResultStatus(result.Status), - ClusterPackVersion: result.ClusterPackVersion, + Status: dispatcherv1alpha1.PackLogResultStatus(result.Status), + PackDeliveryVersion: result.ClusterPackVersion, RBACDigest: result.RBACDigest, WorkloadDigest: result.WorkloadDigest, } @@ -241,7 +240,7 @@ func buildPackOperationResultSpec( } if result.FailureReason != nil { - spec.FailureReason = &seamv1alpha1.PackOperationFailureReason{ + spec.FailureReason = &dispatcherv1alpha1.PackLogFailureReason{ Category: string(result.FailureReason.Category), Reason: result.FailureReason.Reason, FailedStep: result.FailureReason.FailedStep, @@ -249,7 +248,7 @@ func buildPackOperationResultSpec( } for _, dr := range result.DeployedResources { - spec.DeployedResources = append(spec.DeployedResources, seamv1alpha1.PackOperationDeployedResource{ + spec.DeployedResources = append(spec.DeployedResources, dispatcherv1alpha1.PackLogDeployedResource{ APIVersion: dr.APIVersion, Kind: dr.Kind, Namespace: dr.Namespace, @@ -258,7 +257,7 @@ func buildPackOperationResultSpec( } for _, a := range result.Artifacts { - spec.Artifacts = append(spec.Artifacts, seamv1alpha1.PackOperationArtifact{ + spec.Artifacts = append(spec.Artifacts, dispatcherv1alpha1.PackLogArtifact{ Name: a.Name, Kind: a.Kind, Reference: a.Reference, @@ -267,9 +266,9 @@ func buildPackOperationResultSpec( } for _, s := range result.Steps { - sr := seamv1alpha1.PackOperationStepResult{ + sr := dispatcherv1alpha1.PackLogStepResult{ Name: s.Name, - Status: seamv1alpha1.PackResultStatus(s.Status), + Status: dispatcherv1alpha1.PackLogResultStatus(s.Status), Message: s.Message, } if !s.StartedAt.IsZero() { diff --git a/internal/persistence/operationresult_writer_test.go b/internal/persistence/operationresult_writer_test.go index 8a0b6d6..89ec1f6 100644 --- a/internal/persistence/operationresult_writer_test.go +++ b/internal/persistence/operationresult_writer_test.go @@ -10,15 +10,15 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" "github.com/ontai-dev/conductor-sdk/runnerlib" ) func newTestScheme(t *testing.T) *runtime.Scheme { t.Helper() s := runtime.NewScheme() - if err := seamv1alpha1.AddToScheme(s); err != nil { - t.Fatalf("add seam scheme: %v", err) + if err := dispatcherv1alpha1.AddToScheme(s); err != nil { + t.Fatalf("add dispatcher scheme: %v", err) } return s } @@ -41,7 +41,7 @@ func TestWriteResult_FirstWrite(t *testing.T) { t.Fatalf("WriteResult: %v", err) } - list := &seamv1alpha1.PackOperationResultList{} + list := &dispatcherv1alpha1.PackLogList{} if err := cl.List(context.Background(), list, ctrlclient.InNamespace("seam-tenant-ccs-mgmt"), ctrlclient.MatchingLabels{labelPackExecution: "pe-abc"}, @@ -49,7 +49,7 @@ func TestWriteResult_FirstWrite(t *testing.T) { t.Fatalf("list: %v", err) } if len(list.Items) != 1 { - t.Fatalf("expected 1 POR, got %d", len(list.Items)) + t.Fatalf("expected 1 PackLog, got %d", len(list.Items)) } if list.Items[0].Spec.Revision != 1 { t.Errorf("revision = %d, want 1", list.Items[0].Spec.Revision) @@ -59,7 +59,7 @@ func TestWriteResult_FirstWrite(t *testing.T) { } } -// TestWriteResult_UpgradesRevision verifies N→N+1: active POR advances to revision 2, +// TestWriteResult_UpgradesRevision verifies N→N+1: active PackLog advances to revision 2, // predecessor is retained as superseded (ontai.dev/superseded=true) for N-step rollback. func TestWriteResult_UpgradesRevision(t *testing.T) { scheme := newTestScheme(t) @@ -75,7 +75,7 @@ func TestWriteResult_UpgradesRevision(t *testing.T) { t.Fatalf("second write: %v", err) } - list := &seamv1alpha1.PackOperationResultList{} + list := &dispatcherv1alpha1.PackLogList{} if err := cl.List(context.Background(), list, ctrlclient.InNamespace(ns), ctrlclient.MatchingLabels{labelPackExecution: peRef}, @@ -83,11 +83,11 @@ func TestWriteResult_UpgradesRevision(t *testing.T) { t.Fatalf("list: %v", err) } if len(list.Items) != 2 { - t.Fatalf("expected 2 PORs after upgrade (active + superseded), got %d", len(list.Items)) + t.Fatalf("expected 2 PackLogs after upgrade (active + superseded), got %d", len(list.Items)) } // Find active (revision 2) and superseded (revision 1). - var active, superseded *seamv1alpha1.PackOperationResult + var active, superseded *dispatcherv1alpha1.PackLog for i := range list.Items { item := &list.Items[i] if item.Spec.Revision == 2 { @@ -97,10 +97,10 @@ func TestWriteResult_UpgradesRevision(t *testing.T) { } } if active == nil { - t.Fatal("revision 2 POR not found") + t.Fatal("revision 2 PackLog not found") } if superseded == nil { - t.Fatal("revision 1 POR not found (must be retained as superseded)") + t.Fatal("revision 1 PackLog not found (must be retained as superseded)") } if active.Spec.PreviousRevisionRef != "pack-deploy-result-pe-upgrade-r1" { t.Errorf("previousRevisionRef = %q, want %q", @@ -114,12 +114,12 @@ func TestWriteResult_UpgradesRevision(t *testing.T) { } } -// TestWriteResult_SetsOwnerReferenceWhenPEExists verifies that POR gets an ownerReference +// TestWriteResult_SetsOwnerReferenceWhenPEExists verifies that PackLog gets an ownerReference // to the PackExecution so Kubernetes GC cascades deletion. func TestWriteResult_SetsOwnerReferenceWhenPEExists(t *testing.T) { scheme := newTestScheme(t) peUID := types.UID("test-pe-uid-1234") - pe := &seamv1alpha1.InfrastructurePackExecution{ + pe := &dispatcherv1alpha1.PackExecution{ ObjectMeta: metav1.ObjectMeta{ Name: "pe-with-owner", Namespace: "seam-tenant-ccs-mgmt", @@ -133,20 +133,20 @@ func TestWriteResult_SetsOwnerReferenceWhenPEExists(t *testing.T) { t.Fatalf("WriteResult: %v", err) } - por := &seamv1alpha1.PackOperationResult{} + por := &dispatcherv1alpha1.PackLog{} if err := cl.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-ccs-mgmt", Name: "pack-deploy-result-pe-with-owner-r1"}, por, ); err != nil { - t.Fatalf("get POR: %v", err) + t.Fatalf("get PackLog: %v", err) } if len(por.OwnerReferences) != 1 { t.Fatalf("expected 1 ownerReference, got %d", len(por.OwnerReferences)) } ref := por.OwnerReferences[0] - if ref.Kind != "InfrastructurePackExecution" { - t.Errorf("ownerRef.Kind = %q, want InfrastructurePackExecution", ref.Kind) + if ref.Kind != "PackExecution" { + t.Errorf("ownerRef.Kind = %q, want PackExecution", ref.Kind) } if ref.UID != peUID { t.Errorf("ownerRef.UID = %q, want %q", ref.UID, peUID) @@ -167,12 +167,12 @@ func TestWriteResult_NoOwnerReferenceWhenPEAbsent(t *testing.T) { t.Fatalf("WriteResult: %v", err) } - por := &seamv1alpha1.PackOperationResult{} + por := &dispatcherv1alpha1.PackLog{} if err := cl.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-ccs-mgmt", Name: "pack-deploy-result-pe-deleted-r1"}, por, ); err != nil { - t.Fatalf("get POR: %v", err) + t.Fatalf("get PackLog: %v", err) } if len(por.OwnerReferences) != 0 { diff --git a/test/integration/signing/signing_integration_test.go b/test/integration/signing/signing_integration_test.go index 51b2f0d..860c134 100644 --- a/test/integration/signing/signing_integration_test.go +++ b/test/integration/signing/signing_integration_test.go @@ -30,17 +30,17 @@ import ( ) // ── GVR definitions mirroring internal/agent ───────────────────────────────── -// All pack/receipt GVRs use infrastructure.ontai.dev (Decision G, seam-core). +// Dispatcher GVRs use seam.ontai.dev (wrapper). DriftSignal stays in infrastructure.ontai.dev. var ( packInstanceGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructurepackinstances", + Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packinstalleds", } clusterPackGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructureclusterpacks", + Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packdeliveries", } packReceiptGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructurepackreceipts", + Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packreceipts", } secretGVR = schema.GroupVersionResource{ Group: "", Version: "v1", Resource: "secrets", @@ -101,11 +101,10 @@ func signSpec(t *testing.T, spec map[string]interface{}, priv ed25519.PrivateKey // gvrToListKind maps GVRs to their List kind names. // NewSimpleDynamicClientWithCustomListKinds requires this explicit mapping. -// Kind names follow seam-core Infrastructure prefix convention (Decision G). var gvrToListKind = map[schema.GroupVersionResource]string{ - packInstanceGVR: "InfrastructurePackInstanceList", - clusterPackGVR: "InfrastructureClusterPackList", - packReceiptGVR: "InfrastructurePackReceiptList", + packInstanceGVR: "PackInstalledList", + clusterPackGVR: "PackDeliveryList", + packReceiptGVR: "PackReceiptList", secretGVR: "SecretList", permissionSnapshotGVR: "PermissionSnapshotList", permissionSnapshotReceiptGVR: "PermissionSnapshotReceiptList", @@ -152,8 +151,8 @@ func TestSigningLoop_SignsPackInstance_StoresSecret(t *testing.T) { // Pre-create a PackInstance in seam-tenant-ccs-test. packInstance := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackInstance", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackInstalled", "metadata": map[string]interface{}{"name": "nginx", "namespace": "seam-tenant-ccs-test"}, "spec": map[string]interface{}{ "targetClusterRef": "ccs-test", @@ -221,8 +220,8 @@ func TestSigningLoop_IdempotentOnStaleSignature(t *testing.T) { // PackInstance already has a signature annotation. packInstance := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackInstance", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackInstalled", "metadata": map[string]interface{}{ "name": "redis", "namespace": "seam-tenant-ccs-test", diff --git a/test/unit/agent/packinstance_pull_loop_test.go b/test/unit/agent/packinstance_pull_loop_test.go index 2b71953..7342baa 100644 --- a/test/unit/agent/packinstance_pull_loop_test.go +++ b/test/unit/agent/packinstance_pull_loop_test.go @@ -57,8 +57,8 @@ func makePackArtifactSecret(secretName, ns, artifactB64, sigB64 string) *unstruc func makeExistingPackReceipt(name, ns string, verified bool, sigB64 string) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackReceipt", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "Packreceipt", "metadata": map[string]interface{}{ "name": name, "namespace": ns, @@ -416,8 +416,8 @@ func buildVersionedArtifact(t *testing.T, priv ed25519.PrivateKey, deployedResou func buildOldReceiptWithDeployedResources(name, ns string, drs []interface{}) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackReceipt", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "Packreceipt", "metadata": map[string]interface{}{"name": name, "namespace": ns}, "spec": map[string]interface{}{ "packInstanceRef": name, diff --git a/test/unit/agent/receipt_reconciler_test.go b/test/unit/agent/receipt_reconciler_test.go index ab9cf2b..eac3037 100644 --- a/test/unit/agent/receipt_reconciler_test.go +++ b/test/unit/agent/receipt_reconciler_test.go @@ -15,9 +15,9 @@ import ( // packReceiptGVR mirrors the GVR defined in the production receipt_reconciler.go. // Redeclared here to keep the test package self-contained. var packReceiptGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurepackreceipts", + Resource: "packreceipts", } // permissionSnapshotReceiptGVR mirrors the GVR defined in production. diff --git a/test/unit/agent/signing_loop_test.go b/test/unit/agent/signing_loop_test.go index 66e9545..dfffeee 100644 --- a/test/unit/agent/signing_loop_test.go +++ b/test/unit/agent/signing_loop_test.go @@ -23,11 +23,11 @@ import ( "github.com/ontai-dev/conductor/internal/agent" ) -// packInstanceGVR mirrors the GVR defined in signing_loop.go. +// packInstanceGVR mirrors the GVR defined in signing_loop.go (PackInstalled). var packInstanceGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurepackinstances", + Resource: "packinstalleds", } // psGVR mirrors the permissionSnapshotGVR in signing_loop.go. @@ -37,11 +37,11 @@ var psGVR = schema.GroupVersionResource{ Resource: "permissionsnapshots", } -// clusterPackGVR mirrors the GVR defined in signing_loop.go. +// clusterPackGVR mirrors the GVR defined in signing_loop.go (PackDelivery). var clusterPackGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructureclusterpacks", + Resource: "packdeliveries", } // secretGVR mirrors the secretGVR defined in signing_loop.go (core v1 Secrets). diff --git a/test/unit/capability/pack_deploy_split_test.go b/test/unit/capability/pack_deploy_split_test.go index 602a63e..1178dda 100644 --- a/test/unit/capability/pack_deploy_split_test.go +++ b/test/unit/capability/pack_deploy_split_test.go @@ -62,8 +62,8 @@ var _ capability.GuardianIntakeClient = (*stubGuardianClient)(nil) func clusterPackSplitCR(clusterRef, name, version, registryURL, rbacDigest, workloadDigest string) *unstructured.Unstructured { return &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureClusterPack", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackDelivery", "metadata": map[string]interface{}{"name": name, "namespace": "seam-tenant-" + clusterRef}, "spec": map[string]interface{}{ "version": version, @@ -281,8 +281,8 @@ func TestPackDeploy_SplitPath_LayerRefsUsesBaseURLWhenRegistryRefDigestSet(t *te // ClusterPack with registryRef.digest set (helm-compiled pack). cp := &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureClusterPack", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackDelivery", "metadata": map[string]interface{}{"name": "nginx-ingress", "namespace": "seam-tenant-" + clusterRef}, "spec": map[string]interface{}{ "version": "v1.0.0", @@ -491,8 +491,8 @@ metadata: // don't exist, which would fail applyParsedManifest in unit tests). func newThreeBucketDynClient(objects ...*unstructured.Unstructured) *dynamicfake.FakeDynamicClient { kinds := map[string]schema.GroupVersionResource{ - "InfrastructurePackExecution": {Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructurepackexecutions"}, - "InfrastructureClusterPack": {Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructureclusterpacks"}, + "PackExecution": {Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packexecutions"}, + "PackDelivery": {Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packdeliveries"}, "Deployment": {Group: "apps", Version: "v1", Resource: "deployments"}, "MutatingWebhookConfiguration": {Group: "admissionregistration.k8s.io", Version: "v1", Resource: "mutatingwebhookconfigurations"}, "Namespace": {Group: "", Version: "v1", Resource: "namespaces"}, @@ -515,8 +515,8 @@ func newThreeBucketDynClient(objects ...*unstructured.Unstructured) *dynamicfake return true, &unstructured.Unstructured{}, nil }) wrapperGVRs := map[string]schema.GroupVersionResource{ - "InfrastructurePackExecution": {Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructurepackexecutions"}, - "InfrastructureClusterPack": {Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructureclusterpacks"}, + "PackExecution": {Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packexecutions"}, + "PackDelivery": {Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packdeliveries"}, } for _, obj := range objects { gvr, ok := wrapperGVRs[obj.GetKind()] @@ -537,8 +537,8 @@ func newThreeBucketDynClient(objects ...*unstructured.Unstructured) *dynamicfake // and workloadDigest all set (three-bucket pack, e.g., cert-manager). func clusterPackThreeBucketCR(clusterRef, name, version, registryURL, rbacDigest, clusterScopedDigest, workloadDigest string) *unstructured.Unstructured { return &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureClusterPack", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackDelivery", "metadata": map[string]interface{}{"name": name, "namespace": "seam-tenant-" + clusterRef}, "spec": map[string]interface{}{ "version": version, diff --git a/test/unit/capability/wrapper_test.go b/test/unit/capability/wrapper_test.go index dcb3a16..7cd5007 100644 --- a/test/unit/capability/wrapper_test.go +++ b/test/unit/capability/wrapper_test.go @@ -70,8 +70,8 @@ func (s *stubOCIClient) PullManifests(_ context.Context, _ string) ([][]byte, er // --------------------------------------------------------------------------- var wrapperGVRs = map[string]schema.GroupVersionResource{ - "InfrastructurePackExecution": {Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructurepackexecutions"}, - "InfrastructureClusterPack": {Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: "infrastructureclusterpacks"}, + "PackExecution": {Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packexecutions"}, + "PackDelivery": {Group: "seam.ontai.dev", Version: "v1alpha1", Resource: "packdeliveries"}, } func newWrapperDynClient(objects ...*unstructured.Unstructured) *dynamicfake.FakeDynamicClient { @@ -257,12 +257,12 @@ func TestPackDeploy_MultiplePEsSameCluster_DeploysCorrectPack(t *testing.T) { peCertManager := packExecutionCR(clusterRef, "cert-manager", "v1.14.0") // nginx PE: the one this Job should deploy. peNginx := &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackExecution", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackExecution", "metadata": map[string]interface{}{"name": "nginx-pe-" + clusterRef, "namespace": "seam-tenant-" + clusterRef}, "spec": map[string]interface{}{ "targetClusterRef": clusterRef, - "clusterPackRef": map[string]interface{}{ + "packDeliveryRef": map[string]interface{}{ "name": "nginx-pack", "version": "v4.9.0", }, @@ -324,12 +324,12 @@ func TestPackDeploy_ArtifactsNonNil(t *testing.T) { func packExecutionCR(targetClusterRef, packName, packVersion string) *unstructured.Unstructured { return &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackExecution", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackExecution", "metadata": map[string]interface{}{"name": "pe-" + targetClusterRef, "namespace": "seam-tenant-" + targetClusterRef}, "spec": map[string]interface{}{ "targetClusterRef": targetClusterRef, - "clusterPackRef": map[string]interface{}{ + "packDeliveryRef": map[string]interface{}{ "name": packName, "version": packVersion, }, @@ -339,8 +339,8 @@ func packExecutionCR(targetClusterRef, packName, packVersion string) *unstructur func clusterPackCR(clusterRef, name, version, ociRef string) *unstructured.Unstructured { return &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureClusterPack", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackDelivery", "metadata": map[string]interface{}{"name": name, "namespace": "seam-tenant-" + clusterRef}, "spec": map[string]interface{}{ "version": version, diff --git a/test/unit/persistence/operationresult_writer_test.go b/test/unit/persistence/operationresult_writer_test.go index 242dcaf..9a872c0 100644 --- a/test/unit/persistence/operationresult_writer_test.go +++ b/test/unit/persistence/operationresult_writer_test.go @@ -10,7 +10,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" "github.com/ontai-dev/conductor/internal/persistence" "github.com/ontai-dev/conductor-sdk/runnerlib" ) @@ -18,14 +18,14 @@ import ( func buildTestScheme(t *testing.T) *runtime.Scheme { t.Helper() s := runtime.NewScheme() - if err := seamv1alpha1.AddToScheme(s); err != nil { - t.Fatalf("AddToScheme seamv1alpha1: %v", err) + if err := dispatcherv1alpha1.AddToScheme(s); err != nil { + t.Fatalf("AddToScheme dispatcherv1alpha1: %v", err) } return s } // TestOperationResultWriter_FirstWrite verifies that the first WriteResult call -// creates a POR at revision=1 with no predecessor and the expected label. +// creates a PackLog at revision=1 with no predecessor and the expected label. func TestOperationResultWriter_FirstWrite(t *testing.T) { scheme := buildTestScheme(t) fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() @@ -43,11 +43,11 @@ func TestOperationResultWriter_FirstWrite(t *testing.T) { t.Fatalf("WriteResult: %v", err) } - por := &seamv1alpha1.PackOperationResult{} + por := &dispatcherv1alpha1.PackLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-cluster-a", Name: "pack-deploy-result-pe1-r1"}, por); err != nil { - t.Fatalf("POR not found after first write: %v", err) + t.Fatalf("PackLog not found after first write: %v", err) } if por.Spec.Revision != 1 { @@ -74,9 +74,9 @@ func TestOperationResultWriter_FirstWrite(t *testing.T) { } // TestOperationResultWriter_SecondWriteRetainsPredecessorAsSuperseded verifies that the -// second WriteResult call creates a POR at revision=2, sets PreviousRevisionRef, +// second WriteResult call creates a PackLog at revision=2, sets PreviousRevisionRef, // and labels the revision=1 CR ontai.dev/superseded=true (retained for N-step rollback). -// Both PORs must exist after two writes. seam-core-schema.md §7.8. +// Both PackLogs must exist after two writes. seam-core-schema.md §7.8. func TestOperationResultWriter_SecondWriteRetainsPredecessorAsSuperseded(t *testing.T) { scheme := buildTestScheme(t) fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() @@ -103,11 +103,11 @@ func TestOperationResultWriter_SecondWriteRetainsPredecessorAsSuperseded(t *test } // Revision 2 must exist with PreviousRevisionRef set and no superseded label. - r2 := &seamv1alpha1.PackOperationResult{} + r2 := &dispatcherv1alpha1.PackLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-cluster-b", Name: "pack-deploy-result-pe2-r2"}, r2); err != nil { - t.Fatalf("revision 2 POR not found: %v", err) + t.Fatalf("revision 2 PackLog not found: %v", err) } if r2.Spec.Revision != 2 { t.Errorf("r2.Revision=%d, want 2", r2.Spec.Revision) @@ -115,7 +115,7 @@ func TestOperationResultWriter_SecondWriteRetainsPredecessorAsSuperseded(t *test if r2.Spec.PreviousRevisionRef != "pack-deploy-result-pe2-r1" { t.Errorf("r2.PreviousRevisionRef=%q, want pack-deploy-result-pe2-r1", r2.Spec.PreviousRevisionRef) } - if r2.Spec.Status != seamv1alpha1.PackResultSucceeded { + if r2.Spec.Status != dispatcherv1alpha1.PackLogResultSucceeded { t.Errorf("r2.Status=%q, want Succeeded", r2.Spec.Status) } if got := r2.Labels["ontai.dev/superseded"]; got != "" { @@ -123,26 +123,26 @@ func TestOperationResultWriter_SecondWriteRetainsPredecessorAsSuperseded(t *test } // Revision 1 must still exist and be labeled superseded. - r1 := &seamv1alpha1.PackOperationResult{} + r1 := &dispatcherv1alpha1.PackLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-cluster-b", Name: "pack-deploy-result-pe2-r1"}, r1); err != nil { - t.Fatalf("revision 1 POR not found after second write (must be retained): %v", err) + t.Fatalf("revision 1 PackLog not found after second write (must be retained): %v", err) } if got := r1.Labels["ontai.dev/superseded"]; got != "true" { t.Errorf("r1 superseded label=%q, want true", got) } - // Both PORs must be present in the namespace for this packExecutionRef. - list := &seamv1alpha1.PackOperationResultList{} + // Both PackLogs must be present in the namespace for this packExecutionRef. + list := &dispatcherv1alpha1.PackLogList{} if err := fakeClient.List(context.Background(), list, ctrlclient.InNamespace("seam-tenant-cluster-b"), ctrlclient.MatchingLabels{"ontai.dev/pack-execution": "pe2"}, ); err != nil { - t.Fatalf("list PORs: %v", err) + t.Fatalf("list PackLogs: %v", err) } if len(list.Items) != 2 { - t.Errorf("POR count=%d after two writes, want 2 (active + superseded retained)", len(list.Items)) + t.Errorf("PackLog count=%d after two writes, want 2 (active + superseded retained)", len(list.Items)) } } @@ -180,7 +180,7 @@ func TestOperationResultWriter_PredecessorDumpLogged(t *testing.T) { } // TestOperationResultWriter_ClusterPackLabelSet verifies that the ontai.dev/cluster-pack -// label is set on the POR when ClusterPackRef is populated in the result. +// label is set on the PackLog when ClusterPackRef is populated in the result. func TestOperationResultWriter_ClusterPackLabelSet(t *testing.T) { scheme := buildTestScheme(t) fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() @@ -195,22 +195,22 @@ func TestOperationResultWriter_ClusterPackLabelSet(t *testing.T) { t.Fatalf("WriteResult: %v", err) } - por := &seamv1alpha1.PackOperationResult{} + por := &dispatcherv1alpha1.PackLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-ccs-dev", Name: "pack-deploy-result-nginx-ccs-dev-ccs-dev-r1"}, por); err != nil { - t.Fatalf("POR not found: %v", err) + t.Fatalf("PackLog not found: %v", err) } if got := por.Labels["ontai.dev/cluster-pack"]; got != "nginx-ccs-dev" { t.Errorf("label ontai.dev/cluster-pack=%q, want nginx-ccs-dev", got) } - if por.Spec.ClusterPackRef != "nginx-ccs-dev" { - t.Errorf("ClusterPackRef=%q, want nginx-ccs-dev", por.Spec.ClusterPackRef) + if por.Spec.PackDeliveryRef != "nginx-ccs-dev" { + t.Errorf("PackDeliveryRef=%q, want nginx-ccs-dev", por.Spec.PackDeliveryRef) } } // TestOperationResultWriter_SupersededPORRetainsRollbackAnchor verifies that after two -// deploys, the superseded revision 1 POR is retained with its own version/digest fields +// deploys, the superseded revision 1 PackLog is retained with its own version/digest fields // intact -- making N-step rollback possible by reading those fields directly. // seam-core-schema.md §7.8, wrapper-schema.md §6.2. func TestOperationResultWriter_SupersededPORRetainsRollbackAnchor(t *testing.T) { @@ -245,14 +245,14 @@ func TestOperationResultWriter_SupersededPORRetainsRollbackAnchor(t *testing.T) } // Active revision 2 must reflect the second deploy. - r2 := &seamv1alpha1.PackOperationResult{} + r2 := &dispatcherv1alpha1.PackLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-ccs-dev", Name: "pack-deploy-result-pe-rollback-r2"}, r2); err != nil { - t.Fatalf("revision 2 POR not found: %v", err) + t.Fatalf("revision 2 PackLog not found: %v", err) } - if r2.Spec.ClusterPackVersion != "v4.10.0-r1" { - t.Errorf("r2.ClusterPackVersion=%q, want v4.10.0-r1", r2.Spec.ClusterPackVersion) + if r2.Spec.PackDeliveryVersion != "v4.10.0-r1" { + t.Errorf("r2.PackDeliveryVersion=%q, want v4.10.0-r1", r2.Spec.PackDeliveryVersion) } if r2.Spec.RBACDigest != "sha256:cccc" { t.Errorf("r2.RBACDigest=%q, want sha256:cccc", r2.Spec.RBACDigest) @@ -265,19 +265,17 @@ func TestOperationResultWriter_SupersededPORRetainsRollbackAnchor(t *testing.T) } // Superseded revision 1 must still exist with its original anchor fields intact. - r1 := &seamv1alpha1.PackOperationResult{} + r1 := &dispatcherv1alpha1.PackLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Namespace: "seam-tenant-ccs-dev", Name: "pack-deploy-result-pe-rollback-r1"}, r1); err != nil { - t.Fatalf("revision 1 POR must be retained for rollback: %v", err) + t.Fatalf("revision 1 PackLog must be retained for rollback: %v", err) } if got := r1.Labels["ontai.dev/superseded"]; got != "true" { t.Errorf("r1 superseded label=%q, want true", got) } - // The wrapper rollback handler reads ClusterPackVersion/RBACDigest/WorkloadDigest - // directly from this retained POR to restore the ClusterPack spec. - if r1.Spec.ClusterPackVersion != "v4.9.0-r1" { - t.Errorf("r1.ClusterPackVersion=%q, want v4.9.0-r1", r1.Spec.ClusterPackVersion) + if r1.Spec.PackDeliveryVersion != "v4.9.0-r1" { + t.Errorf("r1.PackDeliveryVersion=%q, want v4.9.0-r1", r1.Spec.PackDeliveryVersion) } if r1.Spec.RBACDigest != "sha256:aaaa" { t.Errorf("r1.RBACDigest=%q, want sha256:aaaa", r1.Spec.RBACDigest) From de4a43092a479fc6a925696dd8e6eded2c469e08 Mon Sep 17 00:00:00 2001 From: ontave Date: Tue, 12 May 2026 14:59:24 +0200 Subject: [PATCH 06/13] feat(migration-3.1): update conductor to seam.ontai.dev/TalosCluster GVK Updates all dynamic-client GVR references from infrastructure.ontai.dev/ infrastructuretalosclusters to seam.ontai.dev/talosclusters. Updates kind strings from InfrastructureTalosCluster to TalosCluster. Updates pack execution GVR to seam.ontai.dev/packexecutions. All tests updated to match. --- cmd/compiler/compile.go | 8 +++---- cmd/compiler/compile_bootstrap_test.go | 4 ++-- .../compile_bootstrap_variants_test.go | 4 ++-- cmd/compiler/compile_enable.go | 12 +++++------ internal/agent/drift_signal_handler.go | 13 +++--------- internal/agent/drift_signal_handler_test.go | 8 +++---- .../agent/kubernetes_version_drift_loop.go | 4 ++-- .../kubernetes_version_drift_loop_test.go | 8 +++---- internal/agent/talos_version_drift_loop.go | 4 ++-- internal/agent/talosclusters.go | 6 +++--- internal/capability/platform_cluster.go | 8 +++---- .../agent/talos_version_drift_loop_test.go | 12 +++++------ test/unit/agent/talosclusters_test.go | 12 +++++------ test/unit/capability/platform_test.go | 21 +++++++++---------- 14 files changed, 58 insertions(+), 66 deletions(-) diff --git a/cmd/compiler/compile.go b/cmd/compiler/compile.go index b14054b..0c9c43e 100644 --- a/cmd/compiler/compile.go +++ b/cmd/compiler/compile.go @@ -1170,8 +1170,8 @@ func compileBootstrap(input, output, kubeconfigPath, talosconfigPath string) err } tc := platformv1alpha1.TalosCluster{ TypeMeta: metav1.TypeMeta{ - APIVersion: "infrastructure.ontai.dev/v1alpha1", - Kind: "InfrastructureTalosCluster", + APIVersion: "seam.ontai.dev/v1alpha1", + Kind: "TalosCluster", }, ObjectMeta: metav1.ObjectMeta{ Name: in.Name, @@ -1507,8 +1507,8 @@ func compileImportTalosconfigSecret(in ClusterInput, output, flagValue string) e } tc := platformv1alpha1.TalosCluster{ TypeMeta: metav1.TypeMeta{ - APIVersion: "infrastructure.ontai.dev/v1alpha1", - Kind: "InfrastructureTalosCluster", + APIVersion: "seam.ontai.dev/v1alpha1", + Kind: "TalosCluster", }, ObjectMeta: metav1.ObjectMeta{ Name: in.Name, diff --git a/cmd/compiler/compile_bootstrap_test.go b/cmd/compiler/compile_bootstrap_test.go index e2d978c..35444aa 100644 --- a/cmd/compiler/compile_bootstrap_test.go +++ b/cmd/compiler/compile_bootstrap_test.go @@ -110,8 +110,8 @@ func TestBootstrap_TalosClusterHasCorrectSpec(t *testing.T) { } content := string(data) - assertContainsStr(t, content, "apiVersion: infrastructure.ontai.dev/v1alpha1") - assertContainsStr(t, content, "kind: InfrastructureTalosCluster") + assertContainsStr(t, content, "apiVersion: seam.ontai.dev/v1alpha1") + assertContainsStr(t, content, "kind: TalosCluster") assertContainsStr(t, content, "name: ccs-mgmt") assertContainsStr(t, content, "mode: bootstrap") // capi.enabled=false means nil CAPIConfig pointer -- capi block is absent (C-34). diff --git a/cmd/compiler/compile_bootstrap_variants_test.go b/cmd/compiler/compile_bootstrap_variants_test.go index ff20365..9ddbd72 100644 --- a/cmd/compiler/compile_bootstrap_variants_test.go +++ b/cmd/compiler/compile_bootstrap_variants_test.go @@ -90,8 +90,8 @@ func TestBootstrapVariants_MgmtImport_TalosClusterConformance(t *testing.T) { cr := readTalosClusterCR(t, outDir, "ccs-mgmt") // Type metadata (infrastructure.ontai.dev -- Decision G, seam-core owns TalosCluster). - assertContainsTrimmed(t, cr, "apiVersion: infrastructure.ontai.dev/v1alpha1") - assertContainsTrimmed(t, cr, "kind: InfrastructureTalosCluster") + assertContainsTrimmed(t, cr, "apiVersion: seam.ontai.dev/v1alpha1") + assertContainsTrimmed(t, cr, "kind: TalosCluster") // Mode must be import (importExistingCluster=true in fixture). assertContainsTrimmed(t, cr, "mode: import") diff --git a/cmd/compiler/compile_enable.go b/cmd/compiler/compile_enable.go index ea1ee60..93a9254 100644 --- a/cmd/compiler/compile_enable.go +++ b/cmd/compiler/compile_enable.go @@ -2582,15 +2582,15 @@ func operatorClusterRules(operatorName string) []rbacv1.PolicyRule { Resources: []string{"infrastructurerunnerconfigs"}, Verbs: []string{"get"}, }, - // infrastructure.ontai.dev — ClusterRBACPolicyReconciler (management role) - // watches InfrastructureTalosCluster and manages its finalizer. + // seam.ontai.dev — ClusterRBACPolicyReconciler (management role) + // watches TalosCluster and manages its finalizer. MIGRATION-3.1. // guardian-schema.md §18, CS-INV-008. rbacv1.PolicyRule{ - APIGroups: []string{"infrastructure.ontai.dev"}, + APIGroups: []string{"seam.ontai.dev"}, Resources: []string{ - "infrastructuretalosclusters", - "infrastructuretalosclusters/status", - "infrastructuretalosclusters/finalizers", + "talosclusters", + "talosclusters/status", + "talosclusters/finalizers", }, Verbs: []string{"get", "list", "watch", "update", "patch"}, }, diff --git a/internal/agent/drift_signal_handler.go b/internal/agent/drift_signal_handler.go index 020a607..4df9138 100644 --- a/internal/agent/drift_signal_handler.go +++ b/internal/agent/drift_signal_handler.go @@ -15,9 +15,9 @@ import ( ) var packExecutionGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurepackexecutions", + Resource: "packexecutions", } // DriftSignalHandler runs on conductor role=management. On each cycle it: @@ -84,7 +84,7 @@ func (h *DriftSignalHandler) handleOnce(ctx context.Context) { // InfrastructureTalosCluster version drift signals are handled by platform's // DriftSignalReconciler (TCOR write + observedTalosVersion patch). Skip here. affectedRef, _, _ := unstructuredNestedMap(spec, "affectedCRRef") - if kind, _ := affectedRef["kind"].(string); kind == "InfrastructureTalosCluster" { + if kind, _ := affectedRef["kind"].(string); kind == "TalosCluster" { continue } @@ -213,10 +213,3 @@ func (h *DriftSignalHandler) setTerminalDrift(ctx context.Context, ns, signalNam } } -// packExecutionGVRLocal is identical to packExecutionGVR but declared locally to avoid -// redeclaration if this file is merged with other GVR declarations later. -var _ = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", - Version: "v1alpha1", - Resource: "infrastructurepackexecutions", -} diff --git a/internal/agent/drift_signal_handler_test.go b/internal/agent/drift_signal_handler_test.go index 2f1b4e8..0beef59 100644 --- a/internal/agent/drift_signal_handler_test.go +++ b/internal/agent/drift_signal_handler_test.go @@ -20,10 +20,10 @@ func setupDriftHandlerScheme() *runtime.Scheme { Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "DriftSignalList", }, &unstructured.UnstructuredList{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructurePackExecution", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "PackExecution", }, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructurePackExecutionList", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "PackExecutionList", }, &unstructured.UnstructuredList{}) return s } @@ -52,8 +52,8 @@ func fakeDriftSignal(name, ns, state string, counter int64) *unstructured.Unstru func fakePackExecution(name, ns string) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructurePackExecution", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "PackExecution", "metadata": map[string]interface{}{ "name": name, "namespace": ns, "resourceVersion": "1", diff --git a/internal/agent/kubernetes_version_drift_loop.go b/internal/agent/kubernetes_version_drift_loop.go index 43a1395..d077172 100644 --- a/internal/agent/kubernetes_version_drift_loop.go +++ b/internal/agent/kubernetes_version_drift_loop.go @@ -164,8 +164,8 @@ func (l *KubernetesVersionDriftLoop) emitDriftSignal(ctx context.Context, signal "observedAt": now, "driftReason": driftReason, "affectedCRRef": map[string]interface{}{ - "group": "infrastructure.ontai.dev", - "kind": "InfrastructureTalosCluster", + "group": "seam.ontai.dev", + "kind": "TalosCluster", "namespace": l.namespace, "name": l.clusterRef, }, diff --git a/internal/agent/kubernetes_version_drift_loop_test.go b/internal/agent/kubernetes_version_drift_loop_test.go index 1fc0f7b..25021dd 100644 --- a/internal/agent/kubernetes_version_drift_loop_test.go +++ b/internal/agent/kubernetes_version_drift_loop_test.go @@ -14,10 +14,10 @@ import ( func setupK8sDriftScheme() *runtime.Scheme { s := runtime.NewScheme() s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureTalosCluster", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "TalosCluster", }, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureTalosClusterList", + Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "TalosClusterList", }, &unstructured.UnstructuredList{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "DriftSignal", @@ -37,8 +37,8 @@ func setupK8sDriftScheme() *runtime.Scheme { func fakeTalosCluster(name, ns, k8sVersion string) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureTalosCluster", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "TalosCluster", "metadata": map[string]interface{}{ "name": name, "namespace": ns, "resourceVersion": "1", diff --git a/internal/agent/talos_version_drift_loop.go b/internal/agent/talos_version_drift_loop.go index 4ec639b..c135ad0 100644 --- a/internal/agent/talos_version_drift_loop.go +++ b/internal/agent/talos_version_drift_loop.go @@ -190,8 +190,8 @@ func (l *TalosVersionDriftLoop) emitVersionDriftSignal(ctx context.Context, sign "observedAt": now, "driftReason": driftReason, "affectedCRRef": map[string]interface{}{ - "group": "infrastructure.ontai.dev", - "kind": "InfrastructureTalosCluster", + "group": "seam.ontai.dev", + "kind": "TalosCluster", "namespace": l.namespace, "name": l.clusterRef, }, diff --git a/internal/agent/talosclusters.go b/internal/agent/talosclusters.go index 3c9b946..1e5df31 100644 --- a/internal/agent/talosclusters.go +++ b/internal/agent/talosclusters.go @@ -13,13 +13,13 @@ import ( ) var talosClusterGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructuretalosclusters", + Resource: "talosclusters", } // SetTalosClusterReady patches the Ready condition to True on the -// InfrastructureTalosCluster named clusterName in namespace. Called by +// TalosCluster named clusterName in namespace. Called by // conductor role=tenant after winning leader election to signal that the // tenant conductor is operational. seam-core/conditions ConditionTypeReady. func SetTalosClusterReady(ctx context.Context, dynamicClient dynamic.Interface, namespace, clusterName string) error { diff --git a/internal/capability/platform_cluster.go b/internal/capability/platform_cluster.go index fa839b1..26050af 100644 --- a/internal/capability/platform_cluster.go +++ b/internal/capability/platform_cluster.go @@ -13,12 +13,12 @@ import ( "github.com/ontai-dev/conductor-sdk/runnerlib" ) -// talosClusterGVR is the GroupVersionResource for InfrastructureTalosCluster. -// infrastructure.ontai.dev/v1alpha1/infrastructuretalosclusters — seam-core-schema.md §4. +// talosClusterGVR is the GroupVersionResource for TalosCluster. +// seam.ontai.dev/v1alpha1/talosclusters. MIGRATION-3.1. var talosClusterGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructuretalosclusters", + Resource: "talosclusters", } // clusterResetGVR is the GroupVersionResource for ClusterReset. diff --git a/test/unit/agent/talos_version_drift_loop_test.go b/test/unit/agent/talos_version_drift_loop_test.go index 9f612ea..144c78a 100644 --- a/test/unit/agent/talos_version_drift_loop_test.go +++ b/test/unit/agent/talos_version_drift_loop_test.go @@ -20,8 +20,8 @@ func buildFakeDriftScheme() *runtime.Scheme { s := runtime.NewScheme() s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "DriftSignal"}, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "DriftSignalList"}, &unstructured.UnstructuredList{}) - s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureTalosCluster"}, &unstructured.Unstructured{}) - s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureTalosClusterList"}, &unstructured.UnstructuredList{}) + s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "TalosCluster"}, &unstructured.Unstructured{}) + s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "seam.ontai.dev", Version: "v1alpha1", Kind: "TalosClusterList"}, &unstructured.UnstructuredList{}) s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"}, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "NodeList"}, &unstructured.UnstructuredList{}) return s @@ -45,8 +45,8 @@ func makeNode(name, osImage string) *unstructured.Unstructured { func makeTalosClusterForVersion(clusterRef, ns, talosVersion string) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureTalosCluster", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "TalosCluster", "metadata": map[string]interface{}{"name": clusterRef, "namespace": ns}, "spec": map[string]interface{}{"talosVersion": talosVersion}, }, @@ -96,8 +96,8 @@ func TestTalosVersionDriftLoop_EmitsDriftSignalOnVersionMismatch(t *testing.T) { t.Errorf("expected driftReason to contain observed=%s, got %q", observedVersion, driftReason) } affectedRef, _ := spec["affectedCRRef"].(map[string]interface{}) - if affectedRef["kind"] != "InfrastructureTalosCluster" { - t.Errorf("expected affectedCRRef.Kind=InfrastructureTalosCluster, got %q", affectedRef["kind"]) + if affectedRef["kind"] != "TalosCluster" { + t.Errorf("expected affectedCRRef.Kind=TalosCluster, got %q", affectedRef["kind"]) } } diff --git a/test/unit/agent/talosclusters_test.go b/test/unit/agent/talosclusters_test.go index 4ca48c3..6cb3d65 100644 --- a/test/unit/agent/talosclusters_test.go +++ b/test/unit/agent/talosclusters_test.go @@ -19,16 +19,16 @@ import ( ) var itcGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructuretalosclusters", + Resource: "talosclusters", } func makeItc(name, namespace string) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureTalosCluster", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "TalosCluster", "metadata": map[string]interface{}{ "name": name, "namespace": namespace, @@ -46,7 +46,7 @@ func TestSetTalosClusterReady_PatchesStatusReady(t *testing.T) { scheme := runtime.NewScheme() obj := makeItc("ccs-dev", "ont-system") fakeClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, map[schema.GroupVersionResource]string{ - itcGVR: "InfrastructureTalosClusterList", + itcGVR: "TalosClusterList", }, obj) ctx := context.Background() @@ -111,7 +111,7 @@ func TestSetTalosClusterReady_PatchesStatusReady(t *testing.T) { func TestSetTalosClusterReady_ReturnsErrorWhenNotFound(t *testing.T) { scheme := runtime.NewScheme() fakeClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, map[schema.GroupVersionResource]string{ - itcGVR: "InfrastructureTalosClusterList", + itcGVR: "TalosClusterList", }) // Pre-populate with a different cluster name so the target does not exist. diff --git a/test/unit/capability/platform_test.go b/test/unit/capability/platform_test.go index 32cc861..d9de33f 100644 --- a/test/unit/capability/platform_test.go +++ b/test/unit/capability/platform_test.go @@ -38,10 +38,9 @@ var platformKindToResource = map[string]string{ "HardeningProfile": "hardeningprofiles", } -// infraKindToResource maps seam-core infrastructure.ontai.dev Kind names to GVR resources. -// TalosCluster migrated to infrastructure.ontai.dev in the seam rebranding. -var infraKindToResource = map[string]string{ - "InfrastructureTalosCluster": "infrastructuretalosclusters", +// seamKindToResource maps seam.ontai.dev Kind names to GVR resources. +var seamKindToResource = map[string]string{ + "TalosCluster": "talosclusters", } // newPlatformDynClient builds a fake dynamic client with all platform and infrastructure @@ -59,13 +58,13 @@ func newPlatformDynClient(objects ...*unstructured.Unstructured) *dynamicfake.Fa ) _ = resource } - for kind, resource := range infraKindToResource { + for kind, resource := range seamKindToResource { s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: kind}, + schema.GroupVersionKind{Group: "seam.ontai.dev", Version: "v1alpha1", Kind: kind}, &unstructured.Unstructured{}, ) s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: kind + "List"}, + schema.GroupVersionKind{Group: "seam.ontai.dev", Version: "v1alpha1", Kind: kind + "List"}, &unstructured.UnstructuredList{}, ) _ = resource @@ -83,9 +82,9 @@ func newPlatformDynClient(objects ...*unstructured.Unstructured) *dynamicfake.Fa } else { _, _ = client.Resource(gvr).Namespace(ns).Create(context.Background(), obj, metav1.CreateOptions{}) } - } else if resource, ok := infraKindToResource[kind]; ok { + } else if resource, ok := seamKindToResource[kind]; ok { gvr := schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Resource: resource, + Group: "seam.ontai.dev", Version: "v1alpha1", Resource: resource, } ns := obj.GetNamespace() if ns == "" { @@ -246,8 +245,8 @@ func TestBootstrap_CallsBootstrapAPIWhenClusterExists(t *testing.T) { cluster := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureTalosCluster", + "apiVersion": "seam.ontai.dev/v1alpha1", + "kind": "TalosCluster", "metadata": map[string]interface{}{"name": "ccs-test", "namespace": "ont-system"}, "spec": map[string]interface{}{}, }, From 6325475715e348423d70e6c900a2e7c75b1e3c86 Mon Sep 17 00:00:00 2001 From: ontave Date: Tue, 12 May 2026 15:30:25 +0200 Subject: [PATCH 07/13] feat(migration-3.2): update conductor to platform ClusterLog GVK for day-2 operation records --- cmd/conductor/main.go | 4 +++ internal/persistence/tcor_writer.go | 30 +++++++++++------------ test/unit/persistence/tcor_writer_test.go | 28 ++++++++++----------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/cmd/conductor/main.go b/cmd/conductor/main.go index 22936f5..4c63856 100644 --- a/cmd/conductor/main.go +++ b/cmd/conductor/main.go @@ -32,6 +32,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" + platformv1alpha1 "github.com/ontai-dev/platform/api/seam/v1alpha1" seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" "github.com/ontai-dev/conductor/internal/capability" @@ -44,6 +45,9 @@ import ( var seamScheme = runtime.NewScheme() func init() { + if err := platformv1alpha1.AddToScheme(seamScheme); err != nil { + panic("conductor: failed to register platform scheme: " + err.Error()) + } if err := seamv1alpha1.AddToScheme(seamScheme); err != nil { panic("conductor: failed to register seam-core scheme: " + err.Error()) } diff --git a/internal/persistence/tcor_writer.go b/internal/persistence/tcor_writer.go index 4b0d084..6c7e9f0 100644 --- a/internal/persistence/tcor_writer.go +++ b/internal/persistence/tcor_writer.go @@ -1,7 +1,7 @@ // Package persistence TalosClusterResultWriter appends operation records to the -// per-cluster InfrastructureTalosClusterOperationResult CR. -// One TCOR per cluster, named by cluster name, lives in seam-tenant-{clusterRef}. -// conductor-schema.md §8, seam-core-schema.md §TCOR. +// per-cluster ClusterLog CR. +// One ClusterLog per cluster, named by cluster name, lives in seam-tenant-{clusterRef}. +// conductor-schema.md §8. package persistence import ( @@ -15,7 +15,7 @@ import ( "k8s.io/apimachinery/pkg/types" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" - seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + platformv1alpha1 "github.com/ontai-dev/platform/api/seam/v1alpha1" "github.com/ontai-dev/conductor-sdk/runnerlib" ) @@ -25,10 +25,10 @@ func tenantNamespaceFor(clusterRef string) string { } // TalosClusterResultWriter appends a completed operation record to the -// per-cluster InfrastructureTalosClusterOperationResult CR. +// per-cluster ClusterLog CR. type TalosClusterResultWriter interface { - // AppendOperationRecord appends the result as a TalosClusterOperationRecord - // to the TCOR named clusterRef in seam-tenant-{clusterRef}. + // AppendOperationRecord appends the result as an OperationRecord + // to the ClusterLog named clusterRef in seam-tenant-{clusterRef}. // jobRef is the Kubernetes Job name that produced the result (used by the // platform reconciler to correlate the record with the Job it submitted). // Returns ExecutionFailure if the TCOR does not exist — the platform operator @@ -54,7 +54,7 @@ func (w *kubeTalosClusterResultWriter) AppendOperationRecord( result runnerlib.OperationResultSpec, ) error { tenantNS := tenantNamespaceFor(clusterRef) - tcor := &seamv1alpha1.InfrastructureTalosClusterOperationResult{} + tcor := &platformv1alpha1.ClusterLog{} if err := w.client.Get(ctx, types.NamespacedName{Name: clusterRef, Namespace: tenantNS}, tcor); err != nil { if apierrors.IsNotFound(err) { return fmt.Errorf("tcor writer: TCOR %s/%s not found — platform must create it before submitting day-2 Jobs", tenantNS, clusterRef) @@ -65,7 +65,7 @@ func (w *kubeTalosClusterResultWriter) AppendOperationRecord( record := buildOperationRecord(jobRef, result) patch := ctrlclient.MergeFrom(tcor.DeepCopy()) if tcor.Spec.Operations == nil { - tcor.Spec.Operations = make(map[string]seamv1alpha1.TalosClusterOperationRecord) + tcor.Spec.Operations = make(map[string]platformv1alpha1.OperationRecord) } tcor.Spec.Operations[jobRef] = record tcor.Spec.OperationCount = int64(len(tcor.Spec.Operations)) @@ -80,11 +80,11 @@ func (w *kubeTalosClusterResultWriter) AppendOperationRecord( return nil } -// buildOperationRecord converts an OperationResultSpec into a TalosClusterOperationRecord. -func buildOperationRecord(jobRef string, result runnerlib.OperationResultSpec) seamv1alpha1.TalosClusterOperationRecord { - status := seamv1alpha1.TalosClusterResultSucceeded +// buildOperationRecord converts an OperationResultSpec into an OperationRecord. +func buildOperationRecord(jobRef string, result runnerlib.OperationResultSpec) platformv1alpha1.OperationRecord { + status := platformv1alpha1.ResultSucceeded if result.Status == runnerlib.ResultFailed { - status = seamv1alpha1.TalosClusterResultFailed + status = platformv1alpha1.ResultFailed } message := string(status) @@ -92,7 +92,7 @@ func buildOperationRecord(jobRef string, result runnerlib.OperationResultSpec) s message = result.FailureReason.Reason } - rec := seamv1alpha1.TalosClusterOperationRecord{ + rec := platformv1alpha1.OperationRecord{ Capability: result.Capability, JobRef: jobRef, Status: status, @@ -115,7 +115,7 @@ func buildOperationRecord(jobRef string, result runnerlib.OperationResultSpec) s } if result.FailureReason != nil { - rec.FailureReason = &seamv1alpha1.TalosClusterOperationFailureReason{ + rec.FailureReason = &platformv1alpha1.OperationFailureReason{ Category: string(result.FailureReason.Category), Reason: result.FailureReason.Reason, } diff --git a/test/unit/persistence/tcor_writer_test.go b/test/unit/persistence/tcor_writer_test.go index 121128c..9d53fed 100644 --- a/test/unit/persistence/tcor_writer_test.go +++ b/test/unit/persistence/tcor_writer_test.go @@ -11,7 +11,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + platformv1alpha1 "github.com/ontai-dev/platform/api/seam/v1alpha1" "github.com/ontai-dev/conductor/internal/persistence" "github.com/ontai-dev/conductor-sdk/runnerlib" ) @@ -19,19 +19,19 @@ import ( func buildTCORScheme(t *testing.T) *runtime.Scheme { t.Helper() s := runtime.NewScheme() - if err := seamv1alpha1.AddToScheme(s); err != nil { - t.Fatalf("AddToScheme seamv1alpha1: %v", err) + if err := platformv1alpha1.AddToScheme(s); err != nil { + t.Fatalf("AddToScheme platformv1alpha1: %v", err) } return s } -func preTCOR(clusterRef, talosVersion string) *seamv1alpha1.InfrastructureTalosClusterOperationResult { - return &seamv1alpha1.InfrastructureTalosClusterOperationResult{ +func preTCOR(clusterRef, talosVersion string) *platformv1alpha1.ClusterLog { + return &platformv1alpha1.ClusterLog{ ObjectMeta: metav1.ObjectMeta{ Name: clusterRef, Namespace: "seam-tenant-" + clusterRef, }, - Spec: seamv1alpha1.InfrastructureTalosClusterOperationResultSpec{ + Spec: platformv1alpha1.ClusterLogSpec{ ClusterRef: clusterRef, TalosVersion: talosVersion, Revision: 1, @@ -58,7 +58,7 @@ func TestTCOR_AppendOperationRecord_Succeeded(t *testing.T) { t.Fatalf("AppendOperationRecord: %v", err) } - got := &seamv1alpha1.InfrastructureTalosClusterOperationResult{} + got := &platformv1alpha1.ClusterLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Name: "ccs-test", Namespace: "seam-tenant-ccs-test"}, got); err != nil { @@ -69,7 +69,7 @@ func TestTCOR_AppendOperationRecord_Succeeded(t *testing.T) { if !ok { t.Fatal("operation record not found in TCOR Operations map") } - if rec.Status != seamv1alpha1.TalosClusterResultSucceeded { + if rec.Status != platformv1alpha1.ResultSucceeded { t.Errorf("Status = %q, want Succeeded", rec.Status) } if rec.Capability != "pki-rotate" { @@ -103,7 +103,7 @@ func TestTCOR_AppendOperationRecord_Failed(t *testing.T) { t.Fatalf("AppendOperationRecord: %v", err) } - got := &seamv1alpha1.InfrastructureTalosClusterOperationResult{} + got := &platformv1alpha1.ClusterLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Name: "ccs-fail", Namespace: "seam-tenant-ccs-fail"}, got); err != nil { @@ -114,7 +114,7 @@ func TestTCOR_AppendOperationRecord_Failed(t *testing.T) { if !ok { t.Fatal("operation record not found in TCOR Operations map") } - if rec.Status != seamv1alpha1.TalosClusterResultFailed { + if rec.Status != platformv1alpha1.ResultFailed { t.Errorf("Status = %q, want Failed", rec.Status) } if rec.FailureReason == nil { @@ -133,8 +133,8 @@ func TestTCOR_AppendOperationRecord_Failed(t *testing.T) { func TestTCOR_AppendOperationRecord_SecondWrite(t *testing.T) { scheme := buildTCORScheme(t) tcor := preTCOR("ccs-multi", "v1.9.3") - tcor.Spec.Operations = map[string]seamv1alpha1.TalosClusterOperationRecord{ - "first-job": {Capability: "etcd-backup", JobRef: "first-job", Status: seamv1alpha1.TalosClusterResultSucceeded}, + tcor.Spec.Operations = map[string]platformv1alpha1.OperationRecord{ + "first-job": {Capability: "etcd-backup", JobRef: "first-job", Status: platformv1alpha1.ResultSucceeded}, } fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tcor).Build() writer := persistence.NewKubeTalosClusterResultWriter(fakeClient) @@ -147,7 +147,7 @@ func TestTCOR_AppendOperationRecord_SecondWrite(t *testing.T) { t.Fatalf("AppendOperationRecord: %v", err) } - got := &seamv1alpha1.InfrastructureTalosClusterOperationResult{} + got := &platformv1alpha1.ClusterLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Name: "ccs-multi", Namespace: "seam-tenant-ccs-multi"}, got); err != nil { @@ -214,7 +214,7 @@ func TestTCOR_AppendOperationRecord_SetsTimestamps(t *testing.T) { t.Fatalf("AppendOperationRecord: %v", err) } - got := &seamv1alpha1.InfrastructureTalosClusterOperationResult{} + got := &platformv1alpha1.ClusterLog{} if err := fakeClient.Get(context.Background(), ctrlclient.ObjectKey{Name: "ccs-ts", Namespace: "seam-tenant-ccs-ts"}, got); err != nil { From 38ae38b174ad8f4b59bd861d73130df17c87d51c Mon Sep 17 00:00:00 2001 From: ontave Date: Tue, 12 May 2026 16:55:06 +0200 Subject: [PATCH 08/13] feat(migration-4.3): update conductor to seam + dispatcher module paths Replace seam-core -> seam and wrapper -> dispatcher in go.mod replace/require. Update all Go import paths accordingly. Add seam-sdk replace + require. Update conductor RunnerConfigSpec references and compile_launch.go/test assertions for post-MIGRATION-3.8 CRD names (lineagerecords, runnerconfigs under seam.ontai.dev). --- cmd/compiler/compile.go | 2 +- cmd/compiler/compile_enable.go | 4 +- cmd/compiler/compile_launch.go | 14 +- cmd/compiler/compile_launch_test.go | 16 +- cmd/compiler/compile_packbuild_helm.go | 2 +- cmd/compiler/compile_packbuild_kustomize.go | 2 +- cmd/compiler/compile_packbuild_raw.go | 2 +- cmd/conductor/main.go | 6 +- config/crd/embed.go | 2 +- ...ontai.dev_infrastructurerunnerconfigs.yaml | 325 +----------------- go.mod | 10 +- internal/agent/capability_publisher.go | 2 +- internal/agent/capability_publisher_test.go | 6 +- internal/config/context.go | 4 +- internal/kernel/execute.go | 2 +- .../persistence/operationresult_writer.go | 2 +- .../operationresult_writer_test.go | 2 +- test/e2e/suite_test.go | 2 +- test/e2e/tenant_rbac_sweep_test.go | 2 +- test/integration/runnerconfig_test.go | 14 +- test/integration/suite_test.go | 8 +- test/unit/agent/capability_publisher_test.go | 6 +- test/unit/kernel/execute_capability_test.go | 2 +- test/unit/kernel/execute_sequencer_test.go | 2 +- test/unit/kernel/execute_test.go | 4 +- .../operationresult_writer_test.go | 2 +- test/unit/runnerlib/runnerconfig_test.go | 12 +- 27 files changed, 69 insertions(+), 388 deletions(-) diff --git a/cmd/compiler/compile.go b/cmd/compiler/compile.go index 0c9c43e..02d5ef0 100644 --- a/cmd/compiler/compile.go +++ b/cmd/compiler/compile.go @@ -24,7 +24,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/config/generate/secrets" "github.com/siderolabs/talos/pkg/machinery/config/machine" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" platformv1alpha1 "github.com/ontai-dev/platform/api/v1alpha1" ) diff --git a/cmd/compiler/compile_enable.go b/cmd/compiler/compile_enable.go index 93a9254..df9553e 100644 --- a/cmd/compiler/compile_enable.go +++ b/cmd/compiler/compile_enable.go @@ -1709,14 +1709,14 @@ func writePhase4Conductor(output string, cdt operatorSpec, clusterRole, mgmtSign } // writeConductorCRDs writes the conductor-crds placeholder. -// InfrastructureRunnerConfig CRD is now owned by seam-core and included in +// RunnerConfig CRD is now owned by seam-core and included in // the platform-wrapper-crds.yaml written in phase 3. This file is retained // to preserve the phase 4 directory layout; it carries only a comment header. func writeConductorCRDs(dir string) error { var buf bytes.Buffer buf.WriteString("# Conductor CRD Definitions\n") buf.WriteString("# Generated by: compiler enable (phase 4 conductor)\n") - buf.WriteString("# InfrastructureRunnerConfig is declared in infrastructure.ontai.dev (seam-core).\n") + buf.WriteString("# RunnerConfig is declared in infrastructure.ontai.dev (seam-core).\n") buf.WriteString("# It is included in platform-wrapper-crds.yaml (phase 3). No additional CRDs here.\n") return os.WriteFile(filepath.Join(dir, "conductor-crds.yaml"), buf.Bytes(), 0644) diff --git a/cmd/compiler/compile_launch.go b/cmd/compiler/compile_launch.go index dea3dc9..1be5cc8 100644 --- a/cmd/compiler/compile_launch.go +++ b/cmd/compiler/compile_launch.go @@ -18,8 +18,8 @@ import ( guardiancrd "github.com/ontai-dev/guardian/config/crd" platformcrd "github.com/ontai-dev/platform/config/crd" - seamcorecrd "github.com/ontai-dev/seam-core/config/crd" - wrappercrd "github.com/ontai-dev/wrapper/config/crd" + seamcorecrd "github.com/ontai-dev/seam/config/crd" + wrappercrd "github.com/ontai-dev/dispatcher/config/crd" ) const launchHelp = `Usage: compiler launch --output [--kubeconfig ] @@ -73,8 +73,8 @@ func runLaunchSubcommand(args []string) { // CRD sources (all embedded at build time): // - platform.ontai.dev: TalosCluster, day-2 CRDs, SeamInfrastructureCluster/Machine // - security.ontai.dev: RBACPolicy, RBACProfile, IdentityBinding, IdentityProvider, PermissionSet -// - infrastructure.ontai.dev: InfrastructureRunnerConfig, InfrastructureLineageIndex (seam-core) -// - seam.ontai.dev: PackDelivery, PackExecution, PackInstalled, PackReceipt, PackLog (wrapper) +// - seam.ontai.dev: RunnerConfig, LineageRecord, DriftSignal, SeamMembership (seam) +// - seam.ontai.dev: PackDelivery, PackExecution, PackInstalled, PackReceipt, PackLog (dispatcher) // // Output is deterministic: CRD files within each operator are sorted by name. // conductor-schema.md §9 Step 2. @@ -84,15 +84,15 @@ func compileLaunchBundle(output string) error { } // Collect CRD YAML from all operator embedded filesystems. - // Order: platform, guardian, seam-core, wrapper. + // Order: platform, guardian, seam, dispatcher. sources := []struct { name string fsys fs.FS }{ {"platform", platformcrd.FS}, {"guardian", guardiancrd.FS}, - {"seam-core", seamcorecrd.FS}, - {"wrapper", wrappercrd.FS}, + {"seam", seamcorecrd.FS}, + {"dispatcher", wrappercrd.FS}, } var bundle bytes.Buffer diff --git a/cmd/compiler/compile_launch_test.go b/cmd/compiler/compile_launch_test.go index 4f4508b..05fbbfd 100644 --- a/cmd/compiler/compile_launch_test.go +++ b/cmd/compiler/compile_launch_test.go @@ -68,8 +68,8 @@ func TestLaunch_BundleContainsWrapperCRDs(t *testing.T) { assertContainsStr(t, content, "packdeliveries") } -// TestLaunch_BundleContainsSeamCoreCRDs verifies that infrastructure.ontai.dev -// CRDs (InfrastructureLineageIndex) are present in the bundle. +// TestLaunch_BundleContainsSeamCoreCRDs verifies that seam.ontai.dev CRDs +// (LineageRecord) from the seam repository are present in the bundle. func TestLaunch_BundleContainsSeamCoreCRDs(t *testing.T) { outDir := t.TempDir() if err := compileLaunchBundle(outDir); err != nil { @@ -78,12 +78,12 @@ func TestLaunch_BundleContainsSeamCoreCRDs(t *testing.T) { data, _ := os.ReadFile(filepath.Join(outDir, "crds.yaml")) content := string(data) - assertContainsStr(t, content, "infrastructure.ontai.dev") - assertContainsStr(t, content, "infrastructurelineageindices") + assertContainsStr(t, content, "seam.ontai.dev") + assertContainsStr(t, content, "lineagerecords") } -// TestLaunch_BundleContainsRunnerConfigCRD verifies that the InfrastructureRunnerConfig CRD -// from seam-core (infrastructure.ontai.dev) is present in the bundle. conductor-schema.md §5. +// TestLaunch_BundleContainsRunnerConfigCRD verifies that the RunnerConfig CRD +// from seam (seam.ontai.dev) is present in the bundle. conductor-schema.md §5. func TestLaunch_BundleContainsRunnerConfigCRD(t *testing.T) { outDir := t.TempDir() if err := compileLaunchBundle(outDir); err != nil { @@ -92,8 +92,8 @@ func TestLaunch_BundleContainsRunnerConfigCRD(t *testing.T) { data, _ := os.ReadFile(filepath.Join(outDir, "crds.yaml")) content := string(data) - assertContainsStr(t, content, "infrastructure.ontai.dev") - assertContainsStr(t, content, "infrastructurerunnerconfigs") + assertContainsStr(t, content, "seam.ontai.dev") + assertContainsStr(t, content, "runnerconfigs") } // TestLaunch_BundleIsDeterministic verifies that successive compileLaunchBundle diff --git a/cmd/compiler/compile_packbuild_helm.go b/cmd/compiler/compile_packbuild_helm.go index 5f435fb..4d6b53f 100644 --- a/cmd/compiler/compile_packbuild_helm.go +++ b/cmd/compiler/compile_packbuild_helm.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" ) // HelmSource describes a Helm chart source for automated packbuild. diff --git a/cmd/compiler/compile_packbuild_kustomize.go b/cmd/compiler/compile_packbuild_kustomize.go index 92a3d93..80b3d82 100644 --- a/cmd/compiler/compile_packbuild_kustomize.go +++ b/cmd/compiler/compile_packbuild_kustomize.go @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/kustomize/api/krusty" "sigs.k8s.io/kustomize/kyaml/filesys" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" ) // KustomizeSource describes the kustomize overlay directory for automated packbuild. diff --git a/cmd/compiler/compile_packbuild_raw.go b/cmd/compiler/compile_packbuild_raw.go index 797a496..f20ebc5 100644 --- a/cmd/compiler/compile_packbuild_raw.go +++ b/cmd/compiler/compile_packbuild_raw.go @@ -14,7 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" ) // RawSource describes a directory of raw YAML manifest files for automated packbuild. diff --git a/cmd/conductor/main.go b/cmd/conductor/main.go index 4c63856..20e55e9 100644 --- a/cmd/conductor/main.go +++ b/cmd/conductor/main.go @@ -33,8 +33,8 @@ import ( ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" platformv1alpha1 "github.com/ontai-dev/platform/api/seam/v1alpha1" - seamv1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + seamv1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" "github.com/ontai-dev/conductor/internal/capability" "github.com/ontai-dev/conductor/internal/config" "github.com/ontai-dev/conductor/internal/kernel" @@ -97,7 +97,7 @@ func runExecute() { // BuildExecuteContext populates Capability/ClusterRef/OperationResultCM but // leaves RunnerConfig.Steps empty. kernel.RunExecute requires ≥1 step. // conductor-schema.md §17. - execCtx.RunnerConfig = seamv1alpha1.InfrastructureRunnerConfigSpec{ + execCtx.RunnerConfig = seamv1alpha1.RunnerConfigSpec{ ClusterRef: execCtx.ClusterRef, RunnerImage: os.Getenv("CONDUCTOR_IMAGE"), Steps: []seamv1alpha1.RunnerConfigStep{ diff --git a/config/crd/embed.go b/config/crd/embed.go index 81d9a73..5c60b88 100644 --- a/config/crd/embed.go +++ b/config/crd/embed.go @@ -1,5 +1,5 @@ // Package crd previously embedded conductor's own CRD YAML files. -// After T-2B-9 migration, all conductor CRDs (InfrastructureRunnerConfig, +// After T-2B-9 migration, all conductor CRDs (RunnerConfig, // InfrastructurePackReceipt) are declared in seam-core (infrastructure.ontai.dev). // The compiler bundles them from seam-core/config/crd directly. // This package is retained for structural consistency only. diff --git a/config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml b/config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml index 43ea7a5..96af633 100644 --- a/config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml +++ b/config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml @@ -1,323 +1,2 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.16.1 - name: infrastructurerunnerconfigs.infrastructure.ontai.dev -spec: - group: infrastructure.ontai.dev - names: - kind: InfrastructureRunnerConfig - listKind: InfrastructureRunnerConfigList - plural: infrastructurerunnerconfigs - shortNames: - - irc - singular: infrastructurerunnerconfig - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.clusterRef - name: Cluster - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - InfrastructureRunnerConfig is the seam-core CRD for Conductor agent runtime configuration. - Owned by seam-core; authored exclusively by the platform operator. INV-009. - conductor-schema.md. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: |- - InfrastructureRunnerConfigSpec is the operator-generated operational contract for a - specific cluster. Generated at runtime by platform using the runner shared library. - Never human-authored. INV-009, INV-010. conductor-schema.md. - properties: - clusterRef: - description: ClusterRef is the name of the TalosCluster this RunnerConfig - is authoritative for. - type: string - maintenanceTargetNodes: - description: MaintenanceTargetNodes is the list of node names that - are the subject of the operation. - items: - type: string - type: array - operationalHistory: - description: OperationalHistory is an append-only record of completed - RunnerConfig executions. - items: - description: |- - RunnerOperationalHistoryEntry is a single append-only audit record describing one - configuration change applied to this RunnerConfig. Never truncated. - properties: - appliedAt: - description: AppliedAt is the time this change was applied. - format: date-time - type: string - appliedBy: - description: AppliedBy identifies who applied the change. - type: string - concern: - description: Concern identifies what aspect of configuration - changed. - type: string - newValue: - description: NewValue is the value after the change. - type: string - previousValue: - description: PreviousValue is the value before the change. Empty - for initial entries. - type: string - required: - - appliedAt - - appliedBy - - concern - - newValue - type: object - type: array - operatorLeaderNode: - description: OperatorLeaderNode is the node hosting the leader pod - of the initiating operator. - type: string - phases: - description: Phases is the ordered list of operational phases for - this cluster's Conductor lifecycle. - items: - description: RunnerPhaseConfig carries per-phase parameters for - the runner's execution context. - properties: - name: - description: Name identifies the phase. - type: string - parameters: - additionalProperties: - type: string - description: Parameters holds phase-specific key-value configuration. - type: object - required: - - name - type: object - type: array - runnerImage: - description: |- - RunnerImage is the fully qualified container image reference for the Conductor agent. - Tag convention: v{talosVersion}-r{revision} stable, dev/dev-rc{N} development. INV-011. - type: string - selfOperation: - description: SelfOperation is true when the Job's execution cluster - and the target cluster are the same. - type: boolean - steps: - description: Steps is the ordered list of execution steps across all - phases. - items: - description: RunnerConfigStep declares one step in a multi-step - operation intent. - properties: - capability: - description: Capability is the named Conductor capability to - invoke for this step. - type: string - dependsOn: - description: DependsOn is the name of a prior step that must - complete before this step begins. - type: string - haltOnFailure: - description: |- - HaltOnFailure controls sequencer behaviour when this step fails. - When true, failure terminates the RunnerConfig with no further steps executing. - type: boolean - name: - description: Name is the unique identifier for this step within - the RunnerConfig. - type: string - parameters: - additionalProperties: - type: string - description: Parameters is the input parameter map passed to - the capability at Job materialisation time. - type: object - required: - - capability - - name - type: object - type: array - required: - - clusterRef - - runnerImage - type: object - status: - description: |- - InfrastructureRunnerConfigStatus is written exclusively by the Conductor agent leader. - CR-INV-006. - properties: - agentLeader: - description: AgentLeader is the pod name of the current Conductor - agent leader. - type: string - agentVersion: - description: AgentVersion is the version string of the Conductor agent - binary currently running. - type: string - capabilities: - description: |- - Capabilities is the self-declared capability manifest emitted by the Conductor agent on startup. - CR-INV-005. - items: - description: RunnerCapabilityEntry is one capability declared by - the Conductor agent on startup. - properties: - description: - description: Description is a human-readable description of - what this capability does. - type: string - name: - description: Name is the capability name (e.g., pack-deploy, - talos-upgrade). - type: string - version: - description: Version is the capability version declared by the - agent. - type: string - required: - - name - - version - type: object - type: array - conditions: - description: Conditions is the standard Kubernetes condition list - for this RunnerConfig. - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - failedStep: - description: |- - FailedStep is the name of the first step that reached the Failed phase. - Present only when Phase="Failed". conductor-schema.md §17. - type: string - phase: - description: |- - Phase is the terminal execution phase written by Conductor execute mode. - "Completed" means all steps succeeded. "Failed" means at least one step failed. - Empty means execution is in progress. Platform operators watch this field to - detect terminal conditions without scanning StepResults. conductor-schema.md §17. - type: string - stepResults: - description: StepResults is the ordered list of step result records - written by Conductor execute mode. - items: - description: RunnerConfigStepResult is the status record for one - step. - properties: - completedAt: - description: CompletedAt is the time this step finished execution. - format: date-time - type: string - message: - description: Message is additional context about the step outcome. - type: string - name: - description: Name matches the Name field of the corresponding - RunnerConfigStep in spec. - type: string - startedAt: - description: StartedAt is the time this step began execution. - format: date-time - type: string - status: - allOf: - - enum: - - Succeeded - - Failed - - Skipped - - enum: - - Succeeded - - Failed - - Skipped - description: Status is the terminal status of this step execution. - type: string - required: - - name - - status - type: object - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} +# Tombstone: InfrastructureRunnerConfig migrated to RunnerConfig under seam.ontai.dev (MIGRATION-3.8). +# Conductor CRDs are bundled from seam-core/config/crd. See seam.ontai.dev_runnerconfigs.yaml. diff --git a/go.mod b/go.mod index 7f0d63b..e12cf5a 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,11 @@ go 1.25.3 replace ( github.com/ontai-dev/conductor-sdk => ../conductor-sdk + github.com/ontai-dev/dispatcher => ../wrapper github.com/ontai-dev/guardian => ../guardian github.com/ontai-dev/platform => ../platform - github.com/ontai-dev/seam-core => ../seam-core - github.com/ontai-dev/wrapper => ../wrapper + github.com/ontai-dev/seam => ../seam-core + github.com/ontai-dev/seam-sdk => ../seam-sdk ) require ( @@ -19,8 +20,9 @@ require ( github.com/ontai-dev/conductor-sdk v0.0.0-00010101000000-000000000000 github.com/ontai-dev/guardian v0.0.0-00010101000000-000000000000 github.com/ontai-dev/platform v0.0.0-00010101000000-000000000000 - github.com/ontai-dev/seam-core v0.1.0-alpha.0.20260426085946-e3630ad7b38f - github.com/ontai-dev/wrapper v0.0.0-00010101000000-000000000000 + github.com/ontai-dev/dispatcher v0.0.0-00010101000000-000000000000 + github.com/ontai-dev/seam v0.0.0-00010101000000-000000000000 + github.com/ontai-dev/seam-sdk v0.0.0-00010101000000-000000000000 github.com/prometheus/client_golang v1.23.2 github.com/siderolabs/talos/pkg/machinery v1.12.6 google.golang.org/grpc v1.79.3 diff --git a/internal/agent/capability_publisher.go b/internal/agent/capability_publisher.go index 6d3e903..7e5618c 100644 --- a/internal/agent/capability_publisher.go +++ b/internal/agent/capability_publisher.go @@ -80,7 +80,7 @@ func (p *CapabilityPublisher) emitRunnerConfigMissingSignal(ctx context.Context, "driftReason": "RunnerConfig not found in ont-system -- cluster-state drift", "affectedCRRef": map[string]interface{}{ "group": "infrastructure.ontai.dev", - "kind": "InfrastructureRunnerConfig", + "kind": "RunnerConfig", "name": clusterRef, }, "escalationCounter": int64(0), diff --git a/internal/agent/capability_publisher_test.go b/internal/agent/capability_publisher_test.go index 470a11b..104e4b9 100644 --- a/internal/agent/capability_publisher_test.go +++ b/internal/agent/capability_publisher_test.go @@ -17,7 +17,7 @@ import ( func setupCapabilityPublisherScheme() *runtime.Scheme { s := runtime.NewScheme() s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureRunnerConfig", + Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "RunnerConfig", }, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureRunnerConfigList", @@ -65,8 +65,8 @@ func TestCapabilityPublisher_EmitsDriftSignalAfterMissingThreshold(t *testing.T) t.Errorf("DriftSignal.spec.state = %q, want %q", state, "pending") } affectedRef, _, _ := unstructuredNestedMap(spec, "affectedCRRef") - if kind, _ := affectedRef["kind"].(string); kind != "InfrastructureRunnerConfig" { - t.Errorf("affectedCRRef.kind = %q, want %q", kind, "InfrastructureRunnerConfig") + if kind, _ := affectedRef["kind"].(string); kind != "RunnerConfig" { + t.Errorf("affectedCRRef.kind = %q, want %q", kind, "RunnerConfig") } if name, _ := affectedRef["name"].(string); name != clusterRef { t.Errorf("affectedCRRef.name = %q, want %q", name, clusterRef) diff --git a/internal/config/context.go b/internal/config/context.go index dd82b86..ab51b37 100644 --- a/internal/config/context.go +++ b/internal/config/context.go @@ -7,7 +7,7 @@ import ( "errors" "os" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) // Mode is a typed string declaring which execution mode this binary invocation runs in. @@ -87,7 +87,7 @@ type ExecutionContext struct { // RunnerConfig is the RunnerConfigSpec loaded from the mounted ConfigMap or // environment at startup. Zero value in compile mode. - RunnerConfig seamcorev1alpha1.InfrastructureRunnerConfigSpec + RunnerConfig seamcorev1alpha1.RunnerConfigSpec } // BuildExecuteContext constructs an ExecutionContext for execute mode. diff --git a/internal/kernel/execute.go b/internal/kernel/execute.go index b451e02..c9ddbd7 100644 --- a/internal/kernel/execute.go +++ b/internal/kernel/execute.go @@ -7,7 +7,7 @@ import ( "os" "github.com/ontai-dev/conductor/internal/config" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) // StepExecutor runs a single RunnerConfig step and returns the StepResult. diff --git a/internal/persistence/operationresult_writer.go b/internal/persistence/operationresult_writer.go index 66c1641..39565cf 100644 --- a/internal/persistence/operationresult_writer.go +++ b/internal/persistence/operationresult_writer.go @@ -12,7 +12,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" "github.com/ontai-dev/conductor-sdk/runnerlib" ) diff --git a/internal/persistence/operationresult_writer_test.go b/internal/persistence/operationresult_writer_test.go index 89ec1f6..fe98c10 100644 --- a/internal/persistence/operationresult_writer_test.go +++ b/internal/persistence/operationresult_writer_test.go @@ -10,7 +10,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" "github.com/ontai-dev/conductor-sdk/runnerlib" ) diff --git a/test/e2e/suite_test.go b/test/e2e/suite_test.go index c742a7a..965d9ae 100644 --- a/test/e2e/suite_test.go +++ b/test/e2e/suite_test.go @@ -21,7 +21,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - e2ehelpers "github.com/ontai-dev/seam-core/pkg/e2e" + e2ehelpers "github.com/ontai-dev/seam/pkg/e2e" ) // Suite-level cluster clients, initialized in BeforeSuite. diff --git a/test/e2e/tenant_rbac_sweep_test.go b/test/e2e/tenant_rbac_sweep_test.go index 556f6bc..18bddd0 100644 --- a/test/e2e/tenant_rbac_sweep_test.go +++ b/test/e2e/tenant_rbac_sweep_test.go @@ -41,7 +41,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - e2ehelpers "github.com/ontai-dev/seam-core/pkg/e2e" + e2ehelpers "github.com/ontai-dev/seam/pkg/e2e" ) const ( diff --git a/test/integration/runnerconfig_test.go b/test/integration/runnerconfig_test.go index e0bc73b..4f031be 100644 --- a/test/integration/runnerconfig_test.go +++ b/test/integration/runnerconfig_test.go @@ -24,7 +24,7 @@ import ( "github.com/ontai-dev/conductor/internal/config" "github.com/ontai-dev/conductor/internal/kernel" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) // fakeStepExecutor is a StepExecutor that returns pre-configured results by @@ -57,7 +57,7 @@ func (f *fakeStepExecutor) Execute( // makeExecuteCtx builds a minimal execute-mode ExecutionContext. // The RunnerConfig spec is injected directly -- no env vars required. // This mirrors how the real binary populates ExecutionContext at startup. -func makeExecuteCtx(clusterRef, ns string, spec seamcorev1alpha1.InfrastructureRunnerConfigSpec) config.ExecutionContext { +func makeExecuteCtx(clusterRef, ns string, spec seamcorev1alpha1.RunnerConfigSpec) config.ExecutionContext { return config.ExecutionContext{ Mode: config.ModeExecute, ClusterRef: clusterRef, @@ -83,7 +83,7 @@ func TestRunExecute_ManagementRoleSteps_StepResultsPersistedInAPIServer(t *testi name := "ws3-mgmt-runnerconfig" clusterRef := "ccs-mgmt" - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ Steps: []seamcorev1alpha1.RunnerConfigStep{ {Name: "validate-bootstrap", Capability: "cluster-validate"}, {Name: "apply-rbac", Capability: "rbac-provision", DependsOn: "validate-bootstrap"}, @@ -150,7 +150,7 @@ func TestRunExecute_TenantRoleSteps_NoSigningFieldsInStatus(t *testing.T) { name := "ws3-tenant-runnerconfig" clusterRef := "ccs-test" - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ Steps: []seamcorev1alpha1.RunnerConfigStep{ {Name: "verify-packreceipt", Capability: "pack-verify"}, {Name: "apply-local-rbac", Capability: "rbac-provision", DependsOn: "verify-packreceipt"}, @@ -212,13 +212,13 @@ func TestRunExecute_TwoConcurrentRunnerConfigs_NoStepResultCrossContamination(t ctx := context.Background() ns := "default" - specA := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + specA := seamcorev1alpha1.RunnerConfigSpec{ Steps: []seamcorev1alpha1.RunnerConfigStep{ {Name: "step-alpha-1", Capability: "cluster-validate"}, {Name: "step-alpha-2", Capability: "rbac-provision", DependsOn: "step-alpha-1"}, }, } - specB := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + specB := seamcorev1alpha1.RunnerConfigSpec{ Steps: []seamcorev1alpha1.RunnerConfigStep{ {Name: "step-beta-1", Capability: "pack-verify"}, {Name: "step-beta-2", Capability: "pack-sign", DependsOn: "step-beta-1"}, @@ -318,7 +318,7 @@ func TestRunExecute_HaltOnFailure_TerminalFailedConditionInAPIServer(t *testing. name := "ws3-halt-runnerconfig" clusterRef := "ccs-mgmt" - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ Steps: []seamcorev1alpha1.RunnerConfigStep{ {Name: "pre-flight", Capability: "cluster-validate"}, {Name: "critical-step", Capability: "rbac-provision", DependsOn: "pre-flight", HaltOnFailure: true}, diff --git a/test/integration/suite_test.go b/test/integration/suite_test.go index 24437cc..cb66a3c 100644 --- a/test/integration/suite_test.go +++ b/test/integration/suite_test.go @@ -32,10 +32,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/log/zap" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) -// runnerConfigGVR is the GroupVersionResource for InfrastructureRunnerConfig CRs. +// runnerConfigGVR is the GroupVersionResource for RunnerConfig CRs. // The conductor uses dynamic clients to interact with RunnerConfig -- the types // in runnerlib are plain Go structs (not controller-runtime managed objects), // so all CRD interactions use unstructured.Unstructured. @@ -97,7 +97,7 @@ func poll(t *testing.T, timeout time.Duration, condition func() bool) bool { // createRunnerConfig creates a RunnerConfig CR in the real API server with the // given namespace, name, and spec. Returns the UID of the created object. -func createRunnerConfig(ctx context.Context, t *testing.T, ns, name string, spec seamcorev1alpha1.InfrastructureRunnerConfigSpec) string { +func createRunnerConfig(ctx context.Context, t *testing.T, ns, name string, spec seamcorev1alpha1.RunnerConfigSpec) string { t.Helper() specBytes, err := json.Marshal(spec) @@ -113,7 +113,7 @@ func createRunnerConfig(ctx context.Context, t *testing.T, ns, name string, spec obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureRunnerConfig", + "kind": "RunnerConfig", "metadata": map[string]interface{}{ "name": name, "namespace": ns, diff --git a/test/unit/agent/capability_publisher_test.go b/test/unit/agent/capability_publisher_test.go index 4b2702e..a4b2f55 100644 --- a/test/unit/agent/capability_publisher_test.go +++ b/test/unit/agent/capability_publisher_test.go @@ -29,7 +29,7 @@ func makeRunnerConfig(name, namespace string, hasCaps bool) *unstructured.Unstru obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "infrastructure.ontai.dev/v1alpha1", - "kind": "InfrastructureRunnerConfig", + "kind": "RunnerConfig", "metadata": map[string]interface{}{ "name": name, "namespace": namespace, @@ -57,7 +57,7 @@ func newFakeDynamicClient(scheme *runtime.Scheme) *dynamicfake.FakeDynamicClient gvk := schema.GroupVersionKind{ Group: "infrastructure.ontai.dev", Version: "v1alpha1", - Kind: "InfrastructureRunnerConfig", + Kind: "RunnerConfig", } scheme.AddKnownTypeWithName(gvk, &runtime.Unknown{}) gvkList := schema.GroupVersionKind{ @@ -137,7 +137,7 @@ func TestCapabilityPublisher_ConstructsWithoutPanic(t *testing.T) { // fake tracker knows the list kind mapping. func newAllFakeDynamicClient(scheme *runtime.Scheme) *dynamicfake.FakeDynamicClient { scheme.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "InfrastructureRunnerConfig", + Group: "infrastructure.ontai.dev", Version: "v1alpha1", Kind: "RunnerConfig", }, &unstructured.Unstructured{}) _ = meta.NewDefaultRESTMapper(nil) return dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, diff --git a/test/unit/kernel/execute_capability_test.go b/test/unit/kernel/execute_capability_test.go index edfe609..4fe80c8 100644 --- a/test/unit/kernel/execute_capability_test.go +++ b/test/unit/kernel/execute_capability_test.go @@ -7,7 +7,7 @@ import ( "github.com/ontai-dev/conductor/internal/config" "github.com/ontai-dev/conductor/internal/kernel" "github.com/ontai-dev/conductor-sdk/runnerlib" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) // WS3 -- Execute mode unit tests. diff --git a/test/unit/kernel/execute_sequencer_test.go b/test/unit/kernel/execute_sequencer_test.go index 0b6105e..0e72ccb 100644 --- a/test/unit/kernel/execute_sequencer_test.go +++ b/test/unit/kernel/execute_sequencer_test.go @@ -6,7 +6,7 @@ import ( "github.com/ontai-dev/conductor/internal/kernel" "github.com/ontai-dev/conductor-sdk/runnerlib" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) // WS2 -- RunnerConfig step sequencer unit tests (additions). diff --git a/test/unit/kernel/execute_test.go b/test/unit/kernel/execute_test.go index 66f807a..9471826 100644 --- a/test/unit/kernel/execute_test.go +++ b/test/unit/kernel/execute_test.go @@ -7,7 +7,7 @@ import ( "github.com/ontai-dev/conductor/internal/config" "github.com/ontai-dev/conductor/internal/kernel" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) // --------------------------------------------------------------------------- @@ -73,7 +73,7 @@ func executeCtx(steps []seamcorev1alpha1.RunnerConfigStep) config.ExecutionConte Mode: config.ModeExecute, ClusterRef: "ccs-test", Namespace: "ont-system", - RunnerConfig: seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + RunnerConfig: seamcorev1alpha1.RunnerConfigSpec{ ClusterRef: "ccs-test", RunnerImage: "conductor:dev", Steps: steps, diff --git a/test/unit/persistence/operationresult_writer_test.go b/test/unit/persistence/operationresult_writer_test.go index 9a872c0..bbe920f 100644 --- a/test/unit/persistence/operationresult_writer_test.go +++ b/test/unit/persistence/operationresult_writer_test.go @@ -10,7 +10,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - dispatcherv1alpha1 "github.com/ontai-dev/wrapper/api/seam/v1alpha1" + dispatcherv1alpha1 "github.com/ontai-dev/dispatcher/api/seam/v1alpha1" "github.com/ontai-dev/conductor/internal/persistence" "github.com/ontai-dev/conductor-sdk/runnerlib" ) diff --git a/test/unit/runnerlib/runnerconfig_test.go b/test/unit/runnerlib/runnerconfig_test.go index 1289e1c..768b9c6 100644 --- a/test/unit/runnerlib/runnerconfig_test.go +++ b/test/unit/runnerlib/runnerconfig_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/ontai-dev/conductor-sdk/runnerlib" - seamcorev1alpha1 "github.com/ontai-dev/seam-core/api/v1alpha1" + seamcorev1alpha1 "github.com/ontai-dev/seam/api/v1alpha1" ) // TestResolveNodeExclusions_SelfOperationTrue verifies that when SelfOperation=true, // ResolveNodeExclusionsFromRunnerConfig merges MaintenanceTargetNodes and // OperatorLeaderNode into a single exclusion list. conductor-schema.md §13. func TestResolveNodeExclusions_SelfOperationTrue(t *testing.T) { - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ SelfOperation: true, MaintenanceTargetNodes: []string{"worker-1", "worker-2"}, OperatorLeaderNode: "cp-0", @@ -36,7 +36,7 @@ func TestResolveNodeExclusions_SelfOperationTrue(t *testing.T) { // ResolveNodeExclusionsFromRunnerConfig returns nil regardless of other fields. // Tenant-targeted operations are exempt from node exclusion. conductor-schema.md §13. func TestResolveNodeExclusions_SelfOperationFalse(t *testing.T) { - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ SelfOperation: false, MaintenanceTargetNodes: []string{"worker-1", "worker-2"}, OperatorLeaderNode: "cp-0", @@ -52,7 +52,7 @@ func TestResolveNodeExclusions_SelfOperationFalse(t *testing.T) { // TestResolveNodeExclusions_LeaderNodeOnly verifies that when only OperatorLeaderNode // is set (no MaintenanceTargetNodes), the single leader node is returned. func TestResolveNodeExclusions_LeaderNodeOnly(t *testing.T) { - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ SelfOperation: true, OperatorLeaderNode: "cp-0", } @@ -67,7 +67,7 @@ func TestResolveNodeExclusions_LeaderNodeOnly(t *testing.T) { // TestJobSpecBuilderWithNodeExclusions_SelfOperation verifies end-to-end that // a JobSpec produced with WithNodeExclusions carries the correct exclusion list. func TestJobSpecBuilderWithNodeExclusions_SelfOperation(t *testing.T) { - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ SelfOperation: true, MaintenanceTargetNodes: []string{"worker-1"}, OperatorLeaderNode: "cp-0", @@ -98,7 +98,7 @@ func TestJobSpecBuilderWithNodeExclusions_SelfOperation(t *testing.T) { // TestJobSpecBuilderWithNodeExclusions_TenantOperation verifies that a JobSpec // produced without node exclusions (selfOperation=false path) has nil NodeExclusions. func TestJobSpecBuilderWithNodeExclusions_TenantOperation(t *testing.T) { - spec := seamcorev1alpha1.InfrastructureRunnerConfigSpec{ + spec := seamcorev1alpha1.RunnerConfigSpec{ SelfOperation: false, MaintenanceTargetNodes: []string{"worker-1"}, OperatorLeaderNode: "cp-0", From 94bc8f454848aaaafc30c2e75ca16ffb9cf82a6d Mon Sep 17 00:00:00 2001 From: ontave Date: Tue, 12 May 2026 17:00:06 +0200 Subject: [PATCH 09/13] chore: update replace directives to renamed seam and dispatcher directories Replace ../seam-core with ../seam and ../wrapper with ../dispatcher following the seam-core -> seam and wrapper -> dispatcher filesystem renames. Module paths were already updated in Phase 4. --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index e12cf5a..e88fae0 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.25.3 replace ( github.com/ontai-dev/conductor-sdk => ../conductor-sdk - github.com/ontai-dev/dispatcher => ../wrapper + github.com/ontai-dev/dispatcher => ../dispatcher github.com/ontai-dev/guardian => ../guardian github.com/ontai-dev/platform => ../platform - github.com/ontai-dev/seam => ../seam-core + github.com/ontai-dev/seam => ../seam github.com/ontai-dev/seam-sdk => ../seam-sdk ) From 6e4698d252d6200cd8f2fdd271e78987514f0f63 Mon Sep 17 00:00:00 2001 From: ontave Date: Tue, 12 May 2026 17:55:01 +0200 Subject: [PATCH 10/13] migration(phase-5): security.ontai.dev -> guardian.ontai.dev across conductor Update all guardian.ontai.dev API group references in conductor: - compile_enable.go, compile_launch.go: enable bundle apiVersion strings, webhook names - catalog.go and all 5 catalog YAML entries: apiVersion strings in rendered RBACProfiles - capability/guardian.go, adapters.go: GVR Group fields for snapshot/profile/policy - agent pull loops (rbacpolicy, rbacprofile, receipt, signing): GVR Group fields - All unit, integration, and e2e test fixtures: GVR/GVK Group strings and apiVersion values --- cmd/compiler/compile_component_test.go | 4 +- cmd/compiler/compile_enable.go | 40 +++++++++---------- cmd/compiler/compile_enable_test.go | 4 +- cmd/compiler/compile_launch.go | 2 +- cmd/compiler/compile_launch_test.go | 4 +- docs/conductor-schema.md | 21 ++++------ internal/agent/rbacpolicy_pull_loop.go | 6 +-- internal/agent/rbacprofile_pull_loop.go | 6 +-- internal/agent/receipt_reconciler.go | 4 +- internal/agent/signing_loop.go | 4 +- internal/agent/signing_loop_test.go | 6 +-- internal/capability/adapters.go | 4 +- internal/capability/guardian.go | 4 +- internal/catalog/catalog.go | 2 +- internal/catalog/entries/cert-manager.yaml | 2 +- internal/catalog/entries/cilium.yaml | 2 +- internal/catalog/entries/cnpg.yaml | 2 +- internal/catalog/entries/kueue.yaml | 2 +- .../entries/local-path-provisioner.yaml | 2 +- internal/kernel/agent.go | 2 +- internal/permissionservice/store.go | 4 +- .../rbacprofile_rbacpolicy_pull_loop_test.go | 4 +- test/e2e/snapshot_pull_loop_test.go | 4 +- test/e2e/tenant_rbac_sweep_test.go | 2 +- .../signing/signing_integration_test.go | 8 ++-- test/unit/agent/rbacpolicy_pull_loop_test.go | 4 +- test/unit/agent/rbacprofile_pull_loop_test.go | 4 +- test/unit/agent/receipt_reconciler_test.go | 2 +- test/unit/agent/signing_loop_test.go | 2 +- test/unit/capability/guardian_test.go | 18 ++++----- test/unit/capability/rbacprofile_wait_test.go | 10 ++--- test/unit/catalog/catalog_test.go | 6 +-- 32 files changed, 93 insertions(+), 98 deletions(-) diff --git a/cmd/compiler/compile_component_test.go b/cmd/compiler/compile_component_test.go index 1cc754b..330d7fe 100644 --- a/cmd/compiler/compile_component_test.go +++ b/cmd/compiler/compile_component_test.go @@ -131,7 +131,7 @@ func TestCatalogMode_SingleComponentToFile(t *testing.T) { } yaml := string(data) - assertContainsStr(t, yaml, "apiVersion: security.ontai.dev/v1alpha1") + assertContainsStr(t, yaml, "apiVersion: guardian.ontai.dev/v1alpha1") assertContainsStr(t, yaml, "kind: RBACProfile") assertContainsStr(t, yaml, "name: rbac-cilium") assertContainsStr(t, yaml, "namespace: seam-tenant-management") @@ -226,7 +226,7 @@ rbacPolicyRef: platform-rbac-policy scaffold := string(data) assertContainsStr(t, scaffold, "HUMAN REVIEW REQUIRED") - assertContainsStr(t, scaffold, "apiVersion: security.ontai.dev/v1alpha1") + assertContainsStr(t, scaffold, "apiVersion: guardian.ontai.dev/v1alpha1") assertContainsStr(t, scaffold, "kind: RBACProfile") assertContainsStr(t, scaffold, "name: rbac-my-custom-operator") assertContainsStr(t, scaffold, "namespace: seam-tenant-management") diff --git a/cmd/compiler/compile_enable.go b/cmd/compiler/compile_enable.go index df9553e..c4e45b2 100644 --- a/cmd/compiler/compile_enable.go +++ b/cmd/compiler/compile_enable.go @@ -853,7 +853,7 @@ func writePhase1GuardianBootstrap(output string, gdn operatorSpec) error { Order: 1, ReadinessGate: "Verify that seam-system and kube-system namespaces carry " + "seam.ontai.dev/webhook-mode=exempt before applying phase 2. " + - "Guardian CRDs must be registered (kubectl get crd | grep security.ontai.dev). " + + "Guardian CRDs must be registered (kubectl get crd | grep guardian.ontai.dev). " + "Guardian RBAC must be present. PermissionSets and RBACPolicy must be in the cluster. " + "Guardian RBACProfile must be in the cluster.", ApplyOrder: files, @@ -868,7 +868,7 @@ func writePhase1GuardianBootstrap(output string, gdn operatorSpec) error { return err } - // guardian-crds.yaml — Guardian CRD definitions (security.ontai.dev). + // guardian-crds.yaml — Guardian CRD definitions (guardian.ontai.dev). if err := writeGuardianCRDs(dir); err != nil { return err } @@ -911,7 +911,7 @@ func writePhase1GuardianBootstrap(output string, gdn operatorSpec) error { // guardian-schema.md §6. func writeBootstrapRBACPolicy(dir string) error { policy := map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "RBACPolicy", "metadata": map[string]interface{}{ "name": "management-policy", @@ -1005,7 +1005,7 @@ func writeBootstrapPermissionSets(dir string) error { spec["description"] = s.description } ps := map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "PermissionSet", "metadata": map[string]interface{}{ "name": s.name, @@ -1092,9 +1092,9 @@ func namespaceLabelPatch(name string) map[string]interface{} { } } -// writeGuardianCRDs writes guardian CRD definitions (security.ontai.dev group). +// writeGuardianCRDs writes guardian CRD definitions (guardian.ontai.dev group). // Uses the embedded CRD bundle from the guardian repository, filtering to -// security.ontai.dev group only. +// guardian.ontai.dev group only. func writeGuardianCRDs(dir string) error { // Extract guardian CRDs from the full CRD bundle (which includes all groups). // We generate the full bundle first, then filter to the guardian group. @@ -1103,14 +1103,14 @@ func writeGuardianCRDs(dir string) error { return fmt.Errorf("read CRD bundle: %w", err) } - // Split on --- and filter to security.ontai.dev documents. - guardianCRDs := filterCRDsByGroup(allBuf.String(), "security.ontai.dev") + // Split on --- and filter to guardian.ontai.dev documents. + guardianCRDs := filterCRDsByGroup(allBuf.String(), "guardian.ontai.dev") var buf bytes.Buffer - buf.WriteString("# Guardian CRD Definitions (security.ontai.dev)\n") + buf.WriteString("# Guardian CRD Definitions (guardian.ontai.dev)\n") buf.WriteString("# Generated by: compiler enable (phase 1 guardian-bootstrap)\n") buf.WriteString("# Apply before deploying Guardian. CRDs must be registered before\n") - buf.WriteString("# Guardian can reconcile any security.ontai.dev resources.\n") + buf.WriteString("# Guardian can reconcile any guardian.ontai.dev resources.\n") buf.Write([]byte(guardianCRDs)) return os.WriteFile(filepath.Join(dir, "guardian-crds.yaml"), buf.Bytes(), 0644) @@ -1314,7 +1314,7 @@ func writeGuardianRBACWebhook(dir string) error { }, "webhooks": []map[string]interface{}{ { - "name": "validate-rbac.security.ontai.dev", + "name": "validate-rbac.guardian.ontai.dev", "admissionReviewVersions": []string{"v1"}, "sideEffects": "None", // FailurePolicy: Fail — policy without enforcement is decoration. CS-INV-001. @@ -1379,7 +1379,7 @@ func writeGuardianRBACWebhook(dir string) error { // writeGuardianLineageWebhook writes guardian-lineage-webhook.yaml to dir. // Emits a ValidatingWebhookConfiguration that enforces lineage immutability on -// all security.ontai.dev root declaration CRDs. caBundle is injected automatically +// all guardian.ontai.dev root declaration CRDs. caBundle is injected automatically // by cert-manager's CA injector via the cert-manager.io/inject-ca-from annotation. // CLAUDE.md §14 Decision 1. guardian-schema.md §5. func writeGuardianLineageWebhook(dir string) error { @@ -1395,7 +1395,7 @@ func writeGuardianLineageWebhook(dir string) error { }, "webhooks": []map[string]interface{}{ { - "name": "validate-lineage.security.ontai.dev", + "name": "validate-lineage.guardian.ontai.dev", "admissionReviewVersions": []string{"v1"}, "sideEffects": "None", // FailurePolicy: Fail — a missing lineage check is a security breach. @@ -1415,7 +1415,7 @@ func writeGuardianLineageWebhook(dir string) error { }, "rules": []map[string]interface{}{ { - "apiGroups": []string{"security.ontai.dev"}, + "apiGroups": []string{"guardian.ontai.dev"}, "apiVersions": []string{"v1alpha1"}, "operations": []string{"UPDATE"}, "resources": []string{"rbacpolicies", "rbacprofiles", "identitybindings", "identityproviders", "permissionsets"}, @@ -1442,7 +1442,7 @@ func writeGuardianLineageWebhook(dir string) error { var buf bytes.Buffer buf.WriteString("# Guardian Lineage Immutability ValidatingWebhookConfiguration\n") buf.WriteString("# Generated by: compiler enable (phase 2 guardian-deploy)\n") - buf.WriteString("# Enforces spec.lineage immutability on all security.ontai.dev root declarations.\n") + buf.WriteString("# Enforces spec.lineage immutability on all guardian.ontai.dev root declarations.\n") buf.WriteString("# Rejects any UPDATE that attempts to alter a lineage field after creation.\n") buf.WriteString("# caBundle injected by cert-manager CA injector via cert-manager.io/inject-ca-from annotation.\n") buf.WriteString("# CLAUDE.md §14 Decision 1. guardian-schema.md §5.\n") @@ -2552,9 +2552,9 @@ func operatorClusterRules(operatorName string) []rbacv1.PolicyRule { Resources: []string{"namespaces"}, Verbs: []string{"get", "list", "watch", "update", "patch"}, }, - // security.ontai.dev — all eight Guardian CRD resources plus /status subresources. + // guardian.ontai.dev — all eight Guardian CRD resources plus /status subresources. rbacv1.PolicyRule{ - APIGroups: []string{"security.ontai.dev"}, + APIGroups: []string{"guardian.ontai.dev"}, Resources: []string{ "rbacpolicies", "rbacpolicies/status", "rbacprofiles", "rbacprofiles/status", @@ -2761,7 +2761,7 @@ func writeWrapperRunnerRBACYAML(dir, clusterName string) error { // Read-only access to RBACProfile so the pack-deploy split path can // poll for provisioned=true after submitting RBAC to guardian intake. // wrapper-schema.md §4, INV-004. - APIGroups: []string{"security.ontai.dev"}, + APIGroups: []string{"guardian.ontai.dev"}, Resources: []string{"rbacprofiles"}, Verbs: []string{"get", "list", "watch"}, }, @@ -3137,7 +3137,7 @@ func buildTargetClusters(additional []string) []string { return result } -// Uses the security.ontai.dev/v1alpha1 schema from guardian-schema.md §7. +// Uses the guardian.ontai.dev/v1alpha1 schema from guardian-schema.md §7. // // All RBACProfile CRs live in seam-system regardless of the operator's runtime // namespace. Conductor runs in ont-system but its RBACProfile is in seam-system @@ -3149,7 +3149,7 @@ func buildOperatorRBACProfile(op operatorSpec) map[string]interface{} { profileNamespace := "seam-system" return map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "RBACProfile", "metadata": map[string]interface{}{ "name": "rbac-" + op.Name, diff --git a/cmd/compiler/compile_enable_test.go b/cmd/compiler/compile_enable_test.go index ba586a1..4d27ecc 100644 --- a/cmd/compiler/compile_enable_test.go +++ b/cmd/compiler/compile_enable_test.go @@ -307,7 +307,7 @@ func TestEnable_RBACProfilesYAMLContainsAllProfiles(t *testing.T) { readPhaseFile(t, outDir, "03-platform-wrapper", "platform-wrapper-rbacprofiles.yaml") + readPhaseFile(t, outDir, "04-conductor", "conductor-rbacprofile.yaml") - assertContainsStr(t, content, "apiVersion: security.ontai.dev/v1alpha1") + assertContainsStr(t, content, "apiVersion: guardian.ontai.dev/v1alpha1") assertContainsStr(t, content, "kind: RBACProfile") for _, name := range []string{"conductor", "guardian", "platform", "wrapper", "seam-core"} { if !strings.Contains(content, "rbac-"+name) { @@ -596,7 +596,7 @@ func TestEnable_CRDsYAMLIncludesAllOperatorCRDs(t *testing.T) { for _, group := range []string{ "platform.ontai.dev", - "security.ontai.dev", + "guardian.ontai.dev", "infrastructure.ontai.dev", } { if !strings.Contains(content, group) { diff --git a/cmd/compiler/compile_launch.go b/cmd/compiler/compile_launch.go index 1be5cc8..516cda1 100644 --- a/cmd/compiler/compile_launch.go +++ b/cmd/compiler/compile_launch.go @@ -72,7 +72,7 @@ func runLaunchSubcommand(args []string) { // // CRD sources (all embedded at build time): // - platform.ontai.dev: TalosCluster, day-2 CRDs, SeamInfrastructureCluster/Machine -// - security.ontai.dev: RBACPolicy, RBACProfile, IdentityBinding, IdentityProvider, PermissionSet +// - guardian.ontai.dev: RBACPolicy, RBACProfile, IdentityBinding, IdentityProvider, PermissionSet // - seam.ontai.dev: RunnerConfig, LineageRecord, DriftSignal, SeamMembership (seam) // - seam.ontai.dev: PackDelivery, PackExecution, PackInstalled, PackReceipt, PackLog (dispatcher) // diff --git a/cmd/compiler/compile_launch_test.go b/cmd/compiler/compile_launch_test.go index 05fbbfd..66c5165 100644 --- a/cmd/compiler/compile_launch_test.go +++ b/cmd/compiler/compile_launch_test.go @@ -39,7 +39,7 @@ func TestLaunch_BundleContainsPlatformCRDs(t *testing.T) { assertContainsStr(t, content, "talosclusters") } -// TestLaunch_BundleContainsGuardianCRDs verifies that security.ontai.dev CRDs +// TestLaunch_BundleContainsGuardianCRDs verifies that guardian.ontai.dev CRDs // are present in the bundle. guardian-schema.md §7. func TestLaunch_BundleContainsGuardianCRDs(t *testing.T) { outDir := t.TempDir() @@ -49,7 +49,7 @@ func TestLaunch_BundleContainsGuardianCRDs(t *testing.T) { data, _ := os.ReadFile(filepath.Join(outDir, "crds.yaml")) content := string(data) - assertContainsStr(t, content, "security.ontai.dev") + assertContainsStr(t, content, "guardian.ontai.dev") assertContainsStr(t, content, "rbacprofiles") } diff --git a/docs/conductor-schema.md b/docs/conductor-schema.md index 5565e62..2893053 100644 --- a/docs/conductor-schema.md +++ b/docs/conductor-schema.md @@ -20,9 +20,7 @@ on the management cluster and every target cluster. It also runs as short-lived Jobs on the management cluster for all named operational capabilities. It is the only Seam binary deployed to any cluster. It is distroless. -Human-facing compile operations are invocations of the Compiler binary in compile -mode. Cluster management operations are performed by submitting Seam CRs which -operators translate into Conductor Jobs. +Human-facing compile operations are invocations of the Compiler binary. Cluster management operations are performed by submitting Seam CRs which operators translate into Conductor Jobs. --- @@ -75,17 +73,14 @@ Invoking talos goclient in compile mode is a programming error. INV-013. ## 3. Image Tag Convention and Release Pairing **Stable releases:** -- `registry.ontai.dev/ontai-dev/compiler:v{talosVersion}-r{revision}` -- `registry.ontai.dev/ontai-dev/conductor:v{talosVersion}-r{revision}` +- `registry.ontai.dev/ontai-dev/compiler:` (compiler) +- `registry.ontai.dev/ontai-dev/conductor:` (agent) +- `registry.ontai.dev/ontai-dev/conductor-exec:v{talos-version}` (conductor-exec/ont-runner) The talosVersion component declares Talos API compatibility - not cosmetic. A cluster -at Talos v1.9.3 must use a Conductor tagged v1.9.3-rN. INV-012. +at Talos v1.9.3 must use a Conductor-exec tagged v1.9.3. -Compiler and Conductor always carry the same version tag built from the same source -commit. They are released together. Deploying mismatched versions against the same -cluster is unsupported. INV-024. - -**Development:** dev (floating), dev-rc{N} (release candidates). Applied to both images. +**Development:** **Lab builds:** pushed only to 10.20.0.1:5000/ontai-dev/compiler and 10.20.0.1:5000/ontai-dev/conductor. Lab tags never appear in the public registry. @@ -323,7 +318,7 @@ The management cluster bootstrap sequence is owned exclusively by the Compiler i Forms the management cluster itself. Reads TalosCluster spec and human-provided machineconfig inputs. Validates spec against platform-schema.md rules. SOPS-encrypts talos-secret, machineconfigs, and talosconfig using the admin's age key. Writes encrypted files to output path. Produces bootstrap CRs (TalosCluster in mode: bootstrap) as YAML. No cluster connection required. Compiler never applies resources - the GitOps pipeline or operator's kubectl applies the output. **Step 2 - `compiler launch`** -Installs all Seam CRDs onto the management cluster. Reads the CRD manifest set for all Seam API groups (infrastructure.ontai.dev, security.ontai.dev, platform.ontai.dev). The old groups runner.ontai.dev and infra.ontai.dev are superseded by infrastructure.ontai.dev as of Phase 2B (2026-04-25). Produces a CRD manifest YAML bundle ready for GitOps application. No cluster connection required. Compiler never applies resources. +Installs all Seam CRDs onto the management cluster. Reads the CRD manifest set for all Seam API groups (infrastructure.ontai.dev, guardian.ontai.dev, platform.ontai.dev). The old groups runner.ontai.dev and infra.ontai.dev are superseded by infrastructure.ontai.dev as of Phase 2B (2026-04-25). Produces a CRD manifest YAML bundle ready for GitOps application. No cluster connection required. Compiler never applies resources. **Step 3 - `compiler enable`** Produces the complete Seam operator deployment manifest bundle as YAML output. The bundle @@ -611,7 +606,7 @@ kubernetes client. This establishes the governance baseline on cluster join. Phase 2 (profile creation): creates PermissionSet, RBACPolicy, and RBACProfile for each known third-party component (cert-manager, kueue, cnpg, metallb, local-path-provisioner) via the dynamic client using GVRs under -`security.ontai.dev/v1alpha1`. Components whose namespace is absent on the cluster +`guardian.ontai.dev/v1alpha1`. Components whose namespace is absent on the cluster are silently skipped -- they will be picked up on the next periodic run once their Helm chart is deployed. If security CRDs are not installed (Guardian not deployed), the entire profile creation phase is skipped gracefully. diff --git a/internal/agent/rbacpolicy_pull_loop.go b/internal/agent/rbacpolicy_pull_loop.go index b893d86..19906f1 100644 --- a/internal/agent/rbacpolicy_pull_loop.go +++ b/internal/agent/rbacpolicy_pull_loop.go @@ -13,9 +13,9 @@ import ( ) // rbacPolicyGVR is the GroupVersionResource for RBACPolicy CRs. -// Defined by the Guardian operator in security.ontai.dev. +// Defined by the Guardian operator in guardian.ontai.dev. var rbacPolicyGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacpolicies", } @@ -97,7 +97,7 @@ func (l *RBACPolicyPullLoop) tickOnce(ctx context.Context) { spec, _, _ := unstructuredNestedMap(policy.Object, "spec") payload := map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "RBACPolicy", "metadata": map[string]interface{}{ "name": clusterPolicyName, diff --git a/internal/agent/rbacprofile_pull_loop.go b/internal/agent/rbacprofile_pull_loop.go index 24e9254..8f2664d 100644 --- a/internal/agent/rbacprofile_pull_loop.go +++ b/internal/agent/rbacprofile_pull_loop.go @@ -13,9 +13,9 @@ import ( ) // rbacProfileGVR is the GroupVersionResource for RBACProfile CRs. -// Defined by the Guardian operator in security.ontai.dev. +// Defined by the Guardian operator in guardian.ontai.dev. var rbacProfileGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", } @@ -97,7 +97,7 @@ func (l *RBACProfilePullLoop) tickOnce(ctx context.Context) { spec, _, _ := unstructuredNestedMap(profile.Object, "spec") payload := map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "RBACProfile", "metadata": map[string]interface{}{ "name": conductorTenantRBACProfileName, diff --git a/internal/agent/receipt_reconciler.go b/internal/agent/receipt_reconciler.go index 201fd6a..9c47587 100644 --- a/internal/agent/receipt_reconciler.go +++ b/internal/agent/receipt_reconciler.go @@ -25,9 +25,9 @@ var packReceiptGVR = schema.GroupVersionResource{ } // permissionSnapshotReceiptGVR is the GroupVersionResource for PermissionSnapshotReceipt CRs. -// Defined by the Guardian operator in security.ontai.dev. conductor-schema.md §10. +// Defined by the Guardian operator in guardian.ontai.dev. conductor-schema.md §10. var permissionSnapshotReceiptGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshotreceipts", } diff --git a/internal/agent/signing_loop.go b/internal/agent/signing_loop.go index 9d3ac28..72d2a21 100644 --- a/internal/agent/signing_loop.go +++ b/internal/agent/signing_loop.go @@ -46,9 +46,9 @@ var clusterPackGVR = schema.GroupVersionResource{ const clusterPackSignatureAnnotation = "ontai.dev/pack-signature" // permissionSnapshotGVR is the GroupVersionResource for PermissionSnapshot CRs. -// Defined by the Guardian operator in security.ontai.dev. conductor-schema.md §10. +// Defined by the Guardian operator in guardian.ontai.dev. conductor-schema.md §10. var permissionSnapshotGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", } diff --git a/internal/agent/signing_loop_test.go b/internal/agent/signing_loop_test.go index 43e7be2..8befc2b 100644 --- a/internal/agent/signing_loop_test.go +++ b/internal/agent/signing_loop_test.go @@ -20,10 +20,10 @@ import ( func setupSigningLoopScheme() *runtime.Scheme { s := runtime.NewScheme() s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "security.ontai.dev", Version: "v1alpha1", Kind: "PermissionSnapshot", + Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "PermissionSnapshot", }, &unstructured.Unstructured{}) s.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: "security.ontai.dev", Version: "v1alpha1", Kind: "PermissionSnapshotList", + Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "PermissionSnapshotList", }, &unstructured.UnstructuredList{}) return s } @@ -45,7 +45,7 @@ func fakePermissionSnapshot(name, ns string, annotations map[string]string) *uns } return &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "PermissionSnapshot", "metadata": meta, "spec": map[string]interface{}{ diff --git a/internal/capability/adapters.go b/internal/capability/adapters.go index 54dcc41..1a50188 100644 --- a/internal/capability/adapters.go +++ b/internal/capability/adapters.go @@ -34,9 +34,9 @@ import ( // ── GuardianIntakeClientAdapter ────────────────────────────────────────────── // rbacProfileGVR is the GroupVersionResource for guardian RBACProfile. -// security.ontai.dev/v1alpha1/rbacprofiles -- guardian-schema.md §7. +// guardian.ontai.dev/v1alpha1/rbacprofiles -- guardian-schema.md §7. var rbacProfileGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", } diff --git a/internal/capability/guardian.go b/internal/capability/guardian.go index 8c26139..2acd80b 100644 --- a/internal/capability/guardian.go +++ b/internal/capability/guardian.go @@ -25,9 +25,9 @@ import ( const managementSignatureAnnotation = "infrastructure.ontai.dev/management-signature" // permissionSnapshotGVR is the GroupVersionResource for PermissionSnapshot. -// security.ontai.dev/v1alpha1/permissionsnapshots — guardian-schema.md §7. +// guardian.ontai.dev/v1alpha1/permissionsnapshots — guardian-schema.md §7. var permissionSnapshotGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", } diff --git a/internal/catalog/catalog.go b/internal/catalog/catalog.go index 1cf007d..cacbd0a 100644 --- a/internal/catalog/catalog.go +++ b/internal/catalog/catalog.go @@ -198,7 +198,7 @@ func DescriptorScaffold(descriptor ComponentDescriptor) ([]byte, error) { # repository. New catalog entries require Platform Governor review. # conductor-schema.md §16 Custom Mode. # ============================================================================ -apiVersion: security.ontai.dev/v1alpha1 +apiVersion: guardian.ontai.dev/v1alpha1 kind: RBACProfile metadata: # REVIEW: Adjust the name to match your component naming convention. diff --git a/internal/catalog/entries/cert-manager.yaml b/internal/catalog/entries/cert-manager.yaml index 632183d..7802431 100644 --- a/internal/catalog/entries/cert-manager.yaml +++ b/internal/catalog/entries/cert-manager.yaml @@ -12,7 +12,7 @@ # # spec.lineage is controller-managed — do not author this field manually. # The InfrastructureLineageController sets it after admission. CLAUDE.md §14. -apiVersion: security.ontai.dev/v1alpha1 +apiVersion: guardian.ontai.dev/v1alpha1 kind: RBACProfile metadata: name: {{ .RBACProfileName }} diff --git a/internal/catalog/entries/cilium.yaml b/internal/catalog/entries/cilium.yaml index f1f4936..579e36d 100644 --- a/internal/catalog/entries/cilium.yaml +++ b/internal/catalog/entries/cilium.yaml @@ -11,7 +11,7 @@ # # spec.lineage is controller-managed — do not author this field manually. # The InfrastructureLineageController sets it after admission. CLAUDE.md §14. -apiVersion: security.ontai.dev/v1alpha1 +apiVersion: guardian.ontai.dev/v1alpha1 kind: RBACProfile metadata: name: {{ .RBACProfileName }} diff --git a/internal/catalog/entries/cnpg.yaml b/internal/catalog/entries/cnpg.yaml index cc45078..77628bf 100644 --- a/internal/catalog/entries/cnpg.yaml +++ b/internal/catalog/entries/cnpg.yaml @@ -12,7 +12,7 @@ # # spec.lineage is controller-managed — do not author this field manually. # The InfrastructureLineageController sets it after admission. CLAUDE.md §14. -apiVersion: security.ontai.dev/v1alpha1 +apiVersion: guardian.ontai.dev/v1alpha1 kind: RBACProfile metadata: name: {{ .RBACProfileName }} diff --git a/internal/catalog/entries/kueue.yaml b/internal/catalog/entries/kueue.yaml index f9baadb..6b11747 100644 --- a/internal/catalog/entries/kueue.yaml +++ b/internal/catalog/entries/kueue.yaml @@ -12,7 +12,7 @@ # # spec.lineage is controller-managed — do not author this field manually. # The InfrastructureLineageController sets it after admission. CLAUDE.md §14. -apiVersion: security.ontai.dev/v1alpha1 +apiVersion: guardian.ontai.dev/v1alpha1 kind: RBACProfile metadata: name: {{ .RBACProfileName }} diff --git a/internal/catalog/entries/local-path-provisioner.yaml b/internal/catalog/entries/local-path-provisioner.yaml index 3f4280e..2361ec5 100644 --- a/internal/catalog/entries/local-path-provisioner.yaml +++ b/internal/catalog/entries/local-path-provisioner.yaml @@ -12,7 +12,7 @@ # # spec.lineage is controller-managed — do not author this field manually. # The InfrastructureLineageController sets it after admission. CLAUDE.md §14. -apiVersion: security.ontai.dev/v1alpha1 +apiVersion: guardian.ontai.dev/v1alpha1 kind: RBACProfile metadata: name: {{ .RBACProfileName }} diff --git a/internal/kernel/agent.go b/internal/kernel/agent.go index e3ca69c..59c7ad9 100644 --- a/internal/kernel/agent.go +++ b/internal/kernel/agent.go @@ -363,7 +363,7 @@ func RunAgent(goCtx context.Context, execCtx config.ExecutionContext, client kub // Phase 3 — RBAC enforcement gate (tenant clusters only). // Conductor starts in audit mode and stays there; Guardian role=tenant owns all - // security.ontai.dev provisioning and enforcement on the tenant cluster. + // guardian.ontai.dev provisioning and enforcement on the tenant cluster. // guardian-schema.md §3, §6. CS-INV-001. var enforcementGate *webhook.EnforcementGate if role == RoleTenant { diff --git a/internal/permissionservice/store.go b/internal/permissionservice/store.go index 497dfc2..5cfae95 100644 --- a/internal/permissionservice/store.go +++ b/internal/permissionservice/store.go @@ -2,7 +2,7 @@ package permissionservice import "sync" -// AllowedOperation mirrors security.ontai.dev/v1alpha1 AllowedOperation. +// AllowedOperation mirrors guardian.ontai.dev/v1alpha1 AllowedOperation. // Guardian writes these into PermissionSnapshot.spec; conductor reads them // from the locally acknowledged snapshot to serve the local PermissionService. // guardian-schema.md §7. @@ -22,7 +22,7 @@ type AllowedOperation struct { Clusters []string `json:"clusters,omitempty"` } -// PrincipalPermissionEntry mirrors security.ontai.dev/v1alpha1 PrincipalPermissionEntry. +// PrincipalPermissionEntry mirrors guardian.ontai.dev/v1alpha1 PrincipalPermissionEntry. type PrincipalPermissionEntry struct { // PrincipalRef is the principal name (RBACProfile.Spec.PrincipalRef). PrincipalRef string `json:"principalRef"` diff --git a/test/e2e/rbacprofile_rbacpolicy_pull_loop_test.go b/test/e2e/rbacprofile_rbacpolicy_pull_loop_test.go index 624a64e..afb590c 100644 --- a/test/e2e/rbacprofile_rbacpolicy_pull_loop_test.go +++ b/test/e2e/rbacprofile_rbacpolicy_pull_loop_test.go @@ -48,10 +48,10 @@ const ( var ( rbacProfileGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", } rbacPolicyGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", Version: "v1alpha1", Resource: "rbacpolicies", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacpolicies", } ) diff --git a/test/e2e/snapshot_pull_loop_test.go b/test/e2e/snapshot_pull_loop_test.go index 3192e56..129d580 100644 --- a/test/e2e/snapshot_pull_loop_test.go +++ b/test/e2e/snapshot_pull_loop_test.go @@ -131,9 +131,9 @@ var _ = Describe("Conductor role=agent: SnapshotPullLoop", func() { // to avoid collision with guardian e2e package vars (different test binary). var ( permissionSnapshotGVRc = schema.GroupVersionResource{ - Group: "security.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", } permissionSnapshotReceiptGVRc = schema.GroupVersionResource{ - Group: "security.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshotreceipts", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshotreceipts", } ) diff --git a/test/e2e/tenant_rbac_sweep_test.go b/test/e2e/tenant_rbac_sweep_test.go index 18bddd0..2e3f912 100644 --- a/test/e2e/tenant_rbac_sweep_test.go +++ b/test/e2e/tenant_rbac_sweep_test.go @@ -54,7 +54,7 @@ const ( var ( tenantRBACProfileGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", } ) diff --git a/test/integration/signing/signing_integration_test.go b/test/integration/signing/signing_integration_test.go index 860c134..bfd9e0c 100644 --- a/test/integration/signing/signing_integration_test.go +++ b/test/integration/signing/signing_integration_test.go @@ -46,10 +46,10 @@ var ( Group: "", Version: "v1", Resource: "secrets", } permissionSnapshotGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", } permissionSnapshotReceiptGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshotreceipts", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshotreceipts", } ) @@ -399,7 +399,7 @@ func TestSnapshotPullLoop_InvalidSignature_PatchesDegradedSecurityState(t *testi snapshot := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "PermissionSnapshot", "metadata": map[string]interface{}{ "name": "snapshot-ccs-test", @@ -416,7 +416,7 @@ func TestSnapshotPullLoop_InvalidSignature_PatchesDegradedSecurityState(t *testi // Pre-create the local PermissionSnapshotReceipt so the patch has a target. receipt := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "PermissionSnapshotReceipt", "metadata": map[string]interface{}{"name": "snapshot-ccs-test", "namespace": "ont-system"}, "status": map[string]interface{}{}, diff --git a/test/unit/agent/rbacpolicy_pull_loop_test.go b/test/unit/agent/rbacpolicy_pull_loop_test.go index 455cccd..b643d86 100644 --- a/test/unit/agent/rbacpolicy_pull_loop_test.go +++ b/test/unit/agent/rbacpolicy_pull_loop_test.go @@ -23,7 +23,7 @@ import ( // rbacPolicyTestGVR mirrors rbacPolicyGVR from rbacpolicy_pull_loop.go. var rbacPolicyTestGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacpolicies", } @@ -44,7 +44,7 @@ func newRBACPolicyLocalClient() *dynamicfake.FakeDynamicClient { func buildClusterPolicyObject(namespace string, specMap map[string]interface{}) *unstructured.Unstructured { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "RBACPolicy", "metadata": map[string]interface{}{ "name": "cluster-policy", diff --git a/test/unit/agent/rbacprofile_pull_loop_test.go b/test/unit/agent/rbacprofile_pull_loop_test.go index ca259f0..c6b410e 100644 --- a/test/unit/agent/rbacprofile_pull_loop_test.go +++ b/test/unit/agent/rbacprofile_pull_loop_test.go @@ -23,7 +23,7 @@ import ( // rbacProfileTestGVR mirrors rbacProfileGVR from rbacprofile_pull_loop.go. var rbacProfileTestGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", } @@ -44,7 +44,7 @@ func newRBACProfileLocalClient() *dynamicfake.FakeDynamicClient { func buildConductorTenantProfile(namespace string, specMap map[string]interface{}) *unstructured.Unstructured { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "Rbacprofile", "metadata": map[string]interface{}{ "name": "conductor-tenant", diff --git a/test/unit/agent/receipt_reconciler_test.go b/test/unit/agent/receipt_reconciler_test.go index eac3037..61c0e5e 100644 --- a/test/unit/agent/receipt_reconciler_test.go +++ b/test/unit/agent/receipt_reconciler_test.go @@ -22,7 +22,7 @@ var packReceiptGVR = schema.GroupVersionResource{ // permissionSnapshotReceiptGVR mirrors the GVR defined in production. var permissionSnapshotReceiptGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshotreceipts", } diff --git a/test/unit/agent/signing_loop_test.go b/test/unit/agent/signing_loop_test.go index dfffeee..bfa6cca 100644 --- a/test/unit/agent/signing_loop_test.go +++ b/test/unit/agent/signing_loop_test.go @@ -32,7 +32,7 @@ var packInstanceGVR = schema.GroupVersionResource{ // psGVR mirrors the permissionSnapshotGVR in signing_loop.go. var psGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", } diff --git a/test/unit/capability/guardian_test.go b/test/unit/capability/guardian_test.go index e46b55c..657cad1 100644 --- a/test/unit/capability/guardian_test.go +++ b/test/unit/capability/guardian_test.go @@ -22,7 +22,7 @@ import ( // permissionSnapshotGVR mirrors the GVR in guardian.go. var permissionSnapshotGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "permissionsnapshots", } @@ -32,17 +32,17 @@ var permissionSnapshotGVR = schema.GroupVersionResource{ func newFakeDynamicWithSnapshots(clusterRef string) *dynamicfake.FakeDynamicClient { s := runtime.NewScheme() s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "Permissionsnapshot"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "Permissionsnapshot"}, &unstructured.Unstructured{}, ) s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "PermissionsnapshotList"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "PermissionsnapshotList"}, &unstructured.UnstructuredList{}, ) snap := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "Permissionsnapshot", "metadata": map[string]interface{}{ "name": "snap-" + clusterRef, @@ -108,11 +108,11 @@ func TestRBACProvision_NoSnapshotReturnsExecutionFailure(t *testing.T) { // Empty dynamic client — no PermissionSnapshot. s := runtime.NewScheme() s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "Permissionsnapshot"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "Permissionsnapshot"}, &unstructured.Unstructured{}, ) s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "PermissionsnapshotList"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "PermissionsnapshotList"}, &unstructured.UnstructuredList{}, ) dynClient := dynamicfake.NewSimpleDynamicClient(s) @@ -253,11 +253,11 @@ func snapshotSpec(clusterRef string) map[string]interface{} { func newFakeDynamicWithSignedSnapshot(clusterRef string, privKey ed25519.PrivateKey) *dynamicfake.FakeDynamicClient { s := runtime.NewScheme() s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "Permissionsnapshot"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "Permissionsnapshot"}, &unstructured.Unstructured{}, ) s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "PermissionsnapshotList"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "PermissionsnapshotList"}, &unstructured.UnstructuredList{}, ) @@ -277,7 +277,7 @@ func newFakeDynamicWithSignedSnapshot(clusterRef string, privKey ed25519.Private snap := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "Permissionsnapshot", "metadata": meta, "spec": spec, diff --git a/test/unit/capability/rbacprofile_wait_test.go b/test/unit/capability/rbacprofile_wait_test.go index 98fbaf0..2f8e5b1 100644 --- a/test/unit/capability/rbacprofile_wait_test.go +++ b/test/unit/capability/rbacprofile_wait_test.go @@ -30,7 +30,7 @@ import ( ) var rbacProfileTestGVR = schema.GroupVersionResource{ - Group: "security.ontai.dev", + Group: "guardian.ontai.dev", Version: "v1alpha1", Resource: "rbacprofiles", } @@ -39,11 +39,11 @@ var rbacProfileTestGVR = schema.GroupVersionResource{ func newRBACProfileDynClient(objects ...*unstructured.Unstructured) *dynamicfake.FakeDynamicClient { s := runtime.NewScheme() s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "RBACProfile"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "RBACProfile"}, &unstructured.Unstructured{}, ) s.AddKnownTypeWithName( - schema.GroupVersionKind{Group: "security.ontai.dev", Version: "v1alpha1", Kind: "RBACProfileList"}, + schema.GroupVersionKind{Group: "guardian.ontai.dev", Version: "v1alpha1", Kind: "RBACProfileList"}, &unstructured.UnstructuredList{}, ) client := dynamicfake.NewSimpleDynamicClient(s) @@ -66,7 +66,7 @@ func rbacProfileCR(name, namespace string, provisioned bool) *unstructured.Unstr status = "True" } return &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": "security.ontai.dev/v1alpha1", + "apiVersion": "guardian.ontai.dev/v1alpha1", "kind": "RBACProfile", "metadata": map[string]interface{}{ "name": name, @@ -225,7 +225,7 @@ func TestWaitForRBACProfileProvisioned_NonNotFoundErrorPropagates(t *testing.T) calls := 0 client.Fake.PrependReactor("get", "rbacprofiles", func(action k8stesting.Action) (bool, runtime.Object, error) { calls++ - return true, nil, fmt.Errorf("rbacprofiles.security.ontai.dev is forbidden: User cannot get") + return true, nil, fmt.Errorf("rbacprofiles.guardian.ontai.dev is forbidden: User cannot get") }) err := capability.WaitForRBACProfileProvisioned( diff --git a/test/unit/catalog/catalog_test.go b/test/unit/catalog/catalog_test.go index 5e9f45d..fbbaa36 100644 --- a/test/unit/catalog/catalog_test.go +++ b/test/unit/catalog/catalog_test.go @@ -124,7 +124,7 @@ func TestCiliumRender_ProducesValidRBACProfileYAML(t *testing.T) { yaml := string(out) // Verify RBACProfile structural fields. - assertContains(t, yaml, "apiVersion: security.ontai.dev/v1alpha1") + assertContains(t, yaml, "apiVersion: guardian.ontai.dev/v1alpha1") assertContains(t, yaml, "kind: RBACProfile") assertContains(t, yaml, "name: rbac-cilium") assertContains(t, yaml, "namespace: seam-tenant-management") @@ -156,7 +156,7 @@ func TestCNPGRender_ProducesValidRBACProfileYAML(t *testing.T) { } yaml := string(out) - assertContains(t, yaml, "apiVersion: security.ontai.dev/v1alpha1") + assertContains(t, yaml, "apiVersion: guardian.ontai.dev/v1alpha1") assertContains(t, yaml, "kind: RBACProfile") assertContains(t, yaml, "name: rbac-cnpg") assertContains(t, yaml, "namespace: seam-tenant-ccs-dev") @@ -297,7 +297,7 @@ func TestDescriptorScaffold_ProducesReviewAnnotatedYAML(t *testing.T) { // Must have human-review header. assertContains(t, scaffold, "HUMAN REVIEW REQUIRED") - assertContains(t, scaffold, "apiVersion: security.ontai.dev/v1alpha1") + assertContains(t, scaffold, "apiVersion: guardian.ontai.dev/v1alpha1") assertContains(t, scaffold, "kind: RBACProfile") assertContains(t, scaffold, "name: rbac-my-custom-operator") assertContains(t, scaffold, "namespace: seam-tenant-management") From 2905a4a277cb7e25ed1ac7348b7e29003830f1d1 Mon Sep 17 00:00:00 2001 From: ontave Date: Wed, 13 May 2026 07:38:36 +0200 Subject: [PATCH 11/13] migration(phase-6): fix Dockerfiles for seam/dispatcher renames + conductor-sdk - Dockerfile.compiler/execute/agent: seam-core/ -> seam/, wrapper/ -> dispatcher/ - Add COPY conductor-sdk/ and seam-sdk/ to all three builder stages - cmd/conductor/main.go: fix stale "seam-core scheme" panic message to "seam" - docs/conductor-schema.md: update InfrastructureRunnerConfig -> RunnerConfig, infrastructure.ontai.dev -> seam.ontai.dev throughout Steps 6.1, 6.3, 6.4 were already complete (single binary entrypoint at cmd/conductor/, single build target, go.mod already imports conductor-sdk). --- Dockerfile.agent | 6 ++++-- Dockerfile.compiler | 6 ++++-- Dockerfile.execute | 6 ++++-- cmd/conductor/main.go | 2 +- docs/conductor-schema.md | 16 ++++++++-------- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Dockerfile.agent b/Dockerfile.agent index 0a7e554..4da457d 100644 --- a/Dockerfile.agent +++ b/Dockerfile.agent @@ -12,10 +12,12 @@ FROM golang:1.25 AS builder WORKDIR /build COPY conductor/ . +COPY conductor-sdk/ ../conductor-sdk/ +COPY dispatcher/ ../dispatcher/ COPY guardian/ ../guardian/ COPY platform/ ../platform/ -COPY seam-core/ ../seam-core/ -COPY wrapper/ ../wrapper/ +COPY seam/ ../seam/ +COPY seam-sdk/ ../seam-sdk/ RUN CGO_ENABLED=0 GOOS=linux go build \ -trimpath \ -ldflags="-s -w" \ diff --git a/Dockerfile.compiler b/Dockerfile.compiler index 2e503a6..66d5bb7 100644 --- a/Dockerfile.compiler +++ b/Dockerfile.compiler @@ -9,10 +9,12 @@ FROM golang:1.25 AS builder WORKDIR /build COPY conductor/ . +COPY conductor-sdk/ ../conductor-sdk/ +COPY dispatcher/ ../dispatcher/ COPY guardian/ ../guardian/ COPY platform/ ../platform/ -COPY seam-core/ ../seam-core/ -COPY wrapper/ ../wrapper/ +COPY seam/ ../seam/ +COPY seam-sdk/ ../seam-sdk/ RUN CGO_ENABLED=0 GOOS=linux go build \ -trimpath \ -ldflags="-s -w" \ diff --git a/Dockerfile.execute b/Dockerfile.execute index c4703f0..8adff82 100644 --- a/Dockerfile.execute +++ b/Dockerfile.execute @@ -14,10 +14,12 @@ ARG KUSTOMIZE_VERSION=5.6.0 FROM golang:1.25 AS builder WORKDIR /build COPY conductor/ . +COPY conductor-sdk/ ../conductor-sdk/ +COPY dispatcher/ ../dispatcher/ COPY guardian/ ../guardian/ COPY platform/ ../platform/ -COPY seam-core/ ../seam-core/ -COPY wrapper/ ../wrapper/ +COPY seam/ ../seam/ +COPY seam-sdk/ ../seam-sdk/ RUN CGO_ENABLED=0 GOOS=linux go build \ -trimpath \ -ldflags="-s -w" \ diff --git a/cmd/conductor/main.go b/cmd/conductor/main.go index 20e55e9..4b9587d 100644 --- a/cmd/conductor/main.go +++ b/cmd/conductor/main.go @@ -49,7 +49,7 @@ func init() { panic("conductor: failed to register platform scheme: " + err.Error()) } if err := seamv1alpha1.AddToScheme(seamScheme); err != nil { - panic("conductor: failed to register seam-core scheme: " + err.Error()) + panic("conductor: failed to register seam scheme: " + err.Error()) } if err := dispatcherv1alpha1.AddToScheme(seamScheme); err != nil { panic("conductor: failed to register dispatcher scheme: " + err.Error()) diff --git a/docs/conductor-schema.md b/docs/conductor-schema.md index 2893053..c12f512 100644 --- a/docs/conductor-schema.md +++ b/docs/conductor-schema.md @@ -1,5 +1,5 @@ # conductor-schema -> CRD types: infrastructure.ontai.dev/v1alpha1 (InfrastructureRunnerConfig, InfrastructurePackReceipt) -- schema owned by seam-core (Decision G) +> CRD types: seam.ontai.dev/v1alpha1 (RunnerConfig, InfrastructurePackReceipt) -- schema owned by seam-core (Decision G) > Runtime behavior and capability schema: this document > Repository: conductor (produces both Compiler and Conductor binaries) > All agents absorb this document. This schema governs both binaries. @@ -100,13 +100,13 @@ generation logic, and capability manifest structure. **Library exports:** - RunnerConfig and PackReceipt generation logic (imports types from seam-core/api/v1alpha1; does not define schema) -- GenerateFromTalosCluster(spec) → InfrastructureRunnerConfig -- GenerateFromPackBuild(spec) → InfrastructureRunnerConfig +- GenerateFromTalosCluster(spec) → RunnerConfig +- GenerateFromPackBuild(spec) → RunnerConfig - CapabilityManifest types - OperationResult types - Job spec builder functions -When a new named capability is added to Conductor, the shared library is updated. +When a new named capability is added to Conductor, the conductor-sdk is updated. Operators get the new capability by updating their library dependency version. No operator logic changes are required for new capabilities. @@ -114,9 +114,9 @@ operator logic changes are required for new capabilities. ## 5. CRDs -### InfrastructureRunnerConfig +### RunnerConfig -Kind: InfrastructureRunnerConfig. API group: infrastructure.ontai.dev/v1alpha1. Schema owned by seam-core (Decision G). +Kind: RunnerConfig. API group: seam.ontai.dev/v1alpha1. Schema owned by seam-core (Decision G). Scope: Namespaced - ont-system (management cluster), tenant-{cluster-name} (targets). Short name: rc @@ -318,7 +318,7 @@ The management cluster bootstrap sequence is owned exclusively by the Compiler i Forms the management cluster itself. Reads TalosCluster spec and human-provided machineconfig inputs. Validates spec against platform-schema.md rules. SOPS-encrypts talos-secret, machineconfigs, and talosconfig using the admin's age key. Writes encrypted files to output path. Produces bootstrap CRs (TalosCluster in mode: bootstrap) as YAML. No cluster connection required. Compiler never applies resources - the GitOps pipeline or operator's kubectl applies the output. **Step 2 - `compiler launch`** -Installs all Seam CRDs onto the management cluster. Reads the CRD manifest set for all Seam API groups (infrastructure.ontai.dev, guardian.ontai.dev, platform.ontai.dev). The old groups runner.ontai.dev and infra.ontai.dev are superseded by infrastructure.ontai.dev as of Phase 2B (2026-04-25). Produces a CRD manifest YAML bundle ready for GitOps application. No cluster connection required. Compiler never applies resources. +Installs all Seam CRDs onto the management cluster. Reads the CRD manifest set for all Seam API groups (seam.ontai.dev, guardian.ontai.dev, platform.ontai.dev). The old groups runner.ontai.dev and infra.ontai.dev are superseded by seam.ontai.dev as of Phase 2B (2026-04-25). Produces a CRD manifest YAML bundle ready for GitOps application. No cluster connection required. Compiler never applies resources. **Step 3 - `compiler enable`** Produces the complete Seam operator deployment manifest bundle as YAML output. The bundle @@ -901,7 +901,7 @@ new message types requires a Platform Governor directive before implementation. --- *Conductor behavioral schema - conductor repository* -*CRD type schema authority: seam-core (infrastructure.ontai.dev). Supersedes runner.ontai.dev. Decision G, Phase 2B 2026-04-25.* +*CRD type schema authority: seam-core (seam.ontai.dev). Supersedes runner.ontai.dev. Decision G, Phase 2B 2026-04-25.* *Amendments appended below with date and rationale.* 2026-03-30 - Two-binary model adopted. Compiler confined to compile mode (debian). From b245114aaf5bd9b7ed37a5deabccb3a316ad386d Mon Sep 17 00:00:00 2001 From: ontave Date: Wed, 13 May 2026 08:47:30 +0200 Subject: [PATCH 12/13] docs: session/25m -- Phase 8.5 conductor documentation rewrite Fresh documentation from current codebase. runner.ontai.dev claim removed (conductor owns no API group). pkg/runnerlib replaced with conductor-sdk reference. seam-core replaced with seam. All three image modes documented accurately. Capability table rebuilt from conductor-sdk/runnerlib/constants.go. --- CLAUDE.md | 62 ++- README.md | 188 ++++--- docs/conductor-schema.md | 1137 +++++++++++--------------------------- 3 files changed, 457 insertions(+), 930 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ae2dced..a628029 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,31 +2,55 @@ > Read ~/ontai/CLAUDE.md first. The constraints below extend the root constitutional document. ### Schema authority -Primary: docs/conductor-schema.md (conductor behavioral specification: modes, capabilities, job protocol, signing) -CRD schema authority: ~/ontai/seam-core/docs/seam-core-schema.md (Decision G: seam-core owns InfrastructureRunnerConfig, InfrastructurePackReceipt, and all cross-operator CRD type definitions) -Supporting: all operator schema docs -- conductor implements capabilities for every domain. -Read ALL schema documents before any capability implementation work begins. + +Primary: `docs/conductor-schema.md` -- conductor behavioral specification: modes, capabilities, job protocol, signing, compiler subcommands, role declaration contract. + +CRD schema authority: `~/ontai/seam/docs/seam-schema.md` -- seam owns RunnerConfig, DriftSignal, LineageRecord, SeamMembership, and all cross-operator CRD type definitions under `seam.ontai.dev/v1alpha1`. CRD changes require a seam PR first. + +Supporting: all operator schema docs. conductor implements capabilities for every domain. Read all schema documents before any capability implementation work begins. + +--- ### Invariants -CR-INV-001 -- Three-mode boundary is absolute: compile, executor, agent. No mode bleed. Compile-mode clients raise a fatal error if invoked in executor or agent mode. (root INV-014) -CR-INV-002 -- talos goclient is executor and agent mode only. Never in compile mode. (root INV-013) -CR-INV-003 -- The shared library (pkg/runnerlib) provides generation logic and job-spec builders. CRD type definitions are imported from seam-core/api/v1alpha1, not defined in runnerlib. Breaking changes to generation logic or job-spec builders require a major version bump and operator dependency updates before the runner release is cut. Breaking changes to CRD types require a seam-core PR first (Decision G, Decision 11). + +CR-INV-001 -- Three-mode boundary is absolute: compile, execute, agent. No mode bleed. Compile-mode clients (Helm goclient, Kustomize goclient, SOPS handler) are excluded from the Conductor binary at build time via Go build tags. Invoking them in execute or agent mode is a programming error and causes InvariantViolation. + +CR-INV-002 -- talos goclient is execute and agent mode only. Never in compile mode. (root INV-013) + +CR-INV-003 -- CRD type definitions are imported from `seam/api/v1alpha1`, not defined locally. The shared capability library (conductor-sdk) provides execution contracts and capability constants; it is not the schema authority. Breaking changes to CRD types require a seam PR first. Breaking changes to capability parameter schemas require documenting impact before proceeding. + CR-INV-004 -- Named capabilities are additive. New capabilities never change existing capability behavior. Existing capability parameter schemas are never modified in a breaking way. -CR-INV-005 -- The capability manifest in RunnerConfig status is self-declared by the agent on startup. Operators never hardcode capability availability assumptions. + +CR-INV-005 -- The capability manifest in RunnerConfig status is self-declared by the agent on startup. Operators never hardcode capability availability assumptions. Operators raise CapabilityUnavailable and wait when a required capability is absent from the status manifest. + CR-INV-006 -- Leader election in agent mode is not optional. One leader writes to RunnerConfig status and receipt CRs. All other replicas are standby. -INV-014 -- Helm goclient and kustomize goclient are compile mode only. They exist exclusively in the Compiler binary. Excluded from Conductor at build time via Go build tags. -INV-023 -- Conductor binary supports only execute and agent modes. Compile mode attempted on Conductor causes an immediate InvariantViolation structured exit before any other initialization proceeds. -INV-024 -- Compiler and Conductor are always released together from the same source commit and carry the same version tag. Deploying mismatched versions against the same cluster is unsupported and undefined behavior. -INV-026 -- PackInstance signing and PermissionSnapshot signing are performed exclusively by the management cluster Conductor in agent mode. Target cluster Conductor verifies but never signs. Verification failure blocks receipt acknowledgement. + +INV-014 -- Helm goclient and kustomize goclient are compile mode only. They exist exclusively in the Compiler binary. Excluded from Conductor at build time via Go build tags. (root INV-014) + +INV-023 (conductor-specific) -- Conductor binary supports only execute and agent modes. Compile mode attempted on Conductor causes an immediate InvariantViolation structured exit before any other initialization proceeds. + +INV-026 (conductor-specific) -- PackInstance signing and PermissionSnapshot signing are performed exclusively by the management cluster Conductor in agent mode. Target cluster Conductor verifies but never signs. Verification failure blocks receipt acknowledgement. + +--- ### Image and binary constraints + Three images from this repo (Decision 12 in root CLAUDE.md): -- Compiler: debian-slim. compile mode only. Never deployed to cluster. -- Conductor execute: debian-slim. Kueue Job pods on management cluster only. -- Conductor agent: distroless. Deployed to ont-system on every cluster. -Execute image must never be distroless. Agent image must never be debian-slim. + +- Compiler (`Dockerfile.compiler`): debian-slim. Compile mode only. Never deployed to any cluster. +- Conductor execute (`Dockerfile.execute`): debian-slim. Kueue Job pods on management cluster only. +- Conductor agent (`Dockerfile.agent`): distroless. Deployed to ont-system on every cluster. + +Execute image must never be distroless. Agent image must never be debian-slim. These constraints are permanent and locked. + +Lab image tag for all three: `:dev`. Custom per-build tags are never committed (INV-023). + +--- ### Session protocol additions -Step 4a -- Read conductor-design.md in this repository. -Step 4b -- Before implementing a new named capability, verify it is not a duplicate. Check the capability table in docs/conductor-schema.md. -Step 4c -- Before modifying the shared library, assess operator impact. Document breaking vs non-breaking change in PROGRESS.md before proceeding. + +Step 4a -- Read `conductor-design.md` in this repository before any implementation work. + +Step 4b -- Before implementing a new named capability, verify it is not a duplicate. Check the capability table in `docs/conductor-schema.md`. Verify the constant does not already exist in `conductor-sdk/runnerlib/constants.go`. + +Step 4c -- Before modifying the conductor-sdk shared library, assess operator impact. Document breaking vs non-breaking change in PROGRESS.md before proceeding. Breaking changes require all operator dependency updates before any conductor release is cut. diff --git a/README.md b/README.md index 3e8c01f..42aaa8f 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,150 @@ # conductor -**Seam Platform Intelligence** -**API Group:** `runner.ontai.dev` -**Images:** `registry.ontai.dev/ontai-dev/compiler:` (Compiler) and `registry.ontai.dev/ontai-dev/conductor:` (Conductor) +conductor is the platform intelligence layer for the Seam Platform. It produces two binaries from a single Go module at `github.com/ontai-dev/conductor`. + +Status: Alpha. GitHub issues: https://github.com/ontai-dev/conductor/issues --- -## What this repository is +## Two-Binary Model + +| Binary | Entry point | Role | +|--------|-------------|------| +| Compiler | `cmd/compiler` | Compile-time intelligence. Short-lived CLI tool. Never deployed. | +| Conductor | `cmd/conductor` | Runtime intelligence. Long-lived agent and short-lived executor. | + +The Compiler produces manifests for human review and GitOps pipeline application. It never applies resources to any cluster. The Conductor binary runs in two distinct modes on clusters: as a long-lived Deployment (agent mode) and as short-lived Kueue Job pods (execute mode). + +--- -`conductor` builds two binaries from one Go module: Compiler and Conductor. +## Mode Table -**Compiler** is compile-time intelligence. It runs as a short-lived CLI tool or in -a compile-phase pipeline on the management cluster before Kueue is operational. It -owns the bootstrap launch sequence, enable phase operator installation, pack -compilation, Helm rendering, Kustomize resolution, and SOPS encryption. Compiler -is never deployed as a Deployment and never runs on any cluster. +| Mode | Binary | Invocation | Duration | Image | Cluster scope | +|------|--------|------------|----------|-------|---------------| +| compile | Compiler | Direct CLI | Short-lived | Debian-slim | Never deployed | +| execute | Conductor | Kueue Job pod | Short-lived | Debian-slim | Management cluster only | +| agent | Conductor | Deployment in ont-system | Long-lived | Distroless | Management + all targets | -**Conductor** is runtime intelligence. It runs as a long-running Deployment in -`ont-system` on the management cluster and on every target cluster. It is also -stamped into Kueue Job specs for all named operational capabilities. +Compile mode attempted on the Conductor binary causes immediate InvariantViolation exit. The Conductor binary supports only execute and agent modes. + +Execute-mode Jobs run exclusively on the management cluster. All cluster operations reach target clusters remotely via mounted kubeconfig and talosconfig Secrets. No capability Job runs on a target cluster. --- -## Binary modes +## Compiler Responsibilities + +The Compiler is a CR compiler: it reads human-authored spec files, validates them against platform schema rules, and produces Kubernetes CR YAML ready to apply. All subcommands are compile-only; the Compiler never applies, patches, or deletes any resource. + +**Subcommands:** + +| Subcommand | Purpose | +|------------|---------| +| `compiler bootstrap` | Compile a cluster declaration into Talos machine config Secrets and bootstrap CRs | +| `compiler launch` | Produce the CRD bundle for management cluster bootstrap | +| `compiler enable` | Produce the phased operator deployment manifest bundle | +| `compiler packbuild` | Compile a PackBuild spec into a ClusterPack CR | +| `compiler maintenance` | Compile a MaintenanceBundle CR with pre-resolved scheduling context | +| `compiler component` | Produce RBACProfile CR YAML from the embedded catalog or a descriptor | +| `compiler domain` | Reserved, not yet implemented | -| Mode | Binary | Invocation | Duration | Image | -|---|---|---|---|---| -| compile | Compiler | Direct CLI invocation | Short-lived | Debian | -| execute | Conductor | Kueue Job pod | Short-lived | Distroless | -| agent | Conductor | Deployment in ont-system | Long-lived | Distroless | +Management cluster bootstrap is a three-step Compiler-driven sequence: `bootstrap` (cluster formation), `launch` (CRD installation), `enable` (operator deployment bundle). Platform operator has no involvement in management cluster bootstrap. -Compile mode attempted on the Conductor binary causes an immediate `InvariantViolation` -structured exit before any other initialization proceeds. +Helm rendering, Kustomize overlay resolution, and SOPS encryption live exclusively in the Compiler. These clients are excluded from the Conductor binary at build time via Go build tags. --- -## Management cluster Conductor responsibilities - -- Declares capability manifest to `RunnerConfig` status on startup. -- Implements leader election. -- Maintains PackInstance signing: signs `PackInstance` CRs after wrapper confirms - `ClusterPack` registration. -- Maintains PermissionSnapshot signing: signs `PermissionSnapshot` CRs after guardian - generates them. -- Publishes signed artifacts for target cluster Conductor verification. - -## Target cluster Conductor responsibilities - -- Declares capability manifest to `RunnerConfig` status on startup. -- Implements leader election. -- Maintains `PackReceipt`: verifies signed `PackInstance` from management cluster, - records local drift status. -- Maintains `PermissionSnapshotReceipt`: verifies signed `PermissionSnapshot` from - management cluster, acknowledges delivery. -- Runs local admission webhook: intercepts all RBAC resources, enforces - `ontai.dev/rbac-owner=guardian` annotation. -- Serves local PermissionService gRPC endpoint for authorization decisions. -- Runs drift detection loop: compares expected pack state to live cluster state. +## Management Cluster Conductor Responsibilities + +Conductor in agent mode on the management cluster (stamped `CONDUCTOR_ROLE=management` by `compiler enable`) runs the full agent startup sequence plus: + +- PackInstance signing loop: signs ClusterPack registrations with the platform signing key. +- PermissionSnapshot signing loop: signs PermissionSnapshot CRs produced by Guardian. +- Federation channel: exposes a persistent bidirectional gRPC stream for tenant Conductor connections on the federation port (default 9443). +- Step sequencer: as execute-mode Job, materialises one Kueue Job per RunnerConfig step in declared order, harvests ConfigMap results, and writes StepResults to RunnerConfig status. --- -## Shared library +## Target Cluster Conductor Responsibilities + +Conductor in agent mode on each target cluster (stamped `role=tenant` by Platform operator at Deployment creation) runs: -`pkg/runnerlib` is owned by this repository. All operators and both binaries import -it. It defines the `RunnerConfig` schema, generation logic, and capability manifest -structure. It is the single source of truth for the RunnerConfig contract. +- Capability manifest self-declaration: writes the supported capability list to RunnerConfig status on startup. +- PackReceipt reconciliation: verifies management signatures before acknowledging receipt. +- PermissionSnapshotReceipt reconciliation: verifies guardian signatures before acknowledging. +- Admission webhook: enforces RBAC ownership annotations (audit mode until bootstrap sweep completes, strict mode after). +- PermissionService gRPC server: serves authorization decisions cluster-locally. +- Drift detection loop: detects drift for TalosCluster and PackDelivery CRs. +- PermissionSnapshot pull loop: pulls snapshots from the management cluster. +- TenantBootstrapSweep: stamps governance annotations on existing RBAC resources and creates PermissionSet/RBACPolicy/RBACProfile for known third-party components. --- -## Building +## Shared Capability Library -```sh -# Compiler binary -go build ./cmd/compiler +Named capability constants and execution contracts are defined in the companion library at `github.com/ontai-dev/conductor-sdk` (package `runnerlib`). This is not a package in this repository. All operators import conductor-sdk. Conductor imports it at runtime to resolve capability names to handlers. -# Conductor binary -go build ./cmd/conductor -``` +--- -Container images: +## API Groups -```sh -docker build -f Dockerfile.compiler -t registry.ontai.dev/ontai-dev/compiler: . -docker build -f Dockerfile.agent -t registry.ontai.dev/ontai-dev/conductor: . -``` +Conductor does not own any API group. CRDs it reads and writes are owned by: + +- `seam.ontai.dev` (seam): RunnerConfig, DriftSignal, LineageRecord, SeamMembership. +- `seam.ontai.dev` (dispatcher): PackDelivery, PackExecution, PackInstalled, PackReceipt. +- `seam.ontai.dev` (platform): TalosCluster and day-2 CRDs. +- `guardian.ontai.dev` (guardian): PermissionSet, RBACPolicy, RBACProfile, PermissionSnapshot. --- -## Testing +## Build -```sh -go test ./... +**Compiler binary:** + +``` +go build -tags compiler ./cmd/compiler ``` ---- +**Conductor binary:** -## Schema and design reference +``` +go build -tags conductor ./cmd/conductor +``` -- `docs/conductor-schema.md` - RunnerConfig contract, capability manifest, operational capabilities -- `docs/decisions/ontar.md` - Architecture Decision Records for key design choices -- `conductor-design.md` - Implementation architecture and design +**Docker images:** + +``` +# Compiler image (debian-slim, never deployed) +docker build -f Dockerfile.compiler -t 10.20.0.1:5000/ontai-dev/compiler:dev . + +# Conductor agent image (distroless, agent mode - every cluster) +docker build -f Dockerfile.agent -t 10.20.0.1:5000/ontai-dev/conductor:dev . + +# Conductor execute image (debian-slim, execute mode - Kueue Jobs on management cluster) +docker build -f Dockerfile.execute -t 10.20.0.1:5000/ontai-dev/conductor-exec:dev . +``` + +Lab builds are pushed only to `10.20.0.1:5000/ontai-dev/`. Lab tags never enter the public registry. + +The execute-mode image tag encodes Talos API compatibility: `conductor-exec:`. A cluster at Talos v1.9.3 requires a conductor-exec image tagged v1.9.3. Updating RunnerConfig agentImage to the new Conductor tag is a prerequisite to any Talos version upgrade. --- -## Status +## Testing -Alpha. Deployed and tested on management cluster (ccs-mgmt). -Tenant cluster onboarding is not yet verified end to end. -See [docs/conductor-schema.md](./docs/conductor-schema.md) -for current capability and known gaps. +``` +go test ./... +``` -CRDs are deployed and reconciling on the live management cluster. -The schema specification is published at: -https://schema.ontai.dev/v1alpha1/ +Unit tests are required for all new functionality. End-to-end tests live under `test/e2e/` and require `MGMT_KUBECONFIG` to be set. All e2e specs skip automatically when `MGMT_KUBECONFIG` is absent. -## Contributing +--- -Read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a pull -request. Every new reconciliation behavior requires a written -specification and senior engineer sign-off before any code is -written. +## Schema Reference -File issues at https://github.com/ontai-dev/conductor/issues. -For security issues contact security@ontai.dev directly. +- `docs/conductor-schema.md`: behavioral specification for modes, capabilities, job protocol, signing, and compiler subcommands. +- `conductor-design.md`: design rationale and architectural decisions. +- `~/ontai/seam/docs/seam-schema.md`: CRD schema authority for RunnerConfig, DriftSignal, and all cross-operator types under `seam.ontai.dev`. --- -*conductor - Seam Platform Intelligence (Compiler + Conductor)* -*Apache License, Version 2.0* +conductor - Seam Platform Intelligence (Compiler and Conductor) / Apache License, Version 2.0 diff --git a/docs/conductor-schema.md b/docs/conductor-schema.md index c12f512..2ec8831 100644 --- a/docs/conductor-schema.md +++ b/docs/conductor-schema.md @@ -1,1011 +1,494 @@ # conductor-schema -> CRD types: seam.ontai.dev/v1alpha1 (RunnerConfig, InfrastructurePackReceipt) -- schema owned by seam-core (Decision G) -> Runtime behavior and capability schema: this document -> Repository: conductor (produces both Compiler and Conductor binaries) -> All agents absorb this document. This schema governs both binaries. +> Runtime behavior and capability schema for the conductor repository. +> CRD type definitions: seam.ontai.dev/v1alpha1 -- schema authority is ~/ontai/seam/docs/seam-schema.md. +> Repository: github.com/ontai-dev/conductor (produces Compiler and Conductor binaries). --- ## 1. Central Principle -The conductor repository is the platform intelligence. Operators are thin reconcilers. -All execution logic - compile-time and runtime - lives in the two binaries built from -this repository. +Operators are thin reconcilers. All execution logic -- compile-time and runtime -- lives in the two binaries built from this repository. -**Compiler** is the compile-time binary. It is a short-lived tool invoked by humans -or by the bootstrap pipeline. It never runs as a Deployment on any cluster. +**Compiler** is the compile-time binary. It is a short-lived tool invoked by humans or the bootstrap pipeline on the operator workstation or in a CI/CD pipeline. It never runs as a Deployment on any cluster. -**Conductor** is the runtime binary. It runs as a long-lived Deployment in ont-system -on the management cluster and every target cluster. It also runs as short-lived Kueue -Jobs on the management cluster for all named operational capabilities. It is the only -Seam binary deployed to any cluster. It is distroless. +**Conductor** is the runtime binary. It runs as a long-lived Deployment in ont-system on the management cluster and every target cluster (agent mode). It also runs as short-lived Kueue Jobs on the management cluster for all named operational capabilities (execute mode). -Human-facing compile operations are invocations of the Compiler binary. Cluster management operations are performed by submitting Seam CRs which operators translate into Conductor Jobs. +Human-facing compile operations are invocations of the Compiler binary. Cluster management operations are performed by submitting Seam CRs. Operators translate those CRs into RunnerConfig objects. Conductor executes RunnerConfig steps. --- -## 2. Binary Architecture +## 2. Mode Isolation -Both binaries are built from the same Go module at github.com/ontai-dev/conductor. -They share pkg/runnerlib and all internal modules except the compile-mode client -wrappers, which are excluded from Conductor at build time via Go build tags. +Three modes exist. No others. -**Shared Go clients (both binaries):** -- kube goclient: all Kubernetes API interactions. All modes. +| Mode | Binary | Invocation | Duration | Image | Cluster scope | +|------|--------|------------|----------|-------|---------------| +| compile | Compiler | Direct CLI | Short-lived | Debian-slim | Never deployed | +| execute | Conductor | Kueue Job pod | Short-lived | Debian-slim | Management cluster only | +| agent | Conductor | Deployment in ont-system | Long-lived | Distroless | Management + all targets | -**Compiler exclusive clients (compile mode only, excluded from Conductor build):** -- helm goclient: Helm chart rendering. INV-014. -- kustomize goclient: overlay resolution. INV-014. -- SOPS handler: age key encryption of cluster secrets. +**CR-INV-001:** The three-mode boundary is absolute. Compile-mode clients (Helm goclient, Kustomize goclient, SOPS handler) are excluded from the Conductor binary at build time via Go build tags. Invoking them in execute or agent mode is a programming error and causes InvariantViolation. -**Conductor exclusive clients (execute and agent modes only):** -- talos goclient: all Talos API interactions. INV-013. - Pure Go gRPC library. No talosctl binary. No shell invocation. +**CR-INV-002:** talos goclient is execute and agent mode only. Never in compile mode. -**No system binary invocations in any mode.** All clients are pure Go library -integrations. No kubectl, no talosctl, no helm binary, no kustomize binary, no -shell invocations at any point in execute or agent mode. Compile mode uses Go -library wrappers only - no shell invocations. +Compile mode attempted on the Conductor binary causes immediate InvariantViolation exit before any other initialization proceeds. -**Three modes - no other modes exist:** +Execute-mode Jobs run exclusively on the management cluster. No capability Job runs on a target cluster. All capabilities reach target clusters via mounted kubeconfig and talosconfig Secrets. -| Mode | Binary | Invocation | Duration | Image | Cluster Scope | -|----------|-----------|-------------------------|-------------|-------------|---------------------------| -| compile | Compiler | Direct CLI invocation | Short-lived | Debian | Never deployed to cluster | -| execute | Conductor | Kueue Job pod | Short-lived | Distroless | Management cluster only | -| agent | Conductor | Deployment in ont-system| Long-lived | Distroless | Management + all targets | +No system binary invocations in any mode. All clients are pure Go library integrations. No kubectl, no talosctl, no helm binary, no kustomize binary, no shell invocations at any point. -Execute mode Jobs run exclusively on the management cluster. Target clusters never -run execute-mode Jobs. All cluster operations reach target clusters remotely via -mounted kubeconfig and talosconfig Secrets. +--- -Mode is determined by startup flag and RunnerConfig mounted at startup for execute -and agent modes. Compile mode reads input paths from CLI flags. +## 3. Image Constraints -Invoking helm goclient or kustomize goclient in execute or agent mode is a -programming error; the Conductor binary excludes these clients at build time. -Any attempt causes InvariantViolation and immediate structured exit. +Three images from this repository. -Invoking talos goclient in compile mode is a programming error. INV-013. +| Image | Dockerfile | Base | Modes | Deployment target | +|-------|-----------|------|-------|-------------------| +| Compiler | Dockerfile.compiler | debian:12-slim | compile | Never deployed | +| Conductor execute | Dockerfile.execute | debian:12-slim | execute | Kueue Jobs, management cluster only | +| Conductor agent | Dockerfile.agent | gcr.io/distroless/base:nonroot | agent | ont-system, every cluster | ---- +Execute image must never be distroless. Agent image must never be debian-slim. These constraints are permanent and locked. -## 3. Image Tag Convention and Release Pairing +The debian base is required for the Compiler because of: /etc/ssl/certs (Helm chart HTTPS pulls), SOPS age key operations (python3), and psql (CNPG health verification in enable phase). -**Stable releases:** -- `registry.ontai.dev/ontai-dev/compiler:` (compiler) -- `registry.ontai.dev/ontai-dev/conductor:` (agent) -- `registry.ontai.dev/ontai-dev/conductor-exec:v{talos-version}` (conductor-exec/ont-runner) +gcr.io/distroless/base (not static) is required for the Conductor agent because the talos goclient and gRPC stack require libc for certain TLS and crypto operations. -The talosVersion component declares Talos API compatibility - not cosmetic. A cluster -at Talos v1.9.3 must use a Conductor-exec tagged v1.9.3. +Lab image tag: `:dev` for all three. Custom per-build tags are never committed (root INV-023). The execute-mode image tag in production encodes Talos API compatibility: `conductor-exec:`. A cluster at Talos v1.9.3 requires a conductor-exec image tagged v1.9.3. -**Development:** +--- -**Lab builds:** pushed only to 10.20.0.1:5000/ontai-dev/compiler and -10.20.0.1:5000/ontai-dev/conductor. Lab tags never appear in the public registry. -INV-011. +## 4. RunnerConfig Contract -Updating RunnerConfig agentImage to the new Conductor tag is a prerequisite to any -Talos version upgrade. The upgrade reconciler checks this gate before submitting any -upgrade Job. +RunnerConfig is the operational contract between operators and Conductor for a specific cluster or pack delivery. It is defined under `seam.ontai.dev/v1alpha1`. Schema authority is `~/ontai/seam/docs/seam-schema.md`. This section covers the behavioral contract from Conductor's perspective. ---- +**CR-INV-003:** CRD type definitions are imported from `seam/api/v1alpha1`, not defined locally. Breaking changes to CRD types require a seam PR first. -## 4. Shared Runner Library +**INV-009:** RunnerConfig is operator-generated at runtime. Never human-authored. Never a compile-time artifact. -The shared runner library is owned by the conductor repository and imported by all -operators and by both binaries. It is the single source of RunnerConfig schema, -generation logic, and capability manifest structure. +**Scope:** Namespaced. ont-system on the management cluster; tenant-{cluster-name} on target clusters. -**Library exports:** -- RunnerConfig and PackReceipt generation logic (imports types from seam-core/api/v1alpha1; does not define schema) -- GenerateFromTalosCluster(spec) → RunnerConfig -- GenerateFromPackBuild(spec) → RunnerConfig -- CapabilityManifest types -- OperationResult types -- Job spec builder functions +**Key spec fields consumed by Conductor:** -When a new named capability is added to Conductor, the conductor-sdk is updated. -Operators get the new capability by updating their library dependency version. No -operator logic changes are required for new capabilities. +- `clusterRef`: cluster identity this RunnerConfig governs. +- `agentImage`: fully qualified Conductor agent image including tag. Single source of truth for which Conductor version governs this cluster. +- `steps`: list of step declarations. Each step carries name, capability, parameters, optional dependsOn, and haltOnFailure. +- `maintenanceTargetNodes`: list of node names that are the subject of the operation. Populated by the initiating operator at RunnerConfig creation. Used by execute mode for node affinity exclusion when selfOperation is true. +- `operatorLeaderNode`: the node currently hosting the leader pod of the initiating operator. Resolved at creation time via the Kubernetes downward API. +- `selfOperation`: boolean. True when the Job execution cluster and the target cluster are the same (management cluster self-operations). False for all tenant-targeted operations. Execute mode applies NotIn node affinity constraints only when selfOperation is true. ---- +**Status fields populated by Conductor (agent mode):** -## 5. CRDs - -### RunnerConfig - -Kind: RunnerConfig. API group: seam.ontai.dev/v1alpha1. Schema owned by seam-core (Decision G). -Scope: Namespaced - ont-system (management cluster), tenant-{cluster-name} (targets). -Short name: rc - -The live operational contract between operators and Conductor for a specific cluster -or pack. Operator-generated at runtime using the shared library. Never human-authored. -Never a compile-time artifact. - -Key spec fields: -- clusterRef: cluster identity this RunnerConfig governs. -- agentImage: fully qualified Conductor image including tag. Single source of truth - for which Conductor version handles this cluster's Deployments and Jobs. -- phases: applicable phases for this cluster (launch, enable for management; - launch, bootstrap for tenant clusters). -- operationalHistory: append-only record of every configuration change applied. - Never deleted, only superseded by newer entries for the same concern. -- maintenanceTargetNodes: list of node names that are the subject of the operation. - Populated by the initiating operator at RunnerConfig creation time. Used by - Conductor execute mode for node affinity exclusion when selfOperation is true. -- operatorLeaderNode: the node currently hosting the leader pod of the initiating - operator. Resolved at creation time via the Kubernetes downward API. Used by - Conductor execute mode for node affinity exclusion when selfOperation is true. -- selfOperation: boolean - true when the Job's execution cluster and the target - cluster are the same (management cluster self-operations). false for all - tenant-targeted operations. Conductor execute mode reads this field to determine - whether to apply NotIn node affinity constraints. When false, exclusion logic - is skipped entirely. - -Per-phase parameter sections: -- launch: vmConfig, talosInstallerImage, networkConfig, bootstrapTimeout. -- enable: prerequisiteTimeout, operatorTimeout. Management cluster only. -- bootstrap: agentPackRef, agentNamespace, retryLimit. Tenant clusters only. - -Status fields (Conductor in agent mode populates): -- capabilities: the self-declared capability manifest. List of named capabilities - the current agentImage supports, each with version and parameter schema. - Operators read this before submitting Jobs. If capability absent: raise - CapabilityUnavailable on the operational CR and wait. -- agentVersion: the Conductor image version currently running as agent. -- agentLeader: the pod name currently holding the leader election lease. - -Status conditions: LaunchComplete, EnableComplete, BootstrapComplete, PhaseFailed, -CapabilityUnavailable. +- `capabilities`: self-declared capability manifest. List of named capabilities the current agentImage supports. Operators read this before submitting Jobs. If a capability is absent, the operator raises CapabilityUnavailable on the operational CR and waits. +- `agentVersion`: the Conductor image version currently running as agent. +- `agentLeader`: the pod name currently holding the leader election lease. +- `stepResults`: entries written by execute mode on step completion. ---- +Status conditions: LaunchComplete, EnableComplete, BootstrapComplete, PhaseFailed, CapabilityUnavailable, Completed, Failed. -### OperatorManifest +**Operator responsibility:** The initiating operator populates maintenanceTargetNodes, operatorLeaderNode, and selfOperation. operatorLeaderNode must be resolved fresh at each RunnerConfig creation -- never cached. -Scope: Namespaced - ont-system. Management cluster only. -Short name: om +--- -Declares one operator for Compiler's enable phase to install. Not used for -target cluster operator installation - that is ClusterPack delivery. +## 5. Capability Manifest Protocol -The bootstrap RBACPolicy that is available before Guardian installs must -authorize the enable phase operations declared here. +**CR-INV-005:** The capability manifest in RunnerConfig status is self-declared by the agent on startup. Operators never hardcode capability availability assumptions. -Key spec fields: -- operatorName: canonical name matching repository name. -- version: Helm chart version to install. -- chartRef: chart repository URL and chart name. -- installNamespace: namespace on management cluster. -- ownedCRDs: list of CRD names for post-installation verification. -- requiredCRDs: CRD names that must be registered before installation. -- requiredSecrets: Secrets that must exist before installation. -- upgradeStrategy: RollingUpgrade or RecreateOnChange. -- installOrder: integer defining enable phase sequence. Lower installs first. - Prerequisites use order 0. Guardian must always be order 1. This is enforced - by Compiler - any OperatorManifest with installOrder 1 that is not Guardian - is a programming error. +On agent startup, Conductor reads its own capability registry and writes the complete named capability list to RunnerConfig status. Each entry declares the capability name and parameter schema. -Status conditions: Installed, Healthy, Failed. +Operators read this manifest before submitting any RunnerConfig that references a capability. If the capability is absent from the manifest, the operator raises CapabilityUnavailable on the operational CR and enters a wait loop. When the capability appears (after a Conductor upgrade), the operator resumes. -Compiler processes OperatorManifests in ascending installOrder sequence. It does -not proceed to the next until the current reaches Healthy. This enforces Guardian -deployment priority and all subsequent dependency ordering. +New capabilities are added by updating conductor-sdk and the Conductor binary. Operators get the new capability by updating their conductor-sdk dependency version. No operator reconciler logic changes are required for new capabilities. --- ## 6. Named Capabilities -Named capabilities are Conductor's execution vocabulary in execute mode. Every -operator Job maps to exactly one named capability. New capabilities are added to -Conductor and declared in the shared library. No operator code changes are required. - -**Capability invocation contract:** -The operator stamps the capability name into the Job spec as an environment variable. -Conductor reads the capability name on startup in execute mode and executes the -corresponding implementation. Conductor exits with a structured OperationResult. - -**Current named capabilities:** - -| Capability | Owner operator | Triggering CRD | Description | -|---------------------|----------------|-------------------------------------------------------------|-------------------------------------------| -| bootstrap | Platform | TalosCluster | Full cluster bootstrap from seed nodes | -| talos-upgrade | Platform | UpgradePolicy (capi.enabled=false only) | CAPI-delegated for target clusters; direct runner Job for management cluster | -| kube-upgrade | Platform | UpgradePolicy (capi.enabled=false only) | CAPI-delegated for target clusters; direct runner Job for management cluster | -| stack-upgrade | Platform | UpgradePolicy (capi.enabled=false only) | CAPI-delegated for target clusters; direct runner Job for management cluster | -| node-patch | Platform | NodeMaintenance | Machine config patch to nodes | -| node-scale-up | Platform | NodeOperation (capi.enabled=false only) | CAPI-delegated for target clusters; direct runner Job for management cluster | -| node-decommission | Platform | NodeOperation (capi.enabled=false only) | CAPI-delegated for target clusters; direct runner Job for management cluster | -| node-reboot | Platform | NodeOperation (capi.enabled=false only) | CAPI-delegated for target clusters; direct runner Job for management cluster | -| etcd-backup | Platform | EtcdMaintenance | etcd snapshot + config export to S3 | -| etcd-defrag | Platform | EtcdMaintenance | etcd defrag and optional snapshot | -| etcd-restore | Platform | EtcdMaintenance | Disaster recovery from S3 snapshot | -| pki-rotate | Platform | PKIRotation | PKI certificate rotation | -| credential-rotate | Platform | NodeMaintenance | Service account key rotation | -| hardening-apply | Platform | NodeMaintenance | Apply HardeningProfile | -| cluster-reset | Platform | ClusterReset | Destructive factory reset with human gate | -| pack-compile | Wrapper | PackBuild spec file (compile mode - not a cluster CRD, not a Kueue Job) | Compiler compile mode: renders PackBuild inputs into ClusterPack OCI artifact | -| pack-deploy | Wrapper | PackExecution | Apply ClusterPack to target cluster | -| rbac-provision | Guardian | (agent-initiated) | Provision RBAC artifacts from snapshot | - -Note: talos-upgrade, kube-upgrade, stack-upgrade, node-scale-up, node-decommission, -and node-reboot are confirmed retained. They are not orphaned. The Triggering CRD -for each is UpgradePolicy or NodeOperation, active when TalosCluster -spec.capi.enabled=false only (management cluster direct path). For capi.enabled=true -clusters CAPI handles these operations natively. - -All capabilities except pack-compile run in execute mode on the management cluster as -Kueue Jobs using the distroless Conductor image. No capability runs on a target cluster -as a Job. All capabilities reach target clusters via mounted kubeconfig and talosconfig -Secrets. - -Note on pack-compile: pack-compile is the sole compile-mode entry in this table. It does -not follow the execute-mode Job pattern that all other capabilities follow. It appears -here for completeness - the capability name constant exists in the shared library so that -Wrapper and Compiler share a common vocabulary. However, pack-compile is never submitted -as a Kueue Job, never run by Conductor, and never runs on any cluster. - -**Future-proofing:** When a new named capability is added to Conductor, it is -declared in the shared library's capability manifest. Operators discover it via -RunnerConfig status. No operator requires changes. +Named capabilities are Conductor's execution vocabulary in execute mode. Every operator Job maps to exactly one named capability. The capability name is stamped into the Job spec as the `CAPABILITY` environment variable. Conductor reads this on startup in execute mode and executes the corresponding handler. ---- +**CR-INV-004:** Named capabilities are additive. New capabilities never change existing capability behavior. Existing capability parameter schemas are never modified in a breaking way. -## 7. Inter-Job State - Temporary PVC Protocol +Constants are defined in `github.com/ontai-dev/conductor-sdk/runnerlib/constants.go`. -For multi-step sequence capabilities (bootstrap, stack-upgrade, cluster-reset), -Conductor uses a temporary PVC for inter-step state transfer. +### Platform capabilities (cluster lifecycle and operations) -Protocol: -1. First Job creates a PVC named ont-{capability}-{cr-name}. Executes step. - Writes intermediate artifacts to PVC. Updates operational CR status. -2. Subsequent Jobs mount the same PVC. Read previous step outputs. Execute step. - Write outputs. Update status. -3. Final Job consumes all intermediate artifacts. Creates Kubernetes assets on - management cluster. Deletes PVC. Writes terminal OperationResult. +| Capability | Constant | Triggering CRD | Description | +|------------|---------|----------------|-------------| +| `bootstrap` | CapabilityBootstrap | TalosCluster | Full cluster bootstrap from seed nodes. Multi-step. Uses PVC protocol. | +| `talos-upgrade` | CapabilityTalosUpgrade | TalosUpgrade CR | Rolling Talos OS version upgrade. capi.enabled=false only. | +| `kube-upgrade` | CapabilityKubeUpgrade | TalosKubeUpgrade CR | Kubernetes version upgrade. capi.enabled=false only. | +| `stack-upgrade` | CapabilityStackUpgrade | TalosStackUpgrade CR | Coordinated Talos OS and Kubernetes upgrade. Multi-step. Uses PVC protocol. capi.enabled=false only. | +| `node-patch` | CapabilityNodePatch | TalosNodePatch CR | Machine config patch to one or more nodes. | +| `node-scale-up` | CapabilityNodeScaleUp | TalosNodeScaleUp CR | Provision and bootstrap additional nodes. capi.enabled=false only. | +| `node-decommission` | CapabilityNodeDecommission | TalosNodeDecommission CR | Cordon, drain, and remove a node. capi.enabled=false only. | +| `node-reboot` | CapabilityNodeReboot | TalosReboot CR | Reboot one or all cluster nodes. capi.enabled=false only. | +| `etcd-backup` | CapabilityEtcdBackup | TalosBackup CR | etcd snapshot and machine config export to S3. | +| `etcd-defrag` | CapabilityEtcdDefrag | EtcdMaintenance CR (operation=defrag) | etcd defragmentation on all members. | +| `etcd-restore` | CapabilityEtcdRestore | TalosRecovery CR | Disaster recovery from S3 etcd snapshot. | +| `pki-rotate` | CapabilityPKIRotate | TalosPKIRotation CR | PKI certificate rotation and talosconfig secret update. | +| `credential-rotate` | CapabilityCredentialRotate | TalosCredentialRotation CR | Service account signing key and OIDC credential rotation. | +| `hardening-apply` | CapabilityHardeningApply | TalosHardeningApply CR | Apply TalosHardeningProfile to a running cluster. | +| `cluster-reset` | CapabilityClusterReset | TalosClusterReset CR | Destructive factory reset with human approval gate. Multi-step. Uses PVC protocol. Requires `ontai.dev/reset-approved=true` annotation before proceeding. | +| `machineconfig-backup` | CapabilityMachineConfigBackup | TalosMachineConfigBackup CR | Read each node's running machine config via GetMachineConfig and upload to S3. | +| `machineconfig-restore` | CapabilityMachineConfigRestore | TalosMachineConfigRestore CR | Download node machine config from S3 and apply via ApplyConfiguration. Non-fatal per node. | -The operator never sees the PVC. It only sees CR status advancing. Conductor -manages PVC lifecycle entirely within the Job sequence. Storage class requirement: -the management cluster must have a storage class available. +### Pack delivery capability ---- +| Capability | Constant | Triggering CRD | Description | +|------------|---------|----------------|-------------| +| `pack-deploy` | CapabilityPackDeploy | PackExecution (dispatcher) | Apply a ClusterPack to a target cluster via execute mode on the management cluster. | -## 8. OperationResult Protocol +### Guardian capability + +| Capability | Constant | Triggering CRD | Description | +|------------|---------|----------------|-------------| +| `rbac-provision` | CapabilityRBACProvision | Agent-initiated | Provision RBAC artifacts on a target cluster from the current PermissionSnapshot. | -Every execute-mode Job writes an OperationResult JSON document to a ConfigMap before -exit. ConfigMap name derived from Job name. The operator reads it to advance CR status. -No other inter-process communication channel exists between operator and Conductor. +### Compile-only capability constant -Structure: phase, status (succeeded or failed), startedAt, completedAt, artifacts -(structured references - never raw content), failureReason (category and detail -when failed), steps (individual step results for multi-step capabilities). +| Capability | Constant | Invocation | Description | +|------------|---------|------------|-------------| +| `pack-compile` | CapabilityPackCompile | Compiler CLI only | Renders PackBuild inputs (Helm, Kustomize, raw manifests) into a ClusterPack OCI artifact. Never a Kueue Job. Never run by Conductor. Never runs on any cluster. | -The operator must read OperationResult within the Job's configured TTL. After TTL -expiry the ConfigMap is garbage collected. +The pack-compile constant exists so Compiler and dispatcher share a common vocabulary. It does not follow the execute-mode Job pattern. + +For capi.enabled=true target clusters, CAPI handles talos-upgrade, kube-upgrade, stack-upgrade, node-scale-up, node-decommission, and node-reboot natively. The corresponding capability Jobs are management-cluster-direct path only when capi.enabled=false. --- -## 9. Compile Mode (Compiler binary) +## 7. RunnerConfig Execution Model + +A RunnerConfig represents a multi-step operation intent. The spec carries a `steps` list where each step declares: + +- `name`: unique identifier within this RunnerConfig's step list. +- `capability`: the named capability identifier Conductor execute mode dispatches. +- `parameters`: input parameter map passed to the capability handler. +- `dependsOn`: optional reference to a prior step name. The step is not eligible until the referenced step has reached Succeeded. +- `haltOnFailure`: boolean. When true, any failure terminates the entire RunnerConfig. + +A single-step RunnerConfig is the degenerate case of this model. All RunnerConfigs use the steps list. -### Canonical Compiler Command Surface +**Step sequencer (execute mode):** -**LOCKED INVARIANT - Platform Governor directive 2026-04-05.** +Conductor execute mode is the sole authority over step-to-step progression. -The complete and authoritative compiler command surface is: +1. Scan the steps list for the first eligible step: dependsOn step (if any) has reached Succeeded, and the step has not yet reached a terminal state. +2. Materialise the Kueue Job for that step using the step's capability and parameters. +3. Monitor the Job for terminal state (Succeeded or Failed). +4. On completion, harvest the structured OperationResult from the ConfigMap named after the Job in ont-system. +5. Write a StepResult entry into RunnerConfig status for that step. +6. Evaluate terminal conditions and advance to the next eligible step or write the terminal RunnerConfig condition. -| Subcommand | Purpose | Cluster connectivity | Output | -|---------------|------------------------------------------------------------------|----------------------|-------------------------------------------------| -| `bootstrap` | Management cluster formation - Talos machineconfigs + bootstrap CRs | No | CR YAML + encrypted machineconfig files | -| `launch` | Management cluster CRD installation manifest | No | CRD manifest YAML bundle | -| `enable` | Seam operator deployment manifest bundle | No | Operator YAML bundle (Deployments, RBAC, etc.) | -| `packbuild` | ClusterPack OCI artifact + CR YAML | No | ClusterPack CR YAML + OCI push | -| `maintenance` | MaintenanceBundle CR with pre-resolved scheduling context | Yes (management) | MaintenanceBundle CR YAML | -| `component` | RBACProfile CRs for third-party components | Optional (--discover)| RBACProfile CR YAML(s) | -| `domain` | Reserved - domain CR compilation not yet implemented | - | - | +The owning operator never drives step-to-step progression. The owning operator never submits individual Jobs. -No other compiler subcommands exist. New subcommands require a Platform Governor directive before implementation. +**StepResult fields:** stepName, phase (Pending/Running/Succeeded/Failed), outputRef (ConfigMap reference), result (raw OperationResult JSON payload written verbatim). + +**Terminal conditions:** +- `Completed`: all steps reached Succeeded. +- `Failed`: any step reached Failed with haltOnFailure=true, or the final step failed. + +Once a terminal condition is written, the RunnerConfig is inert. No further Jobs are submitted. + +**Boundary contract:** Conductor harvests and records only. It never interprets what a step result means for the domain. The owning operator watches RunnerConfig status for the terminal condition, reads StepResult entries, and decides what the results mean and what downstream actions to take. This boundary is permanent and locked. --- -### Management Cluster Bootstrap Sequence Authority +## 8. OperationResult Protocol -**LOCKED INVARIANT - Platform Governor directive 2026-04-05.** +Every execute-mode Job writes an OperationResult JSON document to a ConfigMap before exit. ConfigMap name is derived from the Job name. The operator reads it to advance CR status. No other inter-process communication channel exists between operator and Conductor. -The management cluster bootstrap sequence is owned exclusively by the Compiler in three steps. Platform operator has no involvement in management cluster bootstrap. This is a locked invariant. +Structure: phase, status (succeeded or failed), startedAt, completedAt, artifacts (structured references, never raw content), failureReason (category and detail when failed), steps (individual step results for multi-step capabilities). -**Step 1 - `compiler bootstrap`** -Forms the management cluster itself. Reads TalosCluster spec and human-provided machineconfig inputs. Validates spec against platform-schema.md rules. SOPS-encrypts talos-secret, machineconfigs, and talosconfig using the admin's age key. Writes encrypted files to output path. Produces bootstrap CRs (TalosCluster in mode: bootstrap) as YAML. No cluster connection required. Compiler never applies resources - the GitOps pipeline or operator's kubectl applies the output. +The operator must read the OperationResult within the Job's configured TTL. After TTL expiry the ConfigMap is garbage collected. -**Step 2 - `compiler launch`** -Installs all Seam CRDs onto the management cluster. Reads the CRD manifest set for all Seam API groups (seam.ontai.dev, guardian.ontai.dev, platform.ontai.dev). The old groups runner.ontai.dev and infra.ontai.dev are superseded by seam.ontai.dev as of Phase 2B (2026-04-25). Produces a CRD manifest YAML bundle ready for GitOps application. No cluster connection required. Compiler never applies resources. +--- -**Step 3 - `compiler enable`** -Produces the complete Seam operator deployment manifest bundle as YAML output. The bundle -is structured as six sequenced phases applied in ascending order. Each phase carries a -`phase-meta.yaml` declaring name, order, readinessGate, and applyOrder. +## 9. Inter-Job State: PVC Protocol -**Six-phase enable bundle structure:** +For multi-step sequence capabilities (bootstrap, stack-upgrade, cluster-reset), Conductor uses a temporary PVC for inter-step state transfer. -| Phase | Directory | Contents | Readiness gate before next phase | -|-------|----------------------------------|-----------------------------------------------------------------------------------|----------------------------------| -| 0 | `00-infrastructure-dependencies` | CNPG operator manifests + CNPG Cluster CR for management Guardian | CNPG Cluster ready | -| 1 | `01-guardian-bootstrap` | Guardian CRDs, bootstrap RBACPolicy, namespace-labels.yaml (seam.ontai.dev/webhook-mode=exempt on seam-system) | Guardian CRDs registered | -| 2 | `02-guardian-deploy` | Guardian Deployment, Guardian RBACProfile, admission webhook configuration | Guardian webhook operational | -| 3 | `03-platform-wrapper` | Platform Deployment, Wrapper Deployment, seam-core Deployment, their RBACProfiles | All three operators healthy | -| 4 | `04-conductor` | Conductor Deployment in ont-system **stamped with `role=management`** (see §15 Role Declaration Contract), Conductor RBACProfile | Conductor agent ready | -| 5 | `05-post-bootstrap` | Remaining cluster-wide resources, Kueue ClusterQueue scaffold, metallb config | - | +Protocol: +1. First Job creates a PVC named `ont-{capability}-{cr-name}`. Executes step. Writes intermediate artifacts to PVC. Updates operational CR status. +2. Subsequent Jobs mount the same PVC. Read previous step outputs. Execute step. Write outputs. Update status. +3. Final Job consumes all intermediate artifacts. Creates Kubernetes assets. Deletes PVC. Writes terminal OperationResult. + +The operator never sees the PVC. It only sees CR status advancing. Conductor manages PVC lifecycle entirely within the Job sequence. + +--- -Phase 0 is the prerequisite phase that resolves the CNPG dependency before Guardian -is deployed. Guardian's startup migration runner connects to CNPG before registering -any controller - phase 0 must reach readiness before phase 1 may begin. See -guardian-schema.md §16 CNPG Deployment Contract. +## 10. Management Cluster Conductor Responsibilities -Phase 1 namespace-labels.yaml stamps `seam.ontai.dev/webhook-mode=exempt` on -`kube-system` and `seam-system` as SSA metadata-only patches. This satisfies Guardian's -CheckBootstrapLabels startup gate before the webhook registers in phase 2. See -guardian-schema.md §4 Bootstrap RBAC Window. +Conductor in agent mode on the management cluster is stamped `CONDUCTOR_ROLE=management` by `compiler enable` at manifest production time. It activates the full agent startup sequence. -The full bundle also contains: -- All RBAC resources for all Seam operator service accounts (distributed across phases by operator). -- All leader election Lease templates. -- First-class platform-owned RBACProfile CRs for all Seam operator service accounts - one per operator, Guardian-governed, human-reviewed before GitOps commit. +**Startup sequence (agent, all clusters):** +1. Read RunnerConfig for this cluster. +2. Validate mode: refuse compile mode. CR-INV-001. +3. Write capability manifest to RunnerConfig status (self-declaration). CR-INV-005. +4. Start receipt reconciliation loops (PackReceipt, PermissionSnapshotReceipt). +5. Start admission webhook server. +6. Start local PermissionService gRPC server. +7. Start drift detection loop. +8. Start PermissionSnapshot pull loop. -Compiler never applies resources directly. The GitOps pipeline applies this bundle after human review. No cluster connection required. +**Additional startup for role=management:** +9. Start PackInstance signing loop: watches for new ClusterPack registrations, signs PackInstance CRs with the platform signing key. +10. Start PermissionSnapshot signing loop: watches for new PermissionSnapshot CRs generated by Guardian, signs them with the platform signing key. -**F-P8:** Phase 0 (`00-infrastructure-dependencies`) implementation - adding CNPG operator -manifests and CNPG Cluster CR to the enable bundle output - requires a Conductor Engineer -session. See CONTEXT.md F-P8. +**CR-INV-006:** Leader election in agent mode is not optional. One leader writes to RunnerConfig status and receipt CRs. All other replicas are standby. -**Invariant:** Platform operator is not involved at any step of management cluster bootstrap. Management cluster formation, CRD installation, and operator deployment are all compiler-driven. Platform's role begins when the management cluster is operational and CRDs are registered. +Leader election lease: Lease resource in ont-system named `conductor-{cluster-name}`. --- -### compiler maintenance +## 11. Target Cluster Conductor Responsibilities -**Subcommand:** `compiler maintenance --operation [--talosconfig ] [--kubeconfig ] [--output ]` +Conductor in agent mode on each target cluster is stamped `role=tenant` by Platform operator at Deployment creation time. It activates the target-cluster-specific startup sequence (steps 1-8 above). It does not activate signing loops. Signing is management-only (INV-026). -Produces a MaintenanceBundle CR that pre-encodes all scheduling context required for -a maintenance operation. Platform and Conductor execute without rediscovering any of -this context from the live cluster at execution time. See F-P5. +**TenantBootstrapSweep:** On leader election, Conductor runs an RBAC bootstrap sweep. It runs once on startup and then periodically (every 5 minutes) to pick up newly deployed Helm charts. -**Cluster access resolution:** -talosconfig and kubeconfig are resolved from three sources in order, consistent with -how `launch` and `enable` resolve cluster access when operating against a pre-existing -cluster: -1. Explicit flag: `--talosconfig `, `--kubeconfig ` -2. Environment variable: `TALOSCONFIG`, `KUBECONFIG` -3. Conventional path: `./talos/config`, `~/.kube/config` +- Phase 1 (annotation sweep): stamps `ontai.dev/rbac-owner=guardian` and `ontai.dev/rbac-enforcement-mode=audit` on all pre-existing Role, ClusterRole, RoleBinding, ClusterRoleBinding, and ServiceAccount resources using the typed Kubernetes client. +- Phase 2 (profile creation): creates PermissionSet, RBACPolicy, and RBACProfile for each known third-party component (cert-manager, kueue, cnpg, metallb, local-path-provisioner) via the dynamic client under `guardian.ontai.dev/v1alpha1`. Components whose namespace is absent are silently skipped. If Guardian CRDs are not installed, profile creation is skipped gracefully. -There is no special connectivity requirement. The Compiler always has management -cluster access through one of these three sources. +After both phases complete, `EnforcementGate.SetStrict()` is called. The admission webhook transitions from audit mode to strict mode. -**Pre-encoded context written into the MaintenanceBundle CR:** +**Admission webhook:** Registered at `/validate/rbac-ownership`. In audit mode, logs unannotated RBAC resources but admits them. In strict mode, rejects any RBAC resource lacking the `ontai.dev/rbac-owner=guardian` annotation. Exempts kube-system, ont-system, and known system namespaces unconditionally. -- `maintenanceTargetNodes`: the target node set for the operation. Directly populates - `RunnerConfig.MaintenanceTargetNodes` when Platform creates the RunnerConfig from - this bundle. The node set is validated against the live cluster at compile time - - nodes that do not exist cause a compile-time failure. +The management cluster Conductor does not register this webhook. Guardian owns RBAC enforcement on the management cluster. -- `operatorLeaderNode`: the node currently hosting the leader pod of the initiating - Platform operator, resolved live from the management cluster's Platform leader - election lease (`platform-leader` Lease in `seam-system`). Directly populates - `RunnerConfig.OperatorLeaderNode`. Compiler fails fast if the lease does not exist - or has no holder - this is a safety gate, not a recoverable condition. +--- -- `s3ConfigSecretRef`: the S3 configuration Secret reference, resolved and validated - to exist before the MaintenanceBundle CR is committed. If the Secret is absent, - `compiler maintenance` fails fast at compile time with a structured error. A - MaintenanceBundle is never committed without a valid, resolvable S3 reference. - Resolution follows platform-schema.md §10 (Etcd Backup Destination Contract). +## 12. Signing and Verification -- `operation`: one of `drain`, `upgrade`, `etcd-backup`, or `machineconfig-rotation`. - Determines which Conductor capability the resulting RunnerConfig targets. +**INV-026:** PackInstance signing and PermissionSnapshot signing are performed exclusively by the management cluster Conductor in agent mode. Target cluster Conductor verifies but never signs. Verification failure blocks receipt acknowledgement. -**MaintenanceBundle CRD:** -MaintenanceBundle is a new CRD in the `platform.ontai.dev` API group. Its complete -type definition, spec fields, status conditions, and reconciler implementation are -deferred to a Platform Schema Engineer session (F-P5). The contract above establishes -the semantic requirements - the CR carries pre-resolved scheduling context so neither -Platform nor Conductor need to perform cluster queries at execution time. +The management cluster Conductor holds the signing key (mounted Secret). It signs PackInstance and PermissionSnapshot CRs by writing a cryptographic signature to a dedicated annotation field. Target cluster Conductors verify this signature before acknowledging receipt. A signature failure blocks the receipt acknowledgement and raises DegradedSecurityState. + +This chain of custody ensures that target clusters only execute packs and honor permissions that have been explicitly authorized by the management cluster. --- -## 10. Agent Mode (Conductor binary) +## 13. DriftSignal Protocol -Conductor in agent mode is a long-lived Deployment in ont-system on every cluster. -Same binary, agent startup flag. Implements leader election - only one replica -writes to RunnerConfig status and receipt CRs at a time. +DriftSignal is a CRD owned by seam (`seam.ontai.dev/v1alpha1`). Schema authority: `~/ontai/seam/docs/seam-schema.md`. -**On startup (all clusters):** -1. Read RunnerConfig for this cluster. -2. Validate mode: refuse compile mode flag. INV-023. -3. Write capability manifest to RunnerConfig status (self-declaration). -4. Start receipt reconciliation loops (PackReceipt, PermissionSnapshotReceipt). -5. Start admission webhook server (intercepts RBAC resources). -6. Start local PermissionService gRPC server (serves authorization decisions). -7. Start drift detection loop for PackReceipt. -8. Start PermissionSnapshot pull loop (pulls from management cluster). - -**Additional on management cluster Conductor startup:** -9. Start PackInstance signing loop: watches for new ClusterPack registrations, - signs PackInstance CRs with the platform signing key. -10. Start PermissionSnapshot signing loop: watches for new PermissionSnapshot - generation by Guardian, signs them with the platform signing key. - -**Signing and verification model:** -The management cluster Conductor holds the signing key (mounted Secret). It signs -PackInstance and PermissionSnapshot CRs by writing a cryptographic signature -to a dedicated annotation field. Target cluster Conductors verify this signature -before acknowledging receipt. A signature failure blocks the receipt -acknowledgement and raises DegradedSecurityState. This chain of custody ensures -that target clusters only execute packs and honor permissions that have been -explicitly authorized by the management cluster's security plane. - -**Leader election lease:** Lives in ont-system as a Lease resource named -conductor-{cluster-name}. All Conductor Deployment replicas compete. Only the -leader performs writes. +Conductor detects drift only for TalosCluster and PackDelivery CRs. When drift is detected, Conductor writes the drift reason to the CR status and emits a DriftSignal CR. Conductor never remediates drift directly. Remediation is the responsibility of the owning operator. + +Conductor never enters Guardian's governance space during drift handling. --- -## 11. Licensing +## 14. PackReceipt Protocol -Seam is fully open source with no licensing tier. All clusters are equal. No enforcement. +PackReceipt is a CRD owned by dispatcher (`seam.ontai.dev/v1alpha1`). Schema authority: `~/ontai/seam/docs/seam-schema.md`. + +Conductor on each cluster maintains the PackReceipt reconciliation loop. When a PackInstance arrives on the target cluster, Conductor verifies the management signature before acknowledging receipt. Verification failure raises DegradedSecurityState and blocks the receipt write. A successful receipt write confirms that the pack is cryptographically authorized and locally installed. --- -## 12. Operational Readiness Gates +## 15. Role Declaration Contract -**LOCKED INVARIANT - Platform Governor directive 2026-04-05.** +Conductor reads a role declaration field at startup to determine which loops to activate. The role field is stamped externally at Deployment creation time. Conductor never determines its own role and never infers it from cluster state. -### Gate 1 - Port 50000 Talos API Reachability +**Two valid role values:** -Platform operator is the sole owner of Talos apid port 50000 reachability validation -across both native and CAPI cluster paths. No other operator and no Conductor -capability handler performs port 50000 validation. This is a locked invariant. +| Role | Stamped by | When | +|------|-----------|------| +| `management` | `compiler enable` | At management cluster manifest production time | +| `tenant` | Platform operator | At tenant cluster Conductor Deployment creation | -**Native clusters (spec.capi.enabled=false):** -The gate is triggered by node IP declaration in TalosCluster spec. When a node IP is -recorded in TalosCluster spec, Platform operator validates reachability to port 50000 -before proceeding with any node-level operation. +An absent or unrecognized role field causes immediate InvariantViolation exit. No component other than `compiler enable` (management) and Platform operator (tenant) may stamp the role field. -**CAPI clusters (spec.capi.enabled=true):** -The gate is triggered by CAPI Machine reaching provisioned state. Platform operator -validates port 50000 reachability as part of the SeamInfrastructureMachineReconciler -provisioning sequence. No other reconciler or operator repeats this check. +**Import-mode tenant onboarding (role=tenant gRPC handshake):** -**Permanent exclusions:** -- Screen (future operator) never performs this check. Screen's responsibility ends at - infrastructure existence. Port 50000 ownership belongs exclusively to Platform. -- Guardian, Wrapper, Conductor execute mode, and Conductor agent mode never perform - port 50000 validation under any circumstance. -- Adding port 50000 validation to any component other than Platform operator requires - a Platform Governor constitutional amendment. +For import-mode clusters the tenant Conductor drives a gRPC handshake with the management Conductor immediately after leader election. ---- +1. Tenant Conductor dials MGMT_ENDPOINT (set by Platform from TalosCluster spec.endpoint, management-side federation port). +2. Management Conductor validates the tenant identity against the management IdentityProvider chain. +3. Management Conductor pushes the initial PermissionSnapshot: management-policy, management-maximum (Layer 1), and cluster-maximum for this cluster (Layer 2). +4. Tenant Conductor verifies the guardian signature on the PermissionSnapshot (INV-026: mandatory; failure blocks all further operations). +5. On successful verification, tenant Conductor installs management-policy and management-maximum in ont-system. These are signed artifacts; tenant Conductor never authors them locally. +6. Tenant Conductor writes PermissionSnapshotReceipt in ont-system. +7. PermissionSnapshotReceipt is acknowledged to management Conductor over the gRPC channel. Management Conductor writes the acknowledged condition back to TalosCluster status. -## 13. RunnerConfig Self-Operation Contract - -**LOCKED INVARIANT - Platform Governor directive 2026-04-05.** - -The RunnerConfig spec carries three fields as a first-class scheduling contract. These -fields govern Conductor execute mode node affinity exclusion for management cluster -self-operations. They are populated exclusively by the initiating operator at -RunnerConfig creation time. - -**The three fields (defined in Section 5):** -- `maintenanceTargetNodes`: list of node names that are the subject of the operation. -- `operatorLeaderNode`: the node currently hosting the leader pod of the initiating - operator, resolved via the Kubernetes downward API. -- `selfOperation`: boolean - true when the Job's execution cluster and target cluster - are the same (management cluster self-operations); false for all tenant-targeted - operations. - -**Operator responsibility at RunnerConfig creation:** -The initiating operator populates all three fields. `operatorLeaderNode` is resolved -at creation time using the Kubernetes downward API (fieldRef: spec.nodeName on the -operator's own pod). The operator must not cache this value - it must be resolved -fresh at each RunnerConfig creation to reflect the current leader pod's node. - -**Conductor execute mode contract:** -When selfOperation is true, Conductor translates maintenanceTargetNodes and -operatorLeaderNode into Kueue Job node affinity NotIn constraints before submitting -the Job. This ensures the Job pod does not land on a node that is itself a target -of the maintenance operation, and does not land on the node hosting the operator's -leader pod (which would cause a scheduling deadlock if the node were cordoned). - -When selfOperation is false, Conductor skips exclusion resolution entirely. Tenant- -targeted operations are exempt - the Job executes on the management cluster regardless -of which nodes the remote target cluster is operating on. - -**Conductor agent mode recovery path:** -Conductor agent mode acts as a recovery path only. It detects Jobs that landed on -maintenance-targeted nodes due to scheduling races (i.e., the NotIn constraint was -applied but a race between admission and cordoning resulted in incorrect placement) -and signals rescheduling by annotating the Job pod. It does not proactively schedule -Jobs. The agent mode recovery path is not a substitute for correct operator-side -field population. - -**Permanent exclusions:** -- No other component populates these three fields. They are operator-authored at - creation time and Conductor-consumed at Job materialisation time. -- Conductor agent mode does not populate these fields. It reads them. -- These fields are never modified after RunnerConfig creation. They are immutable - for the lifetime of the RunnerConfig instance. +The admission webhook transitions from audit mode to strict mode only after step 6 completes. --- -## 14. Dockerfile Standards +## 16. Federation Channel Contract -**Compiler Dockerfile (compile mode, debian):** +The Conductor agent process on the management cluster exposes two listener ports. -Build pattern: golang:1.25 builder stage with build tag `-tags compiler` compiles the -Compiler binary with compile-mode clients included. debian:12-slim final stage -includes: bash, curl, jq, python3, openssl, psql, helm binary (for CA bundle -verification during chart pull), kubectl, and the compiled Compiler binary. -USER 65532:65532. No package manager retained in the final image. +**Internal port:** Serves cluster-local callers using cluster-internal certificate authority trust. All local components call this port. Environment variable: `INTERNAL_PORT`. -The debian base is required for: /etc/ssl/certs (Helm chart HTTPS pulls), SOPS age -key operations (python3), and psql (CNPG health verification in enable phase). -No other reason for debian exists. This image is never deployed to any cluster. +**Federation port:** Environment variable: `FEDERATION_PORT`, defaulting to `9443`. Serves tenant Conductor connections exclusively. TLS configuration uses management CA-issued client certificates with the connecting tenant's cluster ID embedded as Subject Alternative Name. Rejects any connection that cannot present a valid management CA certificate. -**Conductor Dockerfile (execute and agent modes, distroless):** +**Stream model:** One persistent bidirectional gRPC stream per connected tenant Conductor. The stream is established by the tenant Conductor; the management Conductor accepts it. The management side is stateless with respect to connection lifecycle. -Build pattern: golang:1.25 builder stage with build tag `-tags conductor` compiles the -Conductor binary. Compile-mode clients (helm goclient, kustomize goclient, SOPS -handler) are excluded at build time. gcr.io/distroless/base:nonroot final stage. -USER 65532:65532. No shell. No package manager. No system tools. +**Typed message envelope:** -gcr.io/distroless/base (not static) is required because the talos goclient and -gRPC stack require libc for certain TLS and crypto operations. Verify at build time -that the produced binary runs cleanly on the distroless/base image before release. +| Message | Direction | Purpose | +|---------|-----------|---------| +| RunnerConfigValidationRequest | tenant to management | Validate RunnerConfig parameters before Job materialisation | +| RunnerConfigValidationResponse | management to tenant | Validation result: approved or rejected with structured reason | +| AuditEventBatch | tenant to management | Batch of sequenced audit events for management AuditSink | +| AuditEventAck | management to tenant | Acknowledgement of received audit batch by sequence number | +| RevocationPush | management to tenant | Management-initiated revocation of a PackInstance or PermissionSnapshot | +| HeartBeat | bidirectional | Liveness signal at 30-second intervals | ---- +RevocationPush is management-initiated without waiting for the tenant to request. This is the architectural justification for a persistent bidirectional stream over individual RPCs. -## 15. Role Declaration Contract +**Heartbeat discipline:** Three consecutive missed HeartBeat acknowledgments from a tenant marks that channel degraded in management status tracking. -**LOCKED INVARIANT - Platform Governor directive 2026-04-05.** +**Tenant behavior on channel degradation:** Buffers audit events in a local WAL PVC in ont-system. Fails RunnerConfig validation calls closed. Emits FederationChannelDegraded condition on its local RunnerConfig status. -Conductor reads a role declaration field at startup to determine which loops to -activate and which responsibilities to assume. The role field is stamped externally -at Deployment creation time. Conductor does not determine its own role and never -infers it from cluster state. +**Tenant behavior on reconnect:** Replays unacknowledged audit events from the last acknowledged sequence number. The management AuditSink deduplicates on sequence number. Validation calls resume immediately on stream re-establishment. -**Two valid role values:** +Adding new message envelope types requires a Platform Governor directive before implementation. -| Role | Who stamps it | When | -|--------------|------------------------|---------------------------------------------------| -| `management` | `compiler enable` | At management cluster manifest production time | -| `tenant` | Platform operator | At tenant cluster Conductor Deployment creation | - -**Role field location:** -The role declaration is a first-class field on the Conductor Deployment, not an -environment variable or ConfigMap mount. It is stamped once at Deployment creation -time and never modified by any controller. - -**Management role startup sequence:** -Conductor with role=management activates all loops from §10 - including PackInstance -signing, PermissionSnapshot signing, and the full agent mode startup sequence. - -**Tenant role startup sequence:** -Conductor with role=tenant activates the target-cluster-specific loops from §10 - -PackReceipt, PermissionSnapshotReceipt, admission webhook, PermissionService gRPC, -drift detection, and PermissionSnapshot pull loop. It does not activate signing loops. -Signing is management-only. INV-026. - -**Tenant RBAC bootstrap sweep (TenantBootstrapSweep):** -When role=tenant, Conductor runs an RBAC bootstrap sweep on leader election using -`TenantBootstrapSweep`. The sweep runs once on startup and then periodically (every -5 minutes) so that newly deployed Helm charts are picked up without restart. - -Phase 1 (annotation sweep): stamps `ontai.dev/rbac-owner=guardian` and -`ontai.dev/rbac-enforcement-mode=audit` on all pre-existing Role, ClusterRole, -RoleBinding, ClusterRoleBinding, and ServiceAccount resources using the typed -kubernetes client. This establishes the governance baseline on cluster join. - -Phase 2 (profile creation): creates PermissionSet, RBACPolicy, and RBACProfile -for each known third-party component (cert-manager, kueue, cnpg, metallb, -local-path-provisioner) via the dynamic client using GVRs under -`guardian.ontai.dev/v1alpha1`. Components whose namespace is absent on the cluster -are silently skipped -- they will be picked up on the next periodic run once their -Helm chart is deployed. If security CRDs are not installed (Guardian not deployed), -the entire profile creation phase is skipped gracefully. - -After both phases complete, `EnforcementGate.SetStrict()` is called, which -transitions the admission webhook from audit mode to strict mode. - -**EnforcementGate and admission webhook:** -The `EnforcementGate` is an atomic bool that starts in audit mode (false) and -transitions to strict mode (true) exactly once after the bootstrap sweep completes. -The `TenantRBACOwnershipWebhook` is registered at `/validate/rbac-ownership` on -the tenant cluster Conductor webhook server. In audit mode it logs unannotated RBAC -resources but admits them. In strict mode it rejects any RBAC resource that lacks -the `ontai.dev/rbac-owner=guardian` annotation. The webhook exempts kube-system, -ont-system, and other known system namespaces unconditionally. This mirrors Guardian's -enforcement model (CS-INV-001, guardian-schema.md §5) from the Conductor process so -that tenant clusters without Guardian deployed are still governed. - -The management cluster Conductor does NOT register the RBAC ownership webhook -- -Guardian owns that enforcement path on the management cluster. `NewWebhookServer` -accepts a `*EnforcementGate` parameter: nil suppresses the endpoint (management), -non-nil enables it (tenant). - -**Invariants:** -- Conductor with an absent or unrecognized role field exits immediately with - InvariantViolation structured exit. This is a hard gate - a Conductor without a - valid role declaration is a programming error, not a recoverable condition. -- No component other than `compiler enable` (management) and Platform operator (tenant) - may stamp the role field. Guardian, Wrapper, seam-core, and humans never set this field. -- Platform operator is exclusively responsible for stamping role=tenant on every Conductor - Deployment it creates. See platform-schema.md §12 Conductor Deployment Contract. +--- -**Import-mode tenant onboarding (role=tenant gRPC handshake):** -For import-mode clusters the tenant Conductor drives the gRPC handshake with the -management Conductor immediately after leader election. Sequence: - -1. Tenant Conductor dials MGMT_ENDPOINT (env var set by Platform from - InfrastructureTalosCluster.spec.endpoint, management-side gRPC port). -2. Management Conductor validates the tenant identity against the management - IdentityProvider chain. -3. Management Conductor pushes the initial PermissionSnapshot: payload includes - management-policy, management-maximum (Layer 1), and cluster-maximum for this - cluster (Layer 2). -4. Tenant Conductor verifies the guardian signature on the PermissionSnapshot - (INV-026: verification is mandatory; failure blocks all further operations). -5. On successful verification, tenant Conductor installs management-policy and - management-maximum in ont-system. These are signed artifacts; tenant Conductor - never authors them locally. -6. Tenant Conductor writes PermissionSnapshotReceipt in ont-system. -7. PermissionSnapshotReceipt is acknowledged to management Conductor over the gRPC - channel. Management Conductor writes the acknowledged condition back to - InfrastructureTalosCluster status on the management cluster. +## 17. RunnerConfig Self-Operation Contract -The admission webhook on the tenant cluster transitions from audit mode to strict -mode only after step 6 completes. Platform observes the acknowledged condition and -advances InfrastructureTalosCluster.status.phase to Operational. +When selfOperation=true on the RunnerConfig spec, Conductor execute mode translates maintenanceTargetNodes and operatorLeaderNode into Kueue Job node affinity NotIn constraints before submitting the Job. This ensures the Job pod does not land on a node that is itself a target of the maintenance operation, and does not land on the node hosting the operator's leader pod. -See guardian-schema.md §20 for the complete import-mode onboarding sequence including -Platform's two-site orchestration and conductor RBACProfile placement. +When selfOperation=false, Conductor skips exclusion resolution entirely. Tenant-targeted operations are exempt. + +Conductor agent mode acts as a recovery path only: it detects Jobs that landed on maintenance-targeted nodes due to scheduling races and signals rescheduling by annotating the Job pod. + +These three fields (maintenanceTargetNodes, operatorLeaderNode, selfOperation) are operator-authored at RunnerConfig creation time and Conductor-consumed at Job materialisation. They are never modified after RunnerConfig creation. --- -## 16. compiler component Subcommand +## 18. Compiler Subcommands -**LOCKED INVARIANT - Platform Governor directive 2026-04-05.** +The Compiler is a CR compiler. It never applies resources to any cluster. All output is manifest YAML for human review and GitOps pipeline application. -`compiler component` emits RBACProfile CRs as YAML output for third-party components -operating in a Guardian-governed cluster. Guardian's admission webhook enforces what -RBACProfiles declare - it never generates them and never guesses. -`compiler component` is a prerequisite for any third-party component operating in a -Guardian-governed cluster. +### Management Cluster Bootstrap Sequence -**Subcommand signature:** -``` -compiler component [--component ]... [--descriptor ] [--discover] [--output ] -``` +Management cluster bootstrap is owned exclusively by the Compiler in three steps. Platform operator has no involvement in management cluster bootstrap. -**Two operating modes:** +**Step 1: `compiler bootstrap`** -### Catalog Mode (no cluster connectivity required) +Reads a cluster declaration YAML file (ClusterInput schema). Validates the spec. Generates Talos machine config Secrets (SOPS-encrypted with the admin's age key) and produces TalosCluster CR YAML (mode: bootstrap) and a bootstrap sequence manifest. No cluster connection required. -Compiler ships with a versioned embedded catalog of canonical RBACProfile definitions -for known ecosystem components. The catalog lives at `internal/catalog/` in the -conductor repository as a versioned embedded filesystem. The human selects which -components are in scope via one or more `--component` flags. +Flags: `--input `, `--output `, `--kubeconfig ` (importExistingCluster mode only), `--talosconfig ` (talosconfig-only import path). -Compiler emits the corresponding RBACProfile YAMLs from the embedded catalog, -stamped and ready for Guardian-governed application. No cluster connectivity is -required. +**Step 2: `compiler launch`** -**Current catalog entries:** +Produces the CRD bundle for all Seam API groups (`seam.ontai.dev`, `guardian.ontai.dev`, `platform.ontai.dev`) as a YAML bundle ready for GitOps application. No cluster connection required. -| Component | RBACProfile name | Description | -|-----------------------|--------------------------------------|------------------------------------------| -| cilium | rbac-cilium | Cilium CNI agent and operator | -| cnpg | rbac-cnpg | CloudNativePG operator | -| kueue | rbac-kueue | Kueue batch scheduler | -| cert-manager | rbac-cert-manager | cert-manager controller and webhook | -| local-path-provisioner| rbac-local-path-provisioner | Rancher local-path-provisioner | +Flags: `--output ` -New catalog entries are added via Pull Request to the conductor repository. New -entries require a canonical RBACProfile definition reviewed by the Platform Governor -before merge. +**Step 3: `compiler enable`** -### Custom Mode (optional cluster connectivity) +Produces the complete Seam operator deployment manifest bundle as a phased directory structure. -For components not in the catalog, the human provides a component descriptor file -via `--descriptor `. Compiler renders an RBACProfile scaffold from the descriptor -for human review before GitOps commit. +| Phase | Directory | Contents | Readiness gate | +|-------|-----------|----------|----------------| +| 0 | `00-infrastructure-dependencies` | CNPG operator manifests + CNPG Cluster CR for management Guardian | CNPG Cluster ready | +| 00a | `00a-namespaces` | seam-system (webhook-mode=exempt, privileged PSA) and ont-system (privileged PSA) | -- | +| 00b | `00b-capi-prerequisites` | CAPI core, Talos CAPI bootstrap and controlplane providers, SeamInfrastructure CRDs (emitted only when --capi flag is set) | -- | +| 1 | `01-guardian-bootstrap` | Guardian CRDs, bootstrap RBACPolicy, namespace-labels.yaml (webhook-mode=exempt on seam-system and kube-system), Guardian RBACProfile | Guardian CRDs registered | +| 2 | `02-guardian-deploy` | Guardian Deployment, Guardian admission webhook configuration | Guardian webhook operational | +| 3 | `03-platform-wrapper` | Platform Deployment, Dispatcher Deployment, Seam Deployment, their RBACProfiles | All three operators healthy | +| 4 | `04-conductor` | Conductor Deployment in ont-system stamped with `CONDUCTOR_ROLE=management`, Conductor RBACProfile | Conductor agent ready | +| 5 | `05-post-bootstrap` | Remaining cluster-wide resources, Kueue ClusterQueue scaffold, leader election Lease resources | -- | -The optional `--discover` flag enables cluster connectivity to auto-detect deployed -third-party resources. Cluster access follows the same resolution order as -`compiler maintenance` (flag → env var → conventional path). +Phase 0 must reach readiness before phase 1 may begin. Guardian's startup migration runner connects to CNPG before registering any controller. -**Invariants:** -- `compiler component` is the only path to creating third-party RBACProfiles. No - operator generates third-party RBACProfiles at runtime. -- Guardian's admission webhook enforces RBACProfile declarations but never creates - them. The contract direction is: component declares needs → Guardian enforces. -- The embedded catalog is the versioned source of truth for canonical component - RBACProfiles. Catalog version is tied to the compiler release tag. -- F-P6: compiler component real implementation - catalog scaffold and RBACProfile - template rendering - requires a Conductor Engineer session. +Conductor Deployment in phase 4 carries `CONDUCTOR_ROLE=management` per the Role Declaration Contract. ---- +Flags: `--output `, `--version `, `--signing-private-key `, `--capi` -## 17. RunnerConfig Execution Model +### compiler packbuild -**LOCKED GOVERNOR DECISION - Platform Governor directive 2026-04-05.** +Compiles a PackBuild spec file into a ClusterPack CR. -### Multi-Step Intent +Input: `--input ` (human-authored PackBuild descriptor), `--output `. -A RunnerConfig represents a multi-step operation intent, not a single Job. The spec -carries a `steps` list where each step declares: +Output: `-.yaml` ClusterPack CR ready to apply. -- `name`: unique identifier within this RunnerConfig's step list. -- `capability`: the named capability identifier Conductor execute mode dispatches. -- `parameters`: input parameter map passed to the capability at Job materialisation. -- `dependsOn`: optional reference to a prior step name. The step is not eligible - for execution until the referenced step has reached Succeeded state. -- `haltOnFailure`: boolean. When true, any failure on this step terminates the - entire RunnerConfig with terminal condition Failed and no further steps execute. +### compiler maintenance -A RunnerConfig with a single entry in the steps list is the degenerate case of this -model - not a separate model. All RunnerConfigs use the steps list, even single-step -operations. +Produces a MaintenanceBundle CR that pre-encodes all scheduling context required for a maintenance operation. Neither Platform nor Conductor need to perform cluster queries at execution time when consuming a MaintenanceBundle. -### Step Sequencer Responsibility +Flags: `--operation `, `--cluster `, `--output `, `--talosconfig `, `--kubeconfig `. -Conductor execute mode is the sole authority over step-to-step progression. It -materialises one Job at a time in declared order. The sequencer: +Cluster access resolution order: explicit flag, then environment variable (TALOSCONFIG, KUBECONFIG), then conventional path. -1. Scans the steps list for the first step that is eligible: its `dependsOn` step - (if any) has reached Succeeded, and the step itself has not yet reached a - terminal state. -2. Materialises the Kueue Job for that step using the step's capability and parameter - fields. -3. Monitors the Job for terminal state (Succeeded or Failed). -4. On Job completion, harvests the structured output from the well-known ConfigMap - named after the Job in ont-system. -5. Writes a `StepResult` entry into RunnerConfig status for that step. -6. Evaluates terminal conditions and either advances to the next eligible step or - writes the terminal RunnerConfig condition. +Pre-encoded context written into the MaintenanceBundle CR: +- `maintenanceTargetNodes`: validated against the live cluster at compile time. Nodes that do not exist cause a compile-time failure. +- `operatorLeaderNode`: resolved live from the `platform-leader` Lease in `seam-system`. Fails fast if the lease does not exist or has no holder. +- `s3ConfigSecretRef`: validated to exist before the CR is committed. Fails fast if absent. +- `operation`: one of `drain`, `upgrade`, `etcd-backup`, or `machineconfig-rotation`. -The owning operator never drives step-to-step progression. The owning operator -never submits individual Jobs. This boundary is permanent and locked. +### compiler component -### StepResult +Emits RBACProfile CR YAML for third-party components. Guardian's admission webhook enforces what RBACProfiles declare -- it never generates them. This subcommand is the exclusive authorship path for third-party RBACProfiles. -Each completed step produces one StepResult entry in RunnerConfig status: +Signature: `compiler component [--component ...] [--descriptor ] [--discover] [--output ]` -- `stepName`: matches the step name declared in spec. -- `phase`: lifecycle state - `Pending`, `Running`, `Succeeded`, or `Failed`. -- `outputRef`: reference to the ConfigMap in ont-system from which the result was - harvested. ConfigMap is named after the Job. It is garbage-collected after TTL. -- `result`: raw JSON payload - the structured OperationResult document from the - ConfigMap. Conductor writes this verbatim without semantic interpretation. +**Catalog mode:** Compiler ships with a versioned embedded catalog at `internal/catalog/` for known ecosystem components. No cluster connectivity required. -### Terminal Conditions +Current catalog entries: -RunnerConfig reaches one of two terminal conditions: +| Component | RBACProfile name | +|-----------|-----------------| +| cilium | rbac-cilium | +| cnpg | rbac-cnpg | +| kueue | rbac-kueue | +| cert-manager | rbac-cert-manager | +| local-path-provisioner | rbac-local-path-provisioner | -- `Completed`: all steps in the steps list reached Succeeded state. -- `Failed`: any step reached Failed state with `haltOnFailure: true`, or the final - step failed. +**Custom mode:** For components not in the catalog, provide a component descriptor via `--descriptor `. The optional `--discover` flag enables cluster connectivity to auto-detect deployed third-party resources. -Once a terminal condition is written, the RunnerConfig is inert. No further Jobs -are submitted. The owning operator reads the terminal condition and step results -from status to perform semantic interpretation and downstream action in its own -reconciliation loop. +New catalog entries require a canonical RBACProfile definition reviewed by the Platform Governor before merge. -### Boundary Contract +### compiler domain -**Conductor harvests and records only.** Conductor never interprets what a step -result means for the domain. It does not know whether a step failure is retryable. -It does not know whether a partial completion is acceptable. It writes StepResult -entries and terminal conditions - nothing more. +Reserved. Sovereign Domain surface not yet implemented. + +--- -**The owning operator interprets only.** The owning operator watches RunnerConfig -status for the terminal condition. It reads StepResult entries. It decides what the -results mean and what downstream actions to take. It never submits Jobs directly. -It never calls into Conductor. The operator–Conductor contract is mediated entirely -by RunnerConfig spec and status. +## 19. Operational Readiness Gates -This boundary is permanent and locked. No future implementation work may blur it -without a Governor constitutional amendment. +Platform operator is the sole owner of Talos apid port 50000 reachability validation across both native and CAPI cluster paths. No other operator and no Conductor capability handler performs port 50000 validation. This is a locked invariant. + +Guardian, Dispatcher, Conductor execute mode, and Conductor agent mode never perform port 50000 validation under any circumstance. Adding port 50000 validation to any component other than Platform operator requires a Platform Governor constitutional amendment. --- -## 18. Federation Channel Contract +## 20. Conductor Dockerfile Standards -**LOCKED INVARIANT - Platform Governor directive 2026-04-05.** +**Compiler Dockerfile (Dockerfile.compiler):** -The Conductor agent process on the management cluster exposes two listener ports. +Build pattern: golang:1.25 builder stage with build tag `-tags compiler`. Final stage: debian:12-slim. Includes bash, curl, jq, python3, openssl, psql, helm binary, kubectl, and the compiled Compiler binary. USER 65532:65532. No package manager retained in the final image. -**Internal port:** -Serves cluster-local callers using cluster-internal certificate authority trust. -All local components - operators, Guardian, Compiler tools during bootstrap - call -this port. Environment variable: `INTERNAL_PORT`. - -**Federation port:** -Environment variable: `FEDERATION_PORT`, defaulting to `9443`. Serves tenant -Conductor connections exclusively. The TLS configuration on the federation port uses -management CA-issued client certificates with the connecting tenant's cluster ID -embedded as Subject Alternative Name. The federation port rejects any connection -that cannot present a valid management CA certificate. No other caller type may -connect to the federation port. - -**Stream model:** -One persistent bidirectional gRPC stream per connected tenant Conductor. The stream -is established by the tenant Conductor; the management Conductor accepts it. The -management side is stateless with respect to connection lifecycle - it does not -initiate connections and does not maintain expectations about which tenants are -connected at any given time. +**Conductor agent Dockerfile (Dockerfile.agent):** -**Typed message envelope:** -All messages on the stream carry a typed envelope. The following message types form -the stable contract: - -| Message | Direction | Purpose | -|--------------------------------|-------------------------|----------------------------------------------------------------------| -| RunnerConfigValidationRequest | tenant → management | Validate RunnerConfig parameters before Job materialisation | -| RunnerConfigValidationResponse | management → tenant | Validation result: approved or rejected with structured reason | -| AuditEventBatch | tenant → management | Batch of sequenced audit events for management AuditSink | -| AuditEventAck | management → tenant | Acknowledgement of received audit batch by sequence number | -| RevocationPush | management → tenant | Management-initiated revocation of a PackInstance or PermissionSnapshot | -| HeartBeat | bidirectional | Liveness signal, sent by both sides at 30-second intervals | - -RevocationPush messages are sent management → tenant without waiting for the tenant -to initiate. This is the architectural justification for a persistent bidirectional -stream over individual RPCs: the management side must be able to push revocations -to tenants without the tenant polling. A stateless request-response model cannot -satisfy this requirement. - -**Heartbeat discipline:** -Both sides send HeartBeat messages at 30-second intervals. Three consecutive missed -HeartBeat acknowledgments from a tenant marks that channel degraded in management -status tracking. Management records the degraded state in RunnerConfig status for -the affected tenant - the state is observable but not actionable by the management -side. Reconnect responsibility belongs entirely to the tenant Conductor. - -**Tenant Conductor behavior on channel degradation:** -- Buffers audit events in a local write-ahead buffer backed by a PVC in ont-system. -- Fails RunnerConfig validation calls closed - no validation is attempted without - an active management channel stream. -- Emits `FederationChannelDegraded` condition on its local RunnerConfig status. - -**Tenant Conductor behavior on reconnect:** -Replays unacknowledged audit events starting from the last acknowledged sequence -number. The management AuditSink deduplicates received events on sequence number - -replays are safe and expected. RunnerConfig validation calls resume immediately on -stream re-establishment. - -**Ownership boundary:** -The federation channel is a Conductor concern exclusively. Guardian on both the -management cluster and tenant clusters is a consumer and producer of channel -contents - Guardian produces audit events and receives revocations - but Guardian -does not own the channel, does not configure it, and does not monitor its health. -Guardian health and federation channel health are independent. - -Tenant Guardian running role=management (sovereign mode) has no federation channel -relationship with the management Guardian. A sovereign tenant Guardian is fully -independent - no audit forwarding, no cross-cluster identity federation unless an -explicit `federated-downstream` IdentityProvider CR is authored by a human. The -tenant Conductor for a sovereign tenant cluster still connects to the management -Conductor federation port for RunnerConfig validation - the channel is always -Conductor-to-Conductor, never Guardian-to-Guardian. - -This is a locked invariant. The typed message envelope contract is stable. Adding -new message types requires a Platform Governor directive before implementation. +Build pattern: golang:1.25 builder stage with build tag `-tags conductor`. Compile-mode clients are excluded at build time. Final stage: gcr.io/distroless/base:nonroot. USER 65532:65532. No shell. No package manager. + +**Conductor execute Dockerfile (Dockerfile.execute):** + +Build pattern: golang:1.25 builder stage with build tag `-tags conductor`. Final stage: debian:12-slim with minimal runtime dependencies for S3 and TLS operations. USER 65532:65532. --- -*Conductor behavioral schema - conductor repository* -*CRD type schema authority: seam-core (seam.ontai.dev). Supersedes runner.ontai.dev. Decision G, Phase 2B 2026-04-25.* -*Amendments appended below with date and rationale.* - -2026-03-30 - Two-binary model adopted. Compiler confined to compile mode (debian). - Conductor owns execute and agent modes (distroless). runnerImage field renamed to - agentImage on RunnerConfig. Execute mode Jobs confirmed as pure Go - no system - binaries required. Signing and verification model added to agent - responsibilities. CR-INV-009 through CR-INV-010 merged into root CLAUDE.md as - INV-022 through INV-026. - -2026-03-30 - Capability table updated with Triggering CRD column (Path B ruling). - talos-upgrade, kube-upgrade, stack-upgrade, node-scale-up, node-decommission, and - node-reboot confirmed retained. Triggering CRDs are active when TalosCluster - spec.capi.enabled=false only. For capi.enabled=true target clusters CAPI handles - these operations natively. Orphaned-constant finding closed - these six capability - constants are not orphaned. - -2026-04-03 - Binary rename throughout: conductor → Compiler, conductor → Conductor. - Repository renamed conductor (was conductor). Module path updated to - github.com/ontai-dev/conductor. Section 11 Enterprise License Enforcement removed - entirely - Seam is fully open source with no licensing tier; replaced with single - sentence. All licensing references removed from RunnerConfig spec and status fields: - licenseSecretRef removed, licenseStatus removed, LicenseConstraint condition removed. - Agent startup sequence step 3 (license check) removed; steps renumbered. Section 9 - pack compilation corrected: removed erroneous description of pack-compile as Kueue - Job triggered by Wrapper; pack-compile is a Compiler invocation mode only. - Operator name references updated: Platform (formerly platform), Guardian - (formerly guardian), Wrapper (formerly wrapper). Capability table updated - to reference consolidated day-two CRDs: UpgradePolicy, NodeOperation, - EtcdMaintenance, NodeMaintenance, PKIRotation, ClusterReset, HardeningProfile. - -2026-04-05 - Two locked Governor directives added. Section 12 "Operational Readiness - Gates": Platform operator is the sole owner of port 50000 Talos API reachability - validation across native and CAPI paths; Screen and all other components are - permanently excluded. Section 13 "RunnerConfig Self-Operation Contract": three new - first-class scheduling fields added to RunnerConfig spec (maintenanceTargetNodes, - operatorLeaderNode, selfOperation); Conductor execute mode applies NotIn node - affinity constraints when selfOperation=true; skips exclusion when selfOperation=false; - agent mode acts as recovery path only. Dockerfile Standards renumbered to Section 14. - -2026-04-05 - compiler maintenance subcommand added to Section 9. Produces - MaintenanceBundle CR with pre-encoded scheduling context: maintenanceTargetNodes - (validated node set), operatorLeaderNode (resolved from platform-leader Lease), - s3ConfigSecretRef (validated at compile time per platform-schema.md §10), - operation (drain/upgrade/etcd-backup/machineconfig-rotation). Cluster access - resolved from flag → env var → conventional path, consistent with launch and enable. - Fails fast if leader lease absent, nodes invalid, or S3 Secret missing. - MaintenanceBundle CRD definition deferred to Platform Schema Engineer session (F-P5). - -2026-04-05 - Three locked Governor directives added. (1) Management Cluster Bootstrap - Sequence Authority added to Section 9: bootstrap/launch/enable owned exclusively by - Compiler in three steps; Platform has no involvement; Compiler never applies - resources; compiler enable stamps role=management on Conductor Deployment. Canonical - Compiler Command Surface table added (bootstrap, launch, enable, packbuild, - maintenance, component, domain). (2) Section 15 "Role Declaration Contract" added: - Conductor reads role field (management or tenant) stamped externally at Deployment - creation; compiler enable stamps management; Platform operator stamps tenant; - Conductor never infers its own role; absent/unrecognized role causes InvariantViolation - exit. (3) Section 16 "compiler component Subcommand" added: emits RBACProfile CRs - for third-party components; catalog mode (embedded versioned catalog: Cilium, CNPG, - Kueue, cert-manager, local-path-provisioner) and custom mode (--descriptor flag, - optional --discover for cluster auto-detect); catalog at internal/catalog/; - F-P6 open finding for implementation. - -2026-04-05 - Section 17 "RunnerConfig Execution Model" added as locked Governor - decision. RunnerConfig is a multi-step operation intent: spec carries a steps list - (name, capability, parameters, dependsOn, haltOnFailure). Conductor execute mode is - the sole authority over step-to-step progression - materialises one Job at a time, - harvests OperationResult ConfigMap from ont-system on completion, writes StepResult - (stepName, phase, outputRef, result payload) into RunnerConfig status. Terminal - conditions: Completed (all steps succeeded) and Failed (halt-on-failure semantics). - Owning operator watches terminal condition and interprets step results - never drives - progression. Conductor harvests and records only - never interprets. Boundary is - permanent and locked. F-P7 added to CONTEXT.md: all existing platform day-2 - reconcilers must migrate from single-capability RunnerConfig to step list model. - -2026-04-05 - Two locked Governor directives added. (1) §9 compiler enable bundle - restructured to six phases: phase 0 (00-infrastructure-dependencies: CNPG operator - manifests + CNPG Cluster CR for management Guardian, readiness gate: CNPG Cluster - ready), phase 1 (01-guardian-bootstrap), phases 2-5 are the existing phases - (02-guardian-deploy, 03-platform-wrapper, 04-conductor, 05-post-bootstrap). Phase 0 - resolves the CNPG dependency before Guardian is deployed; Guardian startup migration - runner connects to CNPG before registering any controller. F-P8 recorded: phase 0 - implementation requires a Conductor Engineer session. (2) §18 "Federation Channel - Contract" added (locked invariant): management Conductor exposes two ports - internal - (cluster-local CA) and federation (FEDERATION_PORT=9443, management CA client certs, - cluster ID as SAN, rejects invalid certs). One persistent bidirectional gRPC stream - per connected tenant Conductor; management side stateless on connection lifecycle; - tenant Conductor owns reconnect. Typed message envelope: RunnerConfigValidationRequest/ - Response, AuditEventBatch, AuditEventAck, RevocationPush, HeartBeat. RevocationPush - is management-initiated - architectural justification for persistent stream over RPCs. - Heartbeat: 30s interval; 3 missed ACKs = channel degraded in management RunnerConfig - status. Tenant on degradation: WAL PVC buffer in ont-system, validation calls fail - closed, FederationChannelDegraded condition. On reconnect: replay from last acked - sequence; AuditSink deduplicates. Federation channel is Conductor-Conductor exclusively; - Guardian is a consumer/producer, not an owner. Sovereign tenants (role=management - Guardian) have no Guardian-to-Guardian federation relationship; Conductor channel - remains. Adding message envelope types requires a Governor directive. - -2026-04-26 - §15 Role Declaration Contract extended: import-mode tenant onboarding - gRPC handshake sequence documented. Tenant Conductor (role=tenant) dials MGMT_ENDPOINT - immediately after leader election; management Conductor validates tenant identity and - pushes initial PermissionSnapshot (management-policy, management-maximum, cluster-maximum); - tenant Conductor verifies guardian signature (INV-026); installs signed artifacts in - ont-system; writes PermissionSnapshotReceipt; acknowledges to management Conductor. - Management Conductor writes acknowledged condition to InfrastructureTalosCluster status. - Admission webhook transitions from audit to strict mode only after PermissionSnapshotReceipt - is written. Cross-referenced to guardian-schema.md §20 and platform-schema.md §12. +## Decision Records + +**Decision: Two-binary model.** Compiler confined to compile mode (debian-slim). Conductor owns execute and agent modes (distroless agent, debian-slim execute). Helm goclient and Kustomize goclient excluded from Conductor at build time via Go build tags. + +**Decision: Mode boundary is absolute.** Compile mode attempted on the Conductor binary causes immediate InvariantViolation exit before any other initialization proceeds. No exception path exists. + +**Decision: Capability manifest is self-declared.** The agent writes its own capability list to RunnerConfig status on startup. Operators never hardcode capability availability. This ensures operators remain correct across Conductor upgrades without any operator code changes. + +**Decision: RunnerConfig is a multi-step intent.** A RunnerConfig carries a steps list, not a single capability invocation. The sequencer in execute mode is the sole authority over step-to-step progression. The owning operator never submits individual Jobs. This boundary is permanent. + +**Decision: Signing is management-only.** The management cluster Conductor holds the signing key and signs PackInstance and PermissionSnapshot CRs. Target cluster Conductors verify but never sign. Verification failure is fatal to receipt acknowledgement. + +**Decision: Federation channel is Conductor-to-Conductor.** Guardian is a consumer and producer of channel contents but does not own the channel. Sovereign tenant Conductors still connect to the management Conductor federation port. Guardian-to-Guardian federation is not a channel concept. + +**Decision: Role is externally stamped.** Conductor never infers its own role from cluster state. The role field (management or tenant) is written at Deployment creation time by compiler enable (management) or Platform operator (tenant). An absent or unrecognized role value causes InvariantViolation exit. + +**Decision: seam owns all CRD type definitions.** RunnerConfig, DriftSignal, LineageRecord, SeamMembership, and all cross-operator CRD types are declared under `seam.ontai.dev/v1alpha1`. Conductor imports these types; it does not define them. CRD changes require a seam PR first. (root Decision 13, root INV-010) From 312c43737837f12e2dc3db0fe591adf281416747 Mon Sep 17 00:00:00 2001 From: ontave Date: Sun, 17 May 2026 23:36:56 +0200 Subject: [PATCH 13/13] fix: update CI workflow (seam-core->seam, wrapper->dispatcher, add seam-sdk/conductor-sdk); fix integration test GVR and CRD for RunnerConfig post-migration --- .github/workflows/ci.yaml | 24 +- ...ontai.dev_infrastructurerunnerconfigs.yaml | 2 - config/crd/seam.ontai.dev_runnerconfigs.yaml | 323 ++++++++++++++++++ test/integration/suite_test.go | 6 +- 4 files changed, 344 insertions(+), 11 deletions(-) delete mode 100644 config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml create mode 100644 config/crd/seam.ontai.dev_runnerconfigs.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2eaa05c..62e1aa9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,17 +27,29 @@ jobs: repository: ontai-dev/platform path: platform - - name: Checkout seam-core (replace dep) + - name: Checkout seam (replace dep) uses: actions/checkout@v4 with: - repository: ontai-dev/seam-core - path: seam-core + repository: ontai-dev/seam + path: seam - - name: Checkout wrapper (replace dep) + - name: Checkout seam-sdk (replace dep) uses: actions/checkout@v4 with: - repository: ontai-dev/wrapper - path: wrapper + repository: ontai-dev/seam-sdk + path: seam-sdk + + - name: Checkout dispatcher (replace dep) + uses: actions/checkout@v4 + with: + repository: ontai-dev/dispatcher + path: dispatcher + + - name: Checkout conductor-sdk (replace dep) + uses: actions/checkout@v4 + with: + repository: ontai-dev/conductor-sdk + path: conductor-sdk - name: Set up Go uses: actions/setup-go@v5 diff --git a/config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml b/config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml deleted file mode 100644 index 96af633..0000000 --- a/config/crd/infrastructure.ontai.dev_infrastructurerunnerconfigs.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Tombstone: InfrastructureRunnerConfig migrated to RunnerConfig under seam.ontai.dev (MIGRATION-3.8). -# Conductor CRDs are bundled from seam-core/config/crd. See seam.ontai.dev_runnerconfigs.yaml. diff --git a/config/crd/seam.ontai.dev_runnerconfigs.yaml b/config/crd/seam.ontai.dev_runnerconfigs.yaml new file mode 100644 index 0000000..094bf6e --- /dev/null +++ b/config/crd/seam.ontai.dev_runnerconfigs.yaml @@ -0,0 +1,323 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: runnerconfigs.seam.ontai.dev +spec: + group: seam.ontai.dev + names: + kind: RunnerConfig + listKind: RunnerConfigList + plural: runnerconfigs + shortNames: + - rc + singular: runnerconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.clusterRef + name: Cluster + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + RunnerConfig is the seam-core CRD for Conductor agent runtime configuration. + Owned by seam-core; authored exclusively by the platform operator. INV-009. + conductor-schema.md. MIGRATION-3.8. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + RunnerConfigSpec is the operator-generated operational contract for a + specific cluster. Generated at runtime by platform using the runner shared library. + Never human-authored. INV-009, INV-010. conductor-schema.md. + properties: + clusterRef: + description: ClusterRef is the name of the TalosCluster this RunnerConfig + is authoritative for. + type: string + maintenanceTargetNodes: + description: MaintenanceTargetNodes is the list of node names that + are the subject of the operation. + items: + type: string + type: array + operationalHistory: + description: OperationalHistory is an append-only record of completed + RunnerConfig executions. + items: + description: |- + RunnerOperationalHistoryEntry is a single append-only audit record describing one + configuration change applied to this RunnerConfig. Never truncated. + properties: + appliedAt: + description: AppliedAt is the time this change was applied. + format: date-time + type: string + appliedBy: + description: AppliedBy identifies who applied the change. + type: string + concern: + description: Concern identifies what aspect of configuration + changed. + type: string + newValue: + description: NewValue is the value after the change. + type: string + previousValue: + description: PreviousValue is the value before the change. Empty + for initial entries. + type: string + required: + - appliedAt + - appliedBy + - concern + - newValue + type: object + type: array + operatorLeaderNode: + description: OperatorLeaderNode is the node hosting the leader pod + of the initiating operator. + type: string + phases: + description: Phases is the ordered list of operational phases for + this cluster's Conductor lifecycle. + items: + description: RunnerPhaseConfig carries per-phase parameters for + the runner's execution context. + properties: + name: + description: Name identifies the phase. + type: string + parameters: + additionalProperties: + type: string + description: Parameters holds phase-specific key-value configuration. + type: object + required: + - name + type: object + type: array + runnerImage: + description: |- + RunnerImage is the fully qualified container image reference for the Conductor agent. + Tag convention: v{talosVersion}-r{revision} stable, dev/dev-rc{N} development. INV-011. + type: string + selfOperation: + description: SelfOperation is true when the Job's execution cluster + and the target cluster are the same. + type: boolean + steps: + description: Steps is the ordered list of execution steps across all + phases. + items: + description: RunnerConfigStep declares one step in a multi-step + operation intent. + properties: + capability: + description: Capability is the named Conductor capability to + invoke for this step. + type: string + dependsOn: + description: DependsOn is the name of a prior step that must + complete before this step begins. + type: string + haltOnFailure: + description: |- + HaltOnFailure controls sequencer behaviour when this step fails. + When true, failure terminates the RunnerConfig with no further steps executing. + type: boolean + name: + description: Name is the unique identifier for this step within + the RunnerConfig. + type: string + parameters: + additionalProperties: + type: string + description: Parameters is the input parameter map passed to + the capability at Job materialisation time. + type: object + required: + - capability + - name + type: object + type: array + required: + - clusterRef + - runnerImage + type: object + status: + description: |- + RunnerConfigStatus is written exclusively by the Conductor agent leader. + CR-INV-006. + properties: + agentLeader: + description: AgentLeader is the pod name of the current Conductor + agent leader. + type: string + agentVersion: + description: AgentVersion is the version string of the Conductor agent + binary currently running. + type: string + capabilities: + description: |- + Capabilities is the self-declared capability manifest emitted by the Conductor agent on startup. + CR-INV-005. + items: + description: RunnerCapabilityEntry is one capability declared by + the Conductor agent on startup. + properties: + description: + description: Description is a human-readable description of + what this capability does. + type: string + name: + description: Name is the capability name (e.g., pack-deploy, + talos-upgrade). + type: string + version: + description: Version is the capability version declared by the + agent. + type: string + required: + - name + - version + type: object + type: array + conditions: + description: Conditions is the standard Kubernetes condition list + for this RunnerConfig. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + failedStep: + description: |- + FailedStep is the name of the first step that reached the Failed phase. + Present only when Phase="Failed". conductor-schema.md §17. + type: string + phase: + description: |- + Phase is the terminal execution phase written by Conductor execute mode. + "Completed" means all steps succeeded. "Failed" means at least one step failed. + Empty means execution is in progress. Platform operators watch this field to + detect terminal conditions without scanning StepResults. conductor-schema.md §17. + type: string + stepResults: + description: StepResults is the ordered list of step result records + written by Conductor execute mode. + items: + description: RunnerConfigStepResult is the status record for one + step. + properties: + completedAt: + description: CompletedAt is the time this step finished execution. + format: date-time + type: string + message: + description: Message is additional context about the step outcome. + type: string + name: + description: Name matches the Name field of the corresponding + RunnerConfigStep in spec. + type: string + startedAt: + description: StartedAt is the time this step began execution. + format: date-time + type: string + status: + allOf: + - enum: + - Succeeded + - Failed + - Skipped + - enum: + - Succeeded + - Failed + - Skipped + description: Status is the terminal status of this step execution. + type: string + required: + - name + - status + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/test/integration/suite_test.go b/test/integration/suite_test.go index cb66a3c..aa1e670 100644 --- a/test/integration/suite_test.go +++ b/test/integration/suite_test.go @@ -40,9 +40,9 @@ import ( // in runnerlib are plain Go structs (not controller-runtime managed objects), // so all CRD interactions use unstructured.Unstructured. var runnerConfigGVR = schema.GroupVersionResource{ - Group: "infrastructure.ontai.dev", + Group: "seam.ontai.dev", Version: "v1alpha1", - Resource: "infrastructurerunnerconfigs", + Resource: "runnerconfigs", } var ( @@ -112,7 +112,7 @@ func createRunnerConfig(ctx context.Context, t *testing.T, ns, name string, spec obj := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": "infrastructure.ontai.dev/v1alpha1", + "apiVersion": "seam.ontai.dev/v1alpha1", "kind": "RunnerConfig", "metadata": map[string]interface{}{ "name": name,