Skip to content
Draft
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
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Lattice Bundle v2 Reference Plugin

`lattice-plugin-template` is the canonical self-contained Bundle v2 reference
plugin for Lattice `0.2.1-alpha.3`.
plugin for Lattice `0.2.1-alpha.4`.

It demonstrates four boundaries that production plugins must keep explicit:

Expand All @@ -16,6 +16,45 @@ It demonstrates four boundaries that production plugins must keep explicit:
artifact digest and signed by the LatticeNet publisher key. Local development
must clear the digest and signature before repackaging different bytes.

## The host-risk operation flow (spec §9.3)

A plugin may compile intent into a plan; it may never apply one. This reference
implements the full flow, and it is the shape production plugins copy:

1. **plan** — the `plan`-effect interface method (`example.lattice-plugin/reference`
/ `plan`) returns a `PluginOperationPlan`: a summary, the target nodes, a redacted
preview, ordered steps, a rollback statement, and opaque `data`. It applies
nothing. The server bounds it, authorizes every target, and stores it as a
**pending approval** whose typed columns record the plugin version, artifact
digest, service, method, request hash, and targets.

2. **approve** — an operator reads the preview and approves the exact plan hash. The
server re-checks every bound column against live state at approve/execute time; a
plugin that was upgraded, re-signed, disabled, or whose targets are no longer
authorized is refused.

3. **execute** — the approval executor, and nothing else, invokes the plugin's
`execute` action with a one-time operation grant **bound on the host side**. The
plugin never receives the grant. It reads the approved targets and calls the
`task.enqueue` host call once per node.

4. **enqueue** — `task:run` is *eligibility*, not authorization. The grant says which
nodes, under which approval, and how many times. The host refuses any task aimed
at an unapproved node, past the budget, or belonging to another plugin — and
applies the operator's own task validation, so a plugin can reach no wider an
interpreter set or script than an operator could.

Rules a production plugin must keep:

- **Never apply directly.** The only way to change a host is `plan` → operator
approval → `execute` → `task.enqueue`. There is no in-plugin apply.
- **Redact the preview.** Secrets never appear in a plan; put reversible material in
the encrypted secret store (§9.4), not in the plan or a log.
- **Quote everything you interpolate into a script.** The reference single-quotes the
approval id and node id so neither can break out of the `sh` command.
- **Declare `task:run`** to enqueue, and — for a runtime-backed operation service —
declare the service `backing: "runtime"`.

## Bundle Layout

The packaged artifact must contain exactly the host-facing runtime and UI assets:
Expand Down
9 changes: 5 additions & 4 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
"name": "Lattice Bundle Reference Plugin",
"type": "system",
"capabilities": [
"network:plan"
"network:plan",
"task:run"
],
"version": "0.2.1-alpha.3",
"version": "0.2.1-alpha.4",
"publisher": "latticenet",
"signature_ed25519": "qmzNktLpq5tGDulNK4hFqPLLxs/qhXC4tiQhx2kc+x746He7y7ZwLrRARe7MwcE8ih4lGIm4xox3KrtzPXqJCw==",
"signature_ed25519": "LwoiIGnuDhSjXIhW8LKeBihhkOtHH6L6jOFCDzJWlcpA7zFIiTLB0GVBzQsiB1r6gVW7nqYwhDetyC4ogf8TAQ==",
"bundle": {
"format": "tar+gzip",
"digest_sha256": "a7631567e67d0b0d2f8c971af3b3b5414cf8f2a00c88c396774c4190adb689d1"
"digest_sha256": "7817d1e778ffc17278968b6e4d03096bdf4264dc7bc932cac2daaeac80eaefdc"
},
"runtime": {
"protocol": "stdio-json-v1",
Expand Down
14 changes: 12 additions & 2 deletions system-go/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import (
// 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) {
rt := &runtime{host: refuseHostCalls{t}}
for _, iface := range loadManifestInterfaces(t) {
for _, method := range iface.Methods {
resp := handle(request{
resp := rt.handle(request{
Action: "call",
Service: iface.Service,
Method: method.Name,
Payload: map[string]any{},
Payload: json.RawMessage("{}"),
})
served := !refusedAsUnknown(resp)

Expand Down Expand Up @@ -59,6 +60,15 @@ func TestManifestInterfacesAreServedAsDeclared(t *testing.T) {
// 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.
// 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
}

func refusedAsUnknown(resp response) bool {
if resp.OK {
return false
Expand Down
Loading