diff --git a/internal/codec/policy.json b/internal/codec/policy.json new file mode 100644 index 0000000..c5da6f6 --- /dev/null +++ b/internal/codec/policy.json @@ -0,0 +1,305 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucan.xyz/schemas/policy.json", + "title": "UCAN Policy Language", + "description": "Policy statements and selectors for constraining invocation arguments", + "$defs": { + "Selector": { + "title": "Policy Selector", + "description": "jq-inspired selector for navigating IPLD data structures", + "type": "string", + "pattern": "^\\.(([a-zA-Z_][a-zA-Z0-9_]*)|(\\[[^\\]]+\\])|(\\[\\])|(\\[-?\\d+\\])|(\\[\\d*:\\d*\\]))?((\\.[a-zA-Z_][a-zA-Z0-9_]*)|(\\[[^\\]]+\\])|(\\[\\])|(\\[-?\\d+\\])|(\\[\\d*:\\d*\\]))*\\??$", + "examples": [ + ".", + ".foo", + ".bar[0]", + ".items[-1]", + ".data[2:5]", + ".optional?" + ] + }, + "GlobPattern": { + "title": "Glob Pattern", + "description": "Pattern for 'like' operator. * = wildcard, \\* = literal", + "type": "string", + "examples": [ + "*@example.com", + "prefix*suffix" + ] + }, + "EqualityOperator": { + "title": "Equality Operator", + "type": "string", + "enum": [ + "==", + "!=" + ] + }, + "InequalityOperator": { + "title": "Inequality Operator", + "type": "string", + "enum": [ + ">", + ">=", + "<", + "<=" + ] + }, + "EqualityStatement": { + "title": "Equality Statement", + "description": "Deep comparison: [operator, selector, value]", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/EqualityOperator" + }, + { + "$ref": "#/$defs/Selector" + }, + {} + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "==", + ".status", + "draft" + ], + [ + "!=", + ".deleted", + true + ] + ] + }, + "InequalityStatement": { + "title": "Inequality Statement", + "description": "Numeric comparison: [operator, selector, number]", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/InequalityOperator" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "type": "number" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + ">", + ".age", + 18 + ], + [ + "<=", + ".price", + 100.50 + ] + ] + }, + "LikeStatement": { + "title": "Like Statement", + "description": "Glob pattern matching: ['like', selector, pattern]", + "type": "array", + "prefixItems": [ + { + "const": "like" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "$ref": "#/$defs/GlobPattern" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "like", + ".email", + "*@example.com" + ] + ] + }, + "NotStatement": { + "title": "Not Statement", + "description": "Logical negation: ['not', statement]", + "type": "array", + "prefixItems": [ + { + "const": "not" + }, + { + "$ref": "#/$defs/PolicyStatement" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "AndStatement": { + "title": "And Statement", + "description": "Logical AND: ['and', [statements...]]. Empty array = true", + "type": "array", + "prefixItems": [ + { + "const": "and" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/PolicyStatement" + } + } + ], + "minItems": 2, + "maxItems": 2 + }, + "OrStatement": { + "title": "Or Statement", + "description": "Logical OR: ['or', [statements...]]. Empty array = true", + "type": "array", + "prefixItems": [ + { + "const": "or" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/PolicyStatement" + } + } + ], + "minItems": 2, + "maxItems": 2 + }, + "AllStatement": { + "title": "All Statement", + "description": "Universal quantifier: ['all', selector, statement]", + "type": "array", + "prefixItems": [ + { + "const": "all" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "$ref": "#/$defs/PolicyStatement" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "all", + ".reviewers", + [ + "like", + ".email", + "*@example.com" + ] + ] + ] + }, + "AnyStatement": { + "title": "Any Statement", + "description": "Existential quantifier: ['any', selector, statement]", + "type": "array", + "prefixItems": [ + { + "const": "any" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "$ref": "#/$defs/PolicyStatement" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "any", + ".tags", + [ + "==", + ".", + "urgent" + ] + ] + ] + }, + "PolicyStatement": { + "title": "Policy Statement", + "description": "A single policy predicate expression", + "oneOf": [ + { + "$ref": "#/$defs/EqualityStatement" + }, + { + "$ref": "#/$defs/InequalityStatement" + }, + { + "$ref": "#/$defs/LikeStatement" + }, + { + "$ref": "#/$defs/NotStatement" + }, + { + "$ref": "#/$defs/AndStatement" + }, + { + "$ref": "#/$defs/OrStatement" + }, + { + "$ref": "#/$defs/AllStatement" + }, + { + "$ref": "#/$defs/AnyStatement" + } + ] + }, + "Policy": { + "title": "UCAN Policy", + "description": "Array of statements forming implicit AND. Constrains invocation args.", + "type": "array", + "items": { + "$ref": "#/$defs/PolicyStatement" + }, + "examples": [ + [], + [ + [ + "==", + ".from", + "alice@example.com" + ] + ], + [ + [ + "==", + ".status", + "draft" + ], + [ + "all", + ".reviewer", + [ + "like", + ".email", + "*@example.com" + ] + ] + ] + ] + } + } +} diff --git a/internal/codec/primitives.json b/internal/codec/primitives.json new file mode 100644 index 0000000..819b8bc --- /dev/null +++ b/internal/codec/primitives.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucan.xyz/schemas/primitives.json", + "title": "UCAN Primitive Types", + "description": "Core primitive types used across all UCAN specifications", + "$defs": { + "DID": { + "title": "Decentralized Identifier", + "description": "A W3C Decentralized Identifier (DID) string", + "type": "string", + "pattern": "^did:[a-z0-9]+:[a-zA-Z0-9._%-]+(:[a-zA-Z0-9._%-]+)*([/?#].*)?$", + "examples": [ + "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", + "did:web:example.com" + ] + }, + "CID": { + "title": "Content Identifier", + "description": "IPLD CIDv1 with DAG-CBOR codec and SHA-256 multihash (base58btc)", + "type": "object", + "properties": { + "/": { + "type": "string", + "pattern": "^zdpu[a-km-zA-HJ-NP-Z1-9]+$" + } + }, + "required": [ + "/" + ], + "additionalProperties": false + }, + "Bytes": { + "title": "Binary Data", + "description": "Binary data in DAG-JSON format (base64 encoded)", + "type": "object", + "properties": { + "/": { + "type": "object", + "properties": { + "bytes": { + "type": "string", + "contentEncoding": "base64" + } + }, + "required": [ + "bytes" + ], + "additionalProperties": false + } + }, + "required": [ + "/" + ], + "additionalProperties": false + }, + "Timestamp": { + "title": "Unix Timestamp", + "description": "Unix timestamp in seconds (53-bit integer for JS compatibility)", + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "NullableTimestamp": { + "title": "Nullable Timestamp", + "description": "Timestamp or null for non-expiring tokens", + "oneOf": [ + { + "$ref": "#/$defs/Timestamp" + }, + { + "type": "null" + } + ] + }, + "VarsigHeader": { + "title": "Varsig Header", + "description": "Variable signature header with algorithm metadata", + "allOf": [ + { + "$ref": "#/$defs/Bytes" + } + ] + }, + "Signature": { + "title": "Cryptographic Signature", + "description": "Raw signature bytes", + "allOf": [ + { + "$ref": "#/$defs/Bytes" + } + ] + }, + "Command": { + "title": "UCAN Command", + "description": "Slash-delimited path describing an action (lowercase, starts with /)", + "type": "string", + "pattern": "^/([a-z0-9_\\u00C0-\\u024F]+(/[a-z0-9_\\u00C0-\\u024F]+)*)?$", + "examples": [ + "/", + "/crud/create", + "/msg/send", + "/ucan/revoke" + ] + }, + "Metadata": { + "title": "Metadata", + "description": "Arbitrary metadata map", + "type": "object", + "additionalProperties": true + }, + "Arguments": { + "title": "Command Arguments", + "description": "Map of command arguments", + "type": "object", + "additionalProperties": true + } + } +} diff --git a/internal/codec/ucan-schemas.json b/internal/codec/ucan-schemas.json new file mode 100644 index 0000000..e191bc8 --- /dev/null +++ b/internal/codec/ucan-schemas.json @@ -0,0 +1,1160 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucan.xyz/schemas/ucan-complete.json", + "title": "UCAN Complete Schema Definitions", + "description": "Comprehensive JSON Schema definitions for User-Controlled Authorization Network (UCAN) v1.0.0-rc.1 including Tokens, Delegation, and Invocation specifications", + "$defs": { + "DID": { + "$id": "#DID", + "title": "Decentralized Identifier", + "description": "A W3C Decentralized Identifier (DID) string. Must be a valid DID URL.", + "type": "string", + "pattern": "^did:[a-z0-9]+:[a-zA-Z0-9._%-]+(:[a-zA-Z0-9._%-]+)*([/?#].*)?$", + "examples": [ + "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", + "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + "did:web:example.com", + "did:plc:abc123" + ] + }, + "CID": { + "$id": "#CID", + "title": "Content Identifier", + "description": "IPLD Content Identifier (CIDv1) with DAG-CBOR codec and SHA-256 multihash. Encoded in base58btc, starting with 'zdpu'.", + "type": "object", + "properties": { + "/": { + "type": "string", + "pattern": "^zdpu[a-km-zA-HJ-NP-Z1-9]+$" + } + }, + "required": [ + "/" + ], + "additionalProperties": false, + "examples": [ + { + "/": "zdpuAzx4sBrBCabrZZqXgvK3NDzh7Mf5mKbG11aBkkMCdLtCp" + } + ] + }, + "Bytes": { + "$id": "#Bytes", + "title": "Binary Data", + "description": "Binary data encoded as base64 in DAG-JSON format", + "type": "object", + "properties": { + "/": { + "type": "object", + "properties": { + "bytes": { + "type": "string", + "contentEncoding": "base64" + } + }, + "required": [ + "bytes" + ], + "additionalProperties": false + } + }, + "required": [ + "/" + ], + "additionalProperties": false, + "examples": [ + { + "/": { + "bytes": "bGlnaHQgd29yay4" + } + }, + { + "/": { + "bytes": "TWFueSBopvcs" + } + } + ] + }, + "Timestamp": { + "$id": "#Timestamp", + "title": "Unix Timestamp", + "description": "Unix timestamp in seconds since epoch (UTC). Must be within 53-bit integer range for JavaScript compatibility.", + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991, + "examples": [ + 1529496683, + 1575606941, + 1697409438 + ] + }, + "NullableTimestamp": { + "$id": "#NullableTimestamp", + "title": "Nullable Unix Timestamp", + "description": "Unix timestamp or null for non-expiring tokens", + "oneOf": [ + { + "$ref": "#/$defs/Timestamp" + }, + { + "type": "null" + } + ] + }, + "VarsigHeader": { + "$id": "#VarsigHeader", + "title": "Varsig Header", + "description": "Variable signature header containing cryptographic algorithm metadata", + "allOf": [ + { + "$ref": "#/$defs/Bytes" + } + ], + "examples": [ + { + "/": { + "bytes": "NBIFEgEAcQ" + } + } + ] + }, + "Signature": { + "$id": "#Signature", + "title": "Cryptographic Signature", + "description": "Raw signature bytes over the signed payload", + "allOf": [ + { + "$ref": "#/$defs/Bytes" + } + ], + "examples": [ + { + "/": { + "bytes": "7aEDQLYvb3lygk9yvAbk0OZD0q+iF9c3+wpZC4YlFThkiNShcVriobPFr/wl3akjM18VvIv/Zw2LtA4uUmB5m8PWEAU" + } + } + ] + }, + "Command": { + "$id": "#Command", + "title": "UCAN Command", + "description": "A slash-delimited path describing the action to perform. Must be lowercase, start with '/', and not have a trailing slash.", + "type": "string", + "pattern": "^/([a-z0-9_\\u00C0-\\u024F]+(/[a-z0-9_\\u00C0-\\u024F]+)*)?$", + "examples": [ + "/", + "/crud", + "/crud/create", + "/crud/read", + "/crud/update", + "/crud/delete", + "/msg/send", + "/msg/receive", + "/ucan/revoke", + "/wasm/run", + "/crypto/sign", + "/blog/post/create" + ] + }, + "Selector": { + "$id": "#Selector", + "title": "Policy Selector", + "description": "jq-inspired selector for navigating IPLD data structures in policy evaluation", + "type": "string", + "pattern": "^\\.(([a-zA-Z_][a-zA-Z0-9_]*)|(\\[[^\\]]+\\])|(\\[\\])|(\\[-?\\d+\\])|(\\[\\d*:\\d*\\]))?((\\.[a-zA-Z_][a-zA-Z0-9_]*)|(\\[[^\\]]+\\])|(\\[\\])|(\\[-?\\d+\\])|(\\[\\d*:\\d*\\]))*\\??$", + "examples": [ + ".", + ".foo", + ".bar.baz", + ".items[0]", + ".items[-1]", + ".data[2:5]", + ".optional?", + "[\"special-key\"]", + ".array[]" + ] + }, + "GlobPattern": { + "$id": "#GlobPattern", + "title": "Glob Pattern", + "description": "Pattern string for 'like' operator. Use * for wildcard, \\* for literal asterisk.", + "type": "string", + "examples": [ + "*@example.com", + "prefix*suffix", + "exact-match", + "escaped\\*asterisk" + ] + }, + "EqualityOperator": { + "$id": "#EqualityOperator", + "title": "Equality Operator", + "type": "string", + "enum": [ + "==", + "!=" + ] + }, + "InequalityOperator": { + "$id": "#InequalityOperator", + "title": "Inequality Operator", + "type": "string", + "enum": [ + ">", + ">=", + "<", + "<=" + ] + }, + "ConnectiveOperator": { + "$id": "#ConnectiveOperator", + "title": "Connective Operator", + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "QuantifierOperator": { + "$id": "#QuantifierOperator", + "title": "Quantifier Operator", + "type": "string", + "enum": [ + "all", + "any" + ] + }, + "EqualityStatement": { + "$id": "#EqualityStatement", + "title": "Equality Statement", + "description": "Deep comparison of selector result against any IPLD value", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/EqualityOperator" + }, + { + "$ref": "#/$defs/Selector" + }, + {} + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "==", + ".status", + "draft" + ], + [ + "!=", + ".deleted", + true + ], + [ + "==", + ".tags", + [ + "news", + "press" + ] + ] + ] + }, + "InequalityStatement": { + "$id": "#InequalityStatement", + "title": "Inequality Statement", + "description": "Numeric comparison of selector result", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/InequalityOperator" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "type": "number" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + ">", + ".age", + 18 + ], + [ + "<=", + ".price", + 100.50 + ], + [ + ">=", + ".count", + 0 + ] + ] + }, + "LikeStatement": { + "$id": "#LikeStatement", + "title": "Like Statement", + "description": "Glob pattern matching on string values", + "type": "array", + "prefixItems": [ + { + "const": "like" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "$ref": "#/$defs/GlobPattern" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "like", + ".email", + "*@example.com" + ], + [ + "like", + ".path", + "/users/*" + ] + ] + }, + "NotStatement": { + "$id": "#NotStatement", + "title": "Not Statement", + "description": "Logical negation of a statement", + "type": "array", + "prefixItems": [ + { + "const": "not" + }, + { + "$ref": "#/$defs/PolicyStatement" + } + ], + "minItems": 2, + "maxItems": 2, + "examples": [ + [ + "not", + [ + "==", + ".deleted", + true + ] + ] + ] + }, + "AndStatement": { + "$id": "#AndStatement", + "title": "And Statement", + "description": "Logical AND of multiple statements. Empty array evaluates to true.", + "type": "array", + "prefixItems": [ + { + "const": "and" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/PolicyStatement" + } + } + ], + "minItems": 2, + "maxItems": 2, + "examples": [ + [ + "and", + [ + [ + "==", + ".name", + "Katie" + ], + [ + ">=", + ".age", + 21 + ] + ] + ] + ] + }, + "OrStatement": { + "$id": "#OrStatement", + "title": "Or Statement", + "description": "Logical OR of multiple statements. Empty array evaluates to true.", + "type": "array", + "prefixItems": [ + { + "const": "or" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/PolicyStatement" + } + } + ], + "minItems": 2, + "maxItems": 2, + "examples": [ + [ + "or", + [ + [ + "==", + ".status", + "active" + ], + [ + "==", + ".status", + "pending" + ] + ] + ] + ] + }, + "AllStatement": { + "$id": "#AllStatement", + "title": "All Statement", + "description": "Universal quantifier - statement must hold for all elements in collection", + "type": "array", + "prefixItems": [ + { + "const": "all" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "$ref": "#/$defs/PolicyStatement" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "all", + ".reviewers", + [ + "like", + ".email", + "*@example.com" + ] + ] + ] + }, + "AnyStatement": { + "$id": "#AnyStatement", + "title": "Any Statement", + "description": "Existential quantifier - statement must hold for at least one element in collection", + "type": "array", + "prefixItems": [ + { + "const": "any" + }, + { + "$ref": "#/$defs/Selector" + }, + { + "$ref": "#/$defs/PolicyStatement" + } + ], + "minItems": 3, + "maxItems": 3, + "examples": [ + [ + "any", + ".tags", + [ + "==", + ".", + "urgent" + ] + ] + ] + }, + "PolicyStatement": { + "$id": "#PolicyStatement", + "title": "Policy Statement", + "description": "A single policy predicate expression", + "oneOf": [ + { + "$ref": "#/$defs/EqualityStatement" + }, + { + "$ref": "#/$defs/InequalityStatement" + }, + { + "$ref": "#/$defs/LikeStatement" + }, + { + "$ref": "#/$defs/NotStatement" + }, + { + "$ref": "#/$defs/AndStatement" + }, + { + "$ref": "#/$defs/OrStatement" + }, + { + "$ref": "#/$defs/AllStatement" + }, + { + "$ref": "#/$defs/AnyStatement" + } + ] + }, + "Policy": { + "$id": "#Policy", + "title": "UCAN Policy", + "description": "Array of policy statements forming an implicit AND conjunction. Constrains the args field of eventual invocations.", + "type": "array", + "items": { + "$ref": "#/$defs/PolicyStatement" + }, + "examples": [ + [], + [ + [ + "==", + ".from", + "alice@example.com" + ] + ], + [ + [ + "==", + ".status", + "draft" + ], + [ + "all", + ".reviewer", + [ + "like", + ".email", + "*@example.com" + ] + ], + [ + "any", + ".tags", + [ + "or", + [ + [ + "==", + ".", + "news" + ], + [ + "==", + ".", + "press" + ] + ] + ] + ] + ] + ] + }, + "Metadata": { + "$id": "#Metadata", + "title": "UCAN Metadata", + "description": "Optional map of arbitrary metadata, facts, and proofs of knowledge. Must be self-evident and externally verifiable.", + "type": "object", + "additionalProperties": true, + "examples": [ + { + "challenges": { + "example.com": "abcdef" + } + }, + { + "env": "development", + "tags": [ + "blog", + "post" + ] + } + ] + }, + "Arguments": { + "$id": "#Arguments", + "title": "Command Arguments", + "description": "Map of arguments for a command. Shape is defined by the command type.", + "type": "object", + "additionalProperties": true, + "examples": [ + { + "from": "mailto:alice@example.com", + "to": [ + "bob@example.com" + ], + "subject": "Hello", + "body": "World" + }, + { + "uri": "https://example.com/resource", + "payload": { + "key": "value" + } + } + ] + }, + "Capability": { + "$id": "#Capability", + "title": "UCAN Capability", + "description": "The semantically-relevant claim of a delegation: subject × command × policy", + "type": "object", + "properties": { + "sub": { + "description": "The Subject DID this capability is about, or null for powerline delegation", + "oneOf": [ + { + "$ref": "#/$defs/DID" + }, + { + "type": "null" + } + ] + }, + "cmd": { + "$ref": "#/$defs/Command", + "description": "The command being delegated" + }, + "pol": { + "$ref": "#/$defs/Policy", + "description": "Constraints on eventual invocation arguments" + } + }, + "required": [ + "sub", + "cmd", + "pol" + ], + "additionalProperties": false, + "examples": [ + { + "sub": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", + "cmd": "/crud/read", + "pol": [] + }, + { + "sub": null, + "cmd": "/", + "pol": [] + } + ] + }, + "DelegationPayload": { + "$id": "#DelegationPayload", + "title": "UCAN Delegation Payload", + "description": "The payload of a UCAN Delegation token (ucan/dlg@1.0.0-rc.1)", + "type": "object", + "properties": { + "iss": { + "$ref": "#/$defs/DID", + "description": "Issuer DID - the principal delegating authority" + }, + "aud": { + "$ref": "#/$defs/DID", + "description": "Audience DID - the principal receiving delegated authority" + }, + "sub": { + "description": "Subject DID - the principal the chain is about, or null for powerline", + "oneOf": [ + { + "$ref": "#/$defs/DID" + }, + { + "type": "null" + } + ] + }, + "cmd": { + "$ref": "#/$defs/Command", + "description": "The command being delegated" + }, + "pol": { + "$ref": "#/$defs/Policy", + "description": "Policy constraints on invocation arguments" + }, + "nonce": { + "$ref": "#/$defs/Bytes", + "description": "Random nonce ensuring unique CID" + }, + "meta": { + "$ref": "#/$defs/Metadata", + "description": "Optional arbitrary metadata" + }, + "nbf": { + "$ref": "#/$defs/Timestamp", + "description": "Not-before timestamp (optional)" + }, + "exp": { + "$ref": "#/$defs/NullableTimestamp", + "description": "Expiration timestamp, or null for non-expiring" + } + }, + "required": [ + "iss", + "aud", + "sub", + "cmd", + "pol", + "nonce", + "exp" + ], + "additionalProperties": false + }, + "DelegationSigPayload": { + "$id": "#DelegationSigPayload", + "title": "Delegation Signature Payload", + "description": "The content signed in a UCAN Delegation", + "type": "object", + "properties": { + "h": { + "$ref": "#/$defs/VarsigHeader", + "description": "Varsig v1 header with algorithm metadata" + }, + "ucan/dlg@1.0.0-rc.1": { + "$ref": "#/$defs/DelegationPayload", + "description": "The delegation payload" + } + }, + "required": [ + "h", + "ucan/dlg@1.0.0-rc.1" + ], + "additionalProperties": false + }, + "Delegation": { + "$id": "#Delegation", + "title": "UCAN Delegation", + "description": "Complete UCAN Delegation envelope with signature", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/Signature", + "description": "Signature over the SigPayload by the issuer" + }, + { + "$ref": "#/$defs/DelegationSigPayload", + "description": "The signed content" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "ProofChain": { + "$id": "#ProofChain", + "title": "Proof Chain", + "description": "Ordered array of CID references to Delegations forming the authority chain from Subject to Invoker", + "type": "array", + "items": { + "$ref": "#/$defs/CID" + }, + "examples": [ + [ + { + "/": "zdpuAzx4sBrBCabrZZqXgvK3NDzh7Mf5mKbG11aBkkMCdLtCp" + }, + { + "/": "zdpuApTCXfoKh2sB1KaUaVSGofCBNPUnXoBb6WiCeitXEibZy" + } + ] + ] + }, + "InvocationPayload": { + "$id": "#InvocationPayload", + "title": "UCAN Invocation Payload", + "description": "The payload of a UCAN Invocation token (ucan/inv@1.0.0-rc.1)", + "type": "object", + "properties": { + "iss": { + "$ref": "#/$defs/DID", + "description": "Issuer DID - the invoker requesting execution" + }, + "sub": { + "$ref": "#/$defs/DID", + "description": "Subject DID - the principal being invoked" + }, + "aud": { + "$ref": "#/$defs/DID", + "description": "Optional audience DID if executor differs from subject" + }, + "cmd": { + "$ref": "#/$defs/Command", + "description": "The command to execute" + }, + "args": { + "$ref": "#/$defs/Arguments", + "description": "Command arguments" + }, + "prf": { + "$ref": "#/$defs/ProofChain", + "description": "Proof chain of delegations" + }, + "meta": { + "$ref": "#/$defs/Metadata", + "description": "Optional metadata" + }, + "nonce": { + "$ref": "#/$defs/Bytes", + "description": "Optional nonce for non-idempotent invocations" + }, + "exp": { + "$ref": "#/$defs/NullableTimestamp", + "description": "Expiration timestamp" + }, + "iat": { + "$ref": "#/$defs/Timestamp", + "description": "Optional issuance timestamp" + }, + "cause": { + "$ref": "#/$defs/CID", + "description": "Optional CID of Receipt that enqueued this task" + } + }, + "required": [ + "iss", + "sub", + "cmd", + "args", + "prf", + "exp" + ], + "additionalProperties": false + }, + "InvocationSigPayload": { + "$id": "#InvocationSigPayload", + "title": "Invocation Signature Payload", + "description": "The content signed in a UCAN Invocation", + "type": "object", + "properties": { + "h": { + "$ref": "#/$defs/VarsigHeader", + "description": "Varsig v1 header" + }, + "ucan/inv@1.0.0-rc.1": { + "$ref": "#/$defs/InvocationPayload", + "description": "The invocation payload" + } + }, + "required": [ + "h", + "ucan/inv@1.0.0-rc.1" + ], + "additionalProperties": false + }, + "Invocation": { + "$id": "#Invocation", + "title": "UCAN Invocation", + "description": "Complete UCAN Invocation envelope with signature", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/Signature", + "description": "Signature over the SigPayload by the invoker" + }, + { + "$ref": "#/$defs/InvocationSigPayload", + "description": "The signed content" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "Task": { + "$id": "#Task", + "title": "UCAN Task", + "description": "The subset of Invocation fields uniquely determining work to perform. Task ID is the CID of these fields.", + "type": "object", + "properties": { + "sub": { + "$ref": "#/$defs/DID", + "description": "Subject DID" + }, + "cmd": { + "$ref": "#/$defs/Command", + "description": "Command to execute" + }, + "args": { + "$ref": "#/$defs/Arguments", + "description": "Command arguments" + }, + "nonce": { + "$ref": "#/$defs/Bytes", + "description": "Nonce for uniqueness" + } + }, + "required": [ + "sub", + "cmd", + "args", + "nonce" + ], + "additionalProperties": false + }, + "ReceiptPayload": { + "$id": "#ReceiptPayload", + "title": "UCAN Receipt Payload", + "description": "The payload of a UCAN Receipt (execution result)", + "type": "object", + "properties": { + "iss": { + "$ref": "#/$defs/DID", + "description": "Executor DID that produced this receipt" + }, + "ran": { + "$ref": "#/$defs/CID", + "description": "CID of the Invocation that was executed" + }, + "out": { + "description": "Result of execution - either success or error", + "oneOf": [ + { + "type": "object", + "properties": { + "ok": { + "description": "Success value" + } + }, + "required": [ + "ok" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "err": { + "description": "Error value" + } + }, + "required": [ + "err" + ], + "additionalProperties": false + } + ] + }, + "fx": { + "type": "array", + "items": { + "$ref": "#/$defs/CID" + }, + "description": "Effects - CIDs of Tasks to enqueue" + }, + "meta": { + "$ref": "#/$defs/Metadata", + "description": "Optional metadata" + }, + "iat": { + "$ref": "#/$defs/Timestamp", + "description": "Issuance timestamp" + } + }, + "required": [ + "iss", + "ran", + "out" + ], + "additionalProperties": false + }, + "RevocationPayload": { + "$id": "#RevocationPayload", + "title": "UCAN Revocation Payload", + "description": "Payload for revoking a previously issued delegation", + "type": "object", + "properties": { + "iss": { + "$ref": "#/$defs/DID", + "description": "Revoker DID - must be an issuer in the delegation chain" + }, + "sub": { + "$ref": "#/$defs/DID", + "description": "Subject of the delegation being revoked" + }, + "cmd": { + "const": "/ucan/revoke", + "description": "Revocation command" + }, + "args": { + "type": "object", + "properties": { + "ucan": { + "$ref": "#/$defs/CID", + "description": "CID of the delegation to revoke" + } + }, + "required": [ + "ucan" + ], + "additionalProperties": false + }, + "prf": { + "$ref": "#/$defs/ProofChain", + "description": "Proof chain showing revoker's authority" + }, + "nonce": { + "$ref": "#/$defs/Bytes" + }, + "exp": { + "$ref": "#/$defs/NullableTimestamp" + } + }, + "required": [ + "iss", + "sub", + "cmd", + "args", + "prf", + "nonce", + "exp" + ], + "additionalProperties": false + }, + "UCANEnvelope": { + "$id": "#UCANEnvelope", + "title": "UCAN Envelope", + "description": "Generic UCAN envelope format used by all UCAN token types", + "type": "array", + "prefixItems": [ + { + "$ref": "#/$defs/Signature", + "description": "Signature by payload's issuer over the SigPayload" + }, + { + "type": "object", + "properties": { + "h": { + "$ref": "#/$defs/VarsigHeader", + "description": "Varsig v1 header" + } + }, + "required": [ + "h" + ], + "additionalProperties": true + } + ], + "minItems": 2, + "maxItems": 2 + }, + "CryptoAlgorithm": { + "$id": "#CryptoAlgorithm", + "title": "Supported Cryptographic Algorithm", + "description": "Signature algorithms supported by UCAN", + "type": "string", + "enum": [ + "Ed25519", + "P-256", + "secp256k1" + ], + "default": "Ed25519" + }, + "HashAlgorithm": { + "$id": "#HashAlgorithm", + "title": "Hash Algorithm", + "description": "Hash algorithm for content addressing", + "type": "string", + "enum": [ + "sha2-256" + ], + "default": "sha2-256" + }, + "ValidationError": { + "$id": "#ValidationError", + "title": "Validation Error", + "description": "Error returned when UCAN validation fails", + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "EXPIRED", + "NOT_YET_VALID", + "INVALID_SIGNATURE", + "PRINCIPAL_MISALIGNMENT", + "POLICY_VIOLATION", + "REVOKED", + "INVALID_PROOF_CHAIN", + "UNKNOWN_COMMAND", + "MALFORMED_TOKEN" + ] + }, + "message": { + "type": "string" + }, + "details": { + "type": "object", + "additionalProperties": true + } + }, + "required": [ + "code", + "message" + ] + }, + "ValidationResult": { + "$id": "#ValidationResult", + "title": "Validation Result", + "description": "Result of UCAN validation", + "oneOf": [ + { + "type": "object", + "properties": { + "valid": { + "const": true + }, + "capability": { + "$ref": "#/$defs/Capability" + } + }, + "required": [ + "valid", + "capability" + ] + }, + { + "type": "object", + "properties": { + "valid": { + "const": false + }, + "error": { + "$ref": "#/$defs/ValidationError" + } + }, + "required": [ + "valid", + "error" + ] + } + ] + } + }, + "oneOf": [ + { + "$ref": "#/$defs/Delegation" + }, + { + "$ref": "#/$defs/Invocation" + } + ] +} diff --git a/src/ucan.ts b/src/ucan.ts new file mode 100644 index 0000000..02debb2 --- /dev/null +++ b/src/ucan.ts @@ -0,0 +1,592 @@ +/** + * UCAN Complete TypeScript Type Definitions + * User-Controlled Authorization Network v1.0.0-rc.1 + * + * Comprehensive types for Tokens, Delegation, and Invocation specifications + */ + +// ============================================================================= +// PRIMITIVES +// ============================================================================= + +/** + * Decentralized Identifier (DID) string + * @see https://www.w3.org/TR/did-core/ + */ +export type DID = `did:${string}:${string}`; + +/** + * IPLD Content Identifier in DAG-JSON format + * CIDv1 with DAG-CBOR codec and SHA-256 multihash, base58btc encoded + */ +export interface CID { + "/": string; +} + +/** + * Binary data in DAG-JSON format (base64 encoded) + */ +export interface Bytes { + "/": { + bytes: string; + }; +} + +/** + * Unix timestamp in seconds (53-bit integer for JS compatibility) + * Range: -9007199254740991 to 9007199254740991 + */ +export type Timestamp = number; + +/** + * Nullable timestamp - null indicates non-expiring + */ +export type NullableTimestamp = Timestamp | null; + +/** + * Varsig v1 header containing cryptographic algorithm metadata + */ +export type VarsigHeader = Bytes; + +/** + * Raw signature bytes + */ +export type Signature = Bytes; + +// ============================================================================= +// COMMANDS +// ============================================================================= + +/** + * UCAN Command - slash-delimited path describing the action + * Must be lowercase, start with '/', no trailing slash + */ +export type Command = + | "/" + | `/${string}` + | `/crud/${CRUDAction}` + | `/msg/${MessageAction}` + | `/ucan/${UCANAction}` + | `/wasm/${WasmAction}`; + +export type CRUDAction = "create" | "read" | "update" | "delete" | "mutate"; +export type MessageAction = "send" | "receive"; +export type UCANAction = "revoke"; +export type WasmAction = "run"; + +// ============================================================================= +// POLICY LANGUAGE +// ============================================================================= + +/** + * jq-inspired selector for navigating IPLD data + * @example "." | ".foo" | ".bar[0]" | ".items[-1]" | ".optional?" + */ +export type Selector = string; + +/** + * Glob pattern for 'like' operator + * Use * for wildcard, \* for literal asterisk + */ +export type GlobPattern = string; + +// Policy Operators +export type EqualityOperator = "==" | "!="; +export type InequalityOperator = ">" | ">=" | "<" | "<="; +export type ConnectiveOperator = "and" | "or"; +export type QuantifierOperator = "all" | "any"; + +// Policy Statements +export type EqualityStatement = [EqualityOperator, Selector, unknown]; +export type InequalityStatement = [InequalityOperator, Selector, number]; +export type LikeStatement = ["like", Selector, GlobPattern]; +export type NotStatement = ["not", PolicyStatement]; +export type AndStatement = ["and", PolicyStatement[]]; +export type OrStatement = ["or", PolicyStatement[]]; +export type AllStatement = ["all", Selector, PolicyStatement]; +export type AnyStatement = ["any", Selector, PolicyStatement]; + +/** + * Union of all policy statement types + */ +export type PolicyStatement = + | EqualityStatement + | InequalityStatement + | LikeStatement + | NotStatement + | AndStatement + | OrStatement + | AllStatement + | AnyStatement; + +/** + * UCAN Policy - array of statements forming implicit AND + */ +export type Policy = PolicyStatement[]; + +// ============================================================================= +// METADATA & ARGUMENTS +// ============================================================================= + +/** + * Arbitrary metadata map + */ +export type Metadata = Record; + +/** + * Command arguments - shape defined by command type + */ +export type Arguments = Record; + +// ============================================================================= +// CAPABILITY +// ============================================================================= + +/** + * UCAN Capability - the semantically-relevant claim of a delegation + */ +export interface Capability { + /** Subject DID or null for powerline delegation */ + sub: DID | null; + /** Command being delegated */ + cmd: Command; + /** Policy constraints on invocation arguments */ + pol: Policy; +} + +// ============================================================================= +// DELEGATION +// ============================================================================= + +/** + * UCAN Delegation Payload (ucan/dlg@1.0.0-rc.1) + */ +export interface DelegationPayload { + /** Issuer DID - the delegator */ + iss: DID; + /** Audience DID - the delegate */ + aud: DID; + /** Subject DID or null for powerline */ + sub: DID | null; + /** Command being delegated */ + cmd: Command; + /** Policy constraints */ + pol: Policy; + /** Random nonce for unique CID */ + nonce: Bytes; + /** Optional metadata */ + meta?: Metadata; + /** Not-before timestamp (optional) */ + nbf?: Timestamp; + /** Expiration timestamp or null */ + exp: NullableTimestamp; +} + +/** + * Delegation signature payload structure + */ +export interface DelegationSigPayload { + /** Varsig header */ + h: VarsigHeader; + /** Delegation payload with version tag */ + "ucan/dlg@1.0.0-rc.1": DelegationPayload; +} + +/** + * Complete UCAN Delegation envelope + */ +export type Delegation = [Signature, DelegationSigPayload]; + +// ============================================================================= +// INVOCATION +// ============================================================================= + +/** + * Proof chain - ordered CIDs of delegations from Subject to Invoker + */ +export type ProofChain = CID[]; + +/** + * UCAN Invocation Payload (ucan/inv@1.0.0-rc.1) + */ +export interface InvocationPayload { + /** Issuer DID - the invoker */ + iss: DID; + /** Subject DID being invoked */ + sub: DID; + /** Optional audience DID if executor differs from subject */ + aud?: DID; + /** Command to execute */ + cmd: Command; + /** Command arguments */ + args: Arguments; + /** Proof chain of delegations */ + prf: ProofChain; + /** Optional metadata */ + meta?: Metadata; + /** Optional nonce for non-idempotent invocations */ + nonce?: Bytes; + /** Expiration timestamp */ + exp: NullableTimestamp; + /** Optional issuance timestamp */ + iat?: Timestamp; + /** Optional CID of Receipt that enqueued this task */ + cause?: CID; +} + +/** + * Invocation signature payload structure + */ +export interface InvocationSigPayload { + /** Varsig header */ + h: VarsigHeader; + /** Invocation payload with version tag */ + "ucan/inv@1.0.0-rc.1": InvocationPayload; +} + +/** + * Complete UCAN Invocation envelope + */ +export type Invocation = [Signature, InvocationSigPayload]; + +// ============================================================================= +// TASK +// ============================================================================= + +/** + * UCAN Task - subset of Invocation fields uniquely determining work + * Task ID is the CID of these fields + */ +export interface Task { + /** Subject DID */ + sub: DID; + /** Command to execute */ + cmd: Command; + /** Command arguments */ + args: Arguments; + /** Nonce for uniqueness */ + nonce: Bytes; +} + +// ============================================================================= +// RECEIPT +// ============================================================================= + +/** + * Successful execution result + */ +export interface SuccessResult { + ok: T; +} + +/** + * Failed execution result + */ +export interface ErrorResult { + err: E; +} + +/** + * Execution outcome + */ +export type ExecutionResult = SuccessResult | ErrorResult; + +/** + * UCAN Receipt Payload - execution result + */ +export interface ReceiptPayload { + /** Executor DID */ + iss: DID; + /** CID of executed Invocation */ + ran: CID; + /** Execution result */ + out: ExecutionResult; + /** Effects - CIDs of Tasks to enqueue */ + fx?: CID[]; + /** Optional metadata */ + meta?: Metadata; + /** Issuance timestamp */ + iat?: Timestamp; +} + +// ============================================================================= +// REVOCATION +// ============================================================================= + +/** + * Revocation arguments + */ +export interface RevocationArgs { + /** CID of delegation to revoke */ + ucan: CID; +} + +/** + * UCAN Revocation Payload + */ +export interface RevocationPayload { + /** Revoker DID - must be issuer in delegation chain */ + iss: DID; + /** Subject of delegation being revoked */ + sub: DID; + /** Revocation command */ + cmd: "/ucan/revoke"; + /** Revocation arguments */ + args: RevocationArgs; + /** Proof chain */ + prf: ProofChain; + /** Nonce */ + nonce: Bytes; + /** Expiration */ + exp: NullableTimestamp; +} + +// ============================================================================= +// ENVELOPE +// ============================================================================= + +/** + * Generic UCAN Envelope format + */ +export type UCANEnvelope

= [ + Signature, + { + h: VarsigHeader; + } & P +]; + +// ============================================================================= +// CRYPTOGRAPHY +// ============================================================================= + +/** + * Supported signature algorithms + */ +export type CryptoAlgorithm = "Ed25519" | "P-256" | "secp256k1"; + +/** + * Supported hash algorithms + */ +export type HashAlgorithm = "sha2-256"; + +// ============================================================================= +// VALIDATION +// ============================================================================= + +/** + * Validation error codes + */ +export type ValidationErrorCode = + | "EXPIRED" + | "NOT_YET_VALID" + | "INVALID_SIGNATURE" + | "PRINCIPAL_MISALIGNMENT" + | "POLICY_VIOLATION" + | "REVOKED" + | "INVALID_PROOF_CHAIN" + | "UNKNOWN_COMMAND" + | "MALFORMED_TOKEN"; + +/** + * Validation error structure + */ +export interface ValidationError { + code: ValidationErrorCode; + message: string; + details?: Record; +} + +/** + * Successful validation result + */ +export interface ValidationSuccess { + valid: true; + capability: Capability; +} + +/** + * Failed validation result + */ +export interface ValidationFailure { + valid: false; + error: ValidationError; +} + +/** + * Validation result union + */ +export type ValidationResult = ValidationSuccess | ValidationFailure; + +// ============================================================================= +// ROLES +// ============================================================================= + +/** + * UCAN Agent Roles + */ +export interface Roles { + /** General class of entities interacting with UCAN */ + Agent: DID; + /** Principal delegated to in current UCAN (aud field) */ + Audience: DID; + /** Agent that performs invocation action */ + Executor: DID; + /** Principal requesting execution */ + Invoker: DID; + /** Principal of current UCAN (iss field) */ + Issuer: DID; + /** Subject controlling external resource */ + Owner: DID; + /** Agent identified by DID */ + Principal: DID; + /** Issuer in proof chain that revokes */ + Revoker: DID; + /** Principal whose authority is delegated */ + Subject: DID; + /** Agent interpreting UCAN validity */ + Validator: DID; +} + +// ============================================================================= +// COMMON COMMANDS (for convenience) +// ============================================================================= + +/** + * Standard CRUD command arguments + */ +export namespace CRUDCommands { + export interface CreateArgs { + uri: string; + payload: unknown; + headers?: Record; + } + + export interface ReadArgs { + uri: string; + query?: Record; + } + + export interface UpdateArgs { + uri: string; + payload: unknown; + headers?: Record; + } + + export interface DeleteArgs { + uri: string; + } +} + +/** + * Standard messaging command arguments + */ +export namespace MessageCommands { + export interface SendArgs { + from: string; + to: string[]; + subject?: string; + body: string; + cc?: string[]; + bcc?: string[]; + } + + export interface ReceiveArgs { + mailbox: string; + since?: Timestamp; + limit?: number; + } +} + +/** + * WebAssembly execution command arguments + */ +export namespace WasmCommands { + export interface RunArgs { + /** Wasm module as data URI or CID */ + mod: string | CID; + /** Function to execute */ + fun: string; + /** Function parameters */ + params: unknown[]; + } +} + +// ============================================================================= +// TYPE GUARDS +// ============================================================================= + +export function isDID(value: unknown): value is DID { + return typeof value === "string" && value.startsWith("did:"); +} + +export function isCID(value: unknown): value is CID { + return ( + typeof value === "object" && + value !== null && + "/" in value && + typeof (value as CID)["/"] === "string" + ); +} + +export function isBytes(value: unknown): value is Bytes { + return ( + typeof value === "object" && + value !== null && + "/" in value && + typeof (value as Bytes)["/"] === "object" && + "bytes" in (value as Bytes)["/"] + ); +} + +export function isSuccessResult(result: ExecutionResult): result is SuccessResult { + return "ok" in result; +} + +export function isErrorResult(result: ExecutionResult): result is ErrorResult { + return "err" in result; +} + +export function isDelegation(token: Delegation | Invocation): token is Delegation { + return "ucan/dlg@1.0.0-rc.1" in token[1]; +} + +export function isInvocation(token: Delegation | Invocation): token is Invocation { + return "ucan/inv@1.0.0-rc.1" in token[1]; +} + +// ============================================================================= +// UTILITY TYPES +// ============================================================================= + +/** + * Extract the payload type from a UCAN envelope + */ +export type ExtractPayload> = + E extends UCANEnvelope ? P : never; + +/** + * Create a typed invocation for a specific command + */ +export type TypedInvocation = [ + Signature, + { + h: VarsigHeader; + "ucan/inv@1.0.0-rc.1": Omit & { + cmd: C; + args: A; + }; + } +]; + +/** + * Create a typed delegation for a specific command + */ +export type TypedDelegation = [ + Signature, + { + h: VarsigHeader; + "ucan/dlg@1.0.0-rc.1": Omit & { + cmd: C; + }; + } +];