Skip to content
Closed
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
5 changes: 3 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
],
"version": "0.2.1-alpha.3",
"publisher": "latticenet",
"signature_ed25519": "zNmGQYi9y8B/SzBDBeUlEoN86mmNLMwl5mr1g7acJizTW73NOaJVDYgIDpWVFWlGm6k6nfVVVBquQRUToGj6DA==",
"signature_ed25519": "qmzNktLpq5tGDulNK4hFqPLLxs/qhXC4tiQhx2kc+x746He7y7ZwLrRARe7MwcE8ih4lGIm4xox3KrtzPXqJCw==",
"bundle": {
"format": "tar+gzip",
"digest_sha256": "a7631567e67d0b0d2f8c971af3b3b5414cf8f2a00c88c396774c4190adb689d1"
Expand Down Expand Up @@ -69,7 +69,8 @@
"network:plan"
]
}
]
],
"backing": "runtime"
}
]
}
95 changes: 95 additions & 0 deletions system-go/conformance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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.
//
// Copy this file into every plugin repository. It is the one test that cannot be
// satisfied by a plugin that lies about itself.
func TestManifestInterfacesAreServedAsDeclared(t *testing.T) {
for _, iface := range loadManifestInterfaces(t) {
for _, method := range iface.Methods {
resp := handle(request{
Action: "call",
Service: iface.Service,
Method: method.Name,
Payload: map[string]any{},
})
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 lives 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)
}
}
}
}

// 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
}