Architecture
How Veefore is built, why it is built that way, and where each decision is recorded.
#The shape
text people, CLI, CI agents
│ │
┌───────▼────────┐ ┌────────▼────────┐
│ Control plane │ │ Gateway │
│ /v1/* │ │ /gateway/v1/* │
│ │ │ │
│ management │ │ enforcement │
└───────┬────────┘ └────────┬────────┘
│ ┌──────────┐ │
└──────────▶ Postgres ◀─────────────┘
└─────▲────┘
│
┌────────┴────────┐
│ Worker │
│ queues, rollups │
└─────────────────┘
Two request-serving processes, one worker, one database. The split between the planes is a security boundary, not a packaging choice: Gateway architecture.
#Packages
packages/types |
The shared vocabulary: enums, permissions, policy IR, error codes, event types |
packages/common |
Config, logging, redaction, crypto, pagination, resilience, telemetry |
packages/db |
The driver, migrations, repositories, the schema catalogue |
packages/identity |
Key handling and JWS |
packages/policy |
The evaluator, schema, field descriptors, templates, diff, Rego export |
services/core |
Every domain service: agents, policy, delegation, risk, approval, credentials, audit, enforcement |
apps/control-plane |
The management HTTP surface, plus the worker |
gateway |
The enforcement HTTP surface |
apps/console |
The browser app |
packages/sdk-js, packages/sdk-python |
Client SDKs |
packages/cli |
The veefore command |
docs |
This site |
#Design principles
Prefer structural guarantees to configured ones. An agent belongs to one environment, so a staging identity cannot act in production — not because a rule forbids it, but because the agent is not there. Configuration drifts; structure does not.
Order the pipeline so refusals come before side effects. The credential
broker is stage 10, after everything that can refuse. That is what makes
BLOCK mean blocked rather than "blocked, but the secret went out anyway".
Fail closed, always. No flag turns an error into an allow. → Fail closed
Make determinism and totality non-negotiable in the evaluator. The same input always yields the same output, and no operator can throw. A policy engine that can throw is not something you put in front of production.
Refuse rather than clamp. A delegation exceeding its parent is refused, not silently narrowed. Silent clamping means the caller believes it has authority it does not have.
Generate documentation from the source of truth. The reference pages are built from the registries the product itself reads, so they cannot drift.
Enforce the boundaries in CI. scope:audit fails the build if an
out-of-scope feature appears. quality:audit fails it on mock data, hardcoded
metrics, unconditional authorization or fail-open error handling. A principle
without a gate is a preference.
#Decision records
Thirteen ADRs, each with the context, the decision, the alternatives considered and the consequences accepted.
| 0001 | Scope boundaries |
| 0002 | Gateway architecture |
| 0003 | Policy engine — a TypeScript IR, not OPA |
| 0004 | Delegation semantics |
| 0005 | Identity model |
| 0006 | Passport format |
| 0007 | Trust evidence |
| 0008 | Credential broker |
| 0009 | MCP architecture |
| 0010 | A2A architecture |
| 0011 | Tenant isolation |
| 0012 | Audit architecture |
| 0013 | Deployment model |