From eb9169323343a894498beb097153f11342eecca8 Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Tue, 8 Sep 2026 12:27:27 +0500 Subject: [PATCH 1/2] fix: keep background CI from blocking advisory completion Ordinary gds complete under continuous-development still records unproven required lanes. It does not treat missing GitHub execution evidence as a merge gate. Absent that profile, the existing not-proven refusal remains. Signed-off-by: rldyourmnd Co-authored-by: Cursor --- core/app/complete_workflow.go | 28 +++++++++------- core/app/complete_workflow_test.go | 14 ++++++++ core/cli/complete_test.go | 51 +++++++++++++++++++++++++++++- core/cli/session_test.go | 19 +++++++++++ 4 files changed, 99 insertions(+), 13 deletions(-) diff --git a/core/app/complete_workflow.go b/core/app/complete_workflow.go index 86c206a..7b09d63 100644 --- a/core/app/complete_workflow.go +++ b/core/app/complete_workflow.go @@ -387,18 +387,6 @@ func (services *Services) completeContext( current.assessment.Reason = "published-task-upstream-required" return current, []domain.Finding{completeFinding(info.WorktreeRoot, current.assessment.Reason)} } - if len(current.assessment.RequiredChecks) != 0 { - current.assessment.Reason = "required-checks-not-proven" - findings := []domain.Finding{} - for _, check := range current.assessment.RequiredChecks { - findings = append(findings, domain.Finding{ - Code: "GDS_COMPLETE_CHECK_NOT_PROVEN", Severity: domain.SeverityHigh, - Message: "Completion cannot integrate while a required check lacks execution evidence.", - Evidence: map[string]any{"check": check.Name, "commands": check.Commands}, - }) - } - return current, findings - } if anchor.Git.Integration != "direct" { current.assessment.Reason = "pull-request-provider-unavailable" return current, []domain.Finding{{ @@ -499,6 +487,18 @@ func (services *Services) completeContext( current.assessment.Reason = "policy-not-proven" return current, compiled.Findings } + if unprovenRequiredChecksBlockCompletion(compiler.AdvisoryCI(compiled.Document), current.assessment.RequiredChecks) { + current.assessment.Reason = "required-checks-not-proven" + findings := []domain.Finding{} + for _, check := range current.assessment.RequiredChecks { + findings = append(findings, domain.Finding{ + Code: "GDS_COMPLETE_CHECK_NOT_PROVEN", Severity: domain.SeverityHigh, + Message: "Completion cannot integrate while a required check lacks execution evidence.", + Evidence: map[string]any{"check": check.Name, "commands": check.Commands}, + }) + } + return current, findings + } current.assessment.Eligible = true current.assessment.ApplySupported = applySupported current.assessment.Reason = "integrate-publish-clean" @@ -636,6 +636,10 @@ func checkoutStatusIsClean(status gitprovider.Status) bool { status.Submodules.Conflicted == 0 } +func unprovenRequiredChecksBlockCompletion(advisoryCI bool, checks []HandoffCheck) bool { + return !advisoryCI && len(checks) != 0 +} + func completeFinding(path string, reason string) domain.Finding { return domain.Finding{ Code: "GDS_COMPLETE_NOT_PROVEN", Severity: domain.SeverityHigh, diff --git a/core/app/complete_workflow_test.go b/core/app/complete_workflow_test.go index ebad12b..0b7cfa0 100644 --- a/core/app/complete_workflow_test.go +++ b/core/app/complete_workflow_test.go @@ -11,6 +11,20 @@ import ( gitprovider "github.com/NDDev-OpenNetwork/github-device-sync/core/providers/git" ) +func TestUnprovenRequiredChecksBlockOnlyWithoutAdvisoryCI(t *testing.T) { + t.Parallel() + checks := []HandoffCheck{{Name: "test", Status: "not-proven", Commands: []string{"go test ./..."}}} + if !unprovenRequiredChecksBlockCompletion(false, checks) { + t.Fatal("absent advisory profile must still require execution evidence") + } + if unprovenRequiredChecksBlockCompletion(true, checks) { + t.Fatal("continuous-development must not block ordinary completion on unproven background lanes") + } + if unprovenRequiredChecksBlockCompletion(false, nil) { + t.Fatal("no declared required lanes must not invent a check gate") + } +} + func TestOrderCompletionGraphIsDeterministicWithoutDependencies(t *testing.T) { contexts := []completeContext{ {repositoryID: "repo_c"}, diff --git a/core/cli/complete_test.go b/core/cli/complete_test.go index a9cf356..7d38a09 100644 --- a/core/cli/complete_test.go +++ b/core/cli/complete_test.go @@ -1,6 +1,7 @@ package cli import ( + "encoding/json" "os" "path/filepath" "strings" @@ -13,9 +14,18 @@ func prepareCompleteFixture( t *testing.T, integration string, requiredChecks bool, +) (sessionFixtureState, string, string) { + return prepareCompleteFixtureWithProfiles(t, integration, requiredChecks, nil) +} + +func prepareCompleteFixtureWithProfiles( + t *testing.T, + integration string, + requiredChecks bool, + profiles []string, ) (sessionFixtureState, string, string) { t.Helper() - fixture := sessionFixtureWithPolicies(t, "never", integration, requiredChecks) + fixture := sessionFixtureWithPolicyProfiles(t, "never", integration, requiredChecks, profiles) runSessionGit(t, fixture.client, "switch", "-qc", "task/complete") if err := os.WriteFile(filepath.Join(fixture.client, "fixture.txt"), []byte("complete\n"), 0o644); err != nil { t.Fatal(err) @@ -104,6 +114,17 @@ func TestCompleteBlocksUnprovenChecksPRPolicyAndNetworkApply(t *testing.T) { t.Fatalf("checks plan=%#v stderr=%q", checksPlan.envelope, checksPlan.stderr) } + advisoryFixture, advisoryState, _ := prepareCompleteFixtureWithProfiles( + t, "direct", true, []string{"repository-default", "continuous-development"}, + ) + advisoryPlan := planCompleteFixture(t, advisoryFixture, advisoryState, "advisory-session") + if advisoryPlan.exitCode != 0 || containsFinding(advisoryPlan.envelope.Findings, "GDS_COMPLETE_CHECK_NOT_PROVEN") { + t.Fatalf("advisory plan=%#v stderr=%q", advisoryPlan.envelope, advisoryPlan.stderr) + } + if !completePlanKeepsUnprovenBackgroundChecks(t, advisoryPlan.envelope.Data) { + t.Fatal("advisory completion dropped declared background checks") + } + prFixture, prState, _ := prepareCompleteFixture(t, "pull-request", false) prPlan := planCompleteFixture(t, prFixture, prState, "pr-session") if prPlan.exitCode != 3 || !containsFinding(prPlan.envelope.Findings, "GDS_COMPLETE_PR_INTEGRATION_REQUIRED") { @@ -243,3 +264,31 @@ func TestCompleteFinalizesModuleBeforeConsumerAndLeavesFinalGitlink(t *testing.T t.Fatalf("consumer branch=%q", branch) } } + +func completePlanKeepsUnprovenBackgroundChecks(t *testing.T, data any) bool { + t.Helper() + raw, err := json.Marshal(data) + if err != nil { + t.Fatal(err) + } + var parsed struct { + Assessments []struct { + RequiredChecks []struct { + Name string `json:"name"` + Status string `json:"status"` + } `json:"required_checks"` + } `json:"assessments"` + } + if err := json.Unmarshal(raw, &parsed); err != nil { + t.Fatal(err) + } + if len(parsed.Assessments) != 1 || len(parsed.Assessments[0].RequiredChecks) == 0 { + return false + } + for _, check := range parsed.Assessments[0].RequiredChecks { + if check.Name == "" || check.Status != "not-proven" { + return false + } + } + return true +} diff --git a/core/cli/session_test.go b/core/cli/session_test.go index 4f62e4e..d2575f2 100644 --- a/core/cli/session_test.go +++ b/core/cli/session_test.go @@ -56,6 +56,16 @@ func sessionFixtureWithPolicies( handoffPolicy string, integrationPolicy string, requiredChecks bool, +) sessionFixtureState { + return sessionFixtureWithPolicyProfiles(t, handoffPolicy, integrationPolicy, requiredChecks, nil) +} + +func sessionFixtureWithPolicyProfiles( + t *testing.T, + handoffPolicy string, + integrationPolicy string, + requiredChecks bool, + profiles []string, ) sessionFixtureState { t.Helper() disableGitFixtureMaintenance(t) @@ -88,6 +98,15 @@ func sessionFixtureWithPolicies( string(anchor), "required:\n - \"test\"", "required: []", 1, )) } + if len(profiles) != 0 { + block := " profiles:\n" + for _, profile := range profiles { + block += " - \"" + profile + "\"\n" + } + anchor = []byte(strings.Replace( + string(anchor), " profiles:\n - \"repository-default\"\n", block, 1, + )) + } if err := os.WriteFile(filepath.Join(client, ".gds", "repository.yaml"), anchor, 0o644); err != nil { t.Fatal(err) } From dfbd24c96ea68f9b61028739f9539bc76b4d38f1 Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Tue, 8 Sep 2026 12:48:48 +0500 Subject: [PATCH 2/2] chore(gds): restamp development projections after advisory completion complete_workflow.go is inside the development source boundary, so the advisory-completion change must refresh the applied source_tree_digest. Signed-off-by: rldyourmnd Co-authored-by: Cursor --- .gds/bundle.lock.yaml | 10 +++++----- .github/workflows/gds-ci.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gds/bundle.lock.yaml b/.gds/bundle.lock.yaml index e2d6d0e..b65b0da 100644 --- a/.gds/bundle.lock.yaml +++ b/.gds/bundle.lock.yaml @@ -5,14 +5,14 @@ bundle: version: "0.8.0-dev" release_sequence: 0 channel: "development" - source_tree_digest: "sha256:f650570277313a15cfc92ea1a12cd6f5c9b8af7ea410cffd5e8931ca8d01c70c" - digest: "sha256:7c44e6973edfd5c73f73b9e6d414b263ceee36faaa17072d82bb2bc40b6b6beb" + source_tree_digest: "sha256:fc5b140798948c5f0075bf03c0d9798479a915dc9799b645d72a04f06588f93b" + digest: "sha256:9fc077c1299b2e22608501eddecfc1ed25a5c8fe448f5c1b3587630cdbfc0037" projection: - input_digest: "sha256:9f629a827361728b8ccb873792881b41b4a396ff9a0f73f25d97e47c26e2a3fc" - output_digest: "sha256:eded8fc365dbce57f899de267242c2ee796552f5cb509046af4dbf9eb7dbf701" + input_digest: "sha256:48c167ca2b9a0543540374c3212c44ca9745d56f5f38a5734447dd247832f604" + output_digest: "sha256:0a9798865f28716f1c524884e8d989186cf26dd63606f9737febe77774f9134c" files: - path: ".gds/compiled-policy.json" digest: "sha256:807282f820294914e1c7e6ad1bf27c54a799d56305c58630254ab50ab286f379" - path: ".github/workflows/gds-ci.yml" - digest: "sha256:4b482bb6bc7b3eef9ba5e2a2bf09c955ef6d6213fea8485c8586dbc79328511f" + digest: "sha256:3cebb82fa1407d47f7a4044e0ebf4c1e092bc8d6ef3c568fe2813fb1a508410f" diff --git a/.github/workflows/gds-ci.yml b/.github/workflows/gds-ci.yml index cadbce6..f8216bc 100644 --- a/.github/workflows/gds-ci.yml +++ b/.github/workflows/gds-ci.yml @@ -1,8 +1,8 @@ # GENERATED FILE - DO NOT EDIT DIRECTLY # generator: gds # bundle: 0.8.0-dev -# source-tree-digest: sha256:f650570277313a15cfc92ea1a12cd6f5c9b8af7ea410cffd5e8931ca8d01c70c -# input-digest: sha256:9f629a827361728b8ccb873792881b41b4a396ff9a0f73f25d97e47c26e2a3fc +# source-tree-digest: sha256:fc5b140798948c5f0075bf03c0d9798479a915dc9799b645d72a04f06588f93b +# input-digest: sha256:48c167ca2b9a0543540374c3212c44ca9745d56f5f38a5734447dd247832f604 # output-digest: sha256:8c045e745cc69b731bc695a4a9d58a48c10f1ab7dd85b7354db7bfd0e072711c # edit-source: # - .gds/repository.yaml