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
11 changes: 5 additions & 6 deletions .gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ schema_version: 1
bundle:
version: "0.9.7-dev"
release_sequence: 0
channel: "development"
source_tree_digest: "sha256:1e873c46d68f27b01800a4ee64221b7f53b3b78ad7872374b565f0a08fc67ff1"
digest: "sha256:522661293a809309328a9551766cb89d4589241afa78f507a4b6f50a53244160"
source_tree_digest: "sha256:864917988777e8b9f61f87f08e4f08d94173e37c1352373332b5e02e76562f97"
digest: "sha256:eb2048f08b6e1dbc84d89792a6e3f658f6258ff7c7e3b11c87fd5c2fbd48b0de"

projection:
input_digest: "sha256:6d7ec12102428e0d17daf7501ebec764be4361a90d5f81e4edf201b861bc4ee6"
output_digest: "sha256:d634e459c82a8d5605327d60a0d4167a79056698f3d12d566c957797aa4ddac1"
input_digest: "sha256:f2fb932fd0598eaf673a8cd2b3232f73f7c5b3cfad377fb807dda30ec134e977"
output_digest: "sha256:0caadd7bca7495045f29598f2908a15bab55a2c421b10202095ff99e3c833d69"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:9f498788bdc34e52a0ab793c536e0e6a7b360c2e1a20446cbf03ed51986cdc6f"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:15fd27bccb311133125e6bfe1d8659430b1e18940338efd3933546c93d3979fa"
digest: "sha256:275145c60b6f99f946085685a66b7eabb4a693043588cc55a87250401af28612"
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.9.7-dev
# source-tree-digest: sha256:1e873c46d68f27b01800a4ee64221b7f53b3b78ad7872374b565f0a08fc67ff1
# input-digest: sha256:6d7ec12102428e0d17daf7501ebec764be4361a90d5f81e4edf201b861bc4ee6
# source-tree-digest: sha256:864917988777e8b9f61f87f08e4f08d94173e37c1352373332b5e02e76562f97
# input-digest: sha256:f2fb932fd0598eaf673a8cd2b3232f73f7c5b3cfad377fb807dda30ec134e977
# output-digest: sha256:b9bf3d0c64c0fb371596e7d090e82e62aebbfde91929115fc15fb28644e4fd38
# edit-source:
# - .gds/repository.yaml
Expand Down
230 changes: 125 additions & 105 deletions .github/workflows/release-bundle.yml

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ Versioning.

## [Unreleased]

- Remove release channels from the release pipeline (ADR 0038). Bundle
manifests, release envelopes, locks, rollout documents and trust policy no
longer carry or require `channel`; the fields remain optional for decoding
documents produced while the field existed, and a legacy channel is still
checked against a consumer policy that lists it.
- Classify bundles by `release_sequence` alone: `0` is a development
projection, `>= 1` is a release. Development locks no longer record a
`development` channel.
- Require every release build to come from the exact
`refs/tags/gds-v<version>` ref. The release workflow now runs on pushes to
`main`: a privileged resolve job derives the next patch version and
monotonic sequence from the latest published envelope over the GitHub API
without checking out candidate source, creates the tag, and hands the exact
identity to the unprivileged build job.
- Drop harness evidence from the release builder's inputs and from
`bundle.Build` gating. Harness evidence stays a separately produced and
verified estate/runtime signal in `core/harnessevidence` and the module
release evidence path; it no longer decides whether an artifact may be
published.
- Remove `default_bundle_channel` from the estate schema and the example
estate, and remove the `--channel` flag and evidence inputs from
`gds-release-builder` and the release-candidate command.

## [0.9.7] - 2026-09-19

- Skip hidden directories during workspace discovery so tool-state and
Expand Down
9 changes: 2 additions & 7 deletions core/app/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ type StateInspectionData struct {
type ReleaseCandidateOptions struct {
BundleVersion string
ReleaseSequence int
Channel string
MinimumCLIVersion string
}

Expand Down Expand Up @@ -591,14 +590,10 @@ func (services *Services) BuildReleaseCandidate(
sourceRef := "refs/heads/" + status.Branch.Name
candidate, findings := bundle.Build(info.WorktreeRoot, bundle.BuildOptions{
BundleVersion: options.BundleVersion, ReleaseSequence: options.ReleaseSequence,
Channel: options.Channel, SourceCommit: status.Head.OID,
SourceCommit: status.Head.OID,
MinimumCLIVersion: options.MinimumCLIVersion,
Workflow: trust.Source.AllowedWorkflows[0], SourceRef: sourceRef,
// The candidate command has no evidence input surface and performs no
// publication. Its canary is therefore explicitly provisional; the hosted
// stable/frozen builder still requires the signed active-seven manifest.
HarnessEvidenceProvisional: options.Channel == "canary",
TrackedSources: trackedSources,
TrackedSources: trackedSources,
}, trust, services.Schemas)
if len(findings) != 0 {
return domain.NewEnvelope(
Expand Down
2 changes: 1 addition & 1 deletion core/assurance/generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func exerciseRollout(
RolloutID: "rollout_01J00000000000000000000000", CreatedAt: now,
Envelope: bundle.ReleaseEnvelope{
SchemaVersion: domain.SchemaVersion, BundleVersion: "1.0.0", ReleaseSequence: 1,
Channel: "canary", SourceCommit: "0123456789abcdef0123456789abcdef01234567",
SourceCommit: "0123456789abcdef0123456789abcdef01234567",
ManifestDigest: digestFixture("manifest", 0),
ArtifactDigest: digestFixture("artifact", 0),
},
Expand Down
36 changes: 12 additions & 24 deletions core/bundle/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,15 @@ func Build(
}
manifest := Manifest{
SchemaVersion: domain.SchemaVersion, BundleVersion: options.BundleVersion,
ReleaseSequence: options.ReleaseSequence, Channel: options.Channel,
SourceCommit: options.SourceCommit, SourceRef: options.SourceRef,
MinimumCLIVersion: options.MinimumCLIVersion,
ContentSetDigest: digestJSON(records),
PolicyDigest: subsetDigest(records, "policies/"),
SkillSetDigest: subsetDigest(records, "skills/"),
HarnessProfilesDigest: subsetDigest(records, "harnesses/"),
HarnessEvidenceManifestDigest: options.HarnessEvidenceManifestDigest,
HarnessEvidenceProvisional: options.HarnessEvidenceProvisional,
Files: records,
SupplyChain: SupplyChain{AttestationRequired: true, SBOMRequiredForExecutables: true},
ReleaseSequence: options.ReleaseSequence,
SourceCommit: options.SourceCommit, SourceRef: options.SourceRef,
MinimumCLIVersion: options.MinimumCLIVersion,
ContentSetDigest: digestJSON(records),
PolicyDigest: subsetDigest(records, "policies/"),
SkillSetDigest: subsetDigest(records, "skills/"),
HarnessProfilesDigest: subsetDigest(records, "harnesses/"),
Files: records,
SupplyChain: SupplyChain{AttestationRequired: true, SBOMRequiredForExecutables: true},
}
manifestBytes, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
Expand All @@ -131,8 +129,8 @@ func Build(
})
envelope := ReleaseEnvelope{
SchemaVersion: domain.SchemaVersion, BundleVersion: options.BundleVersion,
ReleaseSequence: options.ReleaseSequence, Channel: options.Channel,
SourceCommit: options.SourceCommit, SourceRef: options.SourceRef,
ReleaseSequence: options.ReleaseSequence,
SourceCommit: options.SourceCommit, SourceRef: options.SourceRef,
ExecutableFiles: executableFiles,
ManifestDigest: digest(manifestBytes), ArtifactDigest: digest(artifact),
ExpectedAttestationIdentityDigest: identityDigest,
Expand Down Expand Up @@ -326,24 +324,14 @@ func writeArchive(files []sourceFile, manifest []byte) ([]byte, error) {

func validateBuildOptions(options BuildOptions, trust TrustPolicy) *domain.Finding {
if options.ReleaseSequence < trust.Release.MinimumReleaseSequence ||
!contains(trust.Release.AllowedChannels, options.Channel) ||
!contains(trust.Source.AllowedWorkflows, options.Workflow) ||
!allowedRef(trust.Source.AllowedRefs, options.SourceRef) {
finding := bundleFinding(
"GDS_BUNDLE_BUILD_POLICY_BLOCKED",
fmt.Errorf("release sequence, channel, workflow, or ref is outside trust policy"),
fmt.Errorf("release sequence, workflow, or ref is outside trust policy"),
)
return &finding
}
if (options.Channel == "stable" || options.Channel == "frozen") &&
(options.HarnessEvidenceManifestDigest == "" || options.HarnessEvidenceProvisional) {
finding := bundleFinding("GDS_HARNESS_EVIDENCE_REQUIRED", fmt.Errorf("stable and frozen releases require exact non-provisional harness evidence"))
return &finding
}
if options.Channel == "canary" && !options.HarnessEvidenceProvisional && options.HarnessEvidenceManifestDigest == "" {
finding := bundleFinding("GDS_HARNESS_EVIDENCE_IDENTITY_MISSING", fmt.Errorf("non-provisional canary requires a bound harness evidence manifest"))
return &finding
}
return nil
}

Expand Down
5 changes: 2 additions & 3 deletions core/bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,11 +347,10 @@ func testTrust() TrustPolicy {

func testBuildOptions(root string) BuildOptions {
return BuildOptions{
BundleVersion: "1.0.0", ReleaseSequence: 1, Channel: "canary",
BundleVersion: "1.0.0", ReleaseSequence: 1,
SourceCommit: testSourceCommit, MinimumCLIVersion: "1.0.0",
Workflow: ".github/workflows/release-bundle.yml", SourceRef: "refs/heads/main",
TrackedSources: fixtureTrackedSources(root),
HarnessEvidenceProvisional: true,
TrackedSources: fixtureTrackedSources(root),
}
}

Expand Down
25 changes: 11 additions & 14 deletions core/bundle/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type Manifest struct {
SchemaVersion int `json:"schema_version"`
BundleVersion string `json:"bundle_version"`
ReleaseSequence int `json:"release_sequence"`
Channel string `json:"channel"`
Channel string `json:"channel,omitempty"`
SourceCommit string `json:"source_commit"`
SourceRef string `json:"source_ref"`
MinimumCLIVersion string `json:"minimum_cli_version"`
Expand All @@ -56,7 +56,7 @@ type Manifest struct {
SkillSetDigest string `json:"skill_set_digest"`
HarnessProfilesDigest string `json:"harness_profiles_digest"`
HarnessEvidenceManifestDigest string `json:"harness_evidence_manifest_digest,omitempty"`
HarnessEvidenceProvisional bool `json:"harness_evidence_provisional"`
HarnessEvidenceProvisional bool `json:"harness_evidence_provisional,omitempty"`
Files []FileRecord `json:"files"`
SupplyChain SupplyChain `json:"supply_chain"`
}
Expand Down Expand Up @@ -87,7 +87,7 @@ type ReleaseEnvelope struct {
SchemaVersion int `json:"schema_version"`
BundleVersion string `json:"bundle_version"`
ReleaseSequence int `json:"release_sequence"`
Channel string `json:"channel"`
Channel string `json:"channel,omitempty"`
SourceCommit string `json:"source_commit"`
SourceRef string `json:"source_ref"`
ExecutableFiles int `json:"executable_files"`
Expand All @@ -97,17 +97,14 @@ type ReleaseEnvelope struct {
}

type BuildOptions struct {
BundleVersion string
ReleaseSequence int
Channel string
SourceCommit string
MinimumCLIVersion string
Workflow string
SourceRef string
TrackedSources []string
AdditionalFiles []AdditionalFile
HarnessEvidenceManifestDigest string
HarnessEvidenceProvisional bool
BundleVersion string
ReleaseSequence int
SourceCommit string
MinimumCLIVersion string
Workflow string
SourceRef string
TrackedSources []string
AdditionalFiles []AdditionalFile
}

type AdditionalFile struct {
Expand Down
2 changes: 1 addition & 1 deletion core/bundle/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func Verify(
now time.Time,
) (VerificationResult, []domain.Finding) {
findings := []domain.Finding{}
if !contains(trust.Release.AllowedChannels, envelope.Channel) {
if envelope.Channel != "" && !contains(trust.Release.AllowedChannels, envelope.Channel) {
findings = append(findings, verificationFinding(
"GDS_BUNDLE_CHANNEL_BLOCKED", "Bundle channel is outside the consumer trust policy.",
))
Expand Down
1 change: 0 additions & 1 deletion core/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,6 @@ func (executor *executor) releaseCommand() *cobra.Command {
}
candidate.Flags().StringVar(&values.BundleVersion, "version", "", "candidate semantic version")
candidate.Flags().IntVar(&values.ReleaseSequence, "sequence", 0, "monotonic release sequence")
candidate.Flags().StringVar(&values.Channel, "channel", "canary", "release channel")
candidate.Flags().StringVar(
&values.MinimumCLIVersion, "minimum-cli-version", "0.1.0", "minimum compatible GDS CLI version",
)
Expand Down
6 changes: 3 additions & 3 deletions core/cli/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,10 @@ func TestGenerateRepositoryConsumesVerifiedReleasedBundle(t *testing.T) {
t.Fatal(err)
}
candidate, findings := bundle.Build(source, bundle.BuildOptions{
BundleVersion: "9.0.0", ReleaseSequence: 900, Channel: "canary",
BundleVersion: "9.0.0", ReleaseSequence: 900,
SourceCommit: sourceCommit, MinimumCLIVersion: "9.0.0",
Workflow: ".github/workflows/release-bundle.yml", SourceRef: "refs/heads/main",
TrackedSources: tracked, HarnessEvidenceProvisional: true,
TrackedSources: tracked,
}, bundle.TrustPolicy{
SchemaVersion: 1, TrustDomain: "gds-release",
Source: bundle.TrustSource{Owner: "NDDev-OpenNetwork", Repository: "github-device-sync",
Expand Down Expand Up @@ -282,7 +282,7 @@ func TestGenerateRepositoryConsumesVerifiedReleasedBundle(t *testing.T) {
data := result.Data.(map[string]any)
released := data["bundle"].(map[string]any)
if released["version"] != "9.0.0" || released["release_sequence"] != float64(900) ||
released["channel"] != "canary" || released["digest"] != candidate.Envelope.ArtifactDigest {
released["channel"] != nil || released["digest"] != candidate.Envelope.ArtifactDigest {
t.Fatalf("released bundle identity = %#v", released)
}
}
Expand Down
3 changes: 0 additions & 3 deletions core/cmd/gds-release-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,8 @@ func run(ctx context.Context, arguments []string, stdout, stderr io.Writer) int
flags.StringVar(&request.OutputDirectory, "output", "", "new release output directory")
flags.StringVar(&request.Version, "version", "", "release SemVer without a v prefix")
flags.IntVar(&request.ReleaseSequence, "sequence", 0, "monotonic release sequence")
flags.StringVar(&request.Channel, "channel", "canary", "canary, stable, or frozen")
flags.StringVar(&request.MinimumCLIVersion, "minimum-cli-version", "", "minimum compatible CLI SemVer")
flags.StringVar(&request.SourceRef, "source-ref", "", "exact refs/heads/* or refs/tags/* source ref")
flags.StringVar(&request.HarnessEvidenceDirectory, "harness-evidence-directory", "", "directory containing manifest.json and isolated active-harness records")
flags.StringVar(&request.HarnessEvidenceTrustPolicy, "harness-evidence-trust-policy", "", "offline public trust policy for harness evidence")
flags.StringVar(
&request.GoBinary, "go-binary", "",
"absolute Go executable (defaults to the running toolchain GOROOT when available)",
Expand Down
4 changes: 2 additions & 2 deletions core/compiler/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

// DevelopmentBundleVersion stamps bundles compiled from policy sources on a
// policy-owner checkout. It tracks the current release line with a -dev
// suffix so a development bundle is dated honestly; the development channel
// field, not this string, is what classifies the bundle.
// suffix so a development bundle is dated honestly; a zero release sequence,
// not this string, is what classifies the bundle.
const DevelopmentBundleVersion = "0.9.7-dev"

type PolicySource struct {
Expand Down
18 changes: 9 additions & 9 deletions core/context/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,22 @@ func (prover *CanonicalPolicyProver) Verify(
err,
)}
}
if document.Bundle.Channel != "development" {
if document.Bundle.ReleaseSequence != 0 {
// Released candidates can only be produced from an archive that passed
// full envelope, manifest and member verification. The committed lock
// retains that artifact/content/attestation identity and the exact
// generated bytes; it deliberately does not require policy source copies
// in every consumer repository.
return nil
}
// The channel alone classifies the bundle. This used to also require the
// version to equal the current DevelopmentBundleVersion, which meant that
// bumping that constant silently reclassified every not-yet-regenerated
// development lock as released and skipped its source verification — the
// opposite of what a version bump should do. A development bundle carrying
// an older dev version is still a development bundle and is verified as
// one.
// A zero release sequence alone classifies the bundle as development: the
// release builder rejects sequence < 1, so no published bundle can carry
// it. This used to also require the version to equal the current
// DevelopmentBundleVersion, which meant that bumping that constant
// silently reclassified every not-yet-regenerated development lock as
// released and skipped its source verification — the opposite of what a
// version bump should do. A development bundle carrying an older dev
// version is still a development bundle and is verified as one.
if estateRoot == "" {
return []domain.Finding{policyProvenanceFinding(
"GDS_CONTEXT_POLICY_ESTATE_NOT_PROVEN",
Expand Down Expand Up @@ -185,7 +186,6 @@ func (prover *CanonicalPolicyProver) Verify(
// legitimately differ, which is the whole point.
if bundle.Version != document.Bundle.Version ||
bundle.ReleaseSequence != document.Bundle.ReleaseSequence ||
bundle.Channel != document.Bundle.Channel ||
bundle.Digest != document.Bundle.Digest {
return []domain.Finding{{
Code: "GDS_CONTEXT_POLICY_BUNDLE_MISMATCH",
Expand Down
4 changes: 2 additions & 2 deletions core/context/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func TestCanonicalPolicyProverRejectsCommittedSelfConsistentReplacement(t *testi
func TestCanonicalPolicyProverRejectsUncommittedReleasedProjection(t *testing.T) {
resolver := newTestResolver(t)
document := bundleLockDocument{}
document.Bundle.Channel = "stable"
document.Bundle.ReleaseSequence = 1
document.Bundle.Version = "1.0.0"
findings := resolver.prover.Verify(
context.Background(), t.TempDir(), t.TempDir(), domain.RepositoryAnchor{}, document,
Expand Down Expand Up @@ -234,7 +234,7 @@ func TestCanonicalPolicyProverAcceptsCommittedReleasedProjectionIdentity(t *test
runContextGit(t, root, "add", "--all")
runContextGit(t, root, "commit", "--quiet", "-m", "released projection")
document := bundleLockDocument{}
document.Bundle.Channel = "stable"
document.Bundle.ReleaseSequence = 1
document.Bundle.Version = "1.0.0"
anchor := domain.RepositoryAnchor{
Repository: domain.RepositoryIdentity{Roles: []string{"module"}},
Expand Down
5 changes: 2 additions & 3 deletions core/estate/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ type MutationGates struct {
}

type Estate struct {
ID string `json:"id"`
Name string `json:"name"`
DefaultBundleChannel string `json:"default_bundle_channel"`
ID string `json:"id"`
Name string `json:"name"`
}

type Rollout struct {
Expand Down
Loading
Loading