Core Concepts

Explainability

A peer plane to Observability. Projects why decisions were made, directly from canonical authorities — never from logs.

Observability tells you what happened: audit rows, operational trails, debug traces. Explainability tells you why a decision was made: which access rule fired, which capability surface was selected, which publication version served the request, which read plan was generated. They are different planes. Explainability projects from canonical authorities directly, not from logs.

Why a peer plane

Inferring "why" from observability data is fragile. Log-derived reasons are paraphrased strings — always wrong at some edge. The reason an access decision came out the way it did is determined by the actor context, the entity's access model, the provider's security capabilities, and the resolved governance envelope. Explainability reads those canonical inputs directly and produces a typed, structured explanation.

Five canonical explainers

  • API Surface explainer — why a route resolved to this operation, on this entity, with this dispatch.
  • Project Runtime explainer — why this publication version is serving traffic, which bindings resolved, what the governance envelope projected.
  • Authored Publication explainer — which authored artifacts are bound to this surface, which signing key signed them, why they were selected over alternatives.
  • Access explainer — why this read or write was allowed or denied, which rule fired, which mask was applied, what the actor identity carried.
  • Read Plan explainer — the AST plan, the access predicate injection, the cache decision, the chosen provider.

Reason codes

Every decision outcome carries a stable ReasonCode from a canonical block of constants. Reason codes are typed enums, not free-form strings — so consumers can switch on them, dashboards can render them with localized explanations, and tests can assert on them.

AccessReasonCodes.AllowedByOwnerRule
AccessReasonCodes.DeniedByMissingClaim
AccessReasonCodes.DeniedByContextSetMismatch
ReadPlanReasonCodes.SatisfiedByCache
ReadPlanReasonCodes.RoutedToReadReplica
ProjectRuntimeReasonCodes.PublicationFrozenByMaintenance

The same pipeline

Every explainer invokes the same internal semantic steps the normal execute / compile path invokes — at most with an optional side-effect-only diagnostics recorder threaded through. There is no parallel "diagnosed" pipeline. Equivalence between normal and explain paths is architecturally enforced.

HTTP surface

GET  /api/Explainability/apiSurface?route=/api/orders
POST /api/Explainability/access
POST /api/Explainability/readPlan
GET  /api/Explainability/projectRuntime
GET  /api/Explainability/authoredPublication

SDK and CLI

// Typed SDK
const explanation = await sdk.explainability.access({
  entity: "Order",
  operation: "read",
  filter: { status: "paid" },
});

# CLI
vadyl explain access --entity Order --filter "status=paid"
vadyl explain read-plan --entity Order --include relations

What this gives you

  • A single source of truth for "why" — no need to search logs, ssh into machines, or reproduce conditions to figure out what happened.
  • Stable reason codes that survive across releases and let you build UI, tests, and alarms on top.
  • Confidence that the reasons you see in the explainer are the same reasons that actually drove the decision — there is no log-vs-truth drift.