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:fc35a6bc483600e82186f5a9f592f6818cd2aca3cb50dbd1e9b9bb1772ead851"
digest: "sha256:f4e6402e5d6729a944595ace0f2b81990cd562fd662cfa1f7adf369d8bfec896"
source_tree_digest: "sha256:670e14659a7a4ee1d99c49841c7ed04210d67fef3828f8cfc8ca135be19ac483"
digest: "sha256:085a6f985c4abb09f96c225e1123996e9c54fe28f2862af1a2f85f8a908c6cbf"

projection:
input_digest: "sha256:f7fa43557bb16cb97cbe7b4926a47af83ff32fec4c72a84a3be7dc8251447e2d"
output_digest: "sha256:4bb6f7dd03e8efc9ca96005abb42cb824723bafb8a67afc8c09b1770bc148cd7"
input_digest: "sha256:eb612421c50ec33e68468e080d48f24c57ba8f8cd4dcf738d0debcfb584936f4"
output_digest: "sha256:0f3891bccad430b0c41acc687d4f9b26e0bb6cba00434f3f13a8598575d6ef66"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:b5517ed46f67866220c2b18dbfbda4a40d99f00327611e56742a118d0ac59d0b"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:aab129beab492fc47a89d991fca50d9d04fa90a242bea316e77c42e0d92fcb39"
digest: "sha256:bba562704e7c072f0be565c4fe3a22b52d691b87aa0fe44c4d998f8e9336b0da"
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:fc35a6bc483600e82186f5a9f592f6818cd2aca3cb50dbd1e9b9bb1772ead851
# input-digest: sha256:f7fa43557bb16cb97cbe7b4926a47af83ff32fec4c72a84a3be7dc8251447e2d
# source-tree-digest: sha256:670e14659a7a4ee1d99c49841c7ed04210d67fef3828f8cfc8ca135be19ac483
# input-digest: sha256:eb612421c50ec33e68468e080d48f24c57ba8f8cd4dcf738d0debcfb584936f4
# output-digest: sha256:01fb4854784be9e4564bcc84e70786484b370879be5e5ab1dd49f8b73ea2dea4
# edit-source:
# - .gds/repository.yaml
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ Versioning.

## [Unreleased]

- Reject ineligible module pins and invalid consumer policy before running
module verification commands; eligible plans and apply still verify the exact
published target and bind that evidence to the transaction.
- Add opt-in `continuous-development` policy with explicit, journaled removal
intent for selected repository ruleset status checks and generated advisory CI
guidance. Preserve omitted rules and unknown status-check parameters across
updates; verify empty ruleset readback and replay without another write.


### Changed

- Ruleset reconciliation compares the complete owned required-check identities,
Expand Down
25 changes: 14 additions & 11 deletions core/app/module_pin.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,20 @@ func (services *Services) modulePinContext(
"GDS_MODULE_PIN_TARGET_NOT_PUBLISHED", "Module default commit is not exactly published on its configured origin.",
)}
}
// Resolve cheap eligibility and policy failures before materializing a
// throwaway checkout or invoking any module command. Rejected pins must
// not spend a full verification run proving a target they cannot consume.
if submodule == nil || submodule.GitlinkOID == "" || submodule.GitlinkStage != 0 ||
submodule.GitlinkOID == targetOID || !pinWorktreeStateIsEligible(*submodule, targetOID) {
return modulePinContext{}, []domain.Finding{modulePinFinding(
"GDS_MODULE_PIN_GITLINK_NOT_ELIGIBLE",
"Consumer requires one changed, stage-zero gitlink whose checkout is absent or already at the target commit.",
)}
}
consumerCompiled := services.Compiler.CompileDirectory(estateRoot, consumer, compiler.DevelopmentBundleVersion)
if len(consumerCompiled.Findings) != 0 {
return modulePinContext{}, consumerCompiled.Findings
}
// Required checks used to refuse the pin outright, whatever the module was:
// any declared lane meant "no verified execution evidence" and there was no
// way to supply any. Every module in this estate declares at least one, so
Expand Down Expand Up @@ -368,17 +382,6 @@ func (services *Services) modulePinContext(
"GDS_MODULE_PIN_CHECKS_NOT_PROVEN", err.Error(),
)}
}
if submodule == nil || submodule.GitlinkOID == "" || submodule.GitlinkStage != 0 ||
submodule.GitlinkOID == targetOID || !pinWorktreeStateIsEligible(*submodule, targetOID) {
return modulePinContext{}, []domain.Finding{modulePinFinding(
"GDS_MODULE_PIN_GITLINK_NOT_ELIGIBLE",
"Consumer requires one changed, stage-zero gitlink whose checkout is absent or already at the target commit.",
)}
}
consumerCompiled := services.Compiler.CompileDirectory(estateRoot, consumer, compiler.DevelopmentBundleVersion)
if len(consumerCompiled.Findings) != 0 {
return modulePinContext{}, consumerCompiled.Findings
}
consumerManifestDigest, err := fileDigest(filepath.Join(consumerInfo.WorktreeRoot, ".gds", "repository.yaml"))
if err != nil {
return modulePinContext{}, []domain.Finding{modulePinFinding("GDS_MODULE_PIN_MANIFEST_NOT_PROVEN", err.Error())}
Expand Down
30 changes: 27 additions & 3 deletions core/cli/module_lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,12 @@ func TestModuleUpdatePinRequiresPublishedModuleAndStagesOnlyGitlink(t *testing.T
// estate does. A module the consumer pins must state how it is proven, and
// the pin must now prove it at the target commit, so the fixture declares a
// lane that succeeds in a clean checkout.
moduleAnchor = append(moduleAnchor, []byte(
"\nverification:\n commands:\n test:\n - \"true\"\n required:\n - \"test\"\n",
)...)
marker := filepath.Join(t.TempDir(), "module-command-ran")
command := "printf verified >> '" + strings.ReplaceAll(marker, "'", "'\"'\"'") + "'"
moduleAnchor = append(moduleAnchor, []byte(fmt.Sprintf(
"\nverification:\n commands:\n test:\n - %q\n required:\n - \"test\"\n", command,
))...)

if err := os.WriteFile(moduleAnchorPath, moduleAnchor, 0o644); err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -168,6 +171,24 @@ func TestModuleUpdatePinRequiresPublishedModuleAndStagesOnlyGitlink(t *testing.T

statePath := sessionStatePath(t)
t.Setenv("GDS_ESTATE_ROOT", testEstateRoot(t))
// A checkout at the old pin is clean but cannot be updated by this
// transaction. Reject it before executing the module's commands.
modulePath := filepath.Join(consumer.client, "modules", "module")
runSessionGit(t, consumer.client, "clone", "-q", module.client, modulePath)
runSessionGit(t, modulePath, "checkout", "-q", oldOID)
code, rejected, diagnostics := executeJSON(
t, "--json", "--cwd", consumer.client, "module", "update-pin", "--plan",
"--module", module.client, "--name", "module",
"--state-path", statePath, "--device-id", syncTestDeviceID,
"--session-id", "module-update-pin",
)
if code == 0 || len(rejected.Findings) != 1 || rejected.Findings[0].Code != "GDS_MODULE_PIN_GITLINK_NOT_ELIGIBLE" {
t.Fatalf("ineligible pin: code=%d diagnostics=%q envelope=%#v", code, diagnostics, rejected)
}
if _, err := os.Stat(marker); !os.IsNotExist(err) {
t.Fatalf("ineligible pin ran module commands: %v", err)
}
runSessionGit(t, modulePath, "checkout", "-q", moduleTargetOID)
exitCode, planned, stderr := executeJSON(
t, "--json", "--cwd", consumer.client, "module", "update-pin", "--plan",
"--module", module.client, "--name", "module",
Expand All @@ -177,6 +198,9 @@ func TestModuleUpdatePinRequiresPublishedModuleAndStagesOnlyGitlink(t *testing.T
if exitCode != 0 || planned.Mutation.Attempted {
t.Fatalf("plan exit=%d stderr=%q envelope=%#v", exitCode, stderr, planned)
}
if content, err := os.ReadFile(marker); err != nil || string(content) != "verified" {
t.Fatalf("eligible pin did not verify its module: content=%q err=%v", content, err)
}
planID := syncPlanID(t, planned.Data)
exitCode, applied, stderr := executeJSON(
t, "--json", "module", "update-pin", "--apply", planID,
Expand Down