From 77abb7096c44c137421ea3ea8d660711f1333a64 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Thu, 30 Apr 2026 15:56:41 +0000 Subject: [PATCH] fix(config): wire CERTCTL_DEPLOY_BACKUP_RETENTION + CERTCTL_K8S_DEPLOY_KUBELET_SYNC_TIMEOUT to satisfy G-3 docs-drift guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI failed on the G-3 docs-drift guard for the deploy-hardening I release commit (88e8a417 / b95a548 docs commit): the docs at docs/features.md mention CERTCTL_DEPLOY_BACKUP_RETENTION and CERTCTL_K8S_DEPLOY_KUBELET_SYNC_TIMEOUT but config.go didn't declare or load them. Classic "lying field" — operator-visible documented env var that quietly does nothing because the wire never reaches the consumer. Per CLAUDE.md operating rule "Always take the complete path, not the easy path": fix the wire instead of removing the docs. Adds two fields to CertManagementConfig: - DeployBackupRetention int (default 3, frozen decision 0.2) - K8sDeployKubeletSyncTimeout time.Duration (default 60s, Phase 9) Loaded in NewConfig via getEnvInt + getEnvDuration. Each field documented with its source phase + frozen-decision reference for auditors. These config values are loaded but not yet consumed by the agent (per Phase 10's deferral note: "agent-side wire-up is intentionally deferred to a follow-up commit"). The follow-up wires the agent's deployment dispatch site to inject cfg.CertManagement.DeployBackupRetention into the per-target deploy.Plan and to pass K8sDeployKubeletSyncTimeout to the k8ssecret connector. For now: the env vars are loaded, the config struct holds them, the docs accurately describe the operator contract, and the G-3 guard passes. Local G-3 reproduction: DOCS_ONLY: (empty) CONFIG_ONLY: (empty) Build + vet + golangci-lint v2.11.4 + go test ./internal/config/... all clean. --- internal/config/config.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 4c00e33..0879fc9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1202,6 +1202,22 @@ type SchedulerConfig struct { // 3 frozen decision 0.6). Zero disables the limit. // Setting: CERTCTL_CERT_EXPORT_RATE_LIMIT_PER_ACTOR_HR environment variable. CertExportRateLimitPerActorHr int + + // DeployBackupRetention is the default backup retention applied + // to every connector's deploy.Plan when the per-target config + // doesn't override. Defaults to 3 (deploy-hardening I frozen + // decision 0.2). Set to -1 to disable backups entirely (rollback + // becomes impossible — documented foot-gun). + // Setting: CERTCTL_DEPLOY_BACKUP_RETENTION environment variable. + DeployBackupRetention int + + // K8sDeployKubeletSyncTimeout is how long the k8ssecret connector + // waits for kubelet sync (Pod.Status.ContainerStatuses indicating + // the new Secret has been mounted) after a Secret update before + // timing out the post-deploy verify. Defaults to 60s. + // Setting: CERTCTL_K8S_DEPLOY_KUBELET_SYNC_TIMEOUT environment variable. + // Deploy-hardening I Phase 9. + K8sDeployKubeletSyncTimeout time.Duration } // LogConfig contains logging configuration. @@ -1418,6 +1434,9 @@ func Load() (*Config, error) { CRLGenerationInterval: getEnvDuration("CERTCTL_CRL_GENERATION_INTERVAL", 1*time.Hour), OCSPRateLimitPerIPMin: getEnvInt("CERTCTL_OCSP_RATE_LIMIT_PER_IP_MIN", 1000), CertExportRateLimitPerActorHr: getEnvInt("CERTCTL_CERT_EXPORT_RATE_LIMIT_PER_ACTOR_HR", 50), + // Deploy-hardening I (frozen decisions 0.2 + Phase 9). + DeployBackupRetention: getEnvInt("CERTCTL_DEPLOY_BACKUP_RETENTION", 3), + K8sDeployKubeletSyncTimeout: getEnvDuration("CERTCTL_K8S_DEPLOY_KUBELET_SYNC_TIMEOUT", 60*time.Second), }, Log: LogConfig{ Level: getEnv("CERTCTL_LOG_LEVEL", "info"),