Files
blackroad-os-docs/docs/reference/kv-schema.md
Alexa Louise 404d6a093a docs: add KV schema reference for governance layer
- Complete schema for all 6 governance namespaces
- ID generation patterns and examples
- Index key patterns for efficient queries
- TypeScript interfaces for all objects
- Query pattern examples with code
- Storage implementation for KV, D1, and Redis

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 20:19:39 -06:00

17 KiB

BlackRoad OS — KV Schema Reference

Version: 1.0 Last Updated: 2025-11-30 Storage: Cloudflare KV / D1 / Railway Redis


Overview

This document defines the key-value schema for the BlackRoad OS governance layer. All governance objects are stored as JSON values with human-readable keys.


Design Principles

  1. Keys are human-readable — Debugging at 2am shouldn't require a decoder ring
  2. Values are JSON — Structured, versionable, extensible
  3. Prefixes define namespacepolicy:, ledger:, agent:, intent:, claim:, delegation:
  4. Timestamps are ISO 8601 — Always UTC
  5. IDs use format {type}-{YYYYMMDD}-{short-hash} — Sortable, unique, traceable

ID Generation

All IDs follow the pattern: {type}-{YYYYMMDD}-{short-hash}

function generateId(type: string): string {
  const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
  const hash = crypto.randomUUID().slice(0, 6);
  return `${type}-${date}-${hash}`;
}

// Examples:
// pol-20251130-a1b2c3  (policy)
// evt-20251130-x7y8z9  (event)
// int-20251130-m4n5o6  (intent)
// clm-20251130-p1q2r3  (claim)
// del-20251130-s4t5u6  (delegation)

Namespace: POLICY

Policies define what is allowed, denied, required, or transformed.

Key Pattern

policy:{scope}:{policy_id}

Examples

policy:email.send:pol-20251130-a1b2c3
policy:finance.charge:pol-20251130-b2c3d4
policy:infra.deploy:pol-20251130-c3d4e5
policy:core.safety:pol-20251130-d4e5f6

Schema

interface Policy {
  policy_id: string;           // pol-YYYYMMDD-xxxxxx
  scope: string;               // e.g., "email.send", "finance.*"
  name: string;                // Human-readable name
  description?: string;        // What this policy does
  rules: PolicyRule[];         // Ordered by priority (highest first)
  active: boolean;             // Is this policy in effect?
  created_at: string;          // ISO 8601
  updated_at: string;          // ISO 8601
  created_by: string;          // user:* or agent:*
}

interface PolicyRule {
  rule_id: string;             // Unique within policy
  condition: string;           // Expression to evaluate
  action: 'allow' | 'deny' | 'require_human_approval' | 'transform';
  priority: number;            // Higher wins (0-1000)
  reason?: string;             // Why this rule exists
  transform_fn?: string;       // For 'transform' action
}

Example Value

{
  "policy_id": "pol-20251130-a1b2c3",
  "scope": "email.send",
  "name": "External Email Approval",
  "description": "Require human approval for emails to external domains",
  "rules": [
    {
      "rule_id": "r1",
      "condition": "recipient.domain NOT IN approved_domains",
      "action": "require_human_approval",
      "priority": 100,
      "reason": "Prevent accidental external disclosure"
    },
    {
      "rule_id": "r2",
      "condition": "true",
      "action": "allow",
      "priority": 0
    }
  ],
  "active": true,
  "created_at": "2025-11-30T12:00:00Z",
  "updated_at": "2025-11-30T12:00:00Z",
  "created_by": "user:alexa"
}

Index Keys

Purpose Key Value
All active policies policy:active ["pol-xxx", "pol-yyy"]
Policies by scope policy:scope:{scope} ["pol-xxx"]
Policy lookup by ID policy:id:{policy_id} Full policy object

Namespace: LEDGER

Immutable audit trail of all governance-relevant actions.

Key Pattern

ledger:{intent_id}:{event_id}

Examples

ledger:int-20251130-x1y2z3:evt-20251130-000001
ledger:int-20251130-x1y2z3:evt-20251130-000002
ledger:int-20251130-a4b5c6:evt-20251130-000003

Schema

interface LedgerEvent {
  event_id: string;            // evt-YYYYMMDD-NNNNNN (sequential within day)
  intent_id: string;           // Parent intent
  timestamp: string;           // ISO 8601 UTC
  agent_id: string;            // Who performed this action
  tool: string;                // gmail, drive, stripe, etc.
  action: string;              // search, create, send, charge, etc.
  inputs_hash: string;         // SHA-256 of inputs
  outputs_hash: string;        // SHA-256 of outputs
  policy_decision: {
    result: 'allowed' | 'denied' | 'pending_approval' | 'transformed';
    policy_id?: string;        // Which policy was applied
    rule_matched?: string;     // Which rule triggered
  };
  metadata?: Record<string, any>;
  notes?: string;              // Human-readable context
}

Example Value

{
  "event_id": "evt-20251130-000001",
  "intent_id": "int-20251130-x1y2z3",
  "timestamp": "2025-11-30T14:32:01.123Z",
  "agent_id": "cece.governor.v1",
  "tool": "gmail",
  "action": "draft",
  "inputs_hash": "sha256:abc123def456...",
  "outputs_hash": "sha256:789ghi012jkl...",
  "policy_decision": {
    "result": "allowed",
    "policy_id": "pol-20251130-a1b2c3",
    "rule_matched": "r2"
  },
  "metadata": {
    "recipient_count": 1,
    "has_attachments": false
  },
  "notes": "Draft created for investor follow-up"
}

Index Keys

Purpose Key Value
Events by date ledger:by_date:{YYYY-MM-DD} ["evt-xxx", "evt-yyy"]
Events by agent ledger:by_agent:{agent_id} ["evt-xxx"]
Events by tool ledger:by_tool:{tool} ["evt-xxx"]
Events by intent ledger:by_intent:{intent_id} ["evt-xxx", "evt-yyy"]
Daily counter ledger:counter:{YYYY-MM-DD} 42 (for sequential IDs)

Namespace: AGENT

Registry of all agents in the system.

Key Pattern

agent:{agent_id}

Examples

agent:cece.governor.v1
agent:email.handler.v1
agent:code.writer.v1
agent:data.analyst.v1

Schema

interface Agent {
  agent_id: string;            // Canonical identifier
  name: string;                // Human-readable name
  description: string;         // What this agent does
  class: 'lucidia' | 'worker' | 'system' | 'integration';
  capabilities: string[];      // Actions this agent can perform
  policies_required: string[]; // Policy IDs this agent must respect
  owner: string;               // user:*, org:*, or blackroad.system
  status: 'active' | 'disabled' | 'pending';
  config?: Record<string, any>;
  created_at: string;
  updated_at: string;
  last_seen?: string;          // Last activity timestamp
}

Example Value

{
  "agent_id": "cece.governor.v1",
  "name": "Cece",
  "description": "Primary governance and orchestration agent for BlackRoad OS",
  "class": "lucidia",
  "capabilities": [
    "intent.create",
    "intent.plan",
    "tool.invoke",
    "policy.evaluate",
    "ledger.write",
    "agent.coordinate"
  ],
  "policies_required": ["pol-core-safety", "pol-core-audit"],
  "owner": "blackroad.system",
  "status": "active",
  "config": {
    "require_human_approval_for": ["finance.*", "email.send_external"],
    "default_mode": "operator"
  },
  "created_at": "2025-11-30T00:00:00Z",
  "updated_at": "2025-11-30T12:00:00Z",
  "last_seen": "2025-11-30T14:32:01Z"
}

Index Keys

Purpose Key Value
Active agents agent:active ["cece.governor.v1", ...]
Agents by class agent:by_class:{class} ["cece.governor.v1"]
Agents by owner agent:by_owner:{owner} ["agent-id-1", ...]

Namespace: INTENT

Tasks, goals, and workflow state.

Key Pattern

intent:{intent_id}

Examples

intent:int-20251130-x1y2z3
intent:int-20251130-a4b5c6

Schema

interface Intent {
  intent_id: string;           // int-YYYYMMDD-xxxxxx
  actor: string;               // Who requested this (user:* or agent:*)
  goal: string;                // Natural language description
  context?: Record<string, any>; // Key details, constraints, references
  priority: 'low' | 'normal' | 'high' | 'critical';
  status: 'proposed' | 'in_planning' | 'executing' | 'completed' | 'blocked' | 'cancelled';
  plan?: IntentStep[];         // Ordered steps with statuses
  assigned_agent?: string;     // Primary agent handling this
  parent_intent_id?: string;   // For sub-tasks
  created_at: string;
  updated_at: string;
  completed_at?: string;
  blocked_reason?: string;
}

interface IntentStep {
  step: number;
  action: string;              // e.g., "drive.search", "notion.create_page"
  status: 'planned' | 'in_progress' | 'completed' | 'failed' | 'skipped';
  result?: any;
  error?: string;
}

Example Value

{
  "intent_id": "int-20251130-x1y2z3",
  "actor": "user:alexa",
  "goal": "Find investor docs, summarize, create Notion page, draft follow-up email",
  "context": {
    "search_terms": ["investor", "deck", "pitch"],
    "urgency": "end of day"
  },
  "priority": "high",
  "status": "executing",
  "plan": [
    {"step": 1, "action": "drive.search", "status": "completed"},
    {"step": 2, "action": "drive.get", "status": "completed"},
    {"step": 3, "action": "summarize", "status": "completed"},
    {"step": 4, "action": "notion.create_page", "status": "in_progress"},
    {"step": 5, "action": "gmail.draft", "status": "planned"}
  ],
  "assigned_agent": "cece.governor.v1",
  "created_at": "2025-11-30T14:00:00Z",
  "updated_at": "2025-11-30T14:32:01Z"
}

Index Keys

Purpose Key Value
Active intents intent:active ["int-xxx", "int-yyy"]
Intents by status intent:by_status:{status} ["int-xxx"]
Intents by actor intent:by_actor:{actor} ["int-xxx"]
Intents by agent intent:by_agent:{agent_id} ["int-xxx"]

Namespace: CLAIM

Assertions about identity, roles, and relationships.

Key Pattern

claim:{subject}:{claim_id}

Examples

claim:user:alexa:clm-20251130-own001
claim:agent:cece.governor.v1:clm-20251130-auth001
claim:org:blackroad:clm-20251130-reg001

Schema

interface Claim {
  claim_id: string;            // clm-YYYYMMDD-xxxxxx
  subject: string;             // Who/what this claim is about
  claim_type: string;          // ownership, authorization, membership, etc.
  object: string;              // What the claim relates to
  assertion: string;           // The actual claim statement
  issued_by: string;           // Who made this claim
  issued_at: string;           // ISO 8601
  expires_at?: string;         // Optional expiration
  revoked?: boolean;
  revoked_at?: string;
  revoked_reason?: string;
  evidence?: Record<string, any>; // Supporting data
}

Example Value

{
  "claim_id": "clm-20251130-own001",
  "subject": "user:alexa",
  "claim_type": "ownership",
  "object": "org:blackroad",
  "assertion": "user:alexa is owner of org:blackroad",
  "issued_by": "blackroad.system",
  "issued_at": "2025-11-30T00:00:00Z",
  "expires_at": null,
  "revoked": false,
  "evidence": {
    "registration_date": "2025-01-01",
    "verification_method": "email"
  }
}

Index Keys

Purpose Key Value
Active claims claim:active ["clm-xxx", ...]
Claims by type claim:by_type:{type} ["clm-xxx"]
Claims by object claim:by_object:{object} ["clm-xxx"]
Claims by subject claim:by_subject:{subject} ["clm-xxx"]

Namespace: DELEGATION

Scoped permissions from delegator to delegate.

Key Pattern

delegation:{delegator}:{delegation_id}

Examples

delegation:user:alexa:del-20251130-d001
delegation:blackroad.system:del-20251130-d002
delegation:org:blackroad:del-20251130-d003

Schema

interface Delegation {
  delegation_id: string;       // del-YYYYMMDD-xxxxxx
  delegator: string;           // Who is granting permission
  delegate: string;            // Who is receiving permission
  scope: string[];             // What actions are permitted
  constraints?: {
    require_approval_for?: string[];  // Actions needing explicit approval
    valid_from?: string;       // Start time
    valid_until?: string;      // End time
    max_uses?: number;         // Usage limit
    conditions?: string[];     // Additional conditions
  };
  active: boolean;
  created_at: string;
  updated_at: string;
  revoked_at?: string;
  revoked_reason?: string;
  uses_count?: number;         // How many times used
}

Example Value

{
  "delegation_id": "del-20251130-d001",
  "delegator": "user:alexa",
  "delegate": "agent:cece.governor.v1",
  "scope": [
    "drive.read",
    "drive.search",
    "notion.*",
    "gmail.draft",
    "gmail.read"
  ],
  "constraints": {
    "require_approval_for": ["gmail.send", "drive.delete", "drive.share"],
    "valid_until": "2026-11-30T00:00:00Z"
  },
  "active": true,
  "created_at": "2025-11-30T00:00:00Z",
  "updated_at": "2025-11-30T00:00:00Z",
  "uses_count": 47
}

Index Keys

Purpose Key Value
Active delegations delegation:active ["del-xxx", ...]
By delegate delegation:by_delegate:{delegate} ["del-xxx"]
By scope delegation:by_scope:{scope} ["del-xxx"]
By delegator delegation:by_delegator:{delegator} ["del-xxx"]

Query Patterns

Common Queries

Use Case Query Pattern
Can Cece send email? Get delegation:by_delegate:agent:cece.governor.v1 → filter for gmail.send
What happened today? Get ledger:by_date:2025-11-30
Who owns BlackRoad? Get claim:by_object:org:blackroad → filter claim_type: ownership
What policies apply to email? Get policy:scope:email.* + policy:scope:email.send
What's Cece working on? Get intent:by_agent:cece.governor.v1 → filter status: executing
Is this action allowed? Evaluate policies for scope, check delegations, log decision

Query Implementation

// Check if agent can perform action
async function canPerform(
  agent: string,
  action: string,
  context: any
): Promise<{allowed: boolean; reason: string; policy?: string}> {

  // 1. Get delegations for this agent
  const delegations = await kv.get(`delegation:by_delegate:${agent}`);

  // 2. Check if action is in scope
  const hasScope = delegations.some(d =>
    d.scope.some(s => matchesScope(s, action))
  );

  if (!hasScope) {
    return {allowed: false, reason: 'No delegation for this action'};
  }

  // 3. Check if approval required
  const needsApproval = delegations.some(d =>
    d.constraints?.require_approval_for?.some(s => matchesScope(s, action))
  );

  if (needsApproval) {
    return {allowed: false, reason: 'Requires human approval', policy: 'delegation'};
  }

  // 4. Evaluate policies
  const policies = await kv.get(`policy:scope:${action.split('.')[0]}.*`);
  for (const policy of policies.sort((a,b) => b.priority - a.priority)) {
    const result = evaluatePolicy(policy, context);
    if (result.matched) {
      return {
        allowed: result.action === 'allow',
        reason: result.reason,
        policy: policy.policy_id
      };
    }
  }

  // 5. Default allow if no policy matched
  return {allowed: true, reason: 'No policy restriction'};
}

Storage Implementation

Cloudflare KV

// Direct key-value storage
const policy = await GOVERNANCE_KV.get('policy:email.send:pol-20251130-a1b2c3', 'json');

// Index management (manual)
const activeIds = await GOVERNANCE_KV.get('policy:active', 'json') || [];
activeIds.push(newPolicyId);
await GOVERNANCE_KV.put('policy:active', JSON.stringify(activeIds));

Cloudflare D1 (SQL)

-- Policies table
CREATE TABLE policies (
  policy_id TEXT PRIMARY KEY,
  scope TEXT NOT NULL,
  name TEXT NOT NULL,
  rules JSON NOT NULL,
  active BOOLEAN DEFAULT true,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);

CREATE INDEX idx_policies_scope ON policies(scope);
CREATE INDEX idx_policies_active ON policies(active);

-- Ledger events table
CREATE TABLE ledger_events (
  event_id TEXT PRIMARY KEY,
  intent_id TEXT NOT NULL,
  timestamp TEXT NOT NULL,
  agent_id TEXT NOT NULL,
  tool TEXT NOT NULL,
  action TEXT NOT NULL,
  inputs_hash TEXT,
  outputs_hash TEXT,
  policy_decision JSON,
  metadata JSON
);

CREATE INDEX idx_ledger_intent ON ledger_events(intent_id);
CREATE INDEX idx_ledger_agent ON ledger_events(agent_id);
CREATE INDEX idx_ledger_date ON ledger_events(date(timestamp));

Railway Redis

# Key-value with JSON
SET policy:email.send:pol-20251130-a1b2c3 '{"policy_id":"pol-20251130-a1b2c3",...}'

# Sets for indexes
SADD policy:active pol-20251130-a1b2c3
SADD policy:scope:email.send pol-20251130-a1b2c3

# Sorted sets for time-based queries
ZADD ledger:by_date:2025-11-30 1732972321 evt-20251130-000001

Migration Notes

KV → D1 Migration

When moving from KV to D1 for better query support:

  1. Export all KV values as JSON
  2. Transform to relational schema
  3. Import to D1 tables
  4. Update application to use SQL queries
  5. Keep KV for hot paths (caching)

Version Compatibility

All schemas include implicit versioning via timestamps. Breaking changes should:

  1. Create new key patterns (e.g., policy.v2:)
  2. Migrate existing data
  3. Deprecate old patterns after transition

References