47be24c4cc
Finding 3 of REVIEW-2026-07-30.md. target_entity_id was accepted as any non-empty string; the engine only compared it against a pinned TargetEntityID, which is empty for every registered capability. Spec §4.3: "Hexis never accepts a free-text target. Ever." Targets are now checked in order: ent_ shape (free, never touches the network), pinned target, existence in Nexus, entity still active, and a match against the capability's TargetTypes. Validation runs before a confirmation is consumed, so a bad target cannot burn one, and at confirmation-mint time too, since a confirmation binds a target. Two deliberate calls: Nexus unreachable fails closed (503, ErrTargetUnverifiable). Failing open would reinstate exactly this hole the moment Nexus blips, and hand it to anyone able to degrade Nexus. Hexis holds no entity table, so "unreachable" and "I cannot tell if this target is real" are the same statement. The cost is that executes now require Nexus liveness; the lookup is bounded at 5s so a hung Nexus fails fast rather than consuming the capability timeout. An empty TargetTypes means no type constraint, not a bypass — the entity must still exist, be canonical and be active. Rejecting empty outright would disable 16 of the 19 registered capabilities, since only the docker.* entries declare a target type. The spec's stronger blessing guard is not implementable: Nexus has no blessing concept at all. This is the achievable guard, and strictly weaker. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd
178 lines
6.5 KiB
Go
178 lines
6.5 KiB
Go
package execution_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/kami/hexis/internal/domain"
|
|
"github.com/kami/hexis/internal/execution"
|
|
"github.com/kami/hexis/internal/nexusclient"
|
|
"github.com/kami/hexis/internal/provider"
|
|
"github.com/kami/hexis/internal/storage"
|
|
)
|
|
|
|
// fakeNexus stands in for the Nexus entity registry: whatever is in entities
|
|
// exists, anything else is a 404, and err simulates Nexus being unreachable.
|
|
type fakeNexus struct {
|
|
entities map[string]*nexusclient.Entity
|
|
err error
|
|
calls int
|
|
}
|
|
|
|
func (f *fakeNexus) GetEntity(ctx context.Context, id string) (*nexusclient.Entity, error) {
|
|
f.calls++
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
if e, ok := f.entities[id]; ok {
|
|
return e, nil
|
|
}
|
|
return nil, nexusclient.ErrEntityNotFound
|
|
}
|
|
|
|
func newTargetEngine(t *testing.T, nexus execution.EntityLookup) (*execution.Engine, *storage.Store) {
|
|
t.Helper()
|
|
store, err := storage.Open(filepath.Join(t.TempDir(), "hexis.db"))
|
|
if err != nil {
|
|
t.Fatalf("open storage: %v", err)
|
|
}
|
|
t.Cleanup(func() { store.Close() })
|
|
|
|
reg := provider.NewRegistry()
|
|
reg.Register(&fakeProvider{name: "fake"})
|
|
|
|
return execution.New(store, reg, execution.WithEntityLookup(nexus)), store
|
|
}
|
|
|
|
func activeNexus() *fakeNexus {
|
|
return &fakeNexus{entities: map[string]*nexusclient.Entity{
|
|
"ent_container_maven": {ID: "ent_container_maven", Type: "container", State: "active"},
|
|
"ent_host_homesrv": {ID: "ent_host_homesrv", Type: "host", State: "active"},
|
|
"ent_retired_thing": {ID: "ent_retired_thing", Type: "container", State: "retired"},
|
|
}}
|
|
}
|
|
|
|
// TestExecute_RejectsFreeTextTarget pins ECOSYSTEM-SPEC.md §4.3: a target
|
|
// that is not a canonical ent_ ID is refused without even asking Nexus.
|
|
func TestExecute_RejectsFreeTextTarget(t *testing.T) {
|
|
nexus := activeNexus()
|
|
eng, store := newTargetEngine(t, nexus)
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = []string{"container"}
|
|
})
|
|
|
|
for _, target := range []string{"maven", "the maven container", "container_maven", "ent_", "ent_bad id", ""} {
|
|
_, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: target})
|
|
if !errors.Is(err, execution.ErrTargetMalformed) {
|
|
t.Errorf("target %q: expected ErrTargetMalformed, got %v", target, err)
|
|
}
|
|
}
|
|
if nexus.calls != 0 {
|
|
t.Errorf("malformed targets must not reach Nexus, got %d calls", nexus.calls)
|
|
}
|
|
}
|
|
|
|
func TestExecute_RejectsUnknownTarget(t *testing.T) {
|
|
eng, store := newTargetEngine(t, activeNexus())
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = []string{"container"}
|
|
})
|
|
|
|
_, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: "ent_does_not_exist"})
|
|
if !errors.Is(err, execution.ErrTargetNotFound) {
|
|
t.Fatalf("expected ErrTargetNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExecute_RejectsWrongTargetType(t *testing.T) {
|
|
eng, store := newTargetEngine(t, activeNexus())
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = []string{"container"}
|
|
})
|
|
|
|
_, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: "ent_host_homesrv"})
|
|
if !errors.Is(err, execution.ErrTargetTypeMismatch) {
|
|
t.Fatalf("expected ErrTargetTypeMismatch, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExecute_RejectsRetiredTarget(t *testing.T) {
|
|
eng, store := newTargetEngine(t, activeNexus())
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = []string{"container"}
|
|
})
|
|
|
|
_, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: "ent_retired_thing"})
|
|
if !errors.Is(err, execution.ErrTargetNotActive) {
|
|
t.Fatalf("expected ErrTargetNotActive, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExecute_AcceptsMatchingTarget(t *testing.T) {
|
|
eng, store := newTargetEngine(t, activeNexus())
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = []string{"container"}
|
|
})
|
|
|
|
res, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: "ent_container_maven"})
|
|
if err != nil {
|
|
t.Fatalf("expected success, got %v", err)
|
|
}
|
|
if res.Execution.Status != domain.ExecutionSucceeded {
|
|
t.Fatalf("expected succeeded, got %q (%s)", res.Execution.Status, res.Execution.Error)
|
|
}
|
|
}
|
|
|
|
// TestExecute_EmptyTargetTypesStillRequiresRealEntity documents the deliberate
|
|
// meaning of an empty TargetTypes: no *type* constraint, but the entity must
|
|
// still exist and be active. It is not a bypass.
|
|
func TestExecute_EmptyTargetTypesStillRequiresRealEntity(t *testing.T) {
|
|
eng, store := newTargetEngine(t, activeNexus())
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = nil
|
|
})
|
|
|
|
if _, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: "ent_nope"}); !errors.Is(err, execution.ErrTargetNotFound) {
|
|
t.Fatalf("expected ErrTargetNotFound, got %v", err)
|
|
}
|
|
if _, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: "ent_host_homesrv"}); err != nil {
|
|
t.Fatalf("any existing type should be accepted, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestExecute_FailsClosedWhenNexusUnreachable is the whole point of the
|
|
// guard: a Nexus outage must not degrade into accepting arbitrary targets.
|
|
func TestExecute_FailsClosedWhenNexusUnreachable(t *testing.T) {
|
|
eng, store := newTargetEngine(t, &fakeNexus{err: errors.New("connection refused")})
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = []string{"container"}
|
|
})
|
|
|
|
_, err := eng.Execute(&domain.ExecuteRequest{CapabilityID: cap.ID, TargetEntityID: "ent_container_maven"})
|
|
if !errors.Is(err, execution.ErrTargetUnverifiable) {
|
|
t.Fatalf("expected ErrTargetUnverifiable, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestCreateConfirmation_ValidatesTarget stops a confirmation being minted
|
|
// for a target that execute would later refuse.
|
|
func TestCreateConfirmation_ValidatesTarget(t *testing.T) {
|
|
eng, store := newTargetEngine(t, activeNexus())
|
|
cap := mustCreateCapability(t, store, func(c *domain.Capability) {
|
|
c.TargetTypes = []string{"container"}
|
|
c.RequiresConfirmation = true
|
|
})
|
|
|
|
if _, err := eng.CreateConfirmation(cap.ID, "just some words", "test", nil); !errors.Is(err, execution.ErrTargetMalformed) {
|
|
t.Fatalf("expected ErrTargetMalformed, got %v", err)
|
|
}
|
|
if _, err := eng.CreateConfirmation(cap.ID, "ent_host_homesrv", "test", nil); !errors.Is(err, execution.ErrTargetTypeMismatch) {
|
|
t.Fatalf("expected ErrTargetTypeMismatch, got %v", err)
|
|
}
|
|
if _, err := eng.CreateConfirmation(cap.ID, "ent_container_maven", "test", nil); err != nil {
|
|
t.Fatalf("expected valid target to be accepted, got %v", err)
|
|
}
|
|
}
|