Decisions & Routing
Every WCP dispatch begins with a RouteInput and ends with a RouteDecision. Nothing executes without a decision. Nothing is denied without a reason. Every decision is recorded.
RouteInput
RouteInput is the capability request envelope sent to the Hall. All six required fields must be present — the Hall rejects requests with empty or missing values.
| Field | Type | Required | Description |
|---|---|---|---|
capability_id | str | yes | The WCP capability requested, e.g. cap.doc.summarize |
env | dev | stage | prod | edge | yes | Deployment environment |
data_label | PUBLIC | INTERNAL | RESTRICTED | yes | Data sensitivity classification |
tenant_risk | low | medium | high | yes | Risk tier of the requesting tenant |
qos_class | P0 | P1 | P2 | P3 | yes | Quality of Service priority |
tenant_id | str | yes | Identifier of the requesting tenant. Must be non-empty. |
correlation_id | str | yes | UUID v4. Propagated through all downstream calls. |
request | dict | no | Arbitrary payload for the target worker |
blast_score | int | no | Pre-computed blast score. Router computes it if absent. |
blast_radius | dict | no | Pre-computed blast radius dimensions |
privilege_context | dict | no | Privilege context for envelope enforcement |
dry_run | bool | no | If true, full decision is made but no worker executes |
tenant_credential | str | no | Signed WCP credential (v0.2+) |
QoS classes:
P0— critical, maximum governance, human escalation may be requiredP1— high priority, full governanceP2— standard, default governanceP3— background, relaxed governance
from pyhall.models import RouteInputimport uuid
inp = RouteInput( capability_id="cap.doc.summarize", env="prod", data_label="INTERNAL", tenant_risk="low", qos_class="P1", tenant_id="org.acme", correlation_id=str(uuid.uuid4()), request={"document_id": "doc-1234"},)Routing Flow
The Hall processes a RouteInput through a fixed pipeline:
1. Validate RouteInput fields (tenant_id non-empty, correlation_id present)2. Match first routing rule by capability_id + env + data_label └─ No match → DENY (code: DENY_NO_WORKER) — fail closed3. Check preconditions (correlation_id enforced, policy version propagated)4. Verify declared controls against registry └─ Required control missing → DENY (code: DENY_CONTROL_MISSING)5. Compute blast score (or use pre-computed value) └─ Score exceeds threshold → DENY (code: DENY_BLAST_EXCEEDED)6. Evaluate policy gate └─ REQUIRE_HUMAN → STEWARD_HOLD (supervisor_required: true)7. Select first available worker candidate matching species └─ No worker available → DENY (code: DENY_NO_WORKER)8. Optional: verify worker code attestation (if require_worker_attestation: true) └─ Hash mismatch → DENY (code: DENY_WORKER_TAMPERED)9. Emit three mandatory telemetry events10. Return RouteDecisionRouteDecision
RouteDecision is the evidence receipt returned by the Hall. It is populated for every dispatch — allowed, denied, or held.
| Field | Type | Description |
|---|---|---|
decision_id | str | UUID v4 identifying this specific decision |
timestamp | str | ISO 8601 UTC timestamp |
correlation_id | str | Propagated from RouteInput.correlation_id |
tenant_id | str | Propagated from RouteInput.tenant_id |
capability_id | str | The capability that was requested |
matched_rule_id | str | The routing rule that matched. NO_MATCH if none. |
selected_worker_species_id | str | None | Set on DISPATCH. Null on DENY. |
env | Env | Propagated from input |
data_label | DataLabel | Propagated from input |
tenant_risk | TenantRisk | Propagated from input |
qos_class | QoSClass | Propagated from input |
denied | bool | true if dispatch was denied |
deny_reason_if_denied | dict | None | Structured deny reason (code + message) |
required_controls_effective | list[str] | Controls that were verified |
recommended_profiles_effective | list[dict] | Suggested profiles for this dispatch |
escalation_effective | Escalation | Escalation policy that applied |
preconditions_checked | PreconditionsChecked | Which preflight checks ran |
artifact_hash | str | None | SHA-256 of the serialized RouteInput |
worker_attestation_checked | bool | Whether code attestation was verified |
worker_attestation_valid | bool | None | true = matched. false = tampered. None = not checked. |
dry_run | bool | true if this was a dry-run decision |
telemetry_envelopes | list[dict] | Mandatory WCP telemetry events |
Outcomes
DISPATCH — all checks passed. denied: false, selected_worker_species_id is set. The caller dispatches the returned worker species.
DENY — a check failed. denied: true, deny_reason_if_denied carries the deny code and human-readable message. No worker is dispatched.
STEWARD_HOLD — the policy gate returned REQUIRE_HUMAN. denied: true, deny_reason_if_denied["code"] = "DENY_REQUIRES_HUMAN_APPROVAL", deny_reason_if_denied["supervisor_required"] = true. Callers must check deny_reason_if_denied["code"] == "DENY_REQUIRES_HUMAN_APPROVAL" to detect STEWARD_HOLD specifically. Execution is held pending human approval via the configured H2A channel.
Deny Codes
Every denial includes a structured deny_reason_if_denied with a code field:
| Code | Cause |
|---|---|
DENY_NO_WORKER | No routing rule matched the capability, or no enrolled worker is available for the matched species |
DENY_POLICY_BLOCK | Policy gate evaluated and blocked dispatch based on risk tier, data label, or tenant risk |
DENY_WORKER_TAMPERED | Worker code attestation check failed — hash mismatch between registered and current code |
DENY_CONTROL_MISSING | A required control declared in the routing rule is not present in the registry |
DENY_BLAST_EXCEEDED | Computed blast score exceeds the maximum allowed for this environment |
DENY_UNKNOWN_TENANT | require_signatory: true is set and the tenant_id is not in the registered signatory list |
DENY_EMPTY_TENANT_ID | tenant_id was empty or whitespace |
decision = make_decision(inp, rules, controls_present, worker_available)
if decision.denied: reason = decision.deny_reason_if_denied print(f"Code: {reason['code']}") print(f"Message: {reason['message']}")Correlation IDs
The correlation_id is the backbone of WCP’s evidence chain. It originates in the RouteInput and propagates through:
- All three mandatory telemetry events (
evt.os.task.routed,evt.os.worker.selected,evt.os.policy.gated) - The
RouteDecisionitself - All downstream worker calls (the worker is responsible for propagating it)
This creates a traceable chain from the original capability request to every downstream action the worker takes. When something goes wrong, the correlation_id lets you reconstruct exactly what happened and in what order.
Idempotency. The same correlation_id submitted twice (when worker state is unchanged) produces the same RouteDecision. This makes dispatch safe to retry — if the network drops after you send the request but before you receive the response, resubmitting with the same correlation_id is safe.
Decision Log
Every RouteDecision — allowed and denied — is recorded. The artifact_hash in the decision is a SHA-256 of the serialized RouteInput, proving what was actually routed. Combined with the telemetry chain and correlation propagation, this creates a complete, auditable record of every governance decision the Hall made.
This record is the foundation of WCP’s attestation model: in a post-incident review or litigation, you can prove exactly what ran, when, under what policy, with what controls verified, and whether the worker’s code matched what was originally enrolled.