Plugin Authoring¶
This guide covers the framework-side runtime plugin contract. Signature trust, materialization, SBOM/evidence, and update policy are product-owned (PRAXOVELA).
Manifest v1¶
A plugin ships a strict JSON manifest. Unknown fields and trailing data are rejected.
{
"schemaVersion": 1,
"id": "com.example.search",
"version": "1.0.0",
"kind": "tool.provider.v1",
"entrypoint": { "type": "process", "command": "./bin/search" },
"protocol": "jsonrpc-2",
"capabilities": ["tool.invoke"],
"permissions": {
"filesystem": [{ "path": "/workspace", "mode": "read" }],
"network": [{ "host": "api.example.com", "port": 443 }],
"env": ["SEARCH_API_KEY"]
},
"limits": { "memoryMB": 256, "cpuMillis": 2000, "timeoutMs": 30000, "maxInflight": 8 },
"isolation": "process",
"health": { "method": "health", "intervalMs": 15000 },
"signature": { "algorithm": "ed25519", "keyId": "example-release-1" },
"sbom": "sbom.spdx.json"
}
kindis one oftool|model|policy|memory|sandbox.provider.v1.entrypoint.typeandisolationareprocess,wasm, orcontainerand must match.entrypoint.commandis required forprocess;entrypoint.moduleforwasm;entrypoint.imageforcontainer.permissionsis deny-by-default. Filesystem modes areread,write, orreadwrite.signatureandsbomare mandatory under the signed trust profile. The unsigned profile is an explicit local-development opt-in.
Parse and validate with:
m, err := plugin.ParseManifest(raw)
if err != nil { /* reject */ }
if err := m.ValidateTrust(plugin.TrustSigned); err != nil { /* reject */ }
Process host¶
The process host speaks JSON-Lines over the child's stdio. Each request is one JSON object per line:
{
"id": "req-1",
"version": 1,
"plugin_id": "com.example.search",
"service": "tool.provider",
"method": "invoke",
"token": "<per-process token>",
"payload": "<opaque contract bytes>"
}
The plugin replies with:
{
"id": "req-1",
"version": 1,
"ok": true,
"payload": "<opaque contract bytes>"
}
Rules:
- Validate
tokenon every request. The host provisions it through thePRAXOVELA_PLUGIN_TOKENenvironment variable by default (override withprocess.Config.TokenEnvVar); it is never passed in argv. - Answer
healthduring start. - Abandon in-flight work on
cancel; exit cleanly aftershutdown. - Map failures to
CAPABILITY_DENIED,NOT_ACTIVE,NOT_FOUND,INVALID_REQUEST,TIMEOUT, orINTERNAL. - Do not write protocol frames to stderr; stderr is captured and bounded.
Host-side configuration (process.Config) includes PluginID, Provider,
Sink, RequireEvidence, Path, Args, Dir, Env, Token, TokenEnvVar,
timeouts, frame bound, Limits, and RequireLimits. Production callers should
set RequireLimits and RequireEvidence.
WASM host¶
A WASM guest must export:
memoryalloc(len i32) i32prax_request(ptr i32, len i32)
and import env.prax_respond(ptr i32, len i32). Each prax_request must deliver
exactly one response frame. The host bounds linear memory with
MaxMemoryPages and terminates a guest when the invocation context ends. CPU
fuel is not enforced in v1.
Conformance kit¶
Implement tck.HostFactory / tck.HostHarness:
type HostHarness interface {
Start(context.Context) error
Stop(context.Context) error
Invoke(ctx context.Context, service, method string, payload []byte) ([]byte, error)
PluginID() string
Contract() plugin.Contract
Methods() tck.HostMethods
}
HostMethods names the fixture methods:
Echo: return the payload unchanged.Block: wait until the context is done.Denied: returnCAPABILITY_DENIED.Timeout: bound the blocking invocation.
Then run:
if err := tck.CheckLifecycle(plugin.NewLifecycle()); err != nil { /* fail */ }
if err := tck.CheckRegistry(registry); err != nil { /* fail */ }
if err := tck.CheckHost(factory); err != nil { /* fail */ }
CheckHost covers framing, timeout, cancellation, capability denial, and the
not-active gate. Each check gets a fresh host from the factory.
Trust boundary¶
Core hosts start already-verified payloads. The product must verify the
descriptor signature, SBOM, identity, ABI, and compatibility before constructing
a host. pkg/plugin never resolves requirements and never calls a trust store.