Standard web decision runtime

Optional OpenAPI document replay

When both the non-default openapi-review feature and explicit runtime option are present, the parent web assessment installs web.review.openapi.document-replay@1 as one native action. It owns no nested runtime or client: selection, the anonymous bodyless GET candidate, exact replay, evidence, and projection reuse the parent exact-origin authority, redirect-disabled broker, budget, cancellation, completeness lifecycle, and final report. At most one document is selected; the complete action costs two requests and one logical active verification. A semantic replay mismatch or any unsupported, malformed, incomplete, truncated, redirected, or over-limit response produces no item.

Optional REST read-only replay

The non-default rest-review child requires explicit web-review and same-run openapi-review. A stable OpenAPI candidate/replay pair may pass one deterministically selected anonymous, bodyless, exact-origin GET with zero required inputs into the existing native action lifecycle. Candidate plus replay use exactly two requests and one active verification through the parent broker, budget, cancellation, deadline, exact-origin authority, evidence registry, completeness lifecycle, and final report. Equivalent successful JSON Status, Fields, and Resources can produce only Informational / KnowledgeOnly. No write, credential, cookie, parameter/body materialization, or chaining to another review family is permitted.

StandardWebDecisionRuntime is the host-facing composition API for one authorized HTTP target. It owns the standard knowledge base, decision loop, executor registry, experience store, and session so a caller does not have to wire those components manually.

authorized target + HTTP policy
              |
              v
 StandardWebDecisionRuntime
              |
              +--> RequestBroker ---> bootstrap GET ---> immutable Evidence
              |                           |
              |                           v
              +--------------------> reasoning + planning
                                          |
                                          v
                                executable action only
                                          |
                                          v
                              evidence + verification
                                          |
                                          v
                                  terminal command

Build and run

let policy = HttpEvidencePolicy::for_origin(target.clone())?
    .with_body_capture(HttpBodyCapture::TextSample { max_chars: 8_192 })?;
let cancellation = tokio_util::sync::CancellationToken::new();

let mut runtime = StandardWebDecisionRuntime::builder(target)
    .http_policy(policy)
    .cancellation_token(cancellation.clone())
    .enable_api_reasoning()
    .business_value(80)
    .planning_budget(100)
    .risk_limit(40)
    .max_action_cycles(8)
    .max_total_requests(32)
    .max_wall_time(std::time::Duration::from_secs(120))
    .max_response_bytes(2 * 1024 * 1024)
    .build()?;

let report = runtime.analyze().await?;

The host may retain the token clone and cancel it for a user stop, scope change, incident response, or shutdown. Host cancellation is reported as Halt { reason: CancelledByHost }; it is distinct from the complete-runtime wall deadline and from an individual request timeout.

The runnable decision_scan example accepts an explicitly supplied target URL and prints the selected actions, verified outcomes, terminal command, and learned experience count.

Defaults and policy ownership

Without overrides, the builder creates a policy restricted to the target's exact origin and uses these deterministic decision defaults:

Setting Default
Business value 80%
Planner action-cost budget 100
Maximum action risk 40%
Passive action cycles 8
Suppression-eligible verified negatives 10
Passive API response/surface reasoning Disabled
Total HTTP requests 32
Complete runtime wall time 120 seconds
Transport-delivered response-body bytes across the session 2 MiB
Buffered request-body bytes across the session 256 KiB
Active verification requests 4
Attempts for one semantic action 3
Consecutive no-progress execution turns 4

The HTTP policy remains authoritative for origins, timeout, body buffering, text sampling, captured headers, and evidence reliability. Redirects are not followed. The runtime performs one bootstrap GET, then only the discovery methods owned by installed semantic executors.

planning_budget is expressed in planner action-cost units; it is not a raw HTTP-request count. max_action_cycles remains the decision loop's passive-action guard. RuntimeBudget is the outer operational envelope and includes bootstrap, planned, adaptive, retry, and active-verification requests.

All runtime dimensions accept zero as a deliberate fail-closed value. A zero request, wall-time, response-byte, or same-action budget prevents bootstrap I/O. A zero request-body budget permits bodyless discovery but refuses the first non-empty body before dispatch. A zero active-verification budget still permits passive work but refuses the first active probe.

Optional API response and surface reasoning

enable_api_reasoning() installs StandardApiReasoning into the runtime's existing rule engine. It is disabled by default, and api_reasoning_installation() returns the installation receipt only when the builder enabled it.

The option evaluates normalized evidence already emitted by the HTTP executor. Generic JSON produces only a JSON response-format hypothesis. An exact GraphQL response media type produces a strong GraphQL surface hypothesis, while a normalized graphql path segment remains weak. Enabling it creates no request, executor, payload, active verification, or planner action, so the runtime's configured limits and request reservations remain unchanged. The additional deterministic rule evaluation still runs under the same wall-time deadline.

Authorized paired-visibility workflows

Transport-free ingestion

The runtime can also own the storage, reasoning, and review side of an authorized paired-visibility workflow. The host still pairs the two contexts and creates a typed ApiVisibilityObservation; the runtime never accepts raw response bodies, credentials, headers, URLs, or principal names through this boundary.

let receipt = runtime.ingest_api_visibility(observation, &resource)?;
let query = ApiVisibilityReviewQuery::new(32)?;
let page = runtime.api_visibility_reviews(&resource, &query)?;

Both methods require .enable_api_reasoning(). A disabled runtime returns RuntimeApiVisibilityError::ApiReasoningDisabled before writing anything. Successful ingestion preserves the observation's isolated comparison subject and its evidence-backed resource relation; it never rewrites comparison evidence onto the runtime endpoint subject.

Ingress is neutral to HTTP request accounting, the decision session, experience, planning, and executor selection. It may be performed before or after analyze() and does not make the paired hypothesis eligible for the endpoint planner. Exact replay remains idempotent. If reasoning fails after the observation commits, RuntimeApiVisibilityError::committed_observation() exposes the commit receipt rather than implying rollback. Producer authentication, authorization of both compared contexts, same-resource pairing, and persistence remain host responsibilities.

Native authorization-context pair

run_api_visibility_pair is the first runtime-native collection path for this model. It is an explicit host call, not a planner-selected capability. The host provides two context-bound GET probes for the exact runtime target, identifies their shared logical resource, and declares which bounded credential or supporting anti-CSRF header names belong to the authorization context. The request constructor rejects a method, URL, non-context header, or primary credential equivalence before I/O. Credentials require HTTPS except on an exact loopback fixture target.

let report = runtime.run_api_visibility_pair(request).await?;

match report.disposition() {
    ApiVisibilityDifferentialDisposition::AwaitHumanReview => {
        // Present the weak, evidence-backed boundary to an authorized reviewer.
    }
    ApiVisibilityDifferentialDisposition::NoDifferenceObserved
    | ApiVisibilityDifferentialDisposition::UnresolvedDifference
    | ApiVisibilityDifferentialDisposition::Inconclusive
    | ApiVisibilityDifferentialDisposition::CancelledByHost
    | ApiVisibilityDifferentialDisposition::RuntimeBudgetLimit => {}
    _ => {}
}

Control and candidate use separate connection pools so connection-bound state cannot cross principal contexts. They still share the runtime's HTTP policy and host-owned accounting authority. Both dispatches are Active, require separate total-request and active-verification leases, and charge request bodies and all transport-delivered response chunks at the broker boundary. Redirect following and implicit retries remain disabled.

Only two complete, non-truncated JSON-compatible responses continue to Comparator V3. The runtime then creates the isolated comparison observation, atomically commits its evidence and resource relation, applies standard API reasoning, and reads the exact committed review. A difference can produce only a weak, supported boundary hypothesis with AwaitHumanReview; it never becomes a vulnerability verdict, endpoint decision-loop command, planner success, or Experience update.

The operation is single-use. Once its pre-I/O configuration and target checks pass, the same runtime cannot later run analyze() or another pair. Incomplete legs produce no comparison, but their charged usage and any completed-leg receipt remain in ApiVisibilityDifferentialAudit. Its transport() audit also preserves every acquired control/candidate dispatch and typed terminal outcome, including a partially received timeout or cancelled in-flight leg. A late cancellation or limit may retain a completed V3 comparison and, if ingestion already happened, the observation and review. Post-commit errors expose the same audit plus the available comparison and commit receipt; they do not imply rollback or disk durability. See ADR 0013.

The opt-in origin assessment composes this workflow for the exact root only. WebAssessmentRootAuthorizationContext consumes one bounded complete Authorization value, builds a distinct Standard child with the assessment's existing SharedWebRuntimeAuthority, and runs the anonymous/authorized pair after root work but before discovery admission. It does not mint another broker or budget. A canonical AwaitHumanReview result is projected as one atomic comparison evidence reference; the assessment does not invent separate control/candidate evidence records. Equivalence emits no item. Incomplete collection, cancellation, or limit exhaustion halts later discovery and keeps the assessment incomplete. The capability has no verifier transition and can never produce Confirmed.

Resource authorization differential

The non-default authorization-review feature composes one policy-selected resource comparison as the native action web.review.authorization.resource-differential inside the existing WebAssessmentRuntime. It does not invoke run_api_visibility_pair, create a second Standard runtime, or finalize a detached report. The feature-enabled builder accepts one validated security.authorization-review-policy/v1 value and one move-only primary/peer principal pair; the CLI supplies those values only after profile, policy-file, transport, source-conflict, and report-output preflight.

The action dispatches primary candidate, peer candidate, primary replay, and peer replay against the same exact resource. The parent redirect-disabled request broker creates a fresh ambient-proxy-free connection pool for each of the four bodyless GET legs. Every pool remains governed by the same exact-origin authority, RuntimeBudget, response accounting, cancellation, deadline and evidence lifecycle. The only principal-varying header is Authorization; there is no cookie, request body, arbitrary header, method change, identifier mutation, or implicit retry. The child is capped at one resource, four requests, and one logical active verification: the passive stage collects both candidate views, then the active decision phase charges its sole logical active-verification lease when the primary replay begins. The peer replay is passive-accounted within that same phase, through the same registered action and executor.

Complete JSON responses are reduced immediately through the shared API comparison foundation. A positive item requires independent primary and peer stability and both cross-principal rounds to match across Status, Fields, and value-sensitive Resources. The committed observer/ledger truth and all request/accounting receipts must reconcile before the common projection can emit one authorization.resource-cross-principal-equivalence@1 item. Its maximum is NeedsReview / KnowledgeOnly. Redirect, defense or rate-limit interference, unsupported media, malformed or truncated JSON, cancellation, budget exhaustion, or any missing lifecycle entry fails closed and cannot produce a completed item set.

The resulting redaction-safe audit is part of the same composed assessment report. It exposes typed outcome and bounded accounting only—not credentials or credential digests/sources, raw URLs or query values, the clear resource handle, JSON Pointer text, scalar values, bodies, or raw errors. The existing two-leg exact-root authorization-context compatibility workflow remains unchanged and cannot be enabled in the same V1 run.

Runtime safety envelope

The runtime checks limits in a stable order before execution: wall time; advisory broker preflight for total requests, remaining response bytes, and active verifications; then attempts for the semantic action. Only the semantic attempt is reserved before an optional scheduler delay. A delay cancellation therefore consumes an action attempt but not a request. The shared host-owned request broker repeats the resource checks atomically, charges the exact buffered request-body length, and records the request immediately before reqwest::Client::execute, so a transport error or timeout after dispatch remains charged. An opaque request body whose length cannot be measured is rejected before dispatch.

The wall deadline starts at the beginning of analyze() and covers bootstrap, reasoning, scheduler delay, network I/O, verification, and state transitions. Awaited delays and requests are cancelled at the monotonic deadline. Synchronous reasoning cannot be interrupted mid-function, so the runtime checks the deadline again immediately after it returns.

Host cancellation is checked before bootstrap, at deterministic planning boundaries, and while bootstrap or action execution is awaited. The runtime uses a deliberately biased wait order: a ready execution result wins first so a just-produced commit receipt is not discarded; an explicit host cancellation wins over a simultaneously ready wall deadline because it is the more specific stop reason. Cancellation after evidence commit but before verification keeps that receipt in unverified_evidence() and does not synthesize an outcome.

RuntimeBudget::max_request_body_bytes is the session-wide total of buffered request bodies accepted by broker leases. The charge and request reservation happen under the same accounting lock before network dispatch, so concurrent capability executors cannot oversubscribe the allowance. Headers, URLs, and wire framing are excluded.

HttpEvidencePolicy::max_body_bytes remains a per-response retention ceiling. RuntimeBudget::max_response_bytes is the session-wide threshold for response-body bytes delivered to broker collection. Metered collectors share one response-read gate and recheck the remaining allowance before every read. Every complete received chunk is charged before its bounded prefix is exposed to the executor. The one serialized chunk that reveals a crossing can make usage exceed the threshold; retention stays capped, no collector starts another body read, and the same turn terminates with a typed ResponseBytes limit. Evidence already committed by that turn remains in bootstrap or unverified_evidence, before verification or Experience updates. Bytes also remain charged when a later read fails, times out, or is cancelled. Content-Length, headers, framing, and unread bytes after collection stops are excluded.

All built-in HTTP executors installed by StandardWebDecisionRuntime and the explicit WordPress metadata-discovery child share one broker and one accounting authority. The broker owns two fixed redirect-disabled, retry-disabled clients: the established general client and a narrower no-proxy client used only for the closed anonymous WordPress metadata GET shape. Bootstrap, planned, adaptive, retry, active-verification, and opted-in metadata dispatches therefore compete for the same atomic envelope. Redirect responses consume the request that produced them but are not followed. Semantic retries re-enter the broker and acquire a fresh lease; WordPress metadata discovery never retries. Low-level callers that construct and run an arbitrary DecisionActionExecutor outside this standard runtime remain responsible for their own transport policy and accounting.

No-progress accounting ignores raw evidence IDs, timing changes, retry case IDs, and experience inserts. A completed execution turn resets the counter only when it inserts or updates a hypothesis, escalates passive verification to an active probe, or reaches a terminal Success/FalsePositive/ConfirmedNegative result. A knowledge-only Success therefore still records objective progress even though it transitions no hypothesis. When the configured count is reached, the next command is not dispatched.

Expected exhaustion is an auditable result rather than an execution error. The report ends with Halt { reason: RuntimeBudgetLimit }, exposes the structured dimension through limit_exceeded(), and carries final broker counters through usage(). If the broker refuses a later dispatch inside an already-started executor, execution_failure() also preserves the exact request, executor, stage, origin, limits, and structured runtime limit. If a natural Complete, AwaitHumanReview, or policy Halt is reached on the same completed turn, that domain terminal takes precedence.

Both successful reports and post-start failure receipts expose transport(). This bounded, raw-target-free audit orders broker leases by dispatch sequence and distinguishes completion, transport failure, per-request timeout, response-budget reach, and caller cancellation. A denied lease is absent because it opened no dispatch; earlier receipts remain present when a later request, accounting check, verification, or state transition fails. The hard retention ceiling is explicit through omitted_receipt_count() rather than silently presenting a partial audit as complete.

Executable-plan boundary

The standard planner currently declares nine semantic actions while the built-in discovery profile implements eight. The runtime compares planner executor identities with the installed registry before each planning cycle. Actions without an executor are supplied as host policy suppressions, remain visible as PolicySuppressed exclusions, and are never handed to the runner.

This prevents a discovered Laravel input hypothesis from becoming an UnknownExecutor runtime failure. A future audited executor can remove its action from that suppression set simply by becoming part of the installed profile.

Lifecycle and audit

A runtime instance is single-use. The started flag is retained even if a network or verification error occurs, because evidence may already have been committed under deterministic case identities. Create a new runtime for a new session.

When an executor reports a failure before evidence commit, StandardWebDecisionRuntimeError::execution_failure() forwards the runner's typed receipt. Built-in HTTP failures are classified from error variants rather than diagnostic text: unsupported subjects are not applicable, authorization/scope refusals and unmetered request bodies are policy blocks, an expired host request/body deadline is a request timeout, other network failures are transport failures, and internal construction/model failures remain executor failures. Transport dispatch, request-body accounting, and received-byte accounting remain monotonic, but provider or policy failures before dispatch consume only the semantic attempt. None of these operational classifications creates a synthetic verifier outcome or changes Experience suppression state.

After analyze() marks the runtime started, unexpected failures are wrapped in RunFailed. failure_receipt() exposes the committed bootstrap receipt, completed planning/outcome turns, and the latest monotonic usage snapshot from before the failing boundary. The nested source remains typed, so execution_failure(), committed_evidence(), and committed_reasoning() keep forwarding the current boundary's receipt. This envelope is process-local; it does not claim disk durability or crash recovery.

StandardWebDecisionRunReport retains:

  • the optional bootstrap evidence receipt (absent when a limit stops bootstrap);
  • each reasoning/planning report;
  • every executor evidence receipt and outcome report;
  • evidence committed immediately before cancellation, exposed separately as unverified_evidence() because no verifier outcome exists;
  • the final Complete, AwaitHumanReview, or Halt command;
  • final resource usage and an optional structured limit record;
  • bounded, ordered per-dispatch transport receipts and an explicit omitted count;
  • an optional execution-failure receipt when the broker refused an in-executor dispatch.

The knowledge base, experience store, and replayable session remain inspectable after execution. A host can also consume the runtime with into_experience() and pass that store into a later builder through experience_store().

Turn commit semantics

Runtime request dispatches, request-body bytes, and transport-delivered response bytes are monotonic and are never rolled back. Executor evidence is provenance-validated as a complete batch and then committed atomically to the knowledge base. Verification, hypothesis transition, experience, and session transition happen after that evidence commit. If one of those later synchronous stages fails, the evidence remains append-only while the runtime returns an error and stays single-use. StandardWebDecisionRuntimeError::committed_evidence() exposes the failed turn's committed in-process receipt, including response-telemetry validation failures, and the consuming getter transfers it without another evidence clone.

A successful outcome report exposes a runtime-only, lightweight before/after session transition summary alongside its verification, hypothesis write, and experience write. The summary is not a full session replay snapshot and is omitted from the report's existing serialized shape. Candidate experience and session state are assigned only after every fallible outcome step succeeds. This is an explicit error-atomic partial-turn boundary, not a rollback or crash-atomic transaction guarantee.

Planning turns use the same candidate-session rule. Planner validation and case construction must finish before the runtime advances or halts the real session, and a final read-locked revision check rejects a plan made stale by concurrent knowledge writes. A successful planning report exposes its runtime-only before/after transition. Reasoning still commits before planning. When those rule writes changed knowledge and planning then fails, StandardWebDecisionRuntimeError::committed_reasoning() exposes the exact application/write statuses plus the subject/ontology revisions of the planner snapshot; the session remains unchanged. This is an explicit post-commit receipt, not rollback of immutable reasoning history or a durability claim.

The hypothesis transition uses the VerificationReport revision token. A concurrent evidence, hypothesis, or ontology write makes the report stale and aborts the candidate experience/session commit. Same-terminal replay remains idempotent, while opposite terminal transitions fail explicitly.

A budget stop is different: it occurs before the refused transport dispatch and moves any outstanding decision session to Halted { reason: RuntimeBudgetLimit }. A dispatch or partial body already charged before a later failure remains visible in usage(). Evidence and outcome receipts from earlier completed turns remain available in the report, while an in-executor broker refusal has its own failure receipt.