ContractRefine Preflight API¶
ContractRefine (Architecture → ContractRefine)
exposes two HTTP endpoints on the runtime tool server. Together they
deliver the semantic preflight loop the rest of the layer’s surfaces
(autonomy preflight CLI, policy bundle requires_preflight,
/v1/tool binding consumption) build on.
Both endpoints are gated behind a single config knob,
runtime.contract_refine.mode, with three positions that mirror the
existing attestation.mode ladder. The post-C12 default position is
advisory — operators who want the pre-C12 silent posture set
mode: disabled explicitly. A non-empty unrecognized value (e.g.
advsiory) falls back to disabled and the runtime logs a startup
warning naming the bad value — typos do not silently enable a new
HTTP surface.
Mode ladder¶
|
|
|
|
|---|---|---|---|
|
503 |
503 |
header ignored entirely |
|
returns a decision |
returns the stored decision |
binding failure emits an |
|
returns a decision |
returns the stored decision |
binding mismatch denies; bundles may require preflight (C08) |
Resolution rules:
""(zero value, field unset) ⇒advisory(post-C12 default)."disabled"/"advisory"/"enforce"(explicit, well-formed) ⇒ value preserved.Anything else (typo) ⇒
disabled(safe fallback) + startup warning.
The C05 PR shipped the POST + GET endpoints and the WAL emit; C08
landed /v1/tool binding consumption; C12 (this default flip) is
the post-soak cut from beta opt-in to the post-C12 advisory default.
POST /v1/preflight¶
Evaluate a PreflightRequest against the wired
ContractVerifier. Returns one of three top-level outcomes
(pass / repair_required / deny) per the
architecture composition rule.
Request body¶
JSON-encoded PreflightRequest. See
runtime/contract/types.go
for the canonical schema. The shape (snake_case JSON tags):
{
"intent": {
"kind": "mission.start",
"description": "human text, audit-only",
"plan_hash": "blake3:planhash..."
},
"actor": {
"node_id": "node-a",
"agent_id": "agent-1",
"session_id": "session-1"
},
"capability": "tool.echo",
"plan": [
{
"kind": "tool.echo",
"params": { "text": "hi" },
"provenance": {
"text": { "source": "operator" }
}
}
],
"context": {
"zone": "default",
"environment": "edge",
"rollback_contract": "rb-prepared-1",
"expected_evidence": ["audit_id"]
}
}
actor.session_id and intent.plan_hash are jointly required to
participate in the bounded-attempts repair ladder (INV-CR-6).
Omitting either field with a tracked verifier returns deny with
reason = "contract: session_metadata_missing".
Response body¶
JSON-encoded PreflightDecision. Top-level outcome is the closed
three-value enum:
{
"preflight_id": "<server-assigned UUIDv4>",
"audit_id": "<server-assigned UUIDv4; shared with the eventual /v1/tool decision>",
"outcome": "pass",
"reason": "contract: passed",
"policy_ref": "test-policy/1.0.0",
"lock_fingerprint": "blake3:c05test0001",
"contract_results": [
{ "contract": "plan_shape", "outcome": "pass", "severity": "blocking" },
...
],
"execution_eligibility": {
"allowed": true,
"bound_tool_kinds": ["tool.echo"],
"expires_at": "2026-06-12T12:05:00Z"
}
}
For repair_required outcomes, repair_hints carries the
deterministic-correction suggestions; the caller may apply them and
re-submit under the same actor.session_id + intent.plan_hash to
consume one repair-attempt budget.
For deny outcomes, execution_eligibility.expires_at is empty
because no binding is issued.
Status codes¶
Code |
Trigger |
|---|---|
|
Verifier reached a verdict. Outcome is in the response body ( |
|
Request body missing or malformed JSON. |
|
Verifier produced a decision but the WAL emit failed. The runtime cannot honor the INV-CR-8 correlation discipline, so it refuses the response rather than return a non-auditable decision. |
|
|
Headers¶
The endpoint does not require any headers. Future surfaces:
/v1/toolwill acceptX-Autonomy-Preflight-Idin C08 to bind the eventual tool execution to a prior preflight verdict.
GET /v1/preflight/{preflight_id}¶
Retrieve a stored PreflightDecision by preflight_id. The server
scans the WAL for the matching autonomy.preflight frame and
deserializes the stored decision.
Path parameters¶
Name |
Type |
Description |
|---|---|---|
|
UUIDv4 string |
The |
Status codes¶
Code |
Trigger |
|---|---|
|
Decision found; the response body is the same shape as |
|
|
|
No |
|
WAL read error. |
|
Mode is |
WAL event¶
Every POST /v1/preflight that reaches a decision emits one
autonomy.preflight WAL frame (event kind defined in
telemetry/emitter.go).
Attributes (workplan §7.2):
Attr |
Type |
Description |
|---|---|---|
|
string (UUIDv4) |
The decision’s id; matches the path of |
|
string (UUIDv4) |
Shared with the eventual |
|
string |
|
|
string |
|
|
string |
Active policy bundle ref at decision time. |
|
string |
Server’s LIVE lock fingerprint at decision time (overrides any value the verifier was constructed with). |
|
string |
The caller-supplied |
|
string |
The requested capability. |
|
string (optional) |
|
|
string (optional) |
|
|
string (optional) |
|
|
string (optional) |
|
|
string (JSON-encoded slice) |
Full per-contract result list. |
|
int |
Length of |
|
string (RFC3339) |
|
|
string (JSON-encoded) |
The full |
Audit query¶
To join a preflight verdict with its eventual /v1/tool decision:
# When C08 lands and /v1/tool emits the shared audit_id:
autonomy wal inspect --filter "attrs.audit_id == '<audit_id>'"
Both frames return chronologically ordered.