io.github.vassiliylakhonin/vizier-guard
README.md
<!-- mcp-name: io.github.vassiliylakhonin/vizier-guard -->
# Vizier
**Deterministic Authorization & Non-Repudiation Audit Firewall for AI Agents.**
[](https://github.com/vassiliylakhonin/vizier/actions/workflows/ci.yml)
[](https://github.com/vassiliylakhonin/vizier/actions/workflows/deploy.yml)
[](packages/python-sdk)
[](https://www.npmjs.com/package/@vizier/sdk)
[](https://www.npmjs.com/package/@vizier/mcp-proxy)
[](https://github.com/modelcontextprotocol/registry)
[](LICENSE)
Vizier is an ultra-fast, edge-native deterministic authorization and audit firewall for action-taking AI agents. Before an agent executes an external side effect (making a payment, executing code, modifying a database, dispatching messages, or deploying infrastructure), it submits the proposed action to Vizier.
> ā” **Try the Live Interactive Playground**: [https://vizier.vassiliy-lakhonin.workers.dev/playground](https://vizier.vassiliy-lakhonin.workers.dev/playground)
> Test policy presets (`ALLOW`, `BLOCK_AMOUNT`, `BLOCK_TARGET`, `SENSITIVE`), inspect sub-25ms edge latency, and verify SHA-256 non-repudiation audit receipts in real time directly from your browser.
---
## šļø Architecture
```mermaid
flowchart TD
subgraph Agents["AI Agent Runtimes"]
A1["Python Agent (LangChain / CrewAI / AutoGen)"]
A2["MCP Client (Claude / Cursor / Tools)"]
A3["TypeScript / Node.js Agent"]
end
subgraph Guards["Vizier Enforcement Boundary"]
G1["@vizier_guard / Python SDK"]
G2["@vizier/mcp-proxy CLI"]
G3["@vizier/sdk (TypeScript)"]
end
subgraph Kernel["Cloudflare Workers Global Edge"]
K["Vizier Deterministic Kernel (/v1/verify)"]
P["Policy Engine: Limits, Targets, Roles, Grants"]
D1["D1 Audit Ledger & Cryptographic Receipts"]
end
subgraph Targets["Protected External Side-Effects"]
T1["Payment / Financial APIs"]
T2["Database Writes & Deletions"]
T3["Worker / Infrastructure Deployments"]
T4["External Message Dispatch"]
end
A1 --> G1
A2 --> G2
A3 --> G3
G1 -->|"POST /v1/verify"| K
G2 -->|"POST /v1/verify"| K
G3 -->|"POST /v1/verify"| K
K --> P
P --> D1
G1 -.->|"Decision: ALLOW"| T1
G2 -.->|"Decision: ALLOW"| T2
G3 -.->|"Decision: ALLOW"| T3
P -.->|"Decision: BLOCK / REVIEW"| G1
P -.->|"Decision: BLOCK / REVIEW"| G2
P -.->|"Decision: BLOCK / REVIEW"| G3
```
The decision path is strictly deterministic ā no non-deterministic LLMs in the critical decision loop. It checks delegated actions, principal identity, amount limits, targets, sensitive operations, and authenticated integration boundaries. Every response includes policy results and a tamper-proof SHA-256 canonical receipt hash.
Status: experimental v0.3.0, deployed on Cloudflare Workers edge. Since v0.3.0, authority can be **proved** rather than asserted: a principal signs a delegation grant, Vizier verifies it against a registered public key, and the receipt records authority provenance. Read the [threat model](docs/THREAT_MODEL.md) before placing this service in an execution path.
---
## š Public Surfaces
- **Edge Worker**: <https://vizier.vassiliy-lakhonin.workers.dev>
- **Action Playground**: <https://vizier.vassiliy-lakhonin.workers.dev/playground>
- **Live Field Reference**: <https://vizier.vassiliy-lakhonin.workers.dev/docs>
- **OpenAPI 3.1 Contract**: <https://vizier.vassiliy-lakhonin.workers.dev/openapi.json>
- **AI Discovery Catalog**: <https://vizier.vassiliy-lakhonin.workers.dev/.well-known/ai-catalog.json>
- **MCP Server Manifest**: <https://vizier.vassiliy-lakhonin.workers.dev/.well-known/mcp.json>
- **MCP Registry Entry**: `io.github.vassiliylakhonin/vizier`
- **Agent Card**: <https://vizier.vassiliy-lakhonin.workers.dev/.well-known/agent-card.json>
- **Public Key Set (JWKS)**: <https://vizier.vassiliy-lakhonin.workers.dev/.well-known/jwks.json>
---
## š Quickstarts
### 1. Python SDK (`vizier-guard`)
Zero external dependencies (Python standard library only):
```bash
pip install vizier-guard
```
```python
from vizier import VizierClient, vizier_guard
client = VizierClient(
base_url="https://vizier.vassiliy-lakhonin.workers.dev",
api_key="your-api-key"
)
# Protect any function or tool:
@vizier_guard(
client=client,
action_type="purchase",
max_amount=500.0,
currency="USD",
allowed_targets=["supplier.example"]
)
def execute_order(amount: float, target: str):
# Runs ONLY if Vizier decision is ALLOW
return {"status": "success", "amount": amount}
execute_order(amount=450.0, target="supplier.example") # Allowed
execute_order(amount=1200.0, target="supplier.example") # Raises ActionBlockedError
```
#### LangChain / LangGraph & CrewAI:
```python
from vizier.integrations.langchain import VizierLangChainToolGuard
from vizier.integrations.crewai import VizierCrewAIToolGuard
# LangChain / LangGraph
safe_tool = VizierLangChainToolGuard(
tool=my_search_tool,
client=client,
allowed_actions=["search"],
max_amount=0.0
)
# CrewAI
safe_crew_tool = VizierCrewAIToolGuard(
tool=my_payment_tool,
client=client,
max_amount=250.0
)
```
#### Human-in-the-Loop (Telegram / CLI / Webhooks) & Async:
```python
from vizier import AsyncVizierClient, vizier_guard, TelegramHITLHandler
# Interactive approval buttons via Telegram Bot when decision is REVIEW
telegram_approver = TelegramHITLHandler(
bot_token=os.environ["TELEGRAM_BOT_TOKEN"],
chat_id=os.environ["TELEGRAM_CHAT_ID"]
)
@vizier_guard(
client=AsyncVizierClient(),
action_type="transfer_funds",
hitl_handler=telegram_approver
)
async def transfer(amount: float, target: str):
# Executes ONLY if human operator clicks [Approve] in Telegram
return await bank_api.send(amount, target)
```
#### MCP Server for Claude Desktop & Cursor:
Equip Claude Desktop or Cursor with deterministic guardrails (`vizier_screen_action`, `vizier_verify_receipt`, `vizier_check_policy`):
```json
{
"mcpServers": {
"vizier": {
"command": "uvx",
"args": ["vizier-guard", "mcp"],
"env": {
"VIZIER_BASE_URL": "https://vizier.vassiliy-lakhonin.workers.dev",
"VIZIER_API_KEY": "your-vizier-api-key"
}
}
}
}
```
---
### 2. MCP Enforcement Proxy CLI
Wrap any local or remote MCP server with deterministic authorization:
```bash
npx @vizier/mcp-proxy \
--upstream http://localhost:3000/mcp \
--tools "query_db,execute_command,fetch_api" \
--vizier https://vizier.vassiliy-lakhonin.workers.dev \
--api-key $VIZIER_API_KEY
```
---
### 3. TypeScript SDK (`@vizier/sdk`)
```bash
npm install @vizier/sdk
```
```ts
import { Vizier } from "@vizier/sdk";
const vizier = new Vizier({
baseUrl: "https://vizier.vassiliy-lakhonin.workers.dev",
apiKey: process.env.VIZIER_API_KEY,
});
const decision = await vizier.verify({
agent: { id: "agent-01", owner: "acme-corp" },
principal: { id: "acme-corp" },
action: {
type: "purchase",
target: "supplier.example",
parameters: { amount: 820, currency: "USD" }
},
authority: {
allowed_actions: ["purchase"],
constraints: { max_amount: 1000, currency: "USD" }
},
context: { source: "rest" }
});
if (decision.decision === "ALLOW") {
// Execute protected operation
}
```
---
### 4. Direct HTTP / cURL
```bash
curl -sS https://vizier.vassiliy-lakhonin.workers.dev/v1/verify \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_KEY' \
-d '{
"agent": { "id": "agent-01", "owner": "acme" },
"principal": { "id": "acme" },
"action": {
"type": "purchase",
"target": "supplier.example",
"parameters": { "amount": 820, "currency": "USD" }
},
"authority": {
"allowed_actions": ["purchase"],
"constraints": { "max_amount": 1000, "currency": "USD" }
},
"context": { "source": "rest" }
}'
```
---
## š Agent Circuit Breaker & Loop Killer
Infinite tool loops and runaway retry storms are among the most catastrophic failure modes of autonomous agents ā in minutes, an agent stuck in a loop can exhaust external API rate limits, burn through thousands of dollars in LLM tokens, or flood production databases.
Vizier provides built-in circuit breakers across both Python and MCP environments:
* **Sliding-Window Loop Detection**: Computes deterministic SHA-256 canonical JSON hashes of tool arguments. If the same tool is invoked repeatedly within a sliding window (e.g. 3 times in 30 seconds), the circuit trips immediately (`CIRCUIT_TRIPPED:LOOP_DETECTED`).
* **Session Action Budgets**: Caps the total number of actions an agent can execute within a single task or session (`CIRCUIT_TRIPPED:BUDGET_EXCEEDED`).
* **Python Guard Decorator**:
```python
from vizier import CircuitBreaker, vizier_guard
breaker = CircuitBreaker(max_repeated_calls=3, time_window_seconds=30.0, max_session_actions=25)
@vizier_guard(action_type="query_db", circuit_breaker=breaker)
def query_database(query: str):
return db.execute(query)
```
* **MCP Enforcement Proxy**:
```typescript
const proxy = createMcpEnforcementProxy({
// ...
circuitBreaker: { maxRepeats: 3, windowMs: 30_000 },
});
```
Returns standardized JSON-RPC 2.0 error `-32028` on tripped loops without invoking the upstream tool.
---
## Proving the authority instead of asserting it
By default the `authority` in a request is whatever the calling application says
it is. Vizier checks the action against it faithfully and signs the result ā but
the receipt then attests to a decision, not to a delegation.
A **delegation grant** closes that gap. The principal signs a compact JWS that
binds one authority to one agent for a bounded window, the agent sends it as a
`grant` field, and Vizier verifies it against a public key registered for that
principal:
```bash
# once, on the principal's machine
node scripts/mint-grant.mjs keygen --kid acme-2026-09 --out principal.jwk.json
# per delegation
node scripts/mint-grant.mjs sign --key principal.jwk.json --grant grant.json --ttl 3600
```
The public half is registered as `VIZIER_PRINCIPAL_KEYS`; the private half never
leaves the principal, and no endpoint would accept it. A verified grant makes the
receipt say so:
```json
{
"authority_provenance": "principal_signed",
"grant": {
"jti": "grant_5f1cā¦",
"issuer": "acme-corp",
"subject": "procurement-agent-01",
"key_id": "acme-2026-09",
"expires_at": "2026-09-07T16:00:00.000Z"
}
}
```
Two properties are worth stating plainly:
- **A grant that does not verify is `BLOCK`,** never a quiet fall back to the
caller-asserted path. Failing it open would make a forged grant strictly
better for an attacker than sending none.
- **The request's `authority` must match the signed one exactly.** A genuine
grant carried beside an enlarged authority is `BLOCK`, not an allow at the
larger limit.
Keys are registered out of band and never fetched at decision time, so the
authorization kernel still makes no outbound request. Full contract, reason
codes and limits: [docs/DELEGATION_GRANTS.md](docs/DELEGATION_GRANTS.md).
## API
`POST /v1/verify` accepts one proposed action and its delegated authority.
Malformed requests return a structured error and never produce `ALLOW`.
JSON bodies are capped at 1 MiB, 64 levels, and 50,000 aggregate values before
recursive schema validation.
When `VIZIER_API_KEY` is absent, the service is in evaluation mode: valid
requests can return `REVIEW` or `BLOCK`, never `ALLOW`. When the secret is
configured, REST and MCP verification calls require `Authorization: Bearer
<key>`. A2A also accepts an anonymous evaluation call for discovery and
conformance checks, but that call can return only `REVIEW` or `BLOCK`.
```json
{
"agent": { "id": "procurement-agent-01", "owner": "acme-corp" },
"principal": { "id": "acme-corp" },
"action": {
"type": "purchase",
"target": "supplier.example",
"parameters": { "amount": 8200, "currency": "USD" }
},
"authority": {
"allowed_actions": ["purchase"],
"constraints": { "max_amount": 10000, "currency": "USD" }
},
"context": {
"request_id": "order-1842",
"timestamp": null,
"source": "rest"
}
}
```
`principal` must be present. Set it to `null` when the principal is unknown;
Vizier returns `REVIEW`. Omitting the field is a validation error.
Decision priority is `BLOCK`, then `REVIEW`, then `ALLOW`. The numeric risk
score explains accumulated risk but does not override policy results.
### Action Covenant lifecycle
The v0.3.0 resources are additive; `/v1/verify` remains compatible.
1. `POST /v1/covenants` accepts a strict `ActionCovenantDraft` plus a
principal acceptance bound to the draft hash. A model may produce the draft,
but it cannot activate it by naming itself as the principal.
2. `POST /v1/authorizations` checks covenant integrity and expiry, exact action
equality, evidence presence and freshness, shallow exact-match invalidation
signals, and the existing delegated-authority policies. It returns a compact
ES256 authorization JWS for every decision.
3. The executor acts only on `ALLOW` before the receipt expires.
4. `POST /v1/outcomes` verifies the authorization receipt, binds the reported
execution outcome, checks exact forbidden-effect rules, and returns a compact
ES256 outcome JWS.
All three resources require authenticated enforcement and
`RECEIPT_SIGNING_KEY`; there is no evaluation-only activation path. Covenants
remain caller-held immutable envelopes in this milestone. Vizier stores bounded
operational metadata and hashes asynchronously, but not full action parameters,
evidence, signals, outcome effects, JWS tokens, or signing material. It does not
retrieve evidence independently.
## Integration rule
Call Vizier immediately before the external action. Treat timeout, invalid JSON,
`REVIEW`, and `BLOCK` as stop conditions.
```ts
const decision = await vizier.verify(proposedAction);
if (decision.decision === "ALLOW") {
await executeAction();
}
```
The thin TypeScript client lives in `packages/sdk`:
```ts
import { Vizier } from "@vizier/sdk";
const vizier = new Vizier({
baseUrl: "https://vizier.vassiliy-lakhonin.workers.dev",
apiKey: process.env.VIZIER_API_KEY,
});
const decision = await vizier.verify(request);
```
### Python SDK (`vizier-guard`)
The Python SDK lives in `packages/python-sdk` with zero external dependencies:
```python
from vizier import VizierClient, vizier_guard
client = VizierClient(
base_url="https://vizier.vassiliy-lakhonin.workers.dev",
api_key=os.environ["VIZIER_API_KEY"],
)
# Protect any function / agent tool:
@vizier_guard(client=client, action_type="purchase", max_amount=1000.0, currency="USD")
def execute_order(amount: float, target: str):
return {"status": "success", "amount": amount}
```
Or protect LangChain / LangGraph tools:
```python
from vizier.integrations.langchain import VizierLangChainToolGuard
guarded_tool = VizierLangChainToolGuard(
tool=my_search_or_db_tool,
client=client,
allowed_actions=["query_db"],
)
```
### MCP Enforcement Proxy
Protect any existing local or remote MCP server with deterministic policy checks:
```bash
npx @vizier/mcp-proxy \
--upstream http://localhost:3000/mcp \
--tools "query_db,transfer_funds,send_message" \
--vizier https://vizier.vassiliy-lakhonin.workers.dev \
--api-key $VIZIER_API_KEY
```
The API key belongs only in a controlled backend or orchestrator. Do not expose it
to the action-taking agent. This key authenticates the integration; it does not
prove that each supplied delegation was issued by the principal.
## Policy rules
| Rule | Result |
| --- | --- |
| No authenticated integration credential is configured | `REVIEW / AUTHORITY_SOURCE_UNTRUSTED` |
| Action is absent from `allowed_actions` | `BLOCK / ACTION_NOT_DELEGATED` |
| Principal is `null` | `REVIEW / PRINCIPAL_UNVERIFIED` |
| Amount exceeds `max_amount` | `BLOCK / AUTHORITY_LIMIT_EXCEEDED` |
| Amount or currency cannot be checked | `REVIEW` |
| Target is blocked or absent from an allowlist | `BLOCK` |
| Sensitive action lacks explicit sensitive authority | `REVIEW / SENSITIVE_ACTION_REVIEW` |
| Authority requires reversibility and the action is not declared reversible | `REVIEW / IRREVERSIBLE_ACTION_REVIEW` |
The default sensitive actions are `transfer_funds`, `delete_data`,
`deploy_worker`, `execute_code`, `send_external_message`,
`modify_permissions`, and `sign_contract`.
`action.is_reversible` is an integration-supplied assertion, not an independently
verified property. `require_review_for_irreversible` fails to `REVIEW` when that
assertion is absent or false; it cannot prove a true assertion is accurate.
## Protocol endpoints
- `GET /openapi.json` and `GET /.well-known/openapi.json` return the same
OpenAPI 3.1 contract for `/v1/verify`, `/v1/covenants`,
`/v1/authorizations`, `/v1/outcomes`, and the authenticated
`/v1/insights`. Request schemas are emitted from the same Zod definitions
used at the runtime boundary.
- `GET /.well-known/ai-catalog.json` routes machines to the A2A Agent Card, the
OpenAPI contract, and the MCP server manifest.
- `GET /.well-known/agent-card.json` returns an A2A v1.0 Agent Card with a
canonical ES256 JWS in `signatures[]` when `AGENT_CARD_SIGNING_KEY` is
configured.
- `GET /.well-known/jwks.json` returns the matching public key. The JWS protected
header points to this endpoint through a same-origin `jku`.
- `POST /a2a` implements the A2A v1.0 JSON-RPC `SendMessage` method. Anonymous
requests run only in evaluation mode; a wrong supplied credential is rejected.
- `POST /mcp` implements MCP `2026-07-28` with `server/discover`, `tools/list`,
and `tools/call` for `vizier_verify_action`. The same endpoint also answers
the session handshake used by shipping clients: `initialize`,
`notifications/initialized`, `ping`, `tools/list`, and `tools/call` over
`2025-06-18`, `2025-03-26`, or `2024-11-05`. The request body selects the
profile: only `2026-07-28` carries its protocol version in `params._meta`.
- `GET /.well-known/mcp.json` returns the MCP server manifest, the same document
published to the MCP Registry from `server.json` at the repository root.
The session profile exists because no off-the-shelf client speaks the stateless
profile yet. Measured 2026-09-02 against the deployed Worker: a standard
`initialize` was rejected with `-32600`, so the endpoint could not be connected
from any MCP client. The stateless contract is unchanged; the session profile is
additive and shares one verification path.
### MCP Enforcement Proxy (`@vizier/mcp-proxy`)
`packages/mcp-proxy` is a standalone reverse proxy adapter that places any existing MCP server behind Vizier. The proxy:
- exposes only the configured upstream tool names;
- authenticates every MCP request with a proxy-specific Bearer token;
- maps the exact tool name and arguments to one `mcp_tool_call` verification;
- forwards the unchanged MCP request only after a verified `ALLOW`;
- stops on `REVIEW`, `BLOCK`, timeout, invalid Vizier output, or upstream error;
- replaces the incoming credential with a separate upstream credential; and
- logs integration ID, request ID, tool name, decision, receipt ID, outcome, and
latency without logging arguments or secrets.
Run directly via `npx`:
```bash
npx @vizier/mcp-proxy \
--upstream http://127.0.0.1:8791/mcp \
--tools "write_file,query_db" \
--vizier https://vizier.vassiliy-lakhonin.workers.dev \
--api-key $VIZIER_API_KEY
```
Or configure via environment variables:
```bash
export VIZIER_BASE_URL="http://127.0.0.1:8787"
export VIZIER_API_KEY="local-development-key"
export VIZIER_PROXY_INTEGRATION_ID="pilot-acme"
export VIZIER_PROXY_CLIENT_TOKEN="replace-with-a-random-client-token"
export VIZIER_PROXY_AGENT_ID="coding-agent-01"
export VIZIER_PROXY_AGENT_OWNER="acme"
export VIZIER_PROXY_PRINCIPAL_ID="platform-team"
export VIZIER_PROXY_UPSTREAM_ID="filesystem"
export VIZIER_PROXY_UPSTREAM_URL="http://127.0.0.1:8791/mcp"
export VIZIER_PROXY_UPSTREAM_BEARER_TOKEN="replace-with-upstream-token"
export VIZIER_PROXY_ALLOWED_TOOLS="write_file"
npm exec --workspace @vizier/mcp-proxy -- vizier-mcp-proxy
```
Point the pilot MCP client at `http://127.0.0.1:8790/mcp`, use
`VIZIER_PROXY_CLIENT_TOKEN` as its Bearer credential, and remove its direct
access to the upstream URL and credential. The proxy is not an enforcement
boundary if the agent can still reach the upstream server, read either backend
credential, or use a shell with equivalent authority.
## Connect an MCP client
The credential is optional at connect time. An anonymous `tools/call` runs in
evaluation mode: the decision is real but the supplied authority is untrusted, so
it can never return `ALLOW`. The credential unlocks enforcement results, and a
credential that is supplied and wrong is rejected with `-32001`.
Anonymous calls share a budget of 60 requests per minute per client IP. Past it
the endpoint answers `429` with JSON-RPC error `-32029` and a `Retry-After`
header. Attaching a wrong credential does not leave that budget; a valid one
does.
An anonymous call leaves no receipt, so the only record of it is a counter: one
row per UTC day per surface per outcome, bumped in place. It holds no client IP,
no arguments, and nothing else about the caller, and it is swept by the same
30-day retention as the rest of the audit store. `GET /v1/insights` reads it
back. Counts are best-effort instrumentation, not proof of adoption, and each
deploy adds exactly four to `mcp` / `served`: the live check below probes the
credential-free path on purpose.
```bash
claude mcp add --transport http vizier \
https://vizier.vassiliy-lakhonin.workers.dev/mcp
```
Add the header once you hold a credential:
```bash
claude mcp add --transport http vizier \
https://vizier.vassiliy-lakhonin.workers.dev/mcp \
--header "Authorization: Bearer <integration-credential>"
```
Any client that accepts a Streamable HTTP URL works the same way. Verify the
handshake without a client:
```bash
curl -sS https://vizier.vassiliy-lakhonin.workers.dev/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
```
### Registry listing
`server.json` at the repository root is the MCP Registry entry for
`io.github.vassiliylakhonin/vizier`, published on 2026-09-02 and validated
against the `2025-12-11` server schema. It carries no `repository` block: the
source repository is private, and an entry pointing at a URL that answers 404 is
worse than no link at all. The namespace is claimed through the GitHub account,
not the repository.
The deploy workflow republishes it. `scripts/publish-registry.mjs` compares
`server.json` against the live entry and publishes only when the registry lacks
that version, authenticating through GitHub Actions OIDC so no registry token is
stored anywhere. The registry keys an entry on its version, so a manifest edit
rides along with a version bump; an edit without one is reported and skipped
rather than rejected by the registry.
Check the decision without making it, using an existing `mcp-publisher login
github` session:
```bash
node scripts/publish-registry.mjs --dry-run
```
Publishing by hand still works and takes the same path:
```bash
mcp-publisher login github
node scripts/publish-registry.mjs
```
Read the live entry back:
```bash
curl -sS "https://registry.modelcontextprotocol.io/v0/servers?search=vizier"
```
An unrelated `io.github.pipeworx-io/vizier` is listed in the same registry. The
namespace is what separates them, so a search by bare name returns both.
`tests/discovery-contracts.test.ts` holds `server.json` and the served
`/.well-known/mcp.json` to the same content, so a registry listing cannot drift
away from the endpoint the Worker serves.
## Receipts
Each successful verification returns a receipt ID, creation time, request hash,
decision, risk score, rule IDs, and reason codes. Object keys are sorted before
SHA-256 hashing. This canonicalization is documented and tested, but it is not
an RFC 8785 claim.
Legacy `/v1/verify` receipts remain unsigned and caller-held for compatibility.
The SDK validates the complete response, checks decision-to-receipt consistency,
and recomputes the request hash before returning a decision.
Action Covenant authorization and outcome receipts are compact ES256 JWS values.
They use a dedicated receipt key and protected `typ` values for domain
separation. The SDK obtains the matching public key from
`/.well-known/jwks.json`, verifies the signature, and recomputes the covenant,
request, action, evidence, signal, authorization-token, and outcome bindings
before returning. Signed does not mean independently timestamped or
principal-issued. Vizier persists only selected receipt metadata and hashes; the
complete signed receipt and token remain caller-held.
`GET /v1/insights` exposes authenticated decision counts, lifecycle totals,
average legacy risk score, and reported failure/violation count. These are
best-effort operational aggregates: asynchronous audit writes can fail, and the
numbers are not proof of production adoption or complete execution history.
Metadata is retained for 30 days and pruned daily by a scheduled Worker handler.
## Development
```bash
npm run typecheck
npm test
npm run build
npm run check
```
`npm run build` compiles `@vizier/sdk`, compiles the private gated-deploy tool,
and runs a Cloudflare deployment dry run. It does not deploy the Worker.
Two checks describe production rather than a commit, so they are separate from
`npm run check` and need the network:
```bash
npm run check:deployed
npm run check:live
```
`check:deployed` compares the current bundle digest against the newest
deployment: is production running this code? `check:live` calls the deployed
Worker and asserts what it answers ā health, the released version on the service
index and the MCP manifest, a signed agent card, both JWKS keys, the MCP session
handshake, `tools/list`, an anonymous `tools/call` that returns a receipt and
cannot grant `ALLOW`, and the stateless `server/discover`. A digest can match
while the endpoint is broken, which is why both exist. Point it elsewhere with
`VIZIER_ORIGIN`. The deploy workflow runs it after the deploy and before the
registry publish, so a Worker that stopped answering is never advertised as a
new version.
To prepare an enforcement deployment after reviewing the threat model:
```bash
npx wrangler whoami
npx wrangler secret put VIZIER_API_KEY
npx wrangler secret put AGENT_CARD_SIGNING_KEY
npx wrangler secret put RECEIPT_SIGNING_KEY
npx wrangler deploy
```
`AGENT_CARD_SIGNING_KEY` and `RECEIPT_SIGNING_KEY` are separate private P-256
JWKs with distinct `kid` values, `alg: "ES256"`, `use: "sig"`, and stable key
identifiers. Wrangler stores them as secrets; they must not be committed. A
malformed configured key fails the affected signed surface instead of silently
downgrading it.
The public Worker completed its one-time v0.1-to-v0.2 bootstrap on 2026-08-24.
After the integration credential has been stored in macOS Keychain under service
`com.vizier.gated-deploy` and account `VIZIER_API_KEY`, normal deployments use:
```bash
npm run deploy:gated
```
The private tool accepts no command arguments. It drafts and accepts a five-minute
covenant for the current commit, the fixed `deploy_worker` action, and the fixed
`worker:vizier` target. A worktree snapshot is freshness evidence and a dirty
worktree is an invalidation signal. It runs `wrangler deploy --strict` only after
the SDK verifies a signed `ALLOW` receipt, then records a signed success or
failure outcome. If outcome recording fails after execution, the command returns
`outcome_unrecorded` and a non-zero exit code instead of reporting a complete lifecycle.
A new Worker name or fresh environment that does not expose the covenant
endpoints needs one explicit bootstrap deployment through its existing v0.1
gate:
```bash
VIZIER_V0_2_BOOTSTRAP=1 npm run deploy:gated
```
This bypass is only for the deployment that introduces the covenant endpoints
and receipt key. Do not set `VIZIER_V0_2_BOOTSTRAP` for normal deployments of the
public Vizier Worker; they use the covenant lifecycle.
This wrapper is an integration test, not an operating-system security boundary.
An agent with unrestricted shell access and Cloudflare credentials can bypass it
by invoking Wrangler directly. A production integration must expose only the
wrapper capability and keep both Cloudflare and Vizier credentials outside the
action-taking agent.
The deployment command is intentionally not part of `npm run build`. Without
the secret, a deployment remains evaluation-only and cannot return `ALLOW`.
The Worker uses D1 only for an asynchronous, metadata-only operational audit
trail. The authorization decision path does not depend on D1 availability. It
uses no KV, Durable Object, queue, AI model, or outbound fetch. `npm run check`
applies every D1 migration in order to an in-memory SQLite database and verifies
that legacy payload-bearing columns are removed.
The repository structure and protocol sources are documented in
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md),
[docs/ADR-0001-ACTION-COVENANTS.md](docs/ADR-0001-ACTION-COVENANTS.md),
[docs/PILOT.md](docs/PILOT.md),
[docs/CLAIMS.md](docs/CLAIMS.md), and
[docs/SECURITY_REVIEW.md](docs/SECURITY_REVIEW.md).
## What is deferred
Independent principal authentication, durable policy/evidence/full-receipt storage,
principal-signed delegation and acceptance, billing, dashboards, reputation
models, payment settlement, and LLM policy evaluation inside the privileged kernel
remain outside v0.3.0. See
[FUTURE.md](FUTURE.md).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues