From 44e10fe7a1838d1f80350aeb2845b31e38bfc5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Stephan=20Mu=C3=B1oz?= Date: Fri, 2 Oct 2026 16:25:54 +0200 Subject: [PATCH] Coordinate governance rollout --- README.md | 10 ++++++ cmd/http_transport_test.go | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/README.md b/README.md index c3888c5..ebfe71d 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,16 @@ go test ./... ## Architecture +### Governance and effective manifests + +Ownership, workspace roles, and Agency approvals are enforced in `cc-server`. +Pending or rejected proposals never change the effective job manifest. After +independent approval, the listener returns the committed definition through the +existing HTTPS v2 poll protocol. Approved disable/delete operations remove the +job on the next successful poll; previously installed cron remains in effect +while a proposal is pending or the gateway is unreachable. No agent upgrade or +new protocol is required for this governance rollout. + ``` ┌─────────────────────────────────────────────────────────┐ │ Host/Container │ diff --git a/cmd/http_transport_test.go b/cmd/http_transport_test.go index 86ab3ac..b009ae6 100644 --- a/cmd/http_transport_test.go +++ b/cmd/http_transport_test.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "sync" "testing" "time" @@ -30,6 +31,70 @@ func TestNormalizeServerURLRequiresExplicitInsecureDevelopmentMode(t *testing.T) } } +// Governance is server-side: pending proposals do not change the effective +// manifest. A committed approval is delivered through the existing v2 protocol. +func TestGovernanceManifestKeepsPendingChangesOffHost(t *testing.T) { + dir := t.TempDir() + cronFilePath := filepath.Join(dir, "croncommander") + t.Setenv("CC_TEST_CRON", cronFilePath) + t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) + if err := os.WriteFile(filepath.Join(dir, "crontab"), []byte("#!/bin/sh\n/bin/cat > \"$CC_TEST_CRON\"\n"), 0700); err != nil { + t.Fatal(err) + } + responses := []protocol.PollResponse{ + {ManifestVersion: "effective-1", Changed: true, Jobs: []protocol.JobDefinition{{JobID: "owned-job", CronExpression: "0 * * * *", Command: "echo approved"}}}, + {ManifestVersion: "effective-1", Changed: false}, // Pending proposal or rejected change. + {ManifestVersion: "effective-2", Changed: true, Jobs: []protocol.JobDefinition{}}, // Approved delete. + } + index := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != "Bearer test-agent-token" { + t.Error("missing agent credential") + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(responses[index]) + index++ + })) + defer server.Close() + d := &daemon{serverURL: server.URL, httpClient: newHTTPClient(), executionMode: "user", + stateFile: filepath.Join(dir, "state.json"), spoolDir: filepath.Join(dir, "spool"), spoolPolicy: defaultSpoolPolicy(), + state: agentState{AgentID: "test-agent", AgentToken: "test-agent-token"}} + if err := d.poll(); err != nil { + t.Fatal(err) + } + before, err := os.ReadFile(cronFilePath) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(before), "echo approved") { + t.Fatal("effective job was not installed") + } + if err := d.poll(); err != nil { + t.Fatal(err) + } + pending, _ := os.ReadFile(cronFilePath) + if string(pending) != string(before) { + t.Fatal("unchanged manifest altered host cron") + } + if err := d.poll(); err != nil { + t.Fatal(err) + } + deleted, _ := os.ReadFile(cronFilePath) + if strings.Contains(string(deleted), "owned-job") { + t.Fatal("approved deletion was not applied") + } + if d.state.ManifestVersion != "effective-2" { + t.Fatal("manifest version was not persisted") + } + info, err := os.Stat(d.stateFile) + if err != nil { + t.Fatal(err) + } + if info.Mode().Perm() != 0600 { + t.Fatalf("credential state permissions: %o", info.Mode().Perm()) + } +} + func TestHTTPClientDoesNotFollowCredentialRedirects(t *testing.T) { redirectTargetCalled := false target := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {