From 199fc3096e46ef77f303b92769c82be10a066323 Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Tue, 1 Sep 2026 20:27:05 +0500 Subject: [PATCH] fix(validate): auxiliary checks resolve engine inputs through the source layout An external estate pins the engine as a module and owns only its anchor, skills and exceptions -- yet doctor's auxiliary checks read the migration registry, harness registry and bridge, harness profiles, bundle trust, source register and plugin static files from the authority root, so an estate doctor could never be green: five unconditional read failures and a phantom invalid plugin source on every run (#65). ValidateCanonical now takes the engine root for engine-distribution inputs, BuildPackage reads plugin static files from a staticRoot, and the harness validators resolve the same development source layout the harness operations already use. On the engine repository both roots coincide and nothing changes. The paired test pins the split and its control: collapsed roots lose exactly the five engine inputs. Fixes #65 Claude-Session: https://claude.ai/code/session_01LsGid6U5RrQdFvJmvYdGCF --- .gds/bundle.lock.yaml | 10 ++--- .github/workflows/gds-ci.yml | 4 +- core/app/services.go | 21 +++++---- core/harness/codex.go | 2 +- core/releasebuilder/builder.go | 2 +- core/skills/package.go | 8 +++- core/skills/package_test.go | 4 +- core/validation/canonical_layout_test.go | 55 ++++++++++++++++++++++++ core/validation/schema.go | 21 ++++++--- core/validation/schema_test.go | 1 + 10 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 core/validation/canonical_layout_test.go diff --git a/.gds/bundle.lock.yaml b/.gds/bundle.lock.yaml index 41fa04f..2886898 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:ed474c953d73b3ec1afab5fd9a576cfe593fc979d1010389574acbe097d3ec01" - digest: "sha256:43889e1cb7cc1d2911819e402b9382e32e0d87ceae4b301f7ecb8e97b0501861" + source_tree_digest: "sha256:0b0bf68b9ab468748b19776ce40aaf9d1cbc8b3ebd764fceeec9f0135cc1457b" + digest: "sha256:42f16cda41db4b4a14b6b6f8bfb39286416c4a0b1cc1bbf4f2e780e10e9fd5f7" projection: - input_digest: "sha256:0ede97348a7da35fcd2d86fe3c27782ffb57935712c8cacb9c058440d673c830" - output_digest: "sha256:18fbc4bd685017f12bf53578fb43248f57738685b95a65285101434896aba652" + input_digest: "sha256:fd431f29d3e475e8c26065692f17245c3dbace5dc8c1c98839eb5ffdac2f3499" + output_digest: "sha256:9026d8394956deebe4214d6cf2c1c8e2cb91d99216f59d9087364105c306723a" files: - path: ".gds/compiled-policy.json" digest: "sha256:b5517ed46f67866220c2b18dbfbda4a40d99f00327611e56742a118d0ac59d0b" - path: ".github/workflows/gds-ci.yml" - digest: "sha256:b5174442c13c9f3b9cea05da57296239a72b3819d4064a46af14cec97fcacb79" + digest: "sha256:7e03a6468d3d0322208874a3ed8433c41c9d2bf6755191b9e63f818f6204909d" diff --git a/.github/workflows/gds-ci.yml b/.github/workflows/gds-ci.yml index 23eac94..75f98f5 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:ed474c953d73b3ec1afab5fd9a576cfe593fc979d1010389574acbe097d3ec01 -# input-digest: sha256:0ede97348a7da35fcd2d86fe3c27782ffb57935712c8cacb9c058440d673c830 +# source-tree-digest: sha256:0b0bf68b9ab468748b19776ce40aaf9d1cbc8b3ebd764fceeec9f0135cc1457b +# input-digest: sha256:fd431f29d3e475e8c26065692f17245c3dbace5dc8c1c98839eb5ffdac2f3499 # output-digest: sha256:01fb4854784be9e4564bcc84e70786484b370879be5e5ab1dd49f8b73ea2dea4 # edit-source: # - .gds/repository.yaml diff --git a/core/app/services.go b/core/app/services.go index 58d8b7d..e5025bf 100644 --- a/core/app/services.go +++ b/core/app/services.go @@ -781,7 +781,8 @@ func (services *Services) ValidateSchemas( return envelopeForError("gds validate schemas", candidate, err) } } - findings := services.Schemas.ValidateCanonical(root, fixturePath) + engineRoot := projections.ResolveDevelopmentSourceLayout(root).EngineRoot + findings := services.Schemas.ValidateCanonical(root, engineRoot, fixturePath) class := classifyFindings(findings) return domain.NewEnvelope("gds validate schemas", class, ValidationData{ Target: root, SchemaCount: len(services.Schemas.Names()), @@ -1006,7 +1007,8 @@ func (services *Services) PackagePlugin( if err != nil { return envelopeForError("gds skill package", path, err) } - candidate, findings := skills.BuildPackage(info.WorktreeRoot, plugin, services.Schemas) + staticRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot + candidate, findings := skills.BuildPackage(info.WorktreeRoot, staticRoot, plugin, services.Schemas) return domain.NewEnvelope( "gds skill package", classifyFindings(findings), candidate, findings..., ) @@ -1021,9 +1023,10 @@ func (services *Services) ValidatePlugins(ctx context.Context, path string) doma findings := append([]domain.Finding{}, catalog.Findings...) data := PluginValidationData{} if len(catalog.Findings) == 0 { + staticRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot for _, plugin := range catalog.Registry.Plugins { candidate, pluginFindings := skills.BuildPackage( - info.WorktreeRoot, plugin.ID, services.Schemas, + info.WorktreeRoot, staticRoot, plugin.ID, services.Schemas, ) data.Packages = append(data.Packages, candidate) findings = append(findings, pluginFindings...) @@ -1045,9 +1048,10 @@ func (services *Services) ValidateHarness( } var report any var findings []domain.Finding + engineRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot switch harnessID { case "all": - report, findings = harness.ValidateAll(info.WorktreeRoot, services.Schemas) + report, findings = harness.ValidateAll(engineRoot, services.Schemas) case "selected": selected, selectionFindings := services.estateSelectedHarnesses(info.WorktreeRoot) if len(selectionFindings) != 0 { @@ -1055,9 +1059,9 @@ func (services *Services) ValidateHarness( "gds validate harnesses", classifyFindings(selectionFindings), nil, selectionFindings..., ) } - report, findings = harness.ValidateSelected(info.WorktreeRoot, selected, services.Schemas) + report, findings = harness.ValidateSelected(engineRoot, selected, services.Schemas) default: - report, findings = harness.Validate(info.WorktreeRoot, harnessID, services.Schemas) + report, findings = harness.Validate(engineRoot, harnessID, services.Schemas) } return domain.NewEnvelope( "gds validate harnesses", classifyFindings(findings), report, findings..., @@ -1118,10 +1122,11 @@ func (services *Services) ValidateHarnessStatic( } var report any var findings []domain.Finding + engineRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot if harnessID == "all" { - report, findings = harness.ValidateStaticAll(info.WorktreeRoot, services.Schemas) + report, findings = harness.ValidateStaticAll(engineRoot, services.Schemas) } else { - report, findings = harness.ValidateStatic(info.WorktreeRoot, harnessID, services.Schemas) + report, findings = harness.ValidateStatic(engineRoot, harnessID, services.Schemas) } return domain.NewEnvelope( "gds validate harnesses", classifyFindings(findings), report, findings..., diff --git a/core/harness/codex.go b/core/harness/codex.go index d8e5a30..0e5d23d 100644 --- a/core/harness/codex.go +++ b/core/harness/codex.go @@ -225,7 +225,7 @@ func validateMarketplaceAndPackages( )) continue } - candidate, packageFindings := skills.BuildPackage(root, plugin, schemas) + candidate, packageFindings := skills.BuildPackage(root, root, plugin, schemas) report.Plugins = append(report.Plugins, candidate) findings = append(findings, packageFindings...) } diff --git a/core/releasebuilder/builder.go b/core/releasebuilder/builder.go index 3b0a03a..fd5ed05 100644 --- a/core/releasebuilder/builder.go +++ b/core/releasebuilder/builder.go @@ -498,7 +498,7 @@ func releaseAdditionalFiles( }) } for _, plugin := range []string{"gds-core", "gds-estate-admin", "gds-module"} { - candidate, findings := skills.BuildPackage(root, plugin, schemas) + candidate, findings := skills.BuildPackage(root, root, plugin, schemas) if len(findings) != 0 { return nil, findingError("build plugin "+plugin, findings) } diff --git a/core/skills/package.go b/core/skills/package.go index cf30c60..d432056 100644 --- a/core/skills/package.go +++ b/core/skills/package.go @@ -73,7 +73,11 @@ type hookHandler struct { StatusMessage string `json:"statusMessage,omitempty"` } -func BuildPackage(root, pluginID string, schemas *validation.Set) (PackageCandidate, []domain.Finding) { +// BuildPackage packages one plugin for the authority rooted at root. The +// registry and every skill source belong to the authority; the plugin static +// files (hooks, manifest chrome) ship with the engine distribution and are +// read from staticRoot, which equals root on the engine repository itself. +func BuildPackage(root, staticRoot, pluginID string, schemas *validation.Set) (PackageCandidate, []domain.Finding) { outcome := Validate(root, schemas) if len(outcome.Findings) != 0 { return PackageCandidate{Plugin: pluginID}, outcome.Findings @@ -89,7 +93,7 @@ func BuildPackage(root, pluginID string, schemas *validation.Set) (PackageCandid if len(findings) != 0 { return PackageCandidate{Plugin: pluginID}, findings } - contents, staticFindings := pluginStaticFiles(root, pluginID) + contents, staticFindings := pluginStaticFiles(staticRoot, pluginID) findings = append(findings, staticFindings...) for _, definition := range selected { for _, relative := range []string{"SKILL.md", filepath.Join("agents", "openai.yaml")} { diff --git a/core/skills/package_test.go b/core/skills/package_test.go index 836cc0d..ad36e60 100644 --- a/core/skills/package_test.go +++ b/core/skills/package_test.go @@ -19,11 +19,11 @@ func TestBuildPackagesAreDeterministicAndStandalone(t *testing.T) { t.Fatal(err) } for _, plugin := range []string{"gds-core", "gds-estate-admin", "gds-module"} { - first, findings := BuildPackage(root, plugin, schemas) + first, findings := BuildPackage(root, root, plugin, schemas) if len(findings) != 0 { t.Fatalf("%s findings: %+v", plugin, findings) } - second, findings := BuildPackage(root, plugin, schemas) + second, findings := BuildPackage(root, root, plugin, schemas) if len(findings) != 0 { t.Fatalf("%s second findings: %+v", plugin, findings) } diff --git a/core/validation/canonical_layout_test.go b/core/validation/canonical_layout_test.go new file mode 100644 index 0000000..405cddb --- /dev/null +++ b/core/validation/canonical_layout_test.go @@ -0,0 +1,55 @@ +package validation + +import ( + "os" + "path/filepath" + "testing" +) + +// An external estate owns its anchor and skills registry but pins the engine +// as a module; the engine-distribution inputs must be validated from the +// engine root, not the authority root. Measured on the live estate: reading +// them from the authority root produced five unconditional read failures +// (issue #65). +func TestValidateCanonicalReadsEngineInputsFromTheEngineRoot(t *testing.T) { + t.Parallel() + set, err := NewSchemaSet() + if err != nil { + t.Fatalf("NewSchemaSet() error = %v", err) + } + engineRoot := repositoryRoot(t) + authorityRoot := t.TempDir() + for _, relative := range []string{ + filepath.Join(".gds", "repository.yaml"), + filepath.Join("skills", "registry.yaml"), + } { + raw, err := os.ReadFile(filepath.Join(engineRoot, relative)) + if err != nil { + t.Fatalf("read %s: %v", relative, err) + } + target := filepath.Join(authorityRoot, relative) + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(target, raw, 0o644); err != nil { + t.Fatal(err) + } + } + + if findings := set.ValidateCanonical(authorityRoot, engineRoot, ""); len(findings) != 0 { + t.Fatalf("external layout findings = %#v", findings) + } + + // The control pins the defect this split fixes: collapsing both roots + // onto the authority loses exactly the five engine-distribution inputs. + missing := map[string]bool{} + for _, finding := range set.ValidateCanonical(authorityRoot, authorityRoot, "") { + if finding.Code != "GDS_INPUT_READ_FAILED" { + t.Fatalf("unexpected finding on collapsed roots: %#v", finding) + } + missing[finding.Message] = true + } + if len(missing) != 5 { + t.Fatalf("collapsed roots lost %d inputs, want the 5 engine inputs: %v", len(missing), missing) + } +} diff --git a/core/validation/schema.go b/core/validation/schema.go index 414bb45..f0e3b62 100644 --- a/core/validation/schema.go +++ b/core/validation/schema.go @@ -173,24 +173,31 @@ func (set *Set) ValidateFile(schemaName, path string) []domain.Finding { return set.Validate(schemaName, value, path) } -func (set *Set) ValidateCanonical(root string, fixtureIndex string) []domain.Finding { +// ValidateCanonical validates the canonical inputs of an authority rooted at +// root. Engine-distribution inputs (the migration registry, the harness +// registry and bridge, harness profiles, bundle trust, the source register) +// are read from engineRoot: on the engine repository the two roots coincide, +// while an external estate pins the engine as a module and owns only its +// anchor, skills and exceptions. Reading engine inputs from the estate root +// produced five unconditional read failures on every external authority. +func (set *Set) ValidateCanonical(root string, engineRoot string, fixtureIndex string) []domain.Finding { findings := []domain.Finding{} findings = append(findings, set.ValidateFile( "repository", filepath.Join(root, ".gds", "repository.yaml"), )...) findings = append(findings, set.ValidateFile( - "migration-registry", filepath.Join(root, "schemas", "migrations", "registry.yaml"), + "migration-registry", filepath.Join(engineRoot, "schemas", "migrations", "registry.yaml"), )...) findings = append(findings, set.ValidateFile( "skill-registry", filepath.Join(root, "skills", "registry.yaml"), )...) findings = append(findings, set.ValidateFile( - "harness-registry", filepath.Join(root, "harnesses", "capability-registry.yaml"), + "harness-registry", filepath.Join(engineRoot, "harnesses", "capability-registry.yaml"), )...) findings = append(findings, set.ValidateFile( - "module-harness-bridge", filepath.Join(root, "harnesses", "module-bridge.yaml"), + "module-harness-bridge", filepath.Join(engineRoot, "harnesses", "module-bridge.yaml"), )...) - profilePaths, err := filepath.Glob(filepath.Join(root, "harnesses", "*", "profile.yaml")) + profilePaths, err := filepath.Glob(filepath.Join(engineRoot, "harnesses", "*", "profile.yaml")) if err != nil { findings = append(findings, domain.Finding{ Code: "GDS_HARNESS_PROFILE_DISCOVERY_FAILED", Severity: domain.SeverityHigh, @@ -204,10 +211,10 @@ func (set *Set) ValidateCanonical(root string, fixtureIndex string) []domain.Fin } } findings = append(findings, set.ValidateFile( - "bundle-trust", filepath.Join(root, "requirements", "bundle-trust.yaml"), + "bundle-trust", filepath.Join(engineRoot, "requirements", "bundle-trust.yaml"), )...) findings = append(findings, set.ValidateFile( - "source-register", filepath.Join(root, "docs", "source-register", "sources.yaml"), + "source-register", filepath.Join(engineRoot, "docs", "source-register", "sources.yaml"), )...) exceptionPaths, err := filepath.Glob(filepath.Join(root, "estate", "exceptions", "*.yaml")) if err != nil { diff --git a/core/validation/schema_test.go b/core/validation/schema_test.go index 6cd1b1b..74f47a3 100644 --- a/core/validation/schema_test.go +++ b/core/validation/schema_test.go @@ -54,6 +54,7 @@ func TestCanonicalSchemasAndFixturesPass(t *testing.T) { } root := repositoryRoot(t) findings := set.ValidateCanonical( + root, root, filepath.Join(root, "tests", "fixtures", "schemas", "v1", "cases.json"), )