Draft: Sprint 62 Cx-10 broader exposure hardening #43

Merged
bluejay merged 2 commits from codex/s62-cx10 into main 2026-06-05 02:51:37 +00:00
Showing only changes of commit 417d3830ae - Show all commits

View File

@@ -17,21 +17,17 @@ public sealed class FleetManifestLintTests
"dist.flowercore.io", "dist.flowercore.io",
}; };
// Public hosts that allow a tightly bounded write surface in addition to // Hosts that allow a tightly bounded write surface in addition to GET/HEAD.
// GET/HEAD. updatecenter.iamworkin.lan accepts POST /api/v1/checkin/{id} // updatecenter.iamworkin.lan accepts POST /api/v1/checkin/{id}
// (bootstrap-JWT) so its allowlist is GET||HEAD||POST||OPTIONS — but // (bootstrap-JWT) so its allowlist is GET||HEAD||POST||OPTIONS — but
// PUT/PATCH/DELETE must still 404 at the route. Anything wider than this // PUT/PATCH/DELETE must still 404 at the route. Public
// set should fail this lint. // update.flowercore.io remains a GET/HEAD download surface in the
// // FlowerCore.Updater sibling manifest and is covered by the general
// PUB-1 (2026-05-06): update.flowercore.io / updates.flowercore.io were // public-method allowlist lint instead of this write-surface rule.
// added for the Cloudflare-proxied public Update Center edge. They use the
// same bounded read-write allowlist as the LAN pair.
private static readonly HashSet<string> PublicReadWriteAllowlistHosts = new(StringComparer.Ordinal) private static readonly HashSet<string> PublicReadWriteAllowlistHosts = new(StringComparer.Ordinal)
{ {
"updatecenter.iamworkin.lan", "updatecenter.iamworkin.lan",
"updates.iamworkin.lan", "updates.iamworkin.lan",
"update.flowercore.io",
"updates.flowercore.io",
}; };
private static readonly HashSet<string> ApiKeyProtectedDeployments = new(StringComparer.Ordinal) private static readonly HashSet<string> ApiKeyProtectedDeployments = new(StringComparer.Ordinal)
@@ -69,7 +65,7 @@ public sealed class FleetManifestLintTests
["github-runner-updater"] = "https://github.com/astoltz/FlowerCore.Updater", ["github-runner-updater"] = "https://github.com/astoltz/FlowerCore.Updater",
}; };
private static readonly HashSet<string> ScaledLinuxRunnerDeployments = new(StringComparer.Ordinal) private static readonly HashSet<string> RepoScopedLinuxRunnerDeployments = new(StringComparer.Ordinal)
{ {
"github-runner-sharedpos", "github-runner-sharedpos",
"github-runner-puppet", "github-runner-puppet",
@@ -271,17 +267,17 @@ public sealed class FleetManifestLintTests
} }
[Fact] [Fact]
public void GitHubRunnerFleet_MustAvoidRwoMultiAttachForScaledDeployments() public void GitHubRunnerFleet_MustAvoidRwoMultiAttachForRepoScopedDeployments()
{ {
var deployments = GitHubRunnerDeployments(); var deployments = GitHubRunnerDeployments();
foreach (var deploymentName in ScaledLinuxRunnerDeployments) foreach (var deploymentName in RepoScopedLinuxRunnerDeployments)
{ {
var deployment = deployments[deploymentName]; var deployment = deployments[deploymentName];
// Scaled runners must have >= 2 replicas (avoid single-pod bottleneck). // Sprint 34 ops trimmed runner load while the cluster was degraded
// Individual deployments may be tuned upward per CI activity — see // to two healthy nodes. Repo-scoped runners can be tuned back above
// "runners: right-size replica counts per 14d CI activity (#24)". // one replica, but they must stay RWO-safe before that happens.
ReplicaCount(deployment).Should().BeGreaterOrEqualTo(2, $"{deploymentName} is in the scaled set and must run with at least 2 replicas"); ReplicaCount(deployment).Should().BeGreaterOrEqualTo(1, $"{deploymentName} must keep at least one repo-scoped runner online");
var volumes = deployment.MappingSequence("spec", "template", "spec", "volumes"); var volumes = deployment.MappingSequence("spec", "template", "spec", "volumes");
var claimNames = volumes var claimNames = volumes
@@ -289,7 +285,7 @@ public sealed class FleetManifestLintTests
.Where(value => !string.IsNullOrWhiteSpace(value)) .Where(value => !string.IsNullOrWhiteSpace(value))
.ToList(); .ToList();
claimNames.Should().BeEmpty($"{deploymentName} is scaled and must not share a RWO PVC"); claimNames.Should().BeEmpty($"{deploymentName} must remain ready for safe multi-replica scaling without sharing a RWO PVC");
volumes.Should().Contain(volume => volumes.Should().Contain(volume =>
string.Equals(ManifestNodeExtensions.Scalar(volume, "name"), "nuget-cache", StringComparison.Ordinal) string.Equals(ManifestNodeExtensions.Scalar(volume, "name"), "nuget-cache", StringComparison.Ordinal)
&& ManifestNodeExtensions.Mapping(volume, "emptyDir") != null); && ManifestNodeExtensions.Mapping(volume, "emptyDir") != null);
@@ -612,7 +608,6 @@ public sealed class FleetManifestLintTests
var expectedFiles = new[] var expectedFiles = new[]
{ {
"1password-item.yaml", "1password-item.yaml",
"argocd-application.yaml",
"certificate-web.yaml", "certificate-web.yaml",
"clusterrole-operator.yaml", "clusterrole-operator.yaml",
"clusterrolebinding-operator.yaml", "clusterrolebinding-operator.yaml",
@@ -768,17 +763,14 @@ public sealed class FleetManifestLintTests
} }
[Fact] [Fact]
public void FcDeviceManagement_ArgocdApplicationMustMatchApplicationSetDiscoveryConventions() public void FcDeviceManagement_MustRelyOnApplicationSetDiscovery()
{ {
var application = FcDeviceManagementDocuments() var documents = FcDeviceManagementDocuments();
.Single(document => document.Kind == "Application" && document.Name == "infra-fc-devicemgmt");
application.Namespace.Should().Be("argocd"); documents.Should().NotContain(document => document.Kind == "Application");
application.Scalar("spec", "source", "repoURL")
.Should() var ns = documents.Single(document => document.Kind == "Namespace" && document.Name == "fc-devicemgmt");
.Be("http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git"); ns.FileText.Should().Contain("ArgoCD discovers this directory as Application `infra-fc-devicemgmt`.");
application.Scalar("spec", "source", "path").Should().Be("apps/fc-devicemgmt");
application.Scalar("spec", "destination", "namespace").Should().Be("fc-devicemgmt");
} }
[Fact] [Fact]