{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://ravikrali.github.io/Agentic-AI-Governance/companion/05-policy-decision-event.schema.json",
  "title": "Agent Policy Decision Event",
  "description": "Companion artifact 5 of 7 for 'Agentic AI Governance: Standards and Guardrails' (R2 Digital LLC). The record a policy decision point writes for every authorization decision on an agent action. Written BEFORE the action executes (AS-11): if this event cannot be persisted, the action must not proceed. Designed to sit alongside OpenTelemetry GenAI spans rather than replace them - carry trace_id and span_id so a decision can be joined to its trace. The OTel GenAI conventions are at Development stability and do not yet cover policy decisions, approvals, delegation or outcomes, so treat this as the enterprise extension that fills that gap, and pin the OTel version you target.",
  "type": "object",
  "required": [
    "event_id",
    "event_time",
    "schema_version",
    "decision",
    "principal",
    "agent",
    "action",
    "policy",
    "enforcement_point"
  ],
  "additionalProperties": false,
  "properties": {
    "schema_version": { "type": "string", "const": "1.0" },
    "event_id": { "type": "string", "format": "uuid", "description": "Unique id for this decision event." },
    "event_time": { "type": "string", "format": "date-time", "description": "RFC 3339 UTC timestamp at decision time." },

    "trace_id": { "type": "string", "description": "W3C trace id, hex. Joins this decision to the agent run trace." },
    "span_id": { "type": "string", "description": "W3C span id of the step that requested the action." },
    "task_id": { "type": "string", "description": "Stable id for the end-to-end task the agent is executing." },
    "step_index": { "type": "integer", "minimum": 0, "description": "Position of this step within the task." },

    "decision": {
      "type": "object",
      "required": ["outcome"],
      "additionalProperties": false,
      "properties": {
        "outcome": {
          "type": "string",
          "enum": ["allow", "allow_with_conditions", "redact", "escalate", "deny"],
          "description": "escalate means a human approval is required before the action may proceed."
        },
        "reason_code": { "type": "string", "description": "Stable machine code, e.g. VALUE_LIMIT_EXCEEDED, TENANT_BOUNDARY, SCOPE_NOT_DELEGATED, TIER_REQUIRES_APPROVAL." },
        "reason_text": { "type": "string", "description": "Human-readable explanation returned to the agent and shown to the owner." },
        "conditions": {
          "type": "array",
          "description": "Obligations attached to an allow_with_conditions outcome.",
          "items": {
            "type": "object",
            "required": ["type"],
            "properties": {
              "type": { "type": "string", "enum": ["redact_fields", "value_cap", "rate_cap", "require_idempotency_key", "log_enhanced", "notify_owner", "expire_after"] },
              "detail": { "type": "string" }
            }
          }
        },
        "latency_ms": { "type": "number", "minimum": 0, "description": "Time taken to reach the decision. Track against your own control-plane SLO." },
        "evaluation_mode": { "type": "string", "enum": ["enforcing", "shadow"], "default": "enforcing", "description": "shadow records what would have happened without blocking. Use when rolling out a new policy." }
      }
    },

    "principal": {
      "type": "object",
      "description": "Who the agent is acting for. AS-04: the agent's effective authority must never exceed this principal's.",
      "required": ["type"],
      "additionalProperties": false,
      "properties": {
        "type": { "type": "string", "enum": ["human", "service", "agent", "scheduled", "anonymous"] },
        "id": { "type": "string", "description": "Stable identifier. Use a pseudonymous id where the log is broadly readable." },
        "tenant_id": { "type": "string" },
        "delegation_chain": {
          "type": "array",
          "description": "Ordered chain from the originating principal to the calling agent. Every hop must narrow or preserve scope, never widen it.",
          "items": {
            "type": "object",
            "required": ["id", "type"],
            "properties": {
              "id": { "type": "string" },
              "type": { "type": "string", "enum": ["human", "service", "agent"] },
              "granted_scopes": { "type": "array", "items": { "type": "string" } },
              "granted_at": { "type": "string", "format": "date-time" },
              "expires_at": { "type": "string", "format": "date-time" }
            }
          }
        },
        "effective_scopes": { "type": "array", "items": { "type": "string" }, "description": "The intersection actually applied to this call." }
      }
    },

    "agent": {
      "type": "object",
      "required": ["registry_id"],
      "additionalProperties": false,
      "properties": {
        "registry_id": { "type": "string", "description": "AS-01 registry key. An unregistered id must be denied." },
        "workload_identity": { "type": "string", "description": "Platform identity, e.g. Entra Agent ID object id or managed identity." },
        "name": { "type": "string" },
        "version": { "type": "string" },
        "autonomy_tier": { "type": "string", "enum": ["R0", "R1", "R2", "R3", "R4"] },
        "accountable_owner": { "type": "string" },
        "environment": { "type": "string", "enum": ["dev", "test", "stage", "prod"] },
        "peer_of": { "type": "string", "description": "Registry id of the delegating agent, for A2A calls." }
      }
    },

    "action": {
      "type": "object",
      "required": ["kind", "name"],
      "additionalProperties": false,
      "properties": {
        "kind": { "type": "string", "enum": ["tool_call", "model_call", "memory_read", "memory_write", "retrieval", "agent_delegation", "workflow_transition"] },
        "name": { "type": "string", "description": "Business-verb tool name, e.g. issue_policy_refund." },
        "risk_class": { "type": "string", "enum": ["A1", "A2", "A3", "A4"], "description": "AS-02: risk is a property of the action, not only of the agent." },
        "mutating": { "type": "boolean" },
        "reversible": { "type": "string", "enum": ["fully", "compensable", "irreversible"] },
        "idempotency_key": { "type": "string" },
        "compensating_action": { "type": "string" },
        "parameters_digest": { "type": "string", "description": "Hash of the parameters. Store the digest here and the full parameters in the restricted-access evidence store." },
        "parameter_summary": { "type": "object", "description": "Non-sensitive parameters safe to retain in the primary log.", "additionalProperties": true },
        "monetary_value": {
          "type": "object",
          "properties": {
            "amount": { "type": "number" },
            "currency": { "type": "string", "minLength": 3, "maxLength": 3 }
          }
        },
        "target_resource": { "type": "string" },
        "target_tenant_id": { "type": "string" },
        "data_classes": { "type": "array", "items": { "type": "string", "enum": ["public", "internal", "confidential", "personal", "special_category", "payment"] } }
      }
    },

    "policy": {
      "type": "object",
      "required": ["bundle_version"],
      "additionalProperties": false,
      "properties": {
        "bundle_version": { "type": "string", "description": "Version of the policy bundle that produced this decision. Required for replay." },
        "bundle_digest": { "type": "string" },
        "rules_evaluated": { "type": "array", "items": { "type": "string" } },
        "rules_matched": { "type": "array", "items": { "type": "string" } },
        "deciding_rule": { "type": "string", "description": "The single rule that determined the outcome." },
        "controls_applied": { "type": "array", "items": { "type": "string" }, "description": "Standards register ids, e.g. AS-04, AS-15." }
      }
    },

    "enforcement_point": {
      "type": "object",
      "required": ["id", "type"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string" },
        "type": { "type": "string", "enum": ["agent_gateway", "tool_broker", "memory_service", "retrieval_layer", "workflow_engine", "model_gateway", "egress_proxy"], "description": "AS-05: one authoritative policy system, many enforcement points." },
        "region": { "type": "string" },
        "decision_source": { "type": "string", "enum": ["remote_pdp", "cached_decision", "embedded_bundle"], "description": "Record when a cached or locally embedded bundle was used, so stale-policy windows are auditable." },
        "policy_age_seconds": { "type": "number", "minimum": 0 }
      }
    },

    "deterministic_limits": {
      "type": "object",
      "description": "AS-15. The hard controls checked for this call. These are the primary safety boundary; model-based screening supplements them.",
      "additionalProperties": false,
      "properties": {
        "tool_allowlisted": { "type": "boolean" },
        "parameters_allowlisted": { "type": "boolean" },
        "value_limit": { "type": "number" },
        "value_limit_breached": { "type": "boolean" },
        "rate_limit_breached": { "type": "boolean" },
        "tenant_boundary_ok": { "type": "boolean" },
        "egress_allowed": { "type": "boolean" },
        "sandboxed": { "type": "boolean" },
        "credential_ttl_seconds": { "type": "integer" },
        "credential_audience": { "type": "string" },
        "budget": {
          "type": "object",
          "properties": {
            "steps_used": { "type": "integer" }, "steps_max": { "type": "integer" },
            "tokens_used": { "type": "integer" }, "tokens_max": { "type": "integer" },
            "spend_used": { "type": "number" }, "spend_max": { "type": "number" },
            "wall_clock_ms_used": { "type": "integer" }, "wall_clock_ms_max": { "type": "integer" }
          }
        },
        "circuit_breaker_state": { "type": "string", "enum": ["closed", "half_open", "open"] }
      }
    },

    "screening": {
      "type": "object",
      "description": "Probabilistic checks. Useful signal and useful telemetry. Never the sole control for an irreversible action.",
      "additionalProperties": false,
      "properties": {
        "input_verdict": { "type": "string", "enum": ["clean", "suspicious", "blocked", "not_run"] },
        "input_signals": { "type": "array", "items": { "type": "string" }, "description": "e.g. injection_pattern, untrusted_source_instruction, pii_detected." },
        "output_verdict": { "type": "string", "enum": ["clean", "suspicious", "blocked", "not_run"] },
        "output_signals": { "type": "array", "items": { "type": "string" } },
        "grounding_score": { "type": "number", "minimum": 0, "maximum": 1 },
        "screening_model": { "type": "string" },
        "screening_model_version": { "type": "string" }
      }
    },

    "approval": {
      "type": "object",
      "description": "Present when the outcome was escalate. Written again on resolution.",
      "additionalProperties": false,
      "properties": {
        "required": { "type": "boolean" },
        "request_id": { "type": "string" },
        "state": { "type": "string", "enum": ["pending", "approved", "rejected", "expired", "auto_expired"] },
        "approver_id": { "type": "string" },
        "approver_role": { "type": "string" },
        "dual_control": { "type": "boolean" },
        "second_approver_id": { "type": "string" },
        "evidence_shown": { "type": "array", "items": { "type": "string" }, "description": "What the approver actually saw. Guards against approval given on a summary alone (ASI09)." },
        "requested_at": { "type": "string", "format": "date-time" },
        "resolved_at": { "type": "string", "format": "date-time" },
        "time_to_decision_ms": { "type": "integer" }
      }
    },

    "outcome": {
      "type": "object",
      "description": "Written after execution and correlated back by event_id. Never overwrite the pre-action record.",
      "additionalProperties": false,
      "properties": {
        "executed": { "type": "boolean" },
        "status": { "type": "string", "enum": ["success", "failure", "timeout", "compensated", "not_executed"] },
        "error_code": { "type": "string" },
        "compensation_event_id": { "type": "string", "format": "uuid" },
        "duration_ms": { "type": "integer" },
        "cost": {
          "type": "object",
          "properties": {
            "input_tokens": { "type": "integer" },
            "output_tokens": { "type": "integer" },
            "amount": { "type": "number" },
            "currency": { "type": "string" }
          }
        }
      }
    },

    "evaluation": {
      "type": "object",
      "description": "Populated when this event is sampled for continuous evaluation (AS-13).",
      "additionalProperties": false,
      "properties": {
        "sampled": { "type": "boolean" },
        "sample_reason": { "type": "string", "enum": ["random", "high_risk_class", "policy_violation", "human_override", "incident", "new_release"] },
        "scores": { "type": "object", "additionalProperties": { "type": "number" } },
        "scorer": { "type": "string", "enum": ["human", "llm_judge", "rule"] },
        "judge_model": { "type": "string" },
        "judge_model_version": { "type": "string" },
        "human_reviewed": { "type": "boolean" },
        "human_agreement": { "type": "boolean", "description": "Whether the human agreed with the judge. Aggregate this to track judge drift." },
        "added_to_regression_set": { "type": "boolean" }
      }
    },

    "retention": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "class": { "type": "string", "description": "Retention class driving the horizon, e.g. eu_ai_act_high_risk." },
        "delete_after": { "type": "string", "format": "date-time" },
        "legal_hold": { "type": "boolean" }
      }
    }
  },

  "examples": [
    {
      "schema_version": "1.0",
      "event_id": "b7c1f0e2-9a44-4c3d-8f21-6d5e0a7b1c93",
      "event_time": "2026-08-26T14:22:07.481Z",
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "span_id": "00f067aa0ba902b7",
      "task_id": "case-4471902",
      "step_index": 6,
      "decision": {
        "outcome": "escalate",
        "reason_code": "VALUE_LIMIT_EXCEEDED",
        "reason_text": "Refund of 1,250.00 USD exceeds the 500.00 USD envelope for tier R3 on this action class. Human approval required.",
        "latency_ms": 41,
        "evaluation_mode": "enforcing"
      },
      "principal": {
        "type": "human",
        "id": "usr_8831",
        "tenant_id": "tnt_eu_west",
        "delegation_chain": [
          { "id": "usr_8831", "type": "human", "granted_scopes": ["claims.read", "claims.refund"], "granted_at": "2026-08-26T14:20:55Z", "expires_at": "2026-08-26T14:35:55Z" },
          { "id": "agt_claims_resolver", "type": "agent", "granted_scopes": ["claims.read", "claims.refund"] }
        ],
        "effective_scopes": ["claims.read", "claims.refund"]
      },
      "agent": {
        "registry_id": "agt_claims_resolver",
        "workload_identity": "entra-agent-3f9c2a11",
        "name": "Claims Resolver",
        "version": "2.4.1",
        "autonomy_tier": "R3",
        "accountable_owner": "j.okonkwo@example.com",
        "environment": "prod"
      },
      "action": {
        "kind": "tool_call",
        "name": "issue_policy_refund",
        "risk_class": "A4",
        "mutating": true,
        "reversible": "compensable",
        "idempotency_key": "refund-case-4471902-v1",
        "compensating_action": "reverse_policy_refund",
        "parameters_digest": "sha256:9f2b...c41d",
        "monetary_value": { "amount": 1250.00, "currency": "USD" },
        "target_resource": "policy/PL-99312",
        "target_tenant_id": "tnt_eu_west",
        "data_classes": ["personal", "payment"]
      },
      "policy": {
        "bundle_version": "2026.08.19-3",
        "bundle_digest": "sha256:1a77...9e02",
        "rules_matched": ["refund.value_envelope", "tier.l3.escalation"],
        "deciding_rule": "refund.value_envelope",
        "controls_applied": ["AS-02", "AS-04", "AS-07", "AS-15"]
      },
      "enforcement_point": {
        "id": "tool-broker-euw-02",
        "type": "tool_broker",
        "region": "eu-west",
        "decision_source": "remote_pdp",
        "policy_age_seconds": 12
      },
      "deterministic_limits": {
        "tool_allowlisted": true,
        "parameters_allowlisted": true,
        "value_limit": 500.00,
        "value_limit_breached": true,
        "rate_limit_breached": false,
        "tenant_boundary_ok": true,
        "egress_allowed": true,
        "sandboxed": true,
        "credential_ttl_seconds": 120,
        "credential_audience": "https://claims.internal/api",
        "budget": { "steps_used": 6, "steps_max": 25, "tokens_used": 48210, "tokens_max": 250000, "spend_used": 0.71, "spend_max": 5.00 },
        "circuit_breaker_state": "closed"
      },
      "screening": {
        "input_verdict": "clean",
        "output_verdict": "not_run",
        "screening_model": "safety-classifier",
        "screening_model_version": "3.2"
      },
      "approval": {
        "required": true,
        "request_id": "apr_55120",
        "state": "pending",
        "evidence_shown": ["refund_diff", "claim_history", "policy_terms_citation", "agent_reasoning_trace"],
        "requested_at": "2026-08-26T14:22:07.500Z"
      },
      "retention": { "class": "financial_record_7y", "delete_after": "2033-08-26T00:00:00Z", "legal_hold": false }
    }
  ]
}
