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: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"
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:ed474c953d73b3ec1afab5fd9a576cfe593fc979d1010389574acbe097d3ec01
# input-digest: sha256:0ede97348a7da35fcd2d86fe3c27782ffb57935712c8cacb9c058440d673c830
# source-tree-digest: sha256:0b0bf68b9ab468748b19776ce40aaf9d1cbc8b3ebd764fceeec9f0135cc1457b
# input-digest: sha256:fd431f29d3e475e8c26065692f17245c3dbace5dc8c1c98839eb5ffdac2f3499
# output-digest: sha256:01fb4854784be9e4564bcc84e70786484b370879be5e5ab1dd49f8b73ea2dea4
# edit-source:
# - .gds/repository.yaml
Expand Down
21 changes: 13 additions & 8 deletions core/app/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down Expand Up @@ -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...,
)
Expand All @@ -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...)
Expand All @@ -1045,19 +1048,20 @@ 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 {
return domain.NewEnvelope(
"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...,
Expand Down Expand Up @@ -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...,
Expand Down
2 changes: 1 addition & 1 deletion core/harness/codex.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
}
Expand Down
2 changes: 1 addition & 1 deletion core/releasebuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
8 changes: 6 additions & 2 deletions core/skills/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")} {
Expand Down
4 changes: 2 additions & 2 deletions core/skills/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
55 changes: 55 additions & 0 deletions core/validation/canonical_layout_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
21 changes: 14 additions & 7 deletions core/validation/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions core/validation/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)
Expand Down