Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ bundle:
version: "0.8.0-dev"
release_sequence: 0
channel: "development"
source_tree_digest: "sha256:14e3b6a5e784f371cbdd35b9cae16751d187312bddadc65f7a8a2dc4fd9229ff"
digest: "sha256:13511083d417c02909b0c2c749b9f854326d80d878de0bd1c04a8a2564516df7"
source_tree_digest: "sha256:c69222d87a7cd8ddd65a6ffd4266a4d92ad42106a382463cd9c7809f096a2b01"
digest: "sha256:5956a186ebc3080d372497c7c95a7bf39653ab4357df9f223cd2792beb05633d"

projection:
input_digest: "sha256:e72c1044b1575802633810e196ff230c0fcdad2e9aedce8931dcf9d659a86c44"
output_digest: "sha256:19e6a3ce782a399ada46509398ad1aae99c9404405049a83aa284b780eec9acc"
input_digest: "sha256:110b7813f13be2c091b16e1a8c5ed12199172480d2497bec68e8bfbc6fcf96e6"
output_digest: "sha256:af2fa4f5d4f89208e410e9f0055cd7393f09eeae69941847a7b230a39d54fa63"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:807282f820294914e1c7e6ad1bf27c54a799d56305c58630254ab50ab286f379"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:91d5089db35ac77e3863844f2d2133f5e3cd30a9b82aecff28d048c84b932950"
digest: "sha256:71910a8ffb233f1cb9071658f6e32595921ab7404bbc829aff10f379c309e5f5"
4 changes: 2 additions & 2 deletions .github/workflows/gds-ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED FILE - DO NOT EDIT DIRECTLY
# generator: gds
# bundle: 0.8.0-dev
# source-tree-digest: sha256:14e3b6a5e784f371cbdd35b9cae16751d187312bddadc65f7a8a2dc4fd9229ff
# input-digest: sha256:e72c1044b1575802633810e196ff230c0fcdad2e9aedce8931dcf9d659a86c44
# source-tree-digest: sha256:c69222d87a7cd8ddd65a6ffd4266a4d92ad42106a382463cd9c7809f096a2b01
# input-digest: sha256:110b7813f13be2c091b16e1a8c5ed12199172480d2497bec68e8bfbc6fcf96e6
# output-digest: sha256:a911ec3d1d5728bbe37fec78e04cc8452b9dbb1e8b394d0b8cace067804eff9a
# edit-source:
# - .gds/repository.yaml
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Versioning.

## [Unreleased]

- Honor explicit estate selection for governance policy compilation and local
comparison, retaining authority checks and the same root during apply.

- Update workflow dependencies through the canonical Go caller anchor and
regenerated provenance. Exclude that generated dependency from direct
Dependabot rewrites while retaining other workflow update proposals.
Expand Down
2 changes: 1 addition & 1 deletion core/app/github_governance_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ func (services *Services) githubGovernanceContext(
})
return githubGovernanceOperationContext{}, &envelope
}
estateRoot, anchor, findings := services.policyInputs(ctx, path)
estateRoot, anchor, findings := services.policyInputsWithEstateRoot(ctx, path, options.EstateRoot)
if len(findings) != 0 {
envelope := domain.NewEnvelope(command, classifyFindings(findings), nil, findings...)
return githubGovernanceOperationContext{}, &envelope
Expand Down
61 changes: 61 additions & 0 deletions core/app/github_governance_operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,64 @@ func appGovernanceOperationServer(t *testing.T) *httptest.Server {
}
}))
}

func TestGovernanceExplicitEstateOverridesEnvironmentForPolicy(t *testing.T) {
root := appTestRepositoryRoot(t)
registered := os.Getenv("GDS_ESTATE_ROOT")
explicit := t.TempDir()
for _, directory := range []string{".gds", "estate", "policies"} {
if err := copyAppTestTree(registered, explicit, directory); err != nil {
t.Fatal(err)
}
}
// Only the explicit estate contains the repository's selected profile.
if err := os.Remove(filepath.Join(registered, "policies", "stacks", "continuous-development.yaml")); err != nil {
t.Fatal(err)
}
runtimePath := appTestRuntimeConfig(t, root)
services, err := NewServices(DefaultClock)
if err != nil {
t.Fatal(err)
}
server := appGovernanceOperationServer(t)
client := server.Client()
client.Timeout = 5 * time.Second
services.GitHubRuntimeBuildOptions = githubruntime.BuildOptions{
BaseURL: server.URL + "/", HTTPClient: client, AllowInsecureLoopback: true,
}
options := GitHubGovernanceOperationOptions{
GitHubGovernanceOptions: GitHubGovernanceOptions{
GitHubReadOptions: GitHubReadOptions{
EstateRoot: explicit, RuntimeConfig: runtimePath,
InstallationID: "installation:github-opennetwork",
},
Owner: "NDDev-OpenNetwork", Repository: "github-device-sync", CompareLocal: true,
},
}
current, failure := services.githubGovernanceContext(context.Background(), root, options, "test")
if failure != nil {
t.Fatalf("explicit policy root was not honored: %#v", failure)
}
if current.estateRoot != explicit || current.observer(services).estateRoot != explicit {
t.Fatal("operation and precondition observer did not bind the selected estate")
}
read := services.GitHubGovernance(context.Background(), root, options.GitHubGovernanceOptions)
if read.ExitClass != domain.ExitSuccess {
t.Fatalf("read-only comparison selected a different policy root: %#v", read)
}
if os.Getenv("GDS_ESTATE_ROOT") != registered {
t.Fatal("operation override changed process environment")
}
options.EstateRoot = ""
_, failure = services.githubGovernanceContext(context.Background(), root, options, "test")
if failure == nil || !appHasFinding(*failure, "GDS_POLICY_PROFILE_MISSING") {
t.Fatalf("default estate should still lack the selected profile: %#v", failure)
}
for _, invalid := range []string{filepath.Join(explicit, "missing"), root} {
options.EstateRoot = invalid
_, failure = services.githubGovernanceContext(context.Background(), root, options, "test")
if failure == nil || failure.Mutation.Attempted {
t.Fatalf("unverified estate must fail before mutation: %#v", failure)
}
}
}
2 changes: 1 addition & 1 deletion core/app/github_readonly.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func (services *Services) GitHubGovernance(
Status: "observed-only", Counts: map[string]int{}, Fields: []governance.FieldResult{},
}
if options.CompareLocal {
estateRoot, anchor, findings := services.policyInputs(ctx, path)
estateRoot, anchor, findings := services.policyInputsWithEstateRoot(ctx, path, options.EstateRoot)
if len(findings) != 0 {
return domain.NewEnvelope(command, classifyFindings(findings), nil, findings...)
}
Expand Down
9 changes: 8 additions & 1 deletion core/app/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,14 @@ func (services *Services) policyInputs(
ctx context.Context,
path string,
) (string, domain.RepositoryAnchor, []domain.Finding) {
outcome := services.Context.Resolve(ctx, path)
return services.policyInputsWithEstateRoot(ctx, path, "")
}

func (services *Services) policyInputsWithEstateRoot(
ctx context.Context,
path, estateRoot string,
) (string, domain.RepositoryAnchor, []domain.Finding) {
outcome := services.Context.ResolveWithEstateRoot(ctx, path, estateRoot)
if outcome.Context.Workspace.GitWorktreeRoot == "" {
return "", domain.RepositoryAnchor{}, outcome.Findings
}
Expand Down
16 changes: 16 additions & 0 deletions core/context/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,22 @@ func NewResolver(
}
}

// ResolveWithEstateRoot applies an operation-local estate override through the
// same authority checks as GDS_ESTATE_ROOT, without changing process state.
func (resolver *Resolver) ResolveWithEstateRoot(ctx context.Context, path, estateRoot string) Outcome {
if estateRoot == "" {
return resolver.Resolve(ctx, path)
}
selected := *resolver
selected.getenv = func(key string) string {
if key == "GDS_ESTATE_ROOT" {
return estateRoot
}
return resolver.getenv(key)
}
return selected.Resolve(ctx, path)
}

func (resolver *Resolver) Resolve(ctx context.Context, path string) Outcome {
resolvedPath, err := resolveDirectory(path)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions docs/runbooks/github-ruleset-reconcile.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ lost this way reports nothing; the only evidence is its absence.

## Preconditions

- `--estate-root` selects the verified control-plane used for both provider
inventory and canonical policy. It overrides environment/registered estate
selection for this operation; the precondition observer retains that root.
- The mutation runtime must be configured. `gds context --json` reporting
`capabilities.mutations.runtime: configuration-required` means the GitHub App
credential does not resolve on this device, and the apply will fail *after* its
Expand Down
Loading