docs(dwn): remove UCAN_SCHEMA_PROPOSAL.md
This commit is contained in:
4
.github/Repo.toml.migrated.20260109_191955
vendored
4
.github/Repo.toml.migrated.20260109_191955
vendored
@@ -1,4 +0,0 @@
|
|||||||
[scopes]
|
|
||||||
docs = ["MIGRATION.md", "README.md"]
|
|
||||||
db = ["db"]
|
|
||||||
config = [".github"]
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
# 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`
|
|
||||||
@@ -6,8 +6,8 @@ let currentResource = 'accounts';
|
|||||||
let currentAction = 'list';
|
let currentAction = 'list';
|
||||||
|
|
||||||
const RESOURCE_ACTIONS = {
|
const RESOURCE_ACTIONS = {
|
||||||
accounts: ['list', 'get'],
|
accounts: ['list', 'get', 'sign'],
|
||||||
enclaves: ['list', 'get', 'rotate', 'archive', 'delete'],
|
enclaves: ['list', 'get', 'sign', 'rotate', 'archive', 'delete'],
|
||||||
credentials: ['list', 'get'],
|
credentials: ['list', 'get'],
|
||||||
sessions: ['list', 'revoke'],
|
sessions: ['list', 'revoke'],
|
||||||
grants: ['list', 'revoke'],
|
grants: ['list', 'revoke'],
|
||||||
@@ -17,7 +17,7 @@ const RESOURCE_ACTIONS = {
|
|||||||
services: ['list', 'get', 'get_by_id'],
|
services: ['list', 'get', 'get_by_id'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACTIONS_REQUIRING_SUBJECT = ['get', 'revoke', 'delete', 'verify', 'rotate', 'archive', 'list_received', 'list_command', 'get_by_id'];
|
const ACTIONS_REQUIRING_SUBJECT = ['get', 'revoke', 'delete', 'verify', 'rotate', 'archive', 'list_received', 'list_command', 'get_by_id', 'sign'];
|
||||||
|
|
||||||
function log(card, level, message, data = null) {
|
function log(card, level, message, data = null) {
|
||||||
const el = document.getElementById(`log-${card}`);
|
const el = document.getElementById(`log-${card}`);
|
||||||
@@ -322,6 +322,7 @@ function updateSubjectRow() {
|
|||||||
list_received: 'Audience DID',
|
list_received: 'Audience DID',
|
||||||
list_command: 'Command (e.g., msg/send)',
|
list_command: 'Command (e.g., msg/send)',
|
||||||
get_by_id: 'Service ID (number)',
|
get_by_id: 'Service ID (number)',
|
||||||
|
sign: currentResource === 'enclaves' ? 'enclave_id:data_to_sign' : 'Data to sign (text)',
|
||||||
};
|
};
|
||||||
|
|
||||||
subjectInput.placeholder = placeholders[currentAction] || 'Subject';
|
subjectInput.placeholder = placeholders[currentAction] || 'Subject';
|
||||||
|
|||||||
Reference in New Issue
Block a user