From 72017a163ba2fd584199447eda416ffeb95aafa4 Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Mon, 7 Sep 2026 09:44:59 +0500 Subject: [PATCH 1/2] perf(module): reject ineligible pins before verification commands Signed-off-by: rldyourmnd --- CHANGELOG.md | 5 +++++ core/app/module_pin.go | 25 ++++++++++++++----------- core/cli/module_lifecycle_test.go | 30 +++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1bb1be..0d40c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ 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. + + ### Changed - Ruleset reconciliation compares the complete owned required-check identities, diff --git a/core/app/module_pin.go b/core/app/module_pin.go index 0de11fa..b857943 100644 --- a/core/app/module_pin.go +++ b/core/app/module_pin.go @@ -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 @@ -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())} diff --git a/core/cli/module_lifecycle_test.go b/core/cli/module_lifecycle_test.go index 9023303..87de880 100644 --- a/core/cli/module_lifecycle_test.go +++ b/core/cli/module_lifecycle_test.go @@ -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) } @@ -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", @@ -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, From f2d7a8c568d240105351ac4a4ecc6d3519794f67 Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Mon, 7 Sep 2026 09:46:35 +0500 Subject: [PATCH 2/2] chore(projections): bind module preflight source Signed-off-by: rldyourmnd --- .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 acf911f..8b74c77 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: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" diff --git a/.github/workflows/gds-ci.yml b/.github/workflows/gds-ci.yml index 1fb9e92..a70097c 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:fc35a6bc483600e82186f5a9f592f6818cd2aca3cb50dbd1e9b9bb1772ead851 -# input-digest: sha256:f7fa43557bb16cb97cbe7b4926a47af83ff32fec4c72a84a3be7dc8251447e2d +# source-tree-digest: sha256:670e14659a7a4ee1d99c49841c7ed04210d67fef3828f8cfc8ca135be19ac483 +# input-digest: sha256:eb612421c50ec33e68468e080d48f24c57ba8f8cd4dcf738d0debcfb584936f4 # output-digest: sha256:01fb4854784be9e4564bcc84e70786484b370879be5e5ab1dd49f8b73ea2dea4 # edit-source: # - .gds/repository.yaml