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 @@ -4,14 +4,14 @@ schema_version: 1
bundle:
version: "0.9.7-dev"
release_sequence: 0
source_tree_digest: "sha256:c8582e4ff6726dc451444553411d81bf681dbcb03db409987de1db35c016f723"
digest: "sha256:f8b44694166c4358ee247362f0d3ec725c421bc681018ad8bea526250440b16b"
source_tree_digest: "sha256:4c7e5f20f939b7c78e0d3432c61d62f0b01d7fade56621491da307d656463448"
digest: "sha256:3b2e0e5abf1515b37407f609e7e35034136c5acea5ad46f37c67aac17f3ed93a"

projection:
input_digest: "sha256:033f6d802a41abddbad4bb0375ca2ad899c2c5651409c3e9541b229179a4248c"
output_digest: "sha256:3ff85f46591c1c6d1479bb305adf866d64537f94345fac5185acba78b0738a6f"
input_digest: "sha256:924e25d3b7a56d3966975e735ec39f9eec2bca3420b0d4ee6af6e6e07d5191e0"
output_digest: "sha256:91c917a3497b68e1c5d6ab372a072a43f0e34f4815511b341357e5b0721b04aa"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:9f498788bdc34e52a0ab793c536e0e6a7b360c2e1a20446cbf03ed51986cdc6f"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:387375f2ea62d4971ae8051ad1d13aa3c0d42ef9e7695a6df40d636808cc06d6"
digest: "sha256:4f263ba0c7c0e71d2eaeacca25f1fbdaa43cac912d12a178b00dee26c051f932"
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:c8582e4ff6726dc451444553411d81bf681dbcb03db409987de1db35c016f723
# input-digest: sha256:033f6d802a41abddbad4bb0375ca2ad899c2c5651409c3e9541b229179a4248c
# source-tree-digest: sha256:4c7e5f20f939b7c78e0d3432c61d62f0b01d7fade56621491da307d656463448
# input-digest: sha256:924e25d3b7a56d3966975e735ec39f9eec2bca3420b0d4ee6af6e6e07d5191e0
# output-digest: sha256:4ef1ee2fcc42927eaedef9c85f7b421f87e5cc75ff7055ef520216a00f7d4b74
# edit-source:
# - .gds/repository.yaml
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Versioning.

## [Unreleased]

- Resolve read-only harness detection, rendering, inspection, evaluation and
device reconciliation against the consumed engine module in external estates;
preserve estate-relative input paths and explicit mutation targets.

- Observe installed Devin CLI as the eighth catalogue identity while retaining
seven verified execution adapters. Bind per-mapping runtime evidence policy
and refuse unproven Devin configuration projections or runtime-proof claims.
Expand Down
7 changes: 5 additions & 2 deletions core/app/harness_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/NDDev-OpenNetwork/github-device-sync/core/domain"
"github.com/NDDev-OpenNetwork/github-device-sync/core/harness"
"github.com/NDDev-OpenNetwork/github-device-sync/core/projections"
)

func (services *Services) RenderHarnessAdapter(
Expand Down Expand Up @@ -155,8 +156,9 @@ func (services *Services) EvaluateHarnessAdapter(
}
options.RuntimeDriver = normalizePath(options.RuntimeDriver)
options.EvidenceDirectory = normalizePath(options.EvidenceDirectory)
engineRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot
run, findings := harness.Evaluate(
ctx, info.WorktreeRoot, harnessID, options, services.Schemas, services.Now(), nil,
ctx, engineRoot, harnessID, options, services.Schemas, services.Now(), nil,
)
exitClass := domain.ExitSuccess
if run.Result == "not-proven" {
Expand Down Expand Up @@ -192,7 +194,8 @@ func (services *Services) resolveHarnessAdapter(
envelope := envelopeForError(command, path, err)
return nil, &envelope
}
adapter, findings := harness.NewAdapter(info.WorktreeRoot, harnessID, services.Schemas)
engineRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot
adapter, findings := harness.NewAdapter(engineRoot, harnessID, services.Schemas)
if len(findings) != 0 {
envelope := domain.NewEnvelope(command, classifyFindings(findings), nil, findings...)
return nil, &envelope
Expand Down
110 changes: 110 additions & 0 deletions core/app/harness_external_estate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package app

import (
"os"
"path/filepath"
"runtime"
"testing"

"github.com/NDDev-OpenNetwork/github-device-sync/core/domain"
"github.com/NDDev-OpenNetwork/github-device-sync/core/harness"
)

func TestHarnessObservationsUseConsumedEngineInExternalEstate(t *testing.T) {
_, current, _, _ := runtime.Caller(0)
source := filepath.Clean(filepath.Join(filepath.Dir(current), "..", ".."))
root := t.TempDir()
engine := filepath.Join(root, "modules", "github-device-sync")
for _, directory := range []string{"harnesses", "skills", "docs", "tests/harness"} {
if err := copyAppTestTree(source, engine, directory); err != nil {
t.Fatal(err)
}
}
runCompletionGraphGit(t, root, "init", "-q")
runCompletionGraphGit(t, root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid",
"-c", "commit.gpgsign=false", "commit", "--allow-empty", "-qm", "fixture")
// An estate-local decoy must not replace the consumed catalogue.
if err := os.Mkdir(filepath.Join(root, "harnesses"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "harnesses", "capability-registry.yaml"), []byte("invalid: true\n"), 0o644); err != nil {
t.Fatal(err)
}
bin := t.TempDir()
for _, name := range []string{"agy", "claude", "codex", "cursor-agent", "devin", "grok", "opencode", "pi"} {
if err := os.WriteFile(filepath.Join(bin, name), []byte("#!/bin/sh\n[ \"$1\" = --version ] || exit 42\nprintf 'fixture 1.2.3\\n'\n"), 0o755); err != nil {
t.Fatal(err)
}
}
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
home := t.TempDir()
t.Setenv("HOME", home)
services, err := NewServices(DefaultClock)
if err != nil {
t.Fatal(err)
}
request := harness.RenderRequest{SkillProfile: "core", Scope: "project"}
target := t.TempDir()
t.Run("detect-one-and-all", func(t *testing.T) {
one := services.DetectHarness(t.Context(), root, "devin")
if one.ExitClass != domain.ExitSuccess || one.Data.(harness.RuntimeObservation).Version != "fixture 1.2.3" {
t.Fatalf("external detection: %+v", one)
}
all := services.DetectHarness(t.Context(), root, "all")
if len(all.Data.(harness.RuntimeDetectionReport).Harnesses) != len(harness.CanonicalIDs) {
t.Fatalf("external catalogue not observed: %+v", all)
}
})
t.Run("render-inspect-and-doctor", func(t *testing.T) {
baseline, findings := harness.NewAdapter(source, "codex", services.Schemas)
if len(findings) != 0 {
t.Fatal(findings)
}
want, findings := baseline.Render(request)
if len(findings) != 0 {
t.Fatal(findings)
}
render := services.RenderHarnessAdapter(t.Context(), root, "codex", request)
if render.ExitClass != domain.ExitSuccess || render.Data.(harness.AdapterCandidate).CandidateDigest != want.CandidateDigest {
t.Fatalf("external rendering differs from canonical source: %+v", render)
}
inspect := services.InspectHarnessAdapter(t.Context(), root, "codex", target, request)
if inspect.ExitClass != domain.ExitSuccess || inspect.Data.(harness.AdapterInspection).CandidateDigest != want.CandidateDigest {
t.Fatalf("external inspection: %+v", inspect)
}
doctor := services.DoctorHarnessAdapter(t.Context(), root, "codex", target, request)
if doctor.Data == nil || doctor.Data.(harness.AdapterDoctorReport).Runtime.Version != "fixture 1.2.3" {
t.Fatalf("external doctor: %+v", doctor)
}
})
t.Run("installed-only-plan-remains-refused", func(t *testing.T) {
plan := services.PlanHarnessAdapter(t.Context(), root, "devin", target, request, "install")
if !appHasFinding(plan, "GDS_HARNESS_PROJECT_SKILLS_NOT_PROVEN") || plan.Mutation.Attempted {
t.Fatalf("unproven configuration was permitted: %+v", plan)
}
})
t.Run("evaluation-without-model-execution", func(t *testing.T) {
result := services.EvaluateHarnessAdapter(t.Context(), root, "codex", harness.EvalOptions{
SkillProfile: "core", ModelLabel: "not-proven", ExecutionProfile: "read-only",
})
run, ok := result.Data.(harness.EvalRun)
if !ok || run.Profile.ID != "codex" || appHasFinding(result, "GDS_INPUT_READ_FAILED") || result.Mutation.Attempted {
t.Fatalf("external evaluation did not resolve the consumed contract: %+v", result)
}
})
t.Run("device-reconciliation", func(t *testing.T) {
result := services.ReconcileDeviceHarnesses(t.Context(), HarnessSyncOptions{
Path: root, DevicePath: filepath.Join(source, "estate", "devices", "example-user-ubuntu-1.yaml"),
TargetRoot: target, SkillProfile: "core", Scope: "project",
})
if _, ok := result.Data.(HarnessSyncData); !ok || appHasFinding(result, "GDS_INPUT_READ_FAILED") {
t.Fatalf("external reconciliation did not inspect the consumed catalogue: %+v", result)
}
})
for _, directory := range []string{home, target} {
entries, err := os.ReadDir(directory)
if err != nil || len(entries) != 0 {
t.Fatalf("read-only operation changed %s: %v %v", directory, entries, err)
}
}
}
4 changes: 3 additions & 1 deletion core/app/harness_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/NDDev-OpenNetwork/github-device-sync/core/domain"
"github.com/NDDev-OpenNetwork/github-device-sync/core/harness"
"github.com/NDDev-OpenNetwork/github-device-sync/core/materialize"
"github.com/NDDev-OpenNetwork/github-device-sync/core/projections"
"github.com/NDDev-OpenNetwork/github-device-sync/core/workspace"
)

Expand Down Expand Up @@ -71,14 +72,15 @@ func (services *Services) ReconcileDeviceHarnesses(
// currently holds. Two selected harnesses that share a skill root collide on
// an empty root too, where no on-disk evidence of the conflict exists yet.
claims := map[string][]harness.AdapterFile{}
engineRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot
for _, id := range harness.CanonicalIDs {
// A selected harness that cannot be rendered is a hard error: the device
// cannot converge on it. An unselected one is skipped instead, so an
// incomplete catalogue entry never blocks a device that does not run it —
// the same separation ValidateSelected already makes. It is recorded, not
// swallowed: an unobservable entry could be a leftover install, and
// removing what cannot be seen is never safe.
adapter, adapterFindings := harness.NewAdapter(info.WorktreeRoot, id, services.Schemas)
adapter, adapterFindings := harness.NewAdapter(engineRoot, id, services.Schemas)
if len(adapterFindings) != 0 {
if wanted[id] {
return domain.NewEnvelope(
Expand Down
5 changes: 3 additions & 2 deletions core/app/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -1323,10 +1323,11 @@ func (services *Services) DetectHarness(
}
var report any
var findings []domain.Finding
engineRoot := projections.ResolveDevelopmentSourceLayout(info.WorktreeRoot).EngineRoot
if harnessID == "all" {
report, findings = harness.DetectAll(ctx, info.WorktreeRoot, services.Schemas)
report, findings = harness.DetectAll(ctx, engineRoot, services.Schemas)
} else {
report, findings = harness.Detect(ctx, info.WorktreeRoot, harnessID, services.Schemas)
report, findings = harness.Detect(ctx, engineRoot, harnessID, services.Schemas)
}
return domain.NewEnvelope(
"gds harness detect", classifyFindings(findings), report, findings...,
Expand Down
Loading