Skip to Content
Numinance
Protocol

Envelope, ActionContext and ActionResult

The messages that carry one action to a plugin and one result back, with grant, policy decision, budget and typed Any rules.

View as .mdUpdated
Available in public spec
These messages are part of the Apache-2.0 numinance-spec protocol (numinance.plugin.v1). The host that builds the envelope and judges the result is in the proprietary runtime.

The Envelope is the authority for one action. The host sets everything that matters; the plugin answers with claims that the host checks or records, and trusts none of them.

When to use this page

Use it to see exactly what a plugin receives for one Invoke (who it acts as, under which grant and policy decision, with what budget and deadline) and what it may report back. The transport for these messages is PluginService.Invoke.

Rules for the whole file

  • Every element is since: "1.0.0".
  • Enums are closed. A consumer rejects the zero value and any unknown number.
  • No field carries credential material or content. Large data travels as a types.v1.Reference: references and digests, never content.
  • Every string is at most 1024 bytes and every repeated field at most 256 entries unless stated; over-limit input is rejected, never truncated. Duplicates are rejected, keyed as: evidence_ids by value, side_effects by kind and target, diagnostics by all four fields. Every repeated bound applies per invocation across the stream.
  • A rejection names an index-based field path and the violated bound, never the offending value.

Envelope

proto/numinance/plugin/v1/envelope.proto
message Envelope {
  string method = 1;
  ActionContext context = 2;
  google.protobuf.Any request = 3;
}
FieldNumberRule
method1The full method name: a slash, the package, a dot, the service, a slash and the method, for example /numinance.ports.scm.v1.ScmService/GetFile. The method must be in a numinance.ports.* file the host serves and have a capability entry in the installed manifest. A non-Core capability must also be in the negotiated HandshakeResponse.capabilities. Otherwise the host does not dispatch.
context2Set by the host after the policy decision. The plugin never supplies or changes it.
request3The typed request, at most 4 MiB serialized (rejected, never truncated). Its type URL is type.googleapis.com/ plus the method's input type in the host's descriptor copy. Any other type URL is rejected.

Typed Any

request and ActionResult.result are google.protobuf.Any, but not free-form: the type URL is derived from the method, not chosen by the sender. A request whose type URL is not exactly type.googleapis.com/ plus the method's input type is rejected, and a result must name the method's output type. Decoding is strict and rejects unknown fields. A Reference inside an Any is never resolvable through ResolveReference, which is why references that must be resolvable sit in ordinary fields of the request.

ActionContext

ActionContext is the authority under which one action runs. Every field is set by the host.

proto/numinance/plugin/v1/envelope.proto
message ActionContext {
  string invocation_id = 1;
  string change_id = 2;
  string run_id = 3;
  numinance.types.v1.PrincipalKind actor_kind = 4;
  string actor_principal = 5;
  string grant_id = 6;
  string policy_decision_id = 7;
  numinance.types.v1.Digest plan_digest = 8;
  numinance.types.v1.Digest profile_digest = 9;
  string idempotency_key = 10;
  google.protobuf.Timestamp deadline = 11;
  ResourceBudget budget = 12;
  numinance.types.v1.ResidencyClass residency = 13;
  bool dry_run = 14;
}
FieldNumberMeaning
invocation_id1Host-assigned: inv_ plus 32 lowercase hex characters (128 bits of host randomness), unique per Invoke. The plugin quotes it in every HostService call; the host rejects an id not in flight for the calling plugin.
change_id2The Change the action belongs to, chg_ prefixed.
run_id3The run the action belongs to, run_ prefixed.
actor_kind4The identity the action runs as, a PrincipalKind. Unspecified is rejected. PRINCIPAL_KIND_ENGINE is producer-only and is refused here: it names who publishes an Engine fact, never an actor.
actor_principal5The principal that acts, as in the evidence record's actor_principal.
grant_id6The grant that allows the action, grant_ prefixed.
policy_decision_id7The policy decision that allowed the action, dec_ prefixed. The host never dispatches without one.
plan_digest8Digest of the plan the action belongs to; its value equals the evidence record's plan_digest. Required for DECLARATIVE Apply.
profile_digest9Digest of the SDLC profile in force.
idempotency_key10The key a KEYED capability uses so a replay has no extra effect.
deadline11When the call must end; the host cancels it then.
budget12The effective ResourceBudget, every field non-zero (the host resolves defaults before dispatch): the minimum of declared and grant budget, under the policy ceiling.
residency13The Change's ResidencyClass, derived by the host at Change start. Informational, never a grant; the plugin never declares it.
dry_run14Asks for no side effects. The host sets it only when the method's supports_dry_run is true; then any reported side effect above READ fails the call and is recorded.

Grants and policy decisions

grant_id and policy_decision_id are how capability-based authority reaches the plugin. The policy decision (DecisionOutcome is ALLOW, DENY or REQUIRE_APPROVAL) is made by the host outside the model before dispatch. The plugin receives only the ids of an allow, never the ability to produce or alter one. A plugin has no way to widen its grant: the context is host-set, and HostService calls are accepted only for an invocation_id the host has in flight. See Authority and policy.

Limits

Limits arrive as deadline and budget and are enforced by the host, not requested by the plugin.

ResourceBudget

proto/numinance/plugin/v1/manifest.proto
message ResourceBudget {
  uint64 cpu_millicores = 1;
  uint64 memory_bytes = 2;
  uint64 disk_bytes = 3;
}

A zero value means the host default in a manifest declaration; in ActionContext.budget every field is non-zero. disk_bytes also caps the total bytes ResolveReference may stream in one invocation.

ActionResult

ActionResult is what the plugin reports. Every field is a claim the host checks or records, never trusts.

proto/numinance/plugin/v1/envelope.proto
message ActionResult {
  InvokeStatus status = 1;
  google.protobuf.Any result = 2;
  repeated string evidence_ids = 3;
  repeated ObservedSideEffect side_effects = 4;
  bytes compensation_token = 5;
  ResourceBudget cost = 6;
  google.protobuf.Duration duration = 7;
  repeated Diagnostic diagnostics = 8;
}
FieldNumberMeaning
status1The reported InvokeStatus.
result2The typed response, at most 4 MiB serialized. Required when status is SUCCEEDED and absent otherwise, except on a server-streaming target (PluginService.Invoke): there every RUNNING message carries exactly one stream message here and the terminal message carries none. A RUNNING message with a result is rejected for any other target.
evidence_ids3ev_ prefixed ids that HostService.EmitEvidence returned for this invocation. Any other id is rejected. Terminal message only.
side_effects4Side effects the plugin observed (ObservedSideEffect). One above the capability's declared side_effect fails the call. Terminal message only.
compensation_token5Opaque, at most 1024 bytes, annotated sensitive. The host stores it outside evidence (evidence carries only its sha256) and passes it back only in a compensation action with its own policy decision and grant, to the same plugin digest inside the same Change. The host deletes it when the Change closes or the breaker trips.
cost6Consumption the plugin reports in ResourceBudget units. Informational; never an input to budget accounting, which uses the host's own measurement. Terminal message only.
duration7Time the plugin reports it took. The host's own measurement is authoritative; negative is rejected.
diagnostics8Structured notes, at most 64. A Diagnostic with a phase reason is rejected.

A RUNNING message carries status and diagnostics only (plus the stream message in result on a server-streaming target).

What the host does with a claim

ClaimHost behavior
status = SUCCEEDEDTaken as a claim. The host derives the evidence ActionOutcome itself; PARTIAL is never claimed but derived when declared evidence is missing.
evidence_idsEach id must be one the host returned for this invocation.
side_effectsA side effect above the capability's declared side_effect fails the call. With dry_run, anything above READ fails and is recorded.
cost, durationRecorded as reported; the host's own measurement decides.

ObservedSideEffect

proto/numinance/plugin/v1/envelope.proto
message ObservedSideEffect {
  SideEffect kind = 1;
  string target = 2;
}

kind is a SideEffect (NONE is rejected here). target identifies the external resource, printable characters only; it is untrusted and rendered delimited and labelled.

SideEffect

proto/numinance/plugin/v1/manifest.proto
enum SideEffect {
  SIDE_EFFECT_UNSPECIFIED = 0;
  SIDE_EFFECT_NONE = 1;
  SIDE_EFFECT_READ = 2;
  SIDE_EFFECT_WRITE = 3;
  SIDE_EFFECT_DELETE = 4;
  SIDE_EFFECT_DEPLOY = 5;
}

READ reads external state, WRITE creates or changes it, DELETE removes it, DEPLOY changes what runs in an environment.

Diagnostic

proto/numinance/plugin/v1/envelope.proto
message Diagnostic {
  Reason reason = 1;
  Severity severity = 2;
  string field_path = 3;
  string subject = 4;
}

A diagnostic has no free text. field_path is empty or starts with the literal request or response, resolves segment by segment against the method's input or output type in the host's descriptor copy (for example request.items[3].name), matches ^[a-z][a-z0-9_]*(\[[0-9]+\])?(\.[a-z][a-z0-9_]*(\[[0-9]+\])?)*$ and is at most 256 bytes. subject is empty or a full name, at most 256 bytes, that resolves in the host's registry to an element of a numinance.ports.* or numinance.plugin.v1 file (for example numinance.ports.scm.v1.GetFileRequest.path). A diagnostic that fails either check is rejected and never rendered.

Enums

InvokeStatus

InvokeStatus is how the plugin reports a call; the host derives the evidence/v1 ActionOutcome itself and never trusts the claim.

NumberValueMeaning
0INVOKE_STATUS_UNSPECIFIEDThe zero value; it is rejected by the host.
1INVOKE_STATUS_RUNNINGMarks a non-terminal message of an OPERATION call; it is rejected on any other call and on the last message; on a target whose method is server-streaming each RUNNING message carries one stream message in ActionResult.result.
2INVOKE_STATUS_SUCCEEDEDClaims the action completed; PARTIAL is never claimed, the host derives it when declared evidence is missing.
3INVOKE_STATUS_FAILEDClaims the action did not complete.

Severity

Severity is the severity of a diagnostic or log entry.

NumberValueMeaning
0SEVERITY_UNSPECIFIEDThe zero value; it is rejected by the host.
1SEVERITY_DEBUGDetail for troubleshooting.
2SEVERITY_INFOA normal event.
3SEVERITY_WARNINGAn event that needs attention but did not fail the call.
4SEVERITY_ERRORAn event that failed the call or part of it.

Reason

Reason is the closed vocabulary of diagnostics, log entries and progress reports; there is no free text. The outcomes (REASON_DEPRECATED through REASON_CANCELLED) are valid in Diagnostic and Log; the phases (REASON_STARTED through REASON_COMPLETED) are valid in ReportProgress and Log; a value added later is placed in a subset by its name in this comment, never by its number; the host rejects a value outside the subset of its consumer. This enum may grow.

NumberValueMeaning
0REASON_UNSPECIFIEDThe zero value; it is rejected by the host.
1REASON_DEPRECATEDMarks use of a deprecated element named by Diagnostic.subject.
2REASON_INVALID_ARGUMENTMarks input that failed validation.
3REASON_LIMIT_EXCEEDEDMarks input or output over a stated bound.
4REASON_NOT_FOUNDMarks a target that does not exist.
5REASON_CONFLICTMarks a target whose state prevents the action.
6REASON_PERMISSION_DENIEDMarks a refusal by the upstream system.
7REASON_UPSTREAM_UNAVAILABLEMarks an upstream system that could not be reached.
8REASON_UPSTREAM_ERRORMarks an upstream system that answered with an error.
9REASON_RATE_LIMITEDMarks a call slowed or refused by a rate limit.
10REASON_TIMEOUTMarks a deadline that passed.
11REASON_CANCELLEDMarks a call cancelled by the host.
12REASON_STARTEDMarks the start of work.
13REASON_RUNNINGMarks work in progress.
14REASON_WAITINGMarks work blocked on an upstream system.
15REASON_COMPLETEDMarks the end of work.

Example: an envelope for a read

This is an illustration of which values the host fills for a read-only call. It is not captured output.

method:              /numinance.ports.scm.v1.ScmService/GetFile
context.invocation_id:     inv_<32 lowercase hex characters>
context.actor_kind:        PRINCIPAL_KIND_AUTHOR_AGENT
context.grant_id:          grant_...
context.policy_decision_id: dec_...
context.dry_run:           false
request:             Any, type URL type.googleapis.com/numinance.ports.scm.v1.GetFileRequest

Failure modes and safeguards

SituationResult
Method not in a served numinance.ports.* file, or no capability entry in the manifestHost does not dispatch.
Non-Core capability not negotiatedHost does not dispatch.
Request type URL differs from the method's input typeRejected.
Plugin reports a side effect above the declared oneCall fails.
Plugin lists an evidence_id the host did not issueRejected.
Diagnostic with phase reason, or unresolvable field_path or subjectRejected, never rendered.
Zero or unknown enum numberRejected (closed enums).

Next steps

On this page