Skip to Content
RuntimePermissions & Evidence

Permissions & Evidence

Two packages give the runtime its safety and its credibility, and both exist because an agentic CFO’s output is worthless unless it can prove where every number came from and who was allowed to see it. The permission engine ensures nothing is read, computed, projected, or acted on without an explicit, hash-provable decision. The evidence graph ensures every claim in an answer traces back to a span of a real source document, through extraction and human review.

Permission engine (packages/permission-engine)

The interface is small on purpose (src/engine.ts):

/** * Central authorization boundary used by query execution, policy execution, * retrieval, projection, and action handlers. */ export interface PermissionEngine { check(input: PermissionCheckInput): Promise<PermissionDecision> checkMany(inputs: readonly PermissionCheckInput[]): Promise<readonly PermissionDecision[]> }

The richness is in the decision model (src/effect.ts). Six gates partition what an operation may do:

GateGuards
retrievalreading evidence spans / documents
computationrunning registered formulas over inputs
projectionshaping outputs for an audience
actionside effects (proposeEffect, workflow tools)
concept_index / concept_useindexing and using resolved concepts

Decisions are four-valued — allow, deny, aggregate_only, requires_approval — so the engine can express “you may see the total but not the rows” (aggregate_only, which flows into AnswerRun projection redaction modes) and “a human must sign off first” (requires_approval, which routes into decision packets). Each decision carries its gate, scope, resource ref, and the plan hash it was issued for; authorization-proof.ts and verifyPermissionDecisionProof make decisions tamper-evident, and fixture-engine.ts provides a deterministic engine for tests and demos. query-runtime’s validator computes the required gates per step (requiredPermissionGatesForOperation), and both AnswerRun and PolicyRun refuse to decode if any step is missing a required gate decision.

Evidence graph (packages/evidence-graph)

A schema-only v0 (src/effect.ts, plus validateEvidenceGraphReferences in src/validate.ts) defining the source-to-fact-to-answer substrate:

Beyond the six core tables, the schemas carry lifecycle semantics: assertion review statuses (unreviewed / approved / corrected / rejected / superseded), assurance states for derived facts (computed → reconciled → review_required → approved → formally_audited), fact lifecycle (active / superseded / invalidated), VersionedReference, and proof records (ApprovalProofSchema, FormalAuditProofSchema). validateEvidenceGraphReferences is database-agnostic — the same referential check runs over fixtures, demos, and integration data (bun run demo:evidence-graph shows it catching a broken reference).

Retrieval and claims

  • packages/retrieval implements the contract query + scope + filters → evidence spans + trace, with a deterministic lexical baseline, a hybrid retriever and indexer, a seeded side-letter/LPA clause corpus, and a recall@k harness: bun run retrieval:evaluate. It is itself behind the retrieval permission gate.
  • packages/source-claims defines per-claim-type normalized_value schemas registered by (claim_type_id, claim_type_version) — decoding a value under an unregistered type or version fails closed (src/claim-types.ts, e.g. SubscriptionCommitmentValueSchema for partner commitments).

Pitfalls. (1) Never call adapters directly around the engine — the engine is the only authorization boundary, and runs that lack a decision for a required gate will not decode, so a bypass fails loudly at trace time, not silently. (2) aggregate_only is a real decision value, not a deny: handle it by projecting aggregates, or you will leak either too much or nothing. (3) Evidence-graph is schema-only — persistence and RLS enforcement live in packages/persistence; validating references locally does not substitute for tenant scoping in the database.

Source documents enter the graph via the ingestion pipeline (see the source pipeline docs), plan-time gate requirements come from the query runtime, and citations surface through the API layer.

Last updated on