diff --git a/manifest.json b/manifest.json index e20603c..926f3db 100644 --- a/manifest.json +++ b/manifest.json @@ -9,7 +9,7 @@ ], "version": "0.3.2-alpha.3", "publisher": "latticenet", - "signature_ed25519": "cGr0AcT5LYLuaYxNIYfwL4jWZtYA04wgGjkhJGkbrk49uBDDqcKcIBT132BfJhhoNlKlb6+n8mokEDQJuAf5Cw==", + "signature_ed25519": "YW8Lzw9MqK7M0PzM5viOuJP4BiujmMHqGOJrIPdDd549zjrG9qIF0UVOsBlTNnnmtPltdDCeE57LQs/3RDQtAA==", "bundle": { "format": "tar+gzip", "digest_sha256": "8e6445836caf38fc7c58582601a98faf6b756587535c2c7e355e97a99de5756f" @@ -86,7 +86,8 @@ "base_url" ] } - ] + ], + "backing": "runtime" } ] } diff --git a/system-go/conformance_test.go b/system-go/conformance_test.go new file mode 100644 index 0000000..e838213 --- /dev/null +++ b/system-go/conformance_test.go @@ -0,0 +1,111 @@ +package main + +import ( + "encoding/json" + "os" + "strings" + "testing" +) + +// A manifest declares which methods a plugin exposes and — since `backing` — who +// actually serves each one. This test holds the artifact to that promise. +// +// It exists because the promise used to go unchecked. Official plugins shipped signed +// manifests declaring interface methods their own artifacts could not answer, and +// lattice-server quietly answered them from an in-core handler instead. Nothing caught +// it: every suite covered what the artifact DOES, never what the manifest CLAIMS. A +// contract nobody verifies is a contract that drifts, and this is the gate that turns +// that drift into a red build. +// +// Sub-Store is genuinely runtime-backed: its own artifact serves every method it +// declares. This test is what keeps that true. +func TestManifestInterfacesAreServedAsDeclared(t *testing.T) { + for _, iface := range loadManifestInterfaces(t) { + for _, method := range iface.Methods { + // An empty payload is rejected before any host call is attempted, so probing + // the dispatcher cannot reach the broker or the network. The stub host proves + // it: it fails the test if anything tries to call out. + rt := &runtime{host: refuseHostCalls{t}} + payload, err := json.Marshal(map[string]any{ + "service": iface.Service, + "method": method.Name, + "payload": map[string]any{}, + }) + if err != nil { + t.Fatalf("marshal call payload: %v", err) + } + resp := rt.handle(request{Action: "call", Payload: payload}) + served := !refusedAsUnknown(resp) + + switch iface.Backing { + case "runtime": + // This artifact is the declared owner, so it must at least recognise the + // method. Rejecting an empty payload is a real answer; not knowing the + // method at all is a broken promise. + if !served { + t.Errorf("%s/%s is declared runtime-backed, but this artifact does not serve it: %s", + iface.Service, method.Name, resp.Error) + } + case "core": + // The engine would live in lattice-server. If the artifact answers as well, + // the manifest names two owners for one method and the host has to guess. + if served { + t.Errorf("%s/%s is declared core-backed, but this artifact answers it too; backing must name exactly one owner", + iface.Service, method.Name) + } + case "": + t.Errorf("%s/%s declares no backing, so who serves it is left to inference", + iface.Service, method.Name) + default: + t.Errorf("%s/%s declares unknown backing %q", iface.Service, method.Name, iface.Backing) + } + } + } +} + +// refuseHostCalls fails the test if the dispatcher reaches for the host. Probing which +// methods exist must never have a side effect. +type refuseHostCalls struct{ t *testing.T } + +func (h refuseHostCalls) call(method string, _ any) (json.RawMessage, error) { + h.t.Fatalf("conformance probe must not reach the host, but it called %q", method) + return nil, nil +} + +// refusedAsUnknown separates "I do not implement this" from "I implement this and your +// payload is wrong". Only the former means the artifact cannot serve the method — a +// validation error proves the method is wired up. +func refusedAsUnknown(resp response) bool { + if resp.OK { + return false + } + return strings.Contains(resp.Error, "unsupported action") || + strings.Contains(resp.Error, "unsupported service") || + strings.Contains(resp.Error, "unsupported method") +} + +type manifestInterface struct { + Service string `json:"service"` + Backing string `json:"backing"` + Methods []struct { + Name string `json:"name"` + } `json:"methods"` +} + +func loadManifestInterfaces(t *testing.T) []manifestInterface { + t.Helper() + raw, err := os.ReadFile("../manifest.json") + if err != nil { + t.Fatalf("read manifest: %v", err) + } + var m struct { + Interfaces []manifestInterface `json:"interfaces"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatalf("parse manifest: %v", err) + } + if len(m.Interfaces) == 0 { + t.Fatal("manifest declares no interfaces to verify") + } + return m.Interfaces +}