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

runtime.contract_refine.mode

POST /v1/preflight

GET /v1/preflight/{id}

/v1/tool binding

disabled (explicit opt-out only)

503

503

header ignored entirely

advisory (default, post-C12 #1032)

returns a decision

returns the stored decision

binding failure emits an autonomy.decision deny WAL frame; request continues to policy (no 403)

enforce

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

200

Verifier reached a verdict. Outcome is in the response body (pass / repair_required / deny).

400

Request body missing or malformed JSON.

500

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.

503

runtime.contract_refine.mode is disabled OR no ContractVerifier is wired (fail-closed per INV-CR-2).

Headers

The endpoint does not require any headers. Future surfaces:

  • /v1/tool will accept X-Autonomy-Preflight-Id in 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

preflight_id

UUIDv4 string

The preflight_id returned by a prior POST /v1/preflight.

Status codes

Code

Trigger

200

Decision found; the response body is the same shape as POST /v1/preflight returns.

400

preflight_id path segment empty (route misconfiguration).

404

No autonomy.preflight WAL frame matches the given id.

500

WAL read error.

503

Mode is disabled OR the telemetry emitter is not wired.

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

preflight_id

string (UUIDv4)

The decision’s id; matches the path of GET /v1/preflight/{id}.

audit_id

string (UUIDv4)

Shared with the eventual /v1/tool autonomy.decision frame for the INV-CR-8 join.

outcome

string

pass, repair_required, or deny.

reason

string

"contract: <subreason>" per the reason-shape regex.

policy_ref

string

Active policy bundle ref at decision time.

lock_fingerprint

string

Server’s LIVE lock fingerprint at decision time (overrides any value the verifier was constructed with).

plan_hash

string

The caller-supplied intent.plan_hash (when present).

capability

string

The requested capability.

zone

string (optional)

context.zone when set.

environment

string (optional)

context.environment when set.

actor_node_id

string (optional)

actor.node_id when set.

actor_agent_id

string (optional)

actor.agent_id when set.

contract_results

string (JSON-encoded slice)

Full per-contract result list.

repair_hints_count

int

Length of RepairHints for quick-filter audit queries.

expires_at

string (RFC3339)

execution_eligibility.expires_at from the decision.

decision_json

string (JSON-encoded)

The full PreflightDecision for the GET endpoint’s verbatim reproduction.

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.