Policy engine
Every event is evaluated against your project's policies before the action is approved. Four outcomes: allow, warn, block, require_approval. All matching enabled policies are collected and the most severe decision wins — a catch-all allow can never shadow a block.
Default policies
Seven built-in policies are seeded per project (toggle them under Policies):
| Policy | Decision |
|---|---|
| Block invalid signatures | block |
| Block unknown tools | block |
| Block agents with trust score below 30 | block |
| Warn on unapproved MCP servers | warn |
| Require approval for sensitive data access | require_approval |
| Warn if trust score below 50 | warn |
| Allow normal approved signed activity | allow |
Custom policies
# Conditions are AND-ed; strings accept exact values, arrays, or "*" (any).
curl -X POST https://www.proofledger.dev/api/policies \
-H "authorization: Bearer $PROOFLEDGER_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "Block prod DB access from experimental agents",
"decision": "block",
"severity": "critical",
"priority": 8,
"conditions": {
"environment": "development",
"apiEndpoint": "*",
"toolName": ["db_query", "db_write"]
}
}'Condition fields: agentId · owner · framework · model · toolName · mcpServer · apiEndpoint · eventType · eventCategory · environment · trustScoreMax · trustScoreMin · sensitiveAction · toolKnown · toolApproved · signatureValid.
Pre-checking an action
// Dry-run: no event logged, no trust change. Use before executing.
const evaluation = await ProofLedger.evaluatePolicy({
toolName: "wire_transfer",
sensitiveAction: true,
});
// { decision: "require_approval",
// matched: [{ name: "Require approval for sensitive data access", … }],
// reason: "Require approval for sensitive data access → require_approval" }Monitor vs. enforce
In monitor mode (default), decisions are logged and returned but never interrupt the agent. In enforce mode:
ProofLedger.enable({ …, policyMode: "enforce" });
// block → PolicyBlockedError is thrown (the attempt IS logged first)
// require_approval → the SDK BLOCKS, polling until a human decides in the
// dashboard's Approvals inbox. Approved → continues.
// Denied or timed out (default 5 min) → ApprovalDeniedError.
// Fail-closed: an undecided action is not an approved action.
await ProofLedger.logDecision({
action: "Access buyer financial records",
sensitiveAction: true, // waits here for human sign-off
});Note
Approve/deny decisions are themselves written to the agent's audit trail as approval_decided events — a replay shows exactly who unblocked the action and when. Tune the wait with approvalTimeoutMs / approvalIntervalMs.