# UCAN v1.0.0-rc.1 Schema Proposal

## Overview

This document proposes schema changes to migrate from JWT-based UCAN to v1.0.0-rc.1 envelope format.

## Design Principles

1. **Single Database** - All data in one SQLite for WASM portability
2. **CID-based Lookup** - Primary key is content identifier (immutable)
3. **Binary Storage** - DAG-CBOR envelopes stored as BLOBs
4. **Indexed Fields** - Extract key fields for efficient queries
5. **DID Ownership** - Foreign key to did_documents for access control

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                     WASM Plugin (Enclave)                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │   WebAuthn   │    │     DID      │    │    UCAN      │      │
│  │   (AuthN)    │    │  (Identity)  │    │   (AuthZ)    │      │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘      │
│         │                   │                   │               │
│         ▼                   ▼                   ▼               │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                  SQLite Database                          │  │
│  │  ┌────────────┐ ┌────────────┐ ┌─────────────────────┐   │  │
│  │  │credentials │ │did_documents│ │ ucan_delegations   │   │  │
│  │  │            │ │            │ │ ucan_invocations   │   │  │
│  │  │            │ │            │ │ ucan_revocations   │   │  │
│  │  └────────────┘ └────────────┘ └─────────────────────┘   │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

## Schema Changes

### 1. Replace `ucan_tokens` with `ucan_delegations`

```sql
-- DROP TABLE IF EXISTS ucan_tokens;  -- Migration step

-- UCAN Delegations: v1.0.0-rc.1 delegation envelopes
CREATE TABLE IF NOT EXISTS ucan_delegations (
    id              INTEGER PRIMARY KEY,
    did_id          INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE,
    
    -- Content Identifier (immutable, unique)
    cid             TEXT NOT NULL UNIQUE,
    
    -- Sealed envelope (DAG-CBOR encoded)
    envelope        BLOB NOT NULL,
    
    -- Extracted fields for indexing/queries
    iss             TEXT NOT NULL,              -- Issuer DID
    aud             TEXT NOT NULL,              -- Audience DID  
    sub             TEXT,                       -- Subject DID (null = powerline)
    cmd             TEXT NOT NULL,              -- Command (e.g., "/vault/read")
    
    -- Policy stored as JSON for inspection (actual evaluation uses envelope)
    pol             TEXT DEFAULT '[]',          -- Policy JSON
    
    -- Temporal fields
    nbf             TEXT,                       -- Not before (ISO8601)
    exp             TEXT,                       -- Expiration (ISO8601, null = never)
    
    -- Metadata
    is_root         INTEGER NOT NULL DEFAULT 0, -- iss == sub
    is_powerline    INTEGER NOT NULL DEFAULT 0, -- sub IS NULL
    created_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_ucan_delegations_cid ON ucan_delegations(cid);
CREATE INDEX idx_ucan_delegations_did_id ON ucan_delegations(did_id);
CREATE INDEX idx_ucan_delegations_iss ON ucan_delegations(iss);
CREATE INDEX idx_ucan_delegations_aud ON ucan_delegations(aud);
CREATE INDEX idx_ucan_delegations_sub ON ucan_delegations(sub);
CREATE INDEX idx_ucan_delegations_cmd ON ucan_delegations(cmd);
CREATE INDEX idx_ucan_delegations_exp ON ucan_delegations(exp);
```

### 2. Add `ucan_invocations` Table

```sql
-- UCAN Invocations: v1.0.0-rc.1 invocation envelopes (audit log)
CREATE TABLE IF NOT EXISTS ucan_invocations (
    id              INTEGER PRIMARY KEY,
    did_id          INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE,
    
    -- Content Identifier
    cid             TEXT NOT NULL UNIQUE,
    
    -- Sealed envelope (DAG-CBOR encoded)
    envelope        BLOB NOT NULL,
    
    -- Extracted fields for indexing
    iss             TEXT NOT NULL,              -- Invoker DID
    sub             TEXT NOT NULL,              -- Subject DID
    aud             TEXT,                       -- Executor DID (if different from sub)
    cmd             TEXT NOT NULL,              -- Command invoked
    
    -- Proof chain (JSON array of delegation CIDs)
    prf             TEXT NOT NULL DEFAULT '[]',
    
    -- Temporal
    exp             TEXT,                       -- Expiration
    iat             TEXT,                       -- Issued at
    
    -- Execution tracking
    executed_at     TEXT,                       -- When actually executed
    result_cid      TEXT,                       -- CID of receipt (if executed)
    
    created_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_ucan_invocations_cid ON ucan_invocations(cid);
CREATE INDEX idx_ucan_invocations_iss ON ucan_invocations(iss);
CREATE INDEX idx_ucan_invocations_sub ON ucan_invocations(sub);
CREATE INDEX idx_ucan_invocations_cmd ON ucan_invocations(cmd);
```

### 3. Update `ucan_revocations` (Minor Changes)

```sql
-- UCAN Revocations: Track revoked delegations
-- (Mostly unchanged, but add invocation reference)
CREATE TABLE IF NOT EXISTS ucan_revocations (
    id              INTEGER PRIMARY KEY,
    delegation_cid  TEXT NOT NULL UNIQUE,       -- CID of revoked delegation
    revoked_by      TEXT NOT NULL,              -- Revoker DID
    invocation_cid  TEXT,                       -- CID of revocation invocation
    reason          TEXT,
    revoked_at      TEXT NOT NULL DEFAULT (datetime('now')),
    
    FOREIGN KEY (delegation_cid) REFERENCES ucan_delegations(cid) ON DELETE CASCADE
);

CREATE INDEX idx_ucan_revocations_delegation_cid ON ucan_revocations(delegation_cid);
CREATE INDEX idx_ucan_revocations_revoked_by ON ucan_revocations(revoked_by);
```

### 4. Remove `delegations` Table

The old `delegations` table extracted fields from `ucan_tokens`. In v1.0.0-rc.1, the delegation IS the token. The `ucan_delegations` table replaces both.

```sql
-- DROP TABLE IF EXISTS delegations;  -- Migration step
```

## Query Examples

### Get Delegation by CID (for go-ucan Loader)

```sql
-- name: GetDelegationByCID :one
SELECT envelope FROM ucan_delegations WHERE cid = ? LIMIT 1;
```

### List Delegations Granted TO a DID (audience)

```sql
-- name: ListDelegationsToAudience :many
SELECT * FROM ucan_delegations 
WHERE aud = ? AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
```

### List Delegations Granted BY a DID (issuer)

```sql
-- name: ListDelegationsByIssuer :many
SELECT * FROM ucan_delegations 
WHERE iss = ? AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
```

### Find Delegations for a Command

```sql
-- name: ListDelegationsForCommand :many
SELECT * FROM ucan_delegations 
WHERE did_id = ? 
  AND (cmd = ? OR cmd = '/' OR ? LIKE cmd || '/%')
  AND (exp IS NULL OR exp > datetime('now'))
ORDER BY created_at DESC;
```

### Check if Delegation is Revoked

```sql
-- name: IsDelegationRevoked :one
SELECT EXISTS(SELECT 1 FROM ucan_revocations WHERE delegation_cid = ?) as revoked;
```

## Go Integration

### Delegation Loader Implementation

```go
// internal/keybase/ucan_loader.go

package keybase

import (
    "context"
    "fmt"
    
    "github.com/ipfs/go-cid"
    "github.com/ucan-wg/go-ucan/token/delegation"
)

// DelegationLoader implements delegation.Loader for go-ucan
type DelegationLoader struct {
    queries *Queries
}

func NewDelegationLoader(queries *Queries) *DelegationLoader {
    return &DelegationLoader{queries: queries}
}

// GetDelegation implements delegation.Loader
func (l *DelegationLoader) GetDelegation(c cid.Cid) (*delegation.Token, error) {
    ctx := context.Background()
    
    envelope, err := l.queries.GetDelegationEnvelopeByCID(ctx, c.String())
    if err != nil {
        return nil, fmt.Errorf("delegation not found: %s", c)
    }
    
    // Decode DAG-CBOR envelope to delegation token
    return delegation.FromSealed(envelope)
}
```

### Action Manager Integration

```go
// internal/keybase/actions_delegation_v2.go

type DelegationV2Result struct {
    CID       string `json:"cid"`
    Issuer    string `json:"iss"`
    Audience  string `json:"aud"`
    Subject   string `json:"sub,omitempty"`
    Command   string `json:"cmd"`
    Policy    string `json:"pol"`
    ExpiresAt string `json:"exp,omitempty"`
    IsRoot    bool   `json:"is_root"`
    CreatedAt string `json:"created_at"`
}

func (am *ActionManager) StoreDelegation(ctx context.Context, sealed []byte, c cid.Cid) (*DelegationV2Result, error) {
    // Decode to extract indexed fields
    token, err := delegation.FromSealed(sealed)
    if err != nil {
        return nil, err
    }
    
    // Store in database
    result, err := am.kb.queries.CreateDelegationV2(ctx, CreateDelegationV2Params{
        DidID:    am.kb.didID,
        Cid:      c.String(),
        Envelope: sealed,
        Iss:      token.Issuer().String(),
        Aud:      token.Audience().String(),
        Sub:      didToNullable(token.Subject()),
        Cmd:      token.Command().String(),
        Pol:      policyToJSON(token.Policy()),
        Exp:      timeToNullable(token.Expiration()),
        Nbf:      timeToNullable(token.NotBefore()),
        IsRoot:   boolToInt(token.IsRoot()),
        IsPowerline: boolToInt(token.IsPowerline()),
    })
    
    return delegationV2ToResult(result), nil
}
```

## Migration Path

1. **Create new tables** alongside old ones
2. **Migrate existing data** (if any JWT tokens exist)
   - Parse old `raw_token` 
   - Re-encode as v1.0.0-rc.1 envelope (requires re-signing)
   - Or: Mark old tokens as legacy, start fresh with v1.0.0-rc.1
3. **Update ActionManager** to use new tables
4. **Drop old tables** after migration verified

## Benefits

| Aspect | Old (JWT) | New (v1.0.0-rc.1) |
|--------|-----------|-------------------|
| Storage | JSON text | Binary CBOR (smaller) |
| Verification | Parse JWT, verify sig | go-ucan handles it |
| Proof Chain | JSON array in token | Separate CID references |
| Policy | `capabilities` JSON | Structured `pol` field |
| Interop | Non-standard | Spec-compliant |

## Conclusion

Integrating UCAN v1.0.0-rc.1 into the keybase schema:

1. **Maintains single-database portability** for WASM plugin
2. **Leverages existing infrastructure** (SQLC, ActionManager)
3. **Enables foreign key relationships** with DID documents
4. **Provides efficient queries** via indexed fields
5. **Supports go-ucan integration** via `delegation.Loader`
