diff --git a/.gds/bundle.lock.yaml b/.gds/bundle.lock.yaml index 8b74c77..53895b1 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:670e14659a7a4ee1d99c49841c7ed04210d67fef3828f8cfc8ca135be19ac483" - digest: "sha256:085a6f985c4abb09f96c225e1123996e9c54fe28f2862af1a2f85f8a908c6cbf" + source_tree_digest: "sha256:a2b5ad7bdb6a7198d0de86e3de879062c71df5e86ff2f257257693aee319aa4a" + digest: "sha256:010d85cbeeeb4b010e438b0ffcbe5c9211f86d8f170b66e187aa35b23480fc17" projection: - input_digest: "sha256:eb612421c50ec33e68468e080d48f24c57ba8f8cd4dcf738d0debcfb584936f4" - output_digest: "sha256:0f3891bccad430b0c41acc687d4f9b26e0bb6cba00434f3f13a8598575d6ef66" + input_digest: "sha256:f914cbb8b69051475867d7fe14977debf309cb4ee1bd9bcddd6e64c7098bd886" + output_digest: "sha256:0a7d6b71492567ed5728b5b49c40225fab549fc3f7c252920f781aa9a671aa3a" files: - path: ".gds/compiled-policy.json" digest: "sha256:b5517ed46f67866220c2b18dbfbda4a40d99f00327611e56742a118d0ac59d0b" - path: ".github/workflows/gds-ci.yml" - digest: "sha256:bba562704e7c072f0be565c4fe3a22b52d691b87aa0fe44c4d998f8e9336b0da" + digest: "sha256:1da9d1ccf7c40ec6328f2c957aeb910c9de68ab91d8e07917d1486165cf209cc" diff --git a/.gds/repository.yaml b/.gds/repository.yaml index 10eb547..9cbda2c 100644 --- a/.gds/repository.yaml +++ b/.gds/repository.yaml @@ -97,7 +97,8 @@ verification: build: - "go build -trimpath ./core/cmd/gds" fast: - - "scripts/validate_go_core.sh --quick" + - "scripts/validate_shell.sh" + - "scripts/validate_go_core.sh --fast" pr-required: - "go test ./..." - "python3 -m pip install --quiet --require-hashes -r requirements/test.txt" diff --git a/.github/workflows/gds-ci.yml b/.github/workflows/gds-ci.yml index a70097c..78b37fa 100644 --- a/.github/workflows/gds-ci.yml +++ b/.github/workflows/gds-ci.yml @@ -1,9 +1,9 @@ # GENERATED FILE - DO NOT EDIT DIRECTLY # generator: gds # bundle: 0.8.0-dev -# source-tree-digest: sha256:670e14659a7a4ee1d99c49841c7ed04210d67fef3828f8cfc8ca135be19ac483 -# input-digest: sha256:eb612421c50ec33e68468e080d48f24c57ba8f8cd4dcf738d0debcfb584936f4 -# output-digest: sha256:01fb4854784be9e4564bcc84e70786484b370879be5e5ab1dd49f8b73ea2dea4 +# source-tree-digest: sha256:a2b5ad7bdb6a7198d0de86e3de879062c71df5e86ff2f257257693aee319aa4a +# input-digest: sha256:f914cbb8b69051475867d7fe14977debf309cb4ee1bd9bcddd6e64c7098bd886 +# output-digest: sha256:4a875d4ed62fe6b2d279515f9a19274e159ae8ed58f3a1a0843c60a49f663886 # edit-source: # - .gds/repository.yaml # - policies/base/repository-default.yaml @@ -42,14 +42,14 @@ jobs: runner: "ubuntu-latest" go_version: "1.26.7" - test_command: "go test ./..." + # Declared fast commands own their build scope; the complete build is below. + build_command: "" + test_command: "scripts/validate_shell.sh && scripts/validate_go_core.sh --fast" timeout_minutes: 30 pr-required: name: GDS PR required - # Serialized behind fast: both tiers run the integrated assurance scenario, - # which asserts wall-clock budgets, and running two of them side by side on - # one host measures contention rather than the code. It is also the right - # gate order — there is no value in the expensive tier once fast is red. + # Run the declared fast checks first. Repositories can keep this phase + # inexpensive and reserve their full test/build commands for this job. needs: fast permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 6518379..c66b660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ Versioning. ### Changed +- Generated Go callers honor declared fast verification commands, falling back + to the existing test command when none are declared. GDS uses a dedicated + formatting/module/vet/schema fast mode and runs its full Go tests once in the + PR-required job; the complete build remains in that job. + - Ruleset reconciliation compares the complete owned required-check identities, including integration IDs and strict policy. Retired extra contexts or a different check producer no longer appear synchronized; external parameters diff --git a/core/projections/generator.go b/core/projections/generator.go index 1bf0ad1..1bc90f9 100644 --- a/core/projections/generator.go +++ b/core/projections/generator.go @@ -95,6 +95,8 @@ type templateData struct { GoVersion string BuildCommand string TestCommand string + FastCommand string + DeclaredFastCommands bool PRRequiredCommand string TimeoutMinutes int WorkflowRef string @@ -606,13 +608,19 @@ func projectionTemplateData( if product == nil { product = &domain.ProductFacts{} } + fastCommand := ciString(anchor.CI, func(value *domain.CIPolicy) string { return value.TestCommand }) + if len(anchor.Verification.Commands.Fast) != 0 { + fastCommand = strings.Join(anchor.Verification.Commands.Fast, " && ") + } return templateData{ - AdvisoryCI: compiler.AdvisoryCI(policy), - Purpose: product.Purpose, - Capabilities: product.Capabilities, - Entrypoints: product.Entrypoints, - RepositoryID: anchor.Repository.ID, - Roles: strings.Join(anchor.Repository.Roles, ", "), BundleVersion: bundle.Version, + FastCommand: fastCommand, + DeclaredFastCommands: len(anchor.Verification.Commands.Fast) != 0, + AdvisoryCI: compiler.AdvisoryCI(policy), + Purpose: product.Purpose, + Capabilities: product.Capabilities, + Entrypoints: product.Entrypoints, + RepositoryID: anchor.Repository.ID, + Roles: strings.Join(anchor.Repository.Roles, ", "), BundleVersion: bundle.Version, ExternalWriteApproval: effectiveString( policy.Effective, "security", "external_write_requires_approval", ), diff --git a/core/projections/generator_test.go b/core/projections/generator_test.go index 62a1f89..ec5868a 100644 --- a/core/projections/generator_test.go +++ b/core/projections/generator_test.go @@ -533,3 +533,33 @@ func TestAdvisoryDeliveryGuidanceDoesNotAlterWorkflowResults(t *testing.T) { t.Fatal("advisory CI must preserve real outcomes") } } + +func TestGoCallerHonorsFastCommandsWithoutRepeatingBroadTests(t *testing.T) { + generator, anchor, policy, bundle := controlPlaneInputs(t) + anchor.CI.TestCommand = "go test ./..." + anchor.Verification.Commands.Fast = []string{"go vet ./...", "scripts/check-format.sh"} + anchor.Verification.Commands.PRRequired = []string{"go test ./..."} + candidate, findings := generator.Generate(anchor, policy, bundle) + if len(findings) != 0 { + t.Fatal(findings) + } + workflow := string(candidateFile(t, candidate, goCIOutputPath).Content) + if !strings.Contains(workflow, `test_command: "go vet ./... && scripts/check-format.sh"`) { + t.Fatalf("fast commands ignored:\n%s", workflow) + } + if strings.Count(workflow, `test_command: "go test ./..."`) != 1 { + t.Fatalf("broad tests repeated:\n%s", workflow) + } + if !strings.Contains(workflow, `build_command: ""`) { + t.Fatal("fast caller repeats the default broad build") + } + anchor.Verification.Commands.Fast = nil + fallback, findings := generator.Generate(anchor, policy, bundle) + if len(findings) != 0 { + t.Fatal(findings) + } + workflow = string(candidateFile(t, fallback, goCIOutputPath).Content) + if strings.Count(workflow, `test_command: "go test ./..."`) != 2 || strings.Contains(workflow, `build_command: ""`) { + t.Fatalf("undeclared fast behavior changed:\n%s", workflow) + } +} diff --git a/core/projections/workflow.go b/core/projections/workflow.go index e0e52ef..a9c21ad 100644 --- a/core/projections/workflow.go +++ b/core/projections/workflow.go @@ -15,7 +15,7 @@ var fullWorkflowSHA = regexp.MustCompile(`(?m)^\s*uses:\s*[A-Za-z0-9_.-]+/[A-Za- func validateGoWorkflowCaller(content []byte, anchor domain.RepositoryAnchor) error { if anchor.CI == nil || anchor.CI.Profile != "go" || anchor.CI.GoVersion == "" || anchor.CI.BuildCommand == "" || anchor.CI.TestCommand == "" || - anchor.CI.WorkflowRef == "" || len(anchor.Verification.Commands.Fast) == 0 || + anchor.CI.WorkflowRef == "" || len(anchor.Verification.Commands.PRRequired) == 0 || anchor.CI.TimeoutMinutes < 1 || anchor.CI.TimeoutMinutes > 120 { return errors.New("Go CI repository policy is incomplete") diff --git a/docs/continuous-development-policy.md b/docs/continuous-development-policy.md index caa0967..ebfea81 100644 --- a/docs/continuous-development-policy.md +++ b/docs/continuous-development-policy.md @@ -48,3 +48,18 @@ GitHub describes the status-check requirement in [available rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets) and the update payload in the [repository rules REST API](https://docs.github.com/en/rest/repos/rules#update-a-repository-ruleset). + +## Fast Go verification + +Generated Go callers use the ordered `verification.commands.fast` list for the +fast job. Its commands own that phase's build scope, so the reusable workflow's +default build is disabled there; `ci.build_command` still runs in PR required. +With no fast list, the caller falls back to `ci.test_command` and the reusable +workflow's normal build. Older generation required a fast list but ignored its +contents. + +This repository selects shell lint plus `scripts/validate_go_core.sh --fast` for +fast CI. The latter checks Go formatting, module consistency, vet, Python lock +metadata and the Go schema validator. Full Go and Python tests remain in PR +required. `--quick` still runs the complete development validation, and release +validation retains its security floor, vulnerability/race checks and rebuilds. diff --git a/scripts/validate_go_core.sh b/scripts/validate_go_core.sh index ca2693a..dccf772 100755 --- a/scripts/validate_go_core.sh +++ b/scripts/validate_go_core.sh @@ -7,12 +7,12 @@ MINIMUM_SECURE_GO_VERSION=go1.26.7 RELEASE_GO_VERSION=${GDS_RELEASE_GO_VERSION:-go1.26.7} GOVULNCHECK_VERSION=v1.6.0 -if [ "${1:-}" = "--quick" ]; then - MODE=quick - shift -fi +case "${1:-}" in + --quick) MODE=quick; shift ;; + --fast) MODE=fast; shift ;; +esac if [ "$#" -ne 0 ]; then - printf 'usage: %s [--quick]\n' "$0" >&2 + printf 'usage: %s [--quick|--fast]\n' "$0" >&2 exit 4 fi @@ -54,7 +54,7 @@ if ! go_version_at_least "$GO_VERSION" "$MINIMUM_SECURE_GO_VERSION"; then fi printf '%s\n' \ "WARNING: $GO_VERSION is older than the security floor $MINIMUM_SECURE_GO_VERSION." \ - "Quick results are development-only; release evidence remains NOT_PROVEN." >&2 + "This validation is development-only; release evidence remains NOT_PROVEN." >&2 fi if [ "$MODE" = "full" ] && [ "$GO_VERSION" != "$RELEASE_GO_VERSION" ]; then @@ -76,6 +76,15 @@ fi go mod tidy -diff go mod verify go vet ./... + +if [ "$MODE" = "fast" ]; then + python3 scripts/validate_python_locks.py + # The Go validator is self-contained; fast CI needs no unpinned Python packages. + go run ./core/cmd/gds --json validate schemas > /dev/null + printf 'GDS Go core validation: PASS (fast; full tests run separately)\n' + exit 0 +fi + go test ./... python3 scripts/validate_python_locks.py python3 scripts/validate_gds_schemas.py --json >/dev/null diff --git a/templates/github-actions/go.yml.tmpl b/templates/github-actions/go.yml.tmpl index ed3cb63..cae4f24 100644 --- a/templates/github-actions/go.yml.tmpl +++ b/templates/github-actions/go.yml.tmpl @@ -31,14 +31,14 @@ jobs: {{ end }} runner: {{ yamlQuote .Runner }} go_version: {{ yamlQuote .GoVersion }} - test_command: {{ yamlQuote .TestCommand }} +{{ if .DeclaredFastCommands }} # Declared fast commands own their build scope; the complete build is below. + build_command: "" +{{ end }} test_command: {{ yamlQuote .FastCommand }} timeout_minutes: {{ .TimeoutMinutes }} pr-required: name: GDS PR required - # Serialized behind fast: both tiers run the integrated assurance scenario, - # which asserts wall-clock budgets, and running two of them side by side on - # one host measures contention rather than the code. It is also the right - # gate order — there is no value in the expensive tier once fast is red. + # Run the declared fast checks first. Repositories can keep this phase + # inexpensive and reserve their full test/build commands for this job. needs: fast permissions: contents: read diff --git a/tests/golden/projections/control-plane/.claude/CLAUDE.md b/tests/golden/projections/control-plane/.claude/CLAUDE.md index 188c3e6..dc476a5 100644 --- a/tests/golden/projections/control-plane/.claude/CLAUDE.md +++ b/tests/golden/projections/control-plane/.claude/CLAUDE.md @@ -3,7 +3,7 @@ GENERATED FILE - DO NOT EDIT DIRECTLY generator: gds bundle: 0.8.0-dev source-tree-digest: sha256:0000000000000000000000000000000000000000000000000000000000000001 -input-digest: sha256:3d7c04888af26c66e2c8b7fedc84b08f90a73d1182cd18ee889c3a849151ce25 +input-digest: sha256:a0d8d237e113629cdf67cf248e3010dfb024ec97d3c692824258f99a66d77421 output-digest: sha256:88cb57297d8d713287872a8afaca8d42f7146ecf7a091e4996e65eee8f962665 edit-source: - .gds/repository.yaml diff --git a/tests/golden/projections/control-plane/.gds/bundle.lock.yaml b/tests/golden/projections/control-plane/.gds/bundle.lock.yaml index 51df09b..6168ae3 100644 --- a/tests/golden/projections/control-plane/.gds/bundle.lock.yaml +++ b/tests/golden/projections/control-plane/.gds/bundle.lock.yaml @@ -6,17 +6,17 @@ bundle: release_sequence: 0 channel: "development" source_tree_digest: "sha256:0000000000000000000000000000000000000000000000000000000000000001" - digest: "sha256:2bf1503b8188a42c0433be96950a6c263a1c715154924ffa682de2b65d955ee3" + digest: "sha256:620a284abd424fa8e2d34d77447d2435f300b6c3c69016f447fbe479b5db655f" projection: - input_digest: "sha256:3d7c04888af26c66e2c8b7fedc84b08f90a73d1182cd18ee889c3a849151ce25" - output_digest: "sha256:7127d81d7b0656bbaac983d1383d1cde816813b7e975bbc2c486954515f325de" + input_digest: "sha256:a0d8d237e113629cdf67cf248e3010dfb024ec97d3c692824258f99a66d77421" + output_digest: "sha256:e64d18089f87b0d26f43e652e10a932573b79294f4c759ac4f921e3fe5437210" files: - path: ".claude/CLAUDE.md" - digest: "sha256:1eea64f1a7ff5fb20ef5c11db42231217a1a4bb1f9f9904d5daed91da33037a4" + digest: "sha256:c20c42cda0f0ab9fecc75bb7fd979c93549d661fb4c23e6cd8caa1ffda6a2151" - path: ".gds/compiled-policy.json" digest: "sha256:24850d3c840160a5bef16753424da983b1e220984e2457fd22ee03ed278f1f1b" - path: ".github/workflows/gds-ci.yml" - digest: "sha256:19a37bc7b84efca6d34e15d7ce68ef4fa9f886ffc208c606b262882d83640489" + digest: "sha256:42d30be16850c301efbf04d94d9982105a0b1669a85e9abb479c6d8ff5b0a0c2" - path: "AGENTS.md" - digest: "sha256:840da70bfecd98bf2b674d6aff3be6cbae808609e0d6ce61292d4a84e1d00e2d" + digest: "sha256:8629ac16dcf62182a0c23c03ad9f57a367ee43e4677700c1c56b97996fc0c108" diff --git a/tests/golden/projections/control-plane/.github/workflows/gds-ci.yml b/tests/golden/projections/control-plane/.github/workflows/gds-ci.yml index 17d3488..9bbc41f 100644 --- a/tests/golden/projections/control-plane/.github/workflows/gds-ci.yml +++ b/tests/golden/projections/control-plane/.github/workflows/gds-ci.yml @@ -2,8 +2,8 @@ # generator: gds # bundle: 0.8.0-dev # source-tree-digest: sha256:0000000000000000000000000000000000000000000000000000000000000001 -# input-digest: sha256:3d7c04888af26c66e2c8b7fedc84b08f90a73d1182cd18ee889c3a849151ce25 -# output-digest: sha256:01fb4854784be9e4564bcc84e70786484b370879be5e5ab1dd49f8b73ea2dea4 +# input-digest: sha256:a0d8d237e113629cdf67cf248e3010dfb024ec97d3c692824258f99a66d77421 +# output-digest: sha256:4a875d4ed62fe6b2d279515f9a19274e159ae8ed58f3a1a0843c60a49f663886 # edit-source: # - .gds/repository.yaml # - policies/base/repository-default.yaml @@ -42,14 +42,14 @@ jobs: runner: "ubuntu-latest" go_version: "1.26.7" - test_command: "go test ./..." + # Declared fast commands own their build scope; the complete build is below. + build_command: "" + test_command: "scripts/validate_shell.sh && scripts/validate_go_core.sh --fast" timeout_minutes: 30 pr-required: name: GDS PR required - # Serialized behind fast: both tiers run the integrated assurance scenario, - # which asserts wall-clock budgets, and running two of them side by side on - # one host measures contention rather than the code. It is also the right - # gate order — there is no value in the expensive tier once fast is red. + # Run the declared fast checks first. Repositories can keep this phase + # inexpensive and reserve their full test/build commands for this job. needs: fast permissions: contents: read diff --git a/tests/golden/projections/control-plane/AGENTS.md b/tests/golden/projections/control-plane/AGENTS.md index 51b28e2..e014004 100644 --- a/tests/golden/projections/control-plane/AGENTS.md +++ b/tests/golden/projections/control-plane/AGENTS.md @@ -3,8 +3,8 @@ GENERATED FILE - DO NOT EDIT DIRECTLY generator: gds bundle: 0.8.0-dev source-tree-digest: sha256:0000000000000000000000000000000000000000000000000000000000000001 -input-digest: sha256:3d7c04888af26c66e2c8b7fedc84b08f90a73d1182cd18ee889c3a849151ce25 -output-digest: sha256:ece934738d1ed0bb3532f6db213366f3d60c064e6c0aa79101b3c0b4a9e4e06c +input-digest: sha256:a0d8d237e113629cdf67cf248e3010dfb024ec97d3c692824258f99a66d77421 +output-digest: sha256:e068119ce6b21b80367eaaeefdb3821e19e8cc2cb9d6613fe020c547ef0892d8 edit-source: - .gds/repository.yaml - policies/base/repository-default.yaml @@ -47,7 +47,8 @@ GDS is the public engine for a multi-owner GitHub estate. It loads an external e - Test: `go test ./...` - Test: `scripts/validate_python.sh` - Build: `go build -trimpath ./core/cmd/gds` -- Fast: `scripts/validate_go_core.sh --quick` +- Fast: `scripts/validate_shell.sh` +- Fast: `scripts/validate_go_core.sh --fast` - PR required: `go test ./...` - PR required: `python3 -m pip install --quiet --require-hashes -r requirements/test.txt` - PR required: `python3 -m pytest`