OAP Decisions vs Harness Enforcement

Purpose

Open Agent Passport (OAP) separates two concerns:

  • Policy decision: the verifier answers whether the requested action is authorized by the passport and policy.
  • Harness enforcement: the runtime integration decides what to do with that decision in the local product experience.

This split keeps the OAP standard deterministic and auditable while still allowing teams to roll out guardrails in report-only mode before they make checks blocking.

Architectural Decision

OAP decisions remain binary:

{
  "decision_id": "8f38c4e7-9f2d-4d60-8619-fcc8b2c8f23a",
  "policy_id": "system.command.execute.v1",
  "agent_id": "ap_example",
  "allow": false,
  "reasons": [
    {
      "code": "oap.command_not_allowed",
      "message": "Command is not in the passport allowlist"
    }
  ],
  "signature": "ed25519:...",
  "kid": "oap:registry:key-2025-01"
}

The verifier should not return warn as a third authorization state. A deny is still a deny: the action did not satisfy the policy.

The harness may be configured to treat that deny as:

Harness mode OAP decision Runtime behavior Intended use
enforce allow: false Block the action Default for production and protected workflows
warn / report-only allow: false Show a warning and let the action continue Policy tuning, audit rollout, developer adoption
fail-open-on-api-error Infrastructure error, not a policy deny Let the action continue with an explicit fail-open warning Narrow availability exception only

warn mode must not rewrite the decision to allow: true. It records the original signed denial and separately records that the operator configured a non-blocking rollout. A pre-action verifier can derive what the harness is expected to do, but it cannot prove the tool actually ran unless the harness reports the runtime result after the action.

Why This Belongs Outside the Core OAP Decision

OAP is the policy decision point. Claude Code, Cursor, LangChain, CrewAI, OpenClaw, GitHub Actions, MCP gateways, and custom applications are policy enforcement points.

Keeping enforcement disposition out of allow has four benefits:

  • Backward compatibility: existing OAP consumers already understand allow: true | false.
  • Audit integrity: the signed decision says what the policy concluded, not what a local UI chose to do during rollout.
  • Security clarity: production systems can fail closed on any unknown state instead of interpreting a new tri-state incorrectly.
  • Framework flexibility: every harness has different UX mechanics for blocking, warning, annotating, requesting approval, or continuing.

This is the same broad separation used by mature authorization systems: the authorization service returns a decision, while the relying party or policy enforcement point performs the local action.

Reconciling Dashboard Deny With Harness Warn

A dashboard or audit view should show both layers when both are available:

Field Meaning
policy_decision The signed OAP decision: allow or deny
decision_id Stable join key for audit, logs, and runtime telemetry
enforcement_mode Harness configuration: enforce, warn, or another explicit local mode
expected_runtime_disposition Pre-action expectation derived from the decision and reported harness mode: allowed, blocked, continued_after_warning, continued_after_fail_open, not_reported
runtime_disposition Optional post-action report of what actually happened locally: allowed, blocked, continued_after_warning, continued_after_fail_open, not_reported

Example:

{
  "decision_id": "8f38c4e7-9f2d-4d60-8619-fcc8b2c8f23a",
  "policy_decision": "deny",
  "enforcement_mode": "warn",
  "expected_runtime_disposition": "continued_after_warning",
  "runtime_disposition": "not_reported",
  "enforced_by": "cursor",
  "reported_at": "2026-09-05T14:30:00Z"
}

In the UI, this should not be presented as a contradiction. Recommended labels:

  • Policy decision: Denied
  • Expected runtime disposition: Warned and continued
  • Runtime disposition: Not reported
  • Operator mode: Report-only

For analytics, count this as a policy denial in risk and posture metrics. Count it separately as non-blocking in rollout and adoption metrics.

If a harness does not report runtime disposition back to APort, the dashboard should only claim the signed policy decision. It should not infer whether the tool actually ran.

Implementer Requirements

Harnesses and SDKs that support report-only rollout should follow these rules:

  1. Default to enforce unless the operator explicitly chooses warn or report-only.
  2. Never let the agent choose or override enforcement mode through tool-call input. Hosted integrations should send enforcement mode as trusted request metadata, not inside policy context.
  3. Preserve the signed OAP decision exactly as returned by the verifier.
  4. Display a clear warning when a deny decision is allowed to continue.
  5. Redact secrets and sanitize untrusted tool names, paths, reasons, and policy messages before printing to terminals or CI logs.
  6. Join any runtime telemetry to the OAP decision with decision_id.
  7. Treat infrastructure fail-open separately from policy warn mode. A real policy deny is not an API outage.

Current Implementation Status

Current APort guardrail harnesses support local enforce and warn modes, with enforce as the default. The hosted verifier can accept harness-reported runtime metadata on the verification request so hosted decisions can be stored and displayed with both layers:

  • Signed OAP decision: binary allow.
  • Harness-reported pre-action metadata: enforcement_mode, enforced_by, and expected_runtime_disposition.
  • Optional post-action telemetry: runtime_disposition when a harness reports what actually happened.

Pure local/offline passport verification can only appear in the hosted dashboard if the operator opts into telemetry. Otherwise it remains a local audit-log concern.

GitHub Repository Guard

For GitHub Actions, report-only means the APort Action can produce a deny decision or high-risk evidence while the workflow still exits successfully. Blocking requires a trusted enforcement path, usually:

  1. mode: hosted
  2. A required branch-protection check
  3. Explicit protected-path or push enforcement where needed

The job summary should still make deny decisions prominent. A passing report-only workflow should say the repository needs review when the signed decision is allow: false; it should not present the result as clean.

Future Standard Extension

If OAP later standardizes enforcement telemetry, the safer path is a separate optional object or event, not changing allow to a tri-state:

{
  "type": "oap.enforcement_result",
  "decision_id": "8f38c4e7-9f2d-4d60-8619-fcc8b2c8f23a",
  "enforcement_mode": "warn",
  "expected_runtime_disposition": "continued_after_warning",
  "runtime_disposition": "continued_after_warning",
  "enforced_by": "github-actions",
  "created_at": "2026-09-05T14:30:00Z"
}

High-assurance environments may require this event to be signed by the harness, CI provider, or gateway. That can be added without changing the core OAP decision schema.