@orangecheck/agent-core 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +47 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -2
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.mts +9 -1
- package/dist/types.d.ts +9 -1
- package/dist/types.js.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/live-time.test.ts +151 -0
- package/src/revocation-authority.test.ts +15 -13
- package/src/revocation-failclosed.test.ts +1 -1
- package/src/test-vectors.test.ts +43 -8
- package/src/types.ts +9 -0
- package/src/verify.ts +100 -0
package/dist/types.d.mts
CHANGED
|
@@ -153,7 +153,7 @@ interface RevocationCanonicalInput {
|
|
|
153
153
|
reason: string;
|
|
154
154
|
signed_at: string;
|
|
155
155
|
}
|
|
156
|
-
type AgentErrorCode = 'E_UNSUPPORTED_VERSION' | 'E_MALFORMED' | 'E_BAD_ID' | 'E_BAD_SIG' | 'E_BAD_SCOPE_GRAMMAR' | 'E_NOT_YET_VALID' | 'E_EXPIRED' | 'E_REVOKED' | 'E_DELEGATION_MISMATCH' | 'E_AGENT_MISMATCH' | 'E_OUT_OF_WINDOW' | 'E_SCOPE_DENIED' | 'E_BAD_ACTION_STAMP' | 'E_NO_BOND' | 'E_BOND_UNMET' | 'E_BOND_UNVERIFIED' | 'E_REVOKER_UNAUTHORIZED' | 'E_CALENDAR_UNREACHABLE' | 'E_SUBDELEGATION_DEPTH_EXCEEDED' | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH' | 'E_SUBDELEGATION_EXPIRES_EXTENDED' | 'E_SUBDELEGATION_SCOPE_ESCALATED' | 'E_SCOPES_BOTH_PROVIDED' | 'E_SCOPES_NEITHER_PROVIDED' | 'E_SCOPES_UNREADABLE' | 'E_BAD_LOCK_ENVELOPE' | 'E_BAD_FEDERATION_DESCRIPTOR' | 'E_THRESHOLD_MISMATCH' | 'E_THRESHOLD_NOT_MET' | 'E_UNKNOWN_GUARDIAN' | 'E_DUPLICATE_GUARDIAN';
|
|
156
|
+
type AgentErrorCode = 'E_UNSUPPORTED_VERSION' | 'E_MALFORMED' | 'E_BAD_ID' | 'E_BAD_SIG' | 'E_BAD_SCOPE_GRAMMAR' | 'E_NOT_YET_VALID' | 'E_EXPIRED' | 'E_REVOKED' | 'E_STALE' | 'E_DELEGATION_MISMATCH' | 'E_AGENT_MISMATCH' | 'E_OUT_OF_WINDOW' | 'E_SCOPE_DENIED' | 'E_BAD_ACTION_STAMP' | 'E_NO_BOND' | 'E_BOND_UNMET' | 'E_BOND_UNVERIFIED' | 'E_REVOKER_UNAUTHORIZED' | 'E_CALENDAR_UNREACHABLE' | 'E_SUBDELEGATION_DEPTH_EXCEEDED' | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH' | 'E_SUBDELEGATION_EXPIRES_EXTENDED' | 'E_SUBDELEGATION_SCOPE_ESCALATED' | 'E_SCOPES_BOTH_PROVIDED' | 'E_SCOPES_NEITHER_PROVIDED' | 'E_SCOPES_UNREADABLE' | 'E_BAD_LOCK_ENVELOPE' | 'E_BAD_FEDERATION_DESCRIPTOR' | 'E_THRESHOLD_MISMATCH' | 'E_THRESHOLD_NOT_MET' | 'E_UNKNOWN_GUARDIAN' | 'E_DUPLICATE_GUARDIAN';
|
|
157
157
|
interface VerifyOk<T> {
|
|
158
158
|
ok: true;
|
|
159
159
|
envelope: T;
|
|
@@ -187,6 +187,14 @@ interface VerifyActionOkExtra {
|
|
|
187
187
|
blockHash: string;
|
|
188
188
|
verified: true;
|
|
189
189
|
};
|
|
190
|
+
asOf: {
|
|
191
|
+
basis: 'now';
|
|
192
|
+
now: string;
|
|
193
|
+
} | {
|
|
194
|
+
basis: 'signed_at';
|
|
195
|
+
signedAt: string;
|
|
196
|
+
anchored: boolean;
|
|
197
|
+
};
|
|
190
198
|
}
|
|
191
199
|
type VerifyActionResult = (VerifyOk<ActionEnvelope> & VerifyActionOkExtra) | VerifyErr;
|
|
192
200
|
|
package/dist/types.d.ts
CHANGED
|
@@ -153,7 +153,7 @@ interface RevocationCanonicalInput {
|
|
|
153
153
|
reason: string;
|
|
154
154
|
signed_at: string;
|
|
155
155
|
}
|
|
156
|
-
type AgentErrorCode = 'E_UNSUPPORTED_VERSION' | 'E_MALFORMED' | 'E_BAD_ID' | 'E_BAD_SIG' | 'E_BAD_SCOPE_GRAMMAR' | 'E_NOT_YET_VALID' | 'E_EXPIRED' | 'E_REVOKED' | 'E_DELEGATION_MISMATCH' | 'E_AGENT_MISMATCH' | 'E_OUT_OF_WINDOW' | 'E_SCOPE_DENIED' | 'E_BAD_ACTION_STAMP' | 'E_NO_BOND' | 'E_BOND_UNMET' | 'E_BOND_UNVERIFIED' | 'E_REVOKER_UNAUTHORIZED' | 'E_CALENDAR_UNREACHABLE' | 'E_SUBDELEGATION_DEPTH_EXCEEDED' | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH' | 'E_SUBDELEGATION_EXPIRES_EXTENDED' | 'E_SUBDELEGATION_SCOPE_ESCALATED' | 'E_SCOPES_BOTH_PROVIDED' | 'E_SCOPES_NEITHER_PROVIDED' | 'E_SCOPES_UNREADABLE' | 'E_BAD_LOCK_ENVELOPE' | 'E_BAD_FEDERATION_DESCRIPTOR' | 'E_THRESHOLD_MISMATCH' | 'E_THRESHOLD_NOT_MET' | 'E_UNKNOWN_GUARDIAN' | 'E_DUPLICATE_GUARDIAN';
|
|
156
|
+
type AgentErrorCode = 'E_UNSUPPORTED_VERSION' | 'E_MALFORMED' | 'E_BAD_ID' | 'E_BAD_SIG' | 'E_BAD_SCOPE_GRAMMAR' | 'E_NOT_YET_VALID' | 'E_EXPIRED' | 'E_REVOKED' | 'E_STALE' | 'E_DELEGATION_MISMATCH' | 'E_AGENT_MISMATCH' | 'E_OUT_OF_WINDOW' | 'E_SCOPE_DENIED' | 'E_BAD_ACTION_STAMP' | 'E_NO_BOND' | 'E_BOND_UNMET' | 'E_BOND_UNVERIFIED' | 'E_REVOKER_UNAUTHORIZED' | 'E_CALENDAR_UNREACHABLE' | 'E_SUBDELEGATION_DEPTH_EXCEEDED' | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH' | 'E_SUBDELEGATION_EXPIRES_EXTENDED' | 'E_SUBDELEGATION_SCOPE_ESCALATED' | 'E_SCOPES_BOTH_PROVIDED' | 'E_SCOPES_NEITHER_PROVIDED' | 'E_SCOPES_UNREADABLE' | 'E_BAD_LOCK_ENVELOPE' | 'E_BAD_FEDERATION_DESCRIPTOR' | 'E_THRESHOLD_MISMATCH' | 'E_THRESHOLD_NOT_MET' | 'E_UNKNOWN_GUARDIAN' | 'E_DUPLICATE_GUARDIAN';
|
|
157
157
|
interface VerifyOk<T> {
|
|
158
158
|
ok: true;
|
|
159
159
|
envelope: T;
|
|
@@ -187,6 +187,14 @@ interface VerifyActionOkExtra {
|
|
|
187
187
|
blockHash: string;
|
|
188
188
|
verified: true;
|
|
189
189
|
};
|
|
190
|
+
asOf: {
|
|
191
|
+
basis: 'now';
|
|
192
|
+
now: string;
|
|
193
|
+
} | {
|
|
194
|
+
basis: 'signed_at';
|
|
195
|
+
signedAt: string;
|
|
196
|
+
anchored: boolean;
|
|
197
|
+
};
|
|
190
198
|
}
|
|
191
199
|
type VerifyActionResult = (VerifyOk<ActionEnvelope> & VerifyActionOkExtra) | VerifyErr;
|
|
192
200
|
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";;;AAEO,IAAM,gBAAA,GAAmB","file":"types.js","sourcesContent":["// Wire types for OC Agent v1 envelopes. See SPEC.md §4, §5, §9.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind =\n | 'agent-delegation'\n | 'agent-action'\n | 'agent-revocation'\n | 'agent-subdelegation';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared building blocks\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActorRef {\n /** mainnet Bitcoin address (P2WPKH, P2TR, or P2PKH). */\n address: string;\n alg: 'bip322';\n}\n\nexport interface Signature {\n alg: 'bip322';\n pubkey: string; // equals the producing actor's address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport type RevocationHolder = 'principal' | 'agent';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Delegation (SPEC §4)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface DelegationBond {\n /** Non-negative sats declared as bonded at issuance time. */\n sats: number;\n /** SHA-256 hex of the OrangeCheck canonical message signed by principal.address. */\n attestation_id: string;\n}\n\nexport interface DelegationRevocationRef {\n /** Who MAY publish a revocation. Default [\"principal\"]. */\n holders: RevocationHolder[];\n /** Optional Nostr-addressable pointer to a published revocation. Non-cryptographic. */\n ref: string | null;\n}\n\n/**\n * v1.2 private-scope mode: a wholesale OC Lock v2 LockEnvelope wrapping the\n * canonical scope list as its payload. We re-import the LockEnvelope type\n * structurally rather than depending on the @orangecheck/lock-core type — agent-\n * core's type surface stays loose so verifiers can be authored in either an\n * agent-only or full-family setup.\n */\nexport interface ScopesEncryptedEnvelope {\n v: 2;\n kind: 'identity';\n id: string;\n alg: { kem: 'x25519'; aead: 'aes-256-gcm'; kdf: 'hkdf-sha256' };\n from: { address: string; attestation_id?: string };\n recipients: Array<{\n address: string;\n device_id: string;\n device_pk: string;\n eph_pk: string;\n wrapped_key: string;\n nonce_kek: string;\n }>;\n ciphertext: string;\n nonce_ct: string;\n hint?: string;\n created_at: string;\n expires_at: string | null;\n payment: unknown | null;\n sig: { alg: 'bip322'; pubkey: string; value: string };\n}\n\nexport interface DelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-delegation';\n id: string; // 64-hex sha256(canonical_message)\n principal: ActorRef;\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: sorted lexicographically in the canonical\n * message; stored in sorted order on the envelope too.\n *\n * v1.2 private mode: this field is OMITTED from the envelope JSON;\n * `scopes_encrypted` is set instead. After decryption, the recovered\n * scope list takes this field's place in the in-memory envelope object\n * for the remainder of verification.\n */\n scopes?: string[];\n /**\n * v1.2 private mode (PRIVATE-SCOPE.md §1.1): an OC Lock v2 envelope\n * wrapping the canonical scope-list bytes as its payload, sealed to one\n * or more recipients (typically the agent ± named verifiers). MUTUALLY\n * EXCLUSIVE with `scopes`.\n */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n bond: DelegationBond | null;\n issued_at: string; // ISO 8601 UTC\n expires_at: string; // ISO 8601 UTC\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface DelegationCanonicalInput {\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n bond_sats: number;\n /** 64-hex attestation id or the literal string \"none\". */\n bond_attestation: string;\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Agent-action (SPEC §5) — strict extension of OC Stamp\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActionContent {\n hash: string; // \"sha256:<64-hex>\"\n length: number;\n mime: string;\n ref: string | null;\n}\n\nexport interface ActionOts {\n status: 'pending' | 'confirmed';\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n upgraded_at: string | null;\n}\n\nexport interface ActionEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-action';\n id: string;\n content: ActionContent;\n signer: ActorRef; // agent\n signed_at: string;\n delegation_id: string; // 64-hex\n scope_exercised: string; // a sub-scope of some granted scope\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface ActionCanonicalInput {\n address: string; // agent address\n content_hash: string;\n content_length: number;\n content_mime: string;\n signed_at: string;\n delegation_id: string;\n scope_exercised: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-delegation (SUB-DELEGATION.md, v1.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SubdelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-subdelegation';\n id: string; // 64-hex sha256(canonical_message)\n parent_id: string; // 64-hex; the immediate parent envelope's id\n /** The sub-principal — equal to parent.agent.address. */\n principal: ActorRef;\n /** The recipient sub-agent. */\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: each scope MUST be a sub-scope of some scope\n * on the parent. v1.2 private mode: omitted; `scopes_encrypted` set.\n */\n scopes?: string[];\n /** v1.2 private mode — same shape as the root delegation field. */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n issued_at: string; // ISO 8601 UTC; >= parent.issued_at\n expires_at: string; // ISO 8601 UTC; <= parent.expires_at\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface SubdelegationCanonicalInput {\n parent_id: string;\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n/** Either a root or a sub envelope; chain links walk up to a root delegation. */\nexport type ChainLink = DelegationEnvelope | SubdelegationEnvelope;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Revocation (SPEC §9)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevocationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-revocation';\n id: string;\n delegation_id: string;\n signer: ActorRef;\n /** Short ASCII rationale, <= 128 bytes. Empty string if omitted. */\n reason: string;\n signed_at: string;\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface RevocationCanonicalInput {\n address: string;\n delegation_id: string;\n reason: string;\n signed_at: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error codes (SPEC §11)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type AgentErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_SCOPE_GRAMMAR'\n | 'E_NOT_YET_VALID'\n | 'E_EXPIRED'\n | 'E_REVOKED'\n | 'E_DELEGATION_MISMATCH'\n | 'E_AGENT_MISMATCH'\n | 'E_OUT_OF_WINDOW'\n | 'E_SCOPE_DENIED'\n | 'E_BAD_ACTION_STAMP'\n | 'E_NO_BOND'\n | 'E_BOND_UNMET'\n | 'E_BOND_UNVERIFIED'\n | 'E_REVOKER_UNAUTHORIZED'\n | 'E_CALENDAR_UNREACHABLE'\n | 'E_SUBDELEGATION_DEPTH_EXCEEDED'\n | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH'\n | 'E_SUBDELEGATION_EXPIRES_EXTENDED'\n | 'E_SUBDELEGATION_SCOPE_ESCALATED'\n | 'E_SCOPES_BOTH_PROVIDED'\n | 'E_SCOPES_NEITHER_PROVIDED'\n | 'E_SCOPES_UNREADABLE'\n | 'E_BAD_LOCK_ENVELOPE'\n // v1.2 federation principal (FEDERATION.md §3.3)\n | 'E_BAD_FEDERATION_DESCRIPTOR'\n | 'E_THRESHOLD_MISMATCH'\n | 'E_THRESHOLD_NOT_MET'\n | 'E_UNKNOWN_GUARDIAN'\n | 'E_DUPLICATE_GUARDIAN';\n\nexport interface VerifyOk<T> {\n ok: true;\n envelope: T;\n canonicalMessage: string;\n id: string;\n}\n\nexport interface VerifyErr {\n ok: false;\n code: AgentErrorCode;\n message: string;\n}\n\nexport type VerifyDelegationResult = VerifyOk<DelegationEnvelope> | VerifyErr;\nexport type VerifyRevocationResult = VerifyOk<RevocationEnvelope> | VerifyErr;\nexport type VerifySubdelegationResult = VerifyOk<SubdelegationEnvelope> | VerifyErr;\n\nexport interface VerifyActionOkExtra {\n /** The ROOT delegation rooting the authority chain. */\n delegation: DelegationEnvelope;\n /**\n * The sub-delegation chain `[S_1, …, S_leaf]`. Empty when the action cites\n * the root directly. The action's authority leaf is\n * `chain[chain.length - 1] ?? delegation`.\n */\n chain: SubdelegationEnvelope[];\n scopeExercised: string;\n /**\n * `claimed` is a confirmed `ots` block no verifier walked: the block is the\n * envelope's say-so. Only a passing `verifyOtsAnchor` yields `confirmed`.\n */\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'claimed'; blockHeight: number; blockHash: string; verified: false }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: true };\n}\n\nexport type VerifyActionResult =\n | (VerifyOk<ActionEnvelope> & VerifyActionOkExtra)\n | VerifyErr;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";;;AAEO,IAAM,gBAAA,GAAmB","file":"types.js","sourcesContent":["// Wire types for OC Agent v1 envelopes. See SPEC.md §4, §5, §9.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind =\n | 'agent-delegation'\n | 'agent-action'\n | 'agent-revocation'\n | 'agent-subdelegation';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared building blocks\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActorRef {\n /** mainnet Bitcoin address (P2WPKH, P2TR, or P2PKH). */\n address: string;\n alg: 'bip322';\n}\n\nexport interface Signature {\n alg: 'bip322';\n pubkey: string; // equals the producing actor's address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport type RevocationHolder = 'principal' | 'agent';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Delegation (SPEC §4)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface DelegationBond {\n /** Non-negative sats declared as bonded at issuance time. */\n sats: number;\n /** SHA-256 hex of the OrangeCheck canonical message signed by principal.address. */\n attestation_id: string;\n}\n\nexport interface DelegationRevocationRef {\n /** Who MAY publish a revocation. Default [\"principal\"]. */\n holders: RevocationHolder[];\n /** Optional Nostr-addressable pointer to a published revocation. Non-cryptographic. */\n ref: string | null;\n}\n\n/**\n * v1.2 private-scope mode: a wholesale OC Lock v2 LockEnvelope wrapping the\n * canonical scope list as its payload. We re-import the LockEnvelope type\n * structurally rather than depending on the @orangecheck/lock-core type — agent-\n * core's type surface stays loose so verifiers can be authored in either an\n * agent-only or full-family setup.\n */\nexport interface ScopesEncryptedEnvelope {\n v: 2;\n kind: 'identity';\n id: string;\n alg: { kem: 'x25519'; aead: 'aes-256-gcm'; kdf: 'hkdf-sha256' };\n from: { address: string; attestation_id?: string };\n recipients: Array<{\n address: string;\n device_id: string;\n device_pk: string;\n eph_pk: string;\n wrapped_key: string;\n nonce_kek: string;\n }>;\n ciphertext: string;\n nonce_ct: string;\n hint?: string;\n created_at: string;\n expires_at: string | null;\n payment: unknown | null;\n sig: { alg: 'bip322'; pubkey: string; value: string };\n}\n\nexport interface DelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-delegation';\n id: string; // 64-hex sha256(canonical_message)\n principal: ActorRef;\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: sorted lexicographically in the canonical\n * message; stored in sorted order on the envelope too.\n *\n * v1.2 private mode: this field is OMITTED from the envelope JSON;\n * `scopes_encrypted` is set instead. After decryption, the recovered\n * scope list takes this field's place in the in-memory envelope object\n * for the remainder of verification.\n */\n scopes?: string[];\n /**\n * v1.2 private mode (PRIVATE-SCOPE.md §1.1): an OC Lock v2 envelope\n * wrapping the canonical scope-list bytes as its payload, sealed to one\n * or more recipients (typically the agent ± named verifiers). MUTUALLY\n * EXCLUSIVE with `scopes`.\n */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n bond: DelegationBond | null;\n issued_at: string; // ISO 8601 UTC\n expires_at: string; // ISO 8601 UTC\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface DelegationCanonicalInput {\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n bond_sats: number;\n /** 64-hex attestation id or the literal string \"none\". */\n bond_attestation: string;\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Agent-action (SPEC §5) — strict extension of OC Stamp\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActionContent {\n hash: string; // \"sha256:<64-hex>\"\n length: number;\n mime: string;\n ref: string | null;\n}\n\nexport interface ActionOts {\n status: 'pending' | 'confirmed';\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n upgraded_at: string | null;\n}\n\nexport interface ActionEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-action';\n id: string;\n content: ActionContent;\n signer: ActorRef; // agent\n signed_at: string;\n delegation_id: string; // 64-hex\n scope_exercised: string; // a sub-scope of some granted scope\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface ActionCanonicalInput {\n address: string; // agent address\n content_hash: string;\n content_length: number;\n content_mime: string;\n signed_at: string;\n delegation_id: string;\n scope_exercised: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-delegation (SUB-DELEGATION.md, v1.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SubdelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-subdelegation';\n id: string; // 64-hex sha256(canonical_message)\n parent_id: string; // 64-hex; the immediate parent envelope's id\n /** The sub-principal — equal to parent.agent.address. */\n principal: ActorRef;\n /** The recipient sub-agent. */\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: each scope MUST be a sub-scope of some scope\n * on the parent. v1.2 private mode: omitted; `scopes_encrypted` set.\n */\n scopes?: string[];\n /** v1.2 private mode — same shape as the root delegation field. */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n issued_at: string; // ISO 8601 UTC; >= parent.issued_at\n expires_at: string; // ISO 8601 UTC; <= parent.expires_at\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface SubdelegationCanonicalInput {\n parent_id: string;\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n/** Either a root or a sub envelope; chain links walk up to a root delegation. */\nexport type ChainLink = DelegationEnvelope | SubdelegationEnvelope;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Revocation (SPEC §9)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevocationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-revocation';\n id: string;\n delegation_id: string;\n signer: ActorRef;\n /** Short ASCII rationale, <= 128 bytes. Empty string if omitted. */\n reason: string;\n signed_at: string;\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface RevocationCanonicalInput {\n address: string;\n delegation_id: string;\n reason: string;\n signed_at: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error codes (SPEC §11)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type AgentErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_SCOPE_GRAMMAR'\n | 'E_NOT_YET_VALID'\n | 'E_EXPIRED'\n | 'E_REVOKED'\n | 'E_STALE'\n | 'E_DELEGATION_MISMATCH'\n | 'E_AGENT_MISMATCH'\n | 'E_OUT_OF_WINDOW'\n | 'E_SCOPE_DENIED'\n | 'E_BAD_ACTION_STAMP'\n | 'E_NO_BOND'\n | 'E_BOND_UNMET'\n | 'E_BOND_UNVERIFIED'\n | 'E_REVOKER_UNAUTHORIZED'\n | 'E_CALENDAR_UNREACHABLE'\n | 'E_SUBDELEGATION_DEPTH_EXCEEDED'\n | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH'\n | 'E_SUBDELEGATION_EXPIRES_EXTENDED'\n | 'E_SUBDELEGATION_SCOPE_ESCALATED'\n | 'E_SCOPES_BOTH_PROVIDED'\n | 'E_SCOPES_NEITHER_PROVIDED'\n | 'E_SCOPES_UNREADABLE'\n | 'E_BAD_LOCK_ENVELOPE'\n // v1.2 federation principal (FEDERATION.md §3.3)\n | 'E_BAD_FEDERATION_DESCRIPTOR'\n | 'E_THRESHOLD_MISMATCH'\n | 'E_THRESHOLD_NOT_MET'\n | 'E_UNKNOWN_GUARDIAN'\n | 'E_DUPLICATE_GUARDIAN';\n\nexport interface VerifyOk<T> {\n ok: true;\n envelope: T;\n canonicalMessage: string;\n id: string;\n}\n\nexport interface VerifyErr {\n ok: false;\n code: AgentErrorCode;\n message: string;\n}\n\nexport type VerifyDelegationResult = VerifyOk<DelegationEnvelope> | VerifyErr;\nexport type VerifyRevocationResult = VerifyOk<RevocationEnvelope> | VerifyErr;\nexport type VerifySubdelegationResult = VerifyOk<SubdelegationEnvelope> | VerifyErr;\n\nexport interface VerifyActionOkExtra {\n /** The ROOT delegation rooting the authority chain. */\n delegation: DelegationEnvelope;\n /**\n * The sub-delegation chain `[S_1, …, S_leaf]`. Empty when the action cites\n * the root directly. The action's authority leaf is\n * `chain[chain.length - 1] ?? delegation`.\n */\n chain: SubdelegationEnvelope[];\n scopeExercised: string;\n /**\n * `claimed` is a confirmed `ots` block no verifier walked: the block is the\n * envelope's say-so. Only a passing `verifyOtsAnchor` yields `confirmed`.\n */\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'claimed'; blockHeight: number; blockHash: string; verified: false }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: true };\n /**\n * The time the verdict holds for. `now`: verifyAction, checked live.\n * `signed_at`: verifyActionAsOfSignedAt, checked as of the time the agent\n * claims; `anchored` is false when only the agent says so.\n */\n asOf:\n | { basis: 'now'; now: string }\n | { basis: 'signed_at'; signedAt: string; anchored: boolean };\n}\n\nexport type VerifyActionResult =\n | (VerifyOk<ActionEnvelope> & VerifyActionOkExtra)\n | VerifyErr;\n"]}
|
package/dist/types.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";AAEO,IAAM,gBAAA,GAAmB","file":"types.mjs","sourcesContent":["// Wire types for OC Agent v1 envelopes. See SPEC.md §4, §5, §9.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind =\n | 'agent-delegation'\n | 'agent-action'\n | 'agent-revocation'\n | 'agent-subdelegation';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared building blocks\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActorRef {\n /** mainnet Bitcoin address (P2WPKH, P2TR, or P2PKH). */\n address: string;\n alg: 'bip322';\n}\n\nexport interface Signature {\n alg: 'bip322';\n pubkey: string; // equals the producing actor's address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport type RevocationHolder = 'principal' | 'agent';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Delegation (SPEC §4)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface DelegationBond {\n /** Non-negative sats declared as bonded at issuance time. */\n sats: number;\n /** SHA-256 hex of the OrangeCheck canonical message signed by principal.address. */\n attestation_id: string;\n}\n\nexport interface DelegationRevocationRef {\n /** Who MAY publish a revocation. Default [\"principal\"]. */\n holders: RevocationHolder[];\n /** Optional Nostr-addressable pointer to a published revocation. Non-cryptographic. */\n ref: string | null;\n}\n\n/**\n * v1.2 private-scope mode: a wholesale OC Lock v2 LockEnvelope wrapping the\n * canonical scope list as its payload. We re-import the LockEnvelope type\n * structurally rather than depending on the @orangecheck/lock-core type — agent-\n * core's type surface stays loose so verifiers can be authored in either an\n * agent-only or full-family setup.\n */\nexport interface ScopesEncryptedEnvelope {\n v: 2;\n kind: 'identity';\n id: string;\n alg: { kem: 'x25519'; aead: 'aes-256-gcm'; kdf: 'hkdf-sha256' };\n from: { address: string; attestation_id?: string };\n recipients: Array<{\n address: string;\n device_id: string;\n device_pk: string;\n eph_pk: string;\n wrapped_key: string;\n nonce_kek: string;\n }>;\n ciphertext: string;\n nonce_ct: string;\n hint?: string;\n created_at: string;\n expires_at: string | null;\n payment: unknown | null;\n sig: { alg: 'bip322'; pubkey: string; value: string };\n}\n\nexport interface DelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-delegation';\n id: string; // 64-hex sha256(canonical_message)\n principal: ActorRef;\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: sorted lexicographically in the canonical\n * message; stored in sorted order on the envelope too.\n *\n * v1.2 private mode: this field is OMITTED from the envelope JSON;\n * `scopes_encrypted` is set instead. After decryption, the recovered\n * scope list takes this field's place in the in-memory envelope object\n * for the remainder of verification.\n */\n scopes?: string[];\n /**\n * v1.2 private mode (PRIVATE-SCOPE.md §1.1): an OC Lock v2 envelope\n * wrapping the canonical scope-list bytes as its payload, sealed to one\n * or more recipients (typically the agent ± named verifiers). MUTUALLY\n * EXCLUSIVE with `scopes`.\n */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n bond: DelegationBond | null;\n issued_at: string; // ISO 8601 UTC\n expires_at: string; // ISO 8601 UTC\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface DelegationCanonicalInput {\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n bond_sats: number;\n /** 64-hex attestation id or the literal string \"none\". */\n bond_attestation: string;\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Agent-action (SPEC §5) — strict extension of OC Stamp\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActionContent {\n hash: string; // \"sha256:<64-hex>\"\n length: number;\n mime: string;\n ref: string | null;\n}\n\nexport interface ActionOts {\n status: 'pending' | 'confirmed';\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n upgraded_at: string | null;\n}\n\nexport interface ActionEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-action';\n id: string;\n content: ActionContent;\n signer: ActorRef; // agent\n signed_at: string;\n delegation_id: string; // 64-hex\n scope_exercised: string; // a sub-scope of some granted scope\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface ActionCanonicalInput {\n address: string; // agent address\n content_hash: string;\n content_length: number;\n content_mime: string;\n signed_at: string;\n delegation_id: string;\n scope_exercised: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-delegation (SUB-DELEGATION.md, v1.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SubdelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-subdelegation';\n id: string; // 64-hex sha256(canonical_message)\n parent_id: string; // 64-hex; the immediate parent envelope's id\n /** The sub-principal — equal to parent.agent.address. */\n principal: ActorRef;\n /** The recipient sub-agent. */\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: each scope MUST be a sub-scope of some scope\n * on the parent. v1.2 private mode: omitted; `scopes_encrypted` set.\n */\n scopes?: string[];\n /** v1.2 private mode — same shape as the root delegation field. */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n issued_at: string; // ISO 8601 UTC; >= parent.issued_at\n expires_at: string; // ISO 8601 UTC; <= parent.expires_at\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface SubdelegationCanonicalInput {\n parent_id: string;\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n/** Either a root or a sub envelope; chain links walk up to a root delegation. */\nexport type ChainLink = DelegationEnvelope | SubdelegationEnvelope;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Revocation (SPEC §9)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevocationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-revocation';\n id: string;\n delegation_id: string;\n signer: ActorRef;\n /** Short ASCII rationale, <= 128 bytes. Empty string if omitted. */\n reason: string;\n signed_at: string;\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface RevocationCanonicalInput {\n address: string;\n delegation_id: string;\n reason: string;\n signed_at: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error codes (SPEC §11)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type AgentErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_SCOPE_GRAMMAR'\n | 'E_NOT_YET_VALID'\n | 'E_EXPIRED'\n | 'E_REVOKED'\n | 'E_DELEGATION_MISMATCH'\n | 'E_AGENT_MISMATCH'\n | 'E_OUT_OF_WINDOW'\n | 'E_SCOPE_DENIED'\n | 'E_BAD_ACTION_STAMP'\n | 'E_NO_BOND'\n | 'E_BOND_UNMET'\n | 'E_BOND_UNVERIFIED'\n | 'E_REVOKER_UNAUTHORIZED'\n | 'E_CALENDAR_UNREACHABLE'\n | 'E_SUBDELEGATION_DEPTH_EXCEEDED'\n | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH'\n | 'E_SUBDELEGATION_EXPIRES_EXTENDED'\n | 'E_SUBDELEGATION_SCOPE_ESCALATED'\n | 'E_SCOPES_BOTH_PROVIDED'\n | 'E_SCOPES_NEITHER_PROVIDED'\n | 'E_SCOPES_UNREADABLE'\n | 'E_BAD_LOCK_ENVELOPE'\n // v1.2 federation principal (FEDERATION.md §3.3)\n | 'E_BAD_FEDERATION_DESCRIPTOR'\n | 'E_THRESHOLD_MISMATCH'\n | 'E_THRESHOLD_NOT_MET'\n | 'E_UNKNOWN_GUARDIAN'\n | 'E_DUPLICATE_GUARDIAN';\n\nexport interface VerifyOk<T> {\n ok: true;\n envelope: T;\n canonicalMessage: string;\n id: string;\n}\n\nexport interface VerifyErr {\n ok: false;\n code: AgentErrorCode;\n message: string;\n}\n\nexport type VerifyDelegationResult = VerifyOk<DelegationEnvelope> | VerifyErr;\nexport type VerifyRevocationResult = VerifyOk<RevocationEnvelope> | VerifyErr;\nexport type VerifySubdelegationResult = VerifyOk<SubdelegationEnvelope> | VerifyErr;\n\nexport interface VerifyActionOkExtra {\n /** The ROOT delegation rooting the authority chain. */\n delegation: DelegationEnvelope;\n /**\n * The sub-delegation chain `[S_1, …, S_leaf]`. Empty when the action cites\n * the root directly. The action's authority leaf is\n * `chain[chain.length - 1] ?? delegation`.\n */\n chain: SubdelegationEnvelope[];\n scopeExercised: string;\n /**\n * `claimed` is a confirmed `ots` block no verifier walked: the block is the\n * envelope's say-so. Only a passing `verifyOtsAnchor` yields `confirmed`.\n */\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'claimed'; blockHeight: number; blockHash: string; verified: false }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: true };\n}\n\nexport type VerifyActionResult =\n | (VerifyOk<ActionEnvelope> & VerifyActionOkExtra)\n | VerifyErr;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";AAEO,IAAM,gBAAA,GAAmB","file":"types.mjs","sourcesContent":["// Wire types for OC Agent v1 envelopes. See SPEC.md §4, §5, §9.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind =\n | 'agent-delegation'\n | 'agent-action'\n | 'agent-revocation'\n | 'agent-subdelegation';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared building blocks\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActorRef {\n /** mainnet Bitcoin address (P2WPKH, P2TR, or P2PKH). */\n address: string;\n alg: 'bip322';\n}\n\nexport interface Signature {\n alg: 'bip322';\n pubkey: string; // equals the producing actor's address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport type RevocationHolder = 'principal' | 'agent';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Delegation (SPEC §4)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface DelegationBond {\n /** Non-negative sats declared as bonded at issuance time. */\n sats: number;\n /** SHA-256 hex of the OrangeCheck canonical message signed by principal.address. */\n attestation_id: string;\n}\n\nexport interface DelegationRevocationRef {\n /** Who MAY publish a revocation. Default [\"principal\"]. */\n holders: RevocationHolder[];\n /** Optional Nostr-addressable pointer to a published revocation. Non-cryptographic. */\n ref: string | null;\n}\n\n/**\n * v1.2 private-scope mode: a wholesale OC Lock v2 LockEnvelope wrapping the\n * canonical scope list as its payload. We re-import the LockEnvelope type\n * structurally rather than depending on the @orangecheck/lock-core type — agent-\n * core's type surface stays loose so verifiers can be authored in either an\n * agent-only or full-family setup.\n */\nexport interface ScopesEncryptedEnvelope {\n v: 2;\n kind: 'identity';\n id: string;\n alg: { kem: 'x25519'; aead: 'aes-256-gcm'; kdf: 'hkdf-sha256' };\n from: { address: string; attestation_id?: string };\n recipients: Array<{\n address: string;\n device_id: string;\n device_pk: string;\n eph_pk: string;\n wrapped_key: string;\n nonce_kek: string;\n }>;\n ciphertext: string;\n nonce_ct: string;\n hint?: string;\n created_at: string;\n expires_at: string | null;\n payment: unknown | null;\n sig: { alg: 'bip322'; pubkey: string; value: string };\n}\n\nexport interface DelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-delegation';\n id: string; // 64-hex sha256(canonical_message)\n principal: ActorRef;\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: sorted lexicographically in the canonical\n * message; stored in sorted order on the envelope too.\n *\n * v1.2 private mode: this field is OMITTED from the envelope JSON;\n * `scopes_encrypted` is set instead. After decryption, the recovered\n * scope list takes this field's place in the in-memory envelope object\n * for the remainder of verification.\n */\n scopes?: string[];\n /**\n * v1.2 private mode (PRIVATE-SCOPE.md §1.1): an OC Lock v2 envelope\n * wrapping the canonical scope-list bytes as its payload, sealed to one\n * or more recipients (typically the agent ± named verifiers). MUTUALLY\n * EXCLUSIVE with `scopes`.\n */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n bond: DelegationBond | null;\n issued_at: string; // ISO 8601 UTC\n expires_at: string; // ISO 8601 UTC\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface DelegationCanonicalInput {\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n bond_sats: number;\n /** 64-hex attestation id or the literal string \"none\". */\n bond_attestation: string;\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Agent-action (SPEC §5) — strict extension of OC Stamp\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ActionContent {\n hash: string; // \"sha256:<64-hex>\"\n length: number;\n mime: string;\n ref: string | null;\n}\n\nexport interface ActionOts {\n status: 'pending' | 'confirmed';\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n upgraded_at: string | null;\n}\n\nexport interface ActionEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-action';\n id: string;\n content: ActionContent;\n signer: ActorRef; // agent\n signed_at: string;\n delegation_id: string; // 64-hex\n scope_exercised: string; // a sub-scope of some granted scope\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface ActionCanonicalInput {\n address: string; // agent address\n content_hash: string;\n content_length: number;\n content_mime: string;\n signed_at: string;\n delegation_id: string;\n scope_exercised: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-delegation (SUB-DELEGATION.md, v1.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SubdelegationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-subdelegation';\n id: string; // 64-hex sha256(canonical_message)\n parent_id: string; // 64-hex; the immediate parent envelope's id\n /** The sub-principal — equal to parent.agent.address. */\n principal: ActorRef;\n /** The recipient sub-agent. */\n agent: ActorRef;\n /**\n * v1.0 / v1.1 public mode: each scope MUST be a sub-scope of some scope\n * on the parent. v1.2 private mode: omitted; `scopes_encrypted` set.\n */\n scopes?: string[];\n /** v1.2 private mode — same shape as the root delegation field. */\n scopes_encrypted?: ScopesEncryptedEnvelope;\n issued_at: string; // ISO 8601 UTC; >= parent.issued_at\n expires_at: string; // ISO 8601 UTC; <= parent.expires_at\n nonce: string; // 32-hex random\n revocation: DelegationRevocationRef;\n sig: Signature;\n}\n\nexport interface SubdelegationCanonicalInput {\n parent_id: string;\n principal: string;\n agent: string;\n scopes: string[]; // pre-canonicalized, pre-sorted\n issued_at: string;\n expires_at: string;\n nonce: string;\n}\n\n/** Either a root or a sub envelope; chain links walk up to a root delegation. */\nexport type ChainLink = DelegationEnvelope | SubdelegationEnvelope;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Revocation (SPEC §9)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevocationEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: 'agent-revocation';\n id: string;\n delegation_id: string;\n signer: ActorRef;\n /** Short ASCII rationale, <= 128 bytes. Empty string if omitted. */\n reason: string;\n signed_at: string;\n ots: ActionOts | null;\n sig: Signature;\n}\n\nexport interface RevocationCanonicalInput {\n address: string;\n delegation_id: string;\n reason: string;\n signed_at: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Error codes (SPEC §11)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type AgentErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_SCOPE_GRAMMAR'\n | 'E_NOT_YET_VALID'\n | 'E_EXPIRED'\n | 'E_REVOKED'\n | 'E_STALE'\n | 'E_DELEGATION_MISMATCH'\n | 'E_AGENT_MISMATCH'\n | 'E_OUT_OF_WINDOW'\n | 'E_SCOPE_DENIED'\n | 'E_BAD_ACTION_STAMP'\n | 'E_NO_BOND'\n | 'E_BOND_UNMET'\n | 'E_BOND_UNVERIFIED'\n | 'E_REVOKER_UNAUTHORIZED'\n | 'E_CALENDAR_UNREACHABLE'\n | 'E_SUBDELEGATION_DEPTH_EXCEEDED'\n | 'E_SUBDELEGATION_PRINCIPAL_MISMATCH'\n | 'E_SUBDELEGATION_EXPIRES_EXTENDED'\n | 'E_SUBDELEGATION_SCOPE_ESCALATED'\n | 'E_SCOPES_BOTH_PROVIDED'\n | 'E_SCOPES_NEITHER_PROVIDED'\n | 'E_SCOPES_UNREADABLE'\n | 'E_BAD_LOCK_ENVELOPE'\n // v1.2 federation principal (FEDERATION.md §3.3)\n | 'E_BAD_FEDERATION_DESCRIPTOR'\n | 'E_THRESHOLD_MISMATCH'\n | 'E_THRESHOLD_NOT_MET'\n | 'E_UNKNOWN_GUARDIAN'\n | 'E_DUPLICATE_GUARDIAN';\n\nexport interface VerifyOk<T> {\n ok: true;\n envelope: T;\n canonicalMessage: string;\n id: string;\n}\n\nexport interface VerifyErr {\n ok: false;\n code: AgentErrorCode;\n message: string;\n}\n\nexport type VerifyDelegationResult = VerifyOk<DelegationEnvelope> | VerifyErr;\nexport type VerifyRevocationResult = VerifyOk<RevocationEnvelope> | VerifyErr;\nexport type VerifySubdelegationResult = VerifyOk<SubdelegationEnvelope> | VerifyErr;\n\nexport interface VerifyActionOkExtra {\n /** The ROOT delegation rooting the authority chain. */\n delegation: DelegationEnvelope;\n /**\n * The sub-delegation chain `[S_1, …, S_leaf]`. Empty when the action cites\n * the root directly. The action's authority leaf is\n * `chain[chain.length - 1] ?? delegation`.\n */\n chain: SubdelegationEnvelope[];\n scopeExercised: string;\n /**\n * `claimed` is a confirmed `ots` block no verifier walked: the block is the\n * envelope's say-so. Only a passing `verifyOtsAnchor` yields `confirmed`.\n */\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'claimed'; blockHeight: number; blockHash: string; verified: false }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: true };\n /**\n * The time the verdict holds for. `now`: verifyAction, checked live.\n * `signed_at`: verifyActionAsOfSignedAt, checked as of the time the agent\n * claims; `anchored` is false when only the agent says so.\n */\n asOf:\n | { basis: 'now'; now: string }\n | { basis: 'signed_at'; signedAt: string; anchored: boolean };\n}\n\nexport type VerifyActionResult =\n | (VerifyOk<ActionEnvelope> & VerifyActionOkExtra)\n | VerifyErr;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecheck/agent-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "OC Agent canonical messages, envelope formats (delegation/action/revocation), scope grammar, and verification. See https://github.com/orangecheck/oc-agent-protocol.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
package/src/index.ts
CHANGED
|
@@ -37,15 +37,19 @@ export type { Scope, ScopeConstraint, ScopeOp, ValidationOptions } from './scope
|
|
|
37
37
|
export {
|
|
38
38
|
verifyDelegation,
|
|
39
39
|
verifyAction,
|
|
40
|
+
verifyActionAsOfSignedAt,
|
|
41
|
+
createReplayGuard,
|
|
40
42
|
verifyRevocation,
|
|
41
43
|
verifySubdelegation,
|
|
42
44
|
AgentError,
|
|
43
45
|
DEFAULT_MAX_CHAIN_DEPTH,
|
|
46
|
+
DEFAULT_MAX_SKEW_MS,
|
|
44
47
|
} from './verify.js';
|
|
45
48
|
export type {
|
|
46
49
|
VerifyBase,
|
|
47
50
|
VerifyDelegationInput,
|
|
48
51
|
VerifyActionInput,
|
|
52
|
+
VerifyActionAsOfSignedAtInput,
|
|
49
53
|
VerifyRevocationInput,
|
|
50
54
|
VerifySubdelegationInput,
|
|
51
55
|
} from './verify.js';
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// SPEC §8.1 step 6 checks the delegation at the time of verification, not at
|
|
2
|
+
// the action's signed_at. The agent picks signed_at, so an action that only
|
|
3
|
+
// had to fit the delegation's window could be backdated past its expiry or past
|
|
4
|
+
// a revocation and still verify. A live verifier checks at `now`; an audit of a
|
|
5
|
+
// past action says outright that it relied on the agent's claimed time.
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { computeActionId, computeRevocationId } from './canonical.js';
|
|
9
|
+
import { createReplayGuard, verifyAction, verifyActionAsOfSignedAt } from './verify.js';
|
|
10
|
+
import type { ActionEnvelope, DelegationEnvelope, RevocationEnvelope } from './types.js';
|
|
11
|
+
|
|
12
|
+
const delegation: DelegationEnvelope = {
|
|
13
|
+
v: 1,
|
|
14
|
+
kind: 'agent-delegation',
|
|
15
|
+
id: '36d79600191db871baa3fc9aa3b5e77750a5c423b1f620ec26cf16bd122e19a7',
|
|
16
|
+
principal: { address: 'bc1qprincipal000000000000000000000000000000', alg: 'bip322' },
|
|
17
|
+
agent: { address: 'bc1qagent0000000000000000000000000000000000', alg: 'bip322' },
|
|
18
|
+
scopes: ['lock:seal(recipient=bc1qalice000000000000000000000000000000000)'],
|
|
19
|
+
bond: null,
|
|
20
|
+
issued_at: '2026-04-22T12:00:00Z',
|
|
21
|
+
expires_at: '2026-04-29T12:00:00Z',
|
|
22
|
+
nonce: '0123456789abcdef0123456789abcdef',
|
|
23
|
+
revocation: { holders: ['principal'], ref: null },
|
|
24
|
+
sig: { alg: 'bip322', pubkey: 'bc1qprincipal000000000000000000000000000000', value: 'AAAA' },
|
|
25
|
+
} as DelegationEnvelope;
|
|
26
|
+
|
|
27
|
+
function action(signed_at: string): ActionEnvelope {
|
|
28
|
+
const canon = {
|
|
29
|
+
address: delegation.agent.address,
|
|
30
|
+
content_hash: 'sha256:' + '00'.repeat(32),
|
|
31
|
+
content_length: 0,
|
|
32
|
+
content_mime: 'text/plain',
|
|
33
|
+
signed_at,
|
|
34
|
+
delegation_id: delegation.id,
|
|
35
|
+
scope_exercised: delegation.scopes![0]!,
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
v: 1,
|
|
39
|
+
kind: 'agent-action',
|
|
40
|
+
id: computeActionId(canon),
|
|
41
|
+
content: { hash: canon.content_hash, length: 0, mime: 'text/plain', ref: null },
|
|
42
|
+
signer: { address: canon.address, alg: 'bip322' },
|
|
43
|
+
signed_at,
|
|
44
|
+
delegation_id: delegation.id,
|
|
45
|
+
scope_exercised: canon.scope_exercised,
|
|
46
|
+
ots: null,
|
|
47
|
+
sig: { alg: 'bip322', pubkey: canon.address, value: 'AAAA' },
|
|
48
|
+
} as ActionEnvelope;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function revocation(signed_at: string): RevocationEnvelope {
|
|
52
|
+
const canon = { address: delegation.principal.address, delegation_id: delegation.id, reason: '', signed_at };
|
|
53
|
+
return {
|
|
54
|
+
v: 1,
|
|
55
|
+
kind: 'agent-revocation',
|
|
56
|
+
id: computeRevocationId(canon),
|
|
57
|
+
delegation_id: delegation.id,
|
|
58
|
+
signer: { address: canon.address, alg: 'bip322' },
|
|
59
|
+
reason: '',
|
|
60
|
+
signed_at,
|
|
61
|
+
ots: null,
|
|
62
|
+
sig: { alg: 'bip322', pubkey: canon.address, value: 'AAAA' },
|
|
63
|
+
} as RevocationEnvelope;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const base = { delegation, skipSignatureVerification: true };
|
|
67
|
+
|
|
68
|
+
describe('live verification checks at now', () => {
|
|
69
|
+
it('refuses an action backdated into the window of a delegation that has expired', async () => {
|
|
70
|
+
const r = await verifyAction({
|
|
71
|
+
...base,
|
|
72
|
+
action: action('2026-04-28T00:00:00Z'),
|
|
73
|
+
revocations: [],
|
|
74
|
+
now: new Date('2026-05-01T00:00:00Z'),
|
|
75
|
+
});
|
|
76
|
+
expect(r.ok === false && r.code).toBe('E_EXPIRED');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('refuses an action backdated to before a revocation that is already in effect', async () => {
|
|
80
|
+
const r = await verifyAction({
|
|
81
|
+
...base,
|
|
82
|
+
action: action('2026-04-24T11:59:00Z'),
|
|
83
|
+
revocations: [revocation('2026-04-24T11:59:30Z')],
|
|
84
|
+
now: new Date('2026-04-24T12:00:00Z'),
|
|
85
|
+
});
|
|
86
|
+
expect(r.ok === false && r.code).toBe('E_REVOKED');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('refuses an action signed further from now than the freshness bound', async () => {
|
|
90
|
+
const now = new Date('2026-04-24T12:00:00Z');
|
|
91
|
+
const old = await verifyAction({ ...base, action: action('2026-04-24T11:50:00Z'), revocations: [], now });
|
|
92
|
+
expect(old.ok === false && old.code).toBe('E_STALE');
|
|
93
|
+
const ahead = await verifyAction({ ...base, action: action('2026-04-24T12:05:00Z'), revocations: [], now });
|
|
94
|
+
expect(ahead.ok === false && ahead.code).toBe('E_STALE');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('accepts a fresh action under a clear feed, and says it was checked at now', async () => {
|
|
98
|
+
const now = new Date('2026-04-24T12:00:00Z');
|
|
99
|
+
const r = await verifyAction({ ...base, action: action('2026-04-24T11:59:30Z'), revocations: [], now });
|
|
100
|
+
expect(r.ok).toBe(true);
|
|
101
|
+
expect(r.ok && r.asOf).toEqual({ basis: 'now', now: '2026-04-24T12:00:00.000Z' });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('takes a wider freshness bound when the caller sets one', async () => {
|
|
105
|
+
const r = await verifyAction({
|
|
106
|
+
...base,
|
|
107
|
+
action: action('2026-04-24T11:50:00Z'),
|
|
108
|
+
revocations: [],
|
|
109
|
+
now: new Date('2026-04-24T12:00:00Z'),
|
|
110
|
+
maxSkewMs: 15 * 60_000,
|
|
111
|
+
});
|
|
112
|
+
expect(r.ok).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('does not let a revocation dated in the future refuse an action today', async () => {
|
|
116
|
+
const r = await verifyAction({
|
|
117
|
+
...base,
|
|
118
|
+
action: action('2026-04-24T11:59:30Z'),
|
|
119
|
+
revocations: [revocation('2026-04-25T00:00:00Z')],
|
|
120
|
+
now: new Date('2026-04-24T12:00:00Z'),
|
|
121
|
+
});
|
|
122
|
+
expect(r.ok).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('an audit checks as of signed_at, and says that time is a claim', () => {
|
|
127
|
+
it('accepts a past action inside its window, with an unanchored signed_at', async () => {
|
|
128
|
+
const r = await verifyActionAsOfSignedAt({ ...base, action: action('2026-04-28T00:00:00Z'), revocations: [] });
|
|
129
|
+
expect(r.ok).toBe(true);
|
|
130
|
+
expect(r.ok && r.asOf).toEqual({ basis: 'signed_at', signedAt: '2026-04-28T00:00:00Z', anchored: false });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('still refuses an action that claims a time after the revocation', async () => {
|
|
134
|
+
const r = await verifyActionAsOfSignedAt({
|
|
135
|
+
...base,
|
|
136
|
+
action: action('2026-04-24T12:00:00Z'),
|
|
137
|
+
revocations: [revocation('2026-04-24T11:59:30Z')],
|
|
138
|
+
});
|
|
139
|
+
expect(r.ok === false && r.code).toBe('E_REVOKED');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('createReplayGuard', () => {
|
|
144
|
+
it('admits an action once, and forgets it when it could no longer pass as fresh', () => {
|
|
145
|
+
const guard = createReplayGuard(120_000);
|
|
146
|
+
const a = action('2026-04-24T12:00:00Z');
|
|
147
|
+
expect(guard.firstUse(a, new Date('2026-04-24T12:00:05Z'))).toBe(true);
|
|
148
|
+
expect(guard.firstUse(a, new Date('2026-04-24T12:01:00Z'))).toBe(false);
|
|
149
|
+
expect(guard.firstUse(a, new Date('2026-04-24T12:02:01Z'))).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { computeActionId, computeDelegationId, computeRevocationId } from './canonical.js';
|
|
8
|
-
import {
|
|
8
|
+
import { verifyActionAsOfSignedAt, verifyDelegation, verifyRevocation } from './verify.js';
|
|
9
9
|
import type { ActionEnvelope, ActionOts, DelegationEnvelope, RevocationEnvelope } from './types.js';
|
|
10
10
|
|
|
11
11
|
const PRINCIPAL = 'bc1qprincipal000000000000000000000000000000';
|
|
@@ -91,6 +91,8 @@ function confirmed(height: number): ActionOts {
|
|
|
91
91
|
|
|
92
92
|
const AT = new Date('2026-04-23T12:00:00Z');
|
|
93
93
|
|
|
94
|
+
// Priority between an action and a revocation (SPEC §9.3) is a question about
|
|
95
|
+
// the action's own time, so these run as audits, as of signed_at.
|
|
94
96
|
describe('revocation authority always includes the principal', () => {
|
|
95
97
|
for (const holders of [['agent'], [], ['principal'], undefined, 'agent']) {
|
|
96
98
|
it(`principal revocation is honoured with holders=${JSON.stringify(holders)}`, async () => {
|
|
@@ -105,7 +107,7 @@ describe('revocation authority always includes the principal', () => {
|
|
|
105
107
|
now: AT,
|
|
106
108
|
});
|
|
107
109
|
expect(dr.ok === false && dr.code).toBe('E_REVOKED');
|
|
108
|
-
const ar = await
|
|
110
|
+
const ar = await verifyActionAsOfSignedAt({
|
|
109
111
|
action: action(d, '2026-04-23T06:00:00Z'),
|
|
110
112
|
delegation: d,
|
|
111
113
|
revocations: [rev],
|
|
@@ -150,7 +152,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
150
152
|
const later = '2026-04-23T06:00:00Z';
|
|
151
153
|
|
|
152
154
|
it('an action anchor with no anchor verifier does not outrank a revocation', async () => {
|
|
153
|
-
const r = await
|
|
155
|
+
const r = await verifyActionAsOfSignedAt({
|
|
154
156
|
action: action(d, later, confirmed(1)),
|
|
155
157
|
delegation: d,
|
|
156
158
|
revocations: [rev],
|
|
@@ -160,7 +162,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
160
162
|
});
|
|
161
163
|
|
|
162
164
|
it('an action anchor the verifier rejects does not outrank a revocation', async () => {
|
|
163
|
-
const r = await
|
|
165
|
+
const r = await verifyActionAsOfSignedAt({
|
|
164
166
|
action: action(d, later, confirmed(1)),
|
|
165
167
|
delegation: d,
|
|
166
168
|
revocations: [rev],
|
|
@@ -168,7 +170,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
168
170
|
verifyOtsAnchor: async () => false,
|
|
169
171
|
});
|
|
170
172
|
expect(r.ok === false && r.code).toBe('E_REVOKED');
|
|
171
|
-
const clean = await
|
|
173
|
+
const clean = await verifyActionAsOfSignedAt({
|
|
172
174
|
action: action(d, later, confirmed(1)),
|
|
173
175
|
delegation: d,
|
|
174
176
|
revocations: [],
|
|
@@ -185,14 +187,14 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
185
187
|
|
|
186
188
|
// The ots block is outside the signature: its block is a claim until walked.
|
|
187
189
|
it('reports an unwalked anchor as claimed, and a walked one as confirmed', async () => {
|
|
188
|
-
const unwalked = await
|
|
190
|
+
const unwalked = await verifyActionAsOfSignedAt({
|
|
189
191
|
action: action(d, later, confirmed(1)),
|
|
190
192
|
delegation: d,
|
|
191
193
|
revocations: [],
|
|
192
194
|
skipSignatureVerification: true,
|
|
193
195
|
});
|
|
194
196
|
expect(unwalked.ok && unwalked.anchor.status).toBe('claimed');
|
|
195
|
-
const walked = await
|
|
197
|
+
const walked = await verifyActionAsOfSignedAt({
|
|
196
198
|
action: action(d, later, confirmed(1)),
|
|
197
199
|
delegation: d,
|
|
198
200
|
revocations: [],
|
|
@@ -211,7 +213,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
211
213
|
const a = action(d, later, confirmed(100));
|
|
212
214
|
const revAnchored = revocation(d, PRINCIPAL, '2026-04-23T00:00:00Z', confirmed(200));
|
|
213
215
|
const seen: string[] = [];
|
|
214
|
-
const r = await
|
|
216
|
+
const r = await verifyActionAsOfSignedAt({
|
|
215
217
|
action: a,
|
|
216
218
|
delegation: d,
|
|
217
219
|
revocations: [revAnchored],
|
|
@@ -227,7 +229,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
227
229
|
});
|
|
228
230
|
|
|
229
231
|
it('a verified action anchor against an unanchored revocation falls back to signed times', async () => {
|
|
230
|
-
const r = await
|
|
232
|
+
const r = await verifyActionAsOfSignedAt({
|
|
231
233
|
action: action(d, later, confirmed(1)),
|
|
232
234
|
delegation: d,
|
|
233
235
|
revocations: [rev],
|
|
@@ -235,7 +237,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
235
237
|
verifyOtsAnchor: async () => true,
|
|
236
238
|
});
|
|
237
239
|
expect(r.ok === false && r.code).toBe('E_REVOKED');
|
|
238
|
-
const before = await
|
|
240
|
+
const before = await verifyActionAsOfSignedAt({
|
|
239
241
|
action: action(d, '2026-04-22T18:00:00Z', confirmed(1)),
|
|
240
242
|
delegation: d,
|
|
241
243
|
revocations: [rev],
|
|
@@ -248,7 +250,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
248
250
|
it('a revocation anchor that does not verify cannot move the revocation later', async () => {
|
|
249
251
|
const revFarFuture = revocation(d, PRINCIPAL, '2026-04-23T00:00:00Z', confirmed(10_000_000));
|
|
250
252
|
const a = action(d, later, confirmed(100));
|
|
251
|
-
const r = await
|
|
253
|
+
const r = await verifyActionAsOfSignedAt({
|
|
252
254
|
action: a,
|
|
253
255
|
delegation: d,
|
|
254
256
|
revocations: [revFarFuture],
|
|
@@ -262,7 +264,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
262
264
|
const a = action(d, later, confirmed(300));
|
|
263
265
|
const revAt = (h: number) => revocation(d, PRINCIPAL, '2026-04-23T00:00:00Z', confirmed(h));
|
|
264
266
|
const verifyOtsAnchor = async () => true;
|
|
265
|
-
const earlierRev = await
|
|
267
|
+
const earlierRev = await verifyActionAsOfSignedAt({
|
|
266
268
|
action: a,
|
|
267
269
|
delegation: d,
|
|
268
270
|
revocations: [revAt(300)],
|
|
@@ -270,7 +272,7 @@ describe('revocation priority uses only verified anchors', () => {
|
|
|
270
272
|
verifyOtsAnchor,
|
|
271
273
|
});
|
|
272
274
|
expect(earlierRev.ok === false && earlierRev.code).toBe('E_REVOKED');
|
|
273
|
-
const laterRev = await
|
|
275
|
+
const laterRev = await verifyActionAsOfSignedAt({
|
|
274
276
|
action: a,
|
|
275
277
|
delegation: d,
|
|
276
278
|
revocations: [revAt(301)],
|
|
@@ -106,7 +106,7 @@ describe('the refusal names a query relays answer', () => {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
it('verifyAction', async () => {
|
|
109
|
-
const r = await verifyAction({ action, delegation, skipSignatureVerification: true });
|
|
109
|
+
const r = await verifyAction({ action, delegation, skipSignatureVerification: true, now: AT });
|
|
110
110
|
expect(r.ok === false && r.code).toBe('E_MALFORMED');
|
|
111
111
|
expect(r.ok === false && r.message).toMatch(/"#t": \[id of every link in the chain\]/);
|
|
112
112
|
expect(r.ok === false && r.message).not.toMatch(/#delegation/);
|
package/src/test-vectors.test.ts
CHANGED
|
@@ -18,7 +18,13 @@ import {
|
|
|
18
18
|
subdelegationCanonicalMessage,
|
|
19
19
|
} from './canonical.js';
|
|
20
20
|
import { parseScope, ScopeParseError } from './scope.js';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
verifyAction,
|
|
23
|
+
verifyActionAsOfSignedAt,
|
|
24
|
+
verifyDelegation,
|
|
25
|
+
verifyRevocation,
|
|
26
|
+
verifySubdelegation,
|
|
27
|
+
} from './verify.js';
|
|
22
28
|
import type {
|
|
23
29
|
ActionEnvelope,
|
|
24
30
|
AgentErrorCode,
|
|
@@ -127,6 +133,12 @@ interface NegativeActionVector extends NegativeVectorBase {
|
|
|
127
133
|
scope_exercised: string;
|
|
128
134
|
content_ref?: string | null;
|
|
129
135
|
ots?: unknown;
|
|
136
|
+
/** Present on a live vector (§8.1 steps 6–7, §8.3 step 11a): the time of verification. */
|
|
137
|
+
verified_at?: string;
|
|
138
|
+
/** The freshness bound a live vector assumes, in seconds. */
|
|
139
|
+
max_skew_seconds?: number;
|
|
140
|
+
/** Revocations of the cited delegation, as the inputs of each envelope. */
|
|
141
|
+
revocations?: { address: string; reason: string; signed_at: string }[];
|
|
130
142
|
};
|
|
131
143
|
}
|
|
132
144
|
|
|
@@ -279,7 +291,8 @@ describe('oc-agent-protocol test vectors', () => {
|
|
|
279
291
|
if (!delegation) {
|
|
280
292
|
throw new Error(`action vector ${name} references missing delegation ${data.inputs.delegation_id}`);
|
|
281
293
|
}
|
|
282
|
-
|
|
294
|
+
// A positive vector is the envelope as of its own signed_at.
|
|
295
|
+
const r = await verifyActionAsOfSignedAt({
|
|
283
296
|
skipRevocationCheck: true,
|
|
284
297
|
action: data.expected.envelope,
|
|
285
298
|
delegation,
|
|
@@ -461,12 +474,34 @@ describe('oc-agent-protocol negative test vectors', () => {
|
|
|
461
474
|
ots: null,
|
|
462
475
|
sig: { alg: 'bip322', pubkey: v.inputs.address, value: 'AAAA' },
|
|
463
476
|
};
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
477
|
+
const revocations: RevocationEnvelope[] = (v.inputs.revocations ?? []).map((rv) => ({
|
|
478
|
+
v: 1,
|
|
479
|
+
kind: 'agent-revocation',
|
|
480
|
+
id: computeRevocationId({ ...rv, delegation_id: v.inputs.delegation_id }),
|
|
481
|
+
delegation_id: v.inputs.delegation_id,
|
|
482
|
+
signer: { address: rv.address, alg: 'bip322' },
|
|
483
|
+
reason: rv.reason,
|
|
484
|
+
signed_at: rv.signed_at,
|
|
485
|
+
ots: null,
|
|
486
|
+
sig: { alg: 'bip322', pubkey: rv.address, value: 'AAAA' },
|
|
487
|
+
}));
|
|
488
|
+
// A vector with verified_at is checked live at that time; one
|
|
489
|
+
// without is an audit of the envelope as of its signed_at.
|
|
490
|
+
const r = v.inputs.verified_at
|
|
491
|
+
? await verifyAction({
|
|
492
|
+
action,
|
|
493
|
+
delegation,
|
|
494
|
+
skipSignatureVerification: true,
|
|
495
|
+
revocations,
|
|
496
|
+
now: new Date(v.inputs.verified_at),
|
|
497
|
+
maxSkewMs: (v.inputs.max_skew_seconds ?? 120) * 1000,
|
|
498
|
+
})
|
|
499
|
+
: await verifyActionAsOfSignedAt({
|
|
500
|
+
skipRevocationCheck: true,
|
|
501
|
+
action,
|
|
502
|
+
delegation,
|
|
503
|
+
skipSignatureVerification: true,
|
|
504
|
+
});
|
|
470
505
|
expect(r.ok).toBe(false);
|
|
471
506
|
if (!r.ok) {
|
|
472
507
|
// v07 specifically allows either E_OUT_OF_WINDOW or E_EXPIRED
|
package/src/types.ts
CHANGED
|
@@ -237,6 +237,7 @@ export type AgentErrorCode =
|
|
|
237
237
|
| 'E_NOT_YET_VALID'
|
|
238
238
|
| 'E_EXPIRED'
|
|
239
239
|
| 'E_REVOKED'
|
|
240
|
+
| 'E_STALE'
|
|
240
241
|
| 'E_DELEGATION_MISMATCH'
|
|
241
242
|
| 'E_AGENT_MISMATCH'
|
|
242
243
|
| 'E_OUT_OF_WINDOW'
|
|
@@ -298,6 +299,14 @@ export interface VerifyActionOkExtra {
|
|
|
298
299
|
| { status: 'pending' }
|
|
299
300
|
| { status: 'claimed'; blockHeight: number; blockHash: string; verified: false }
|
|
300
301
|
| { status: 'confirmed'; blockHeight: number; blockHash: string; verified: true };
|
|
302
|
+
/**
|
|
303
|
+
* The time the verdict holds for. `now`: verifyAction, checked live.
|
|
304
|
+
* `signed_at`: verifyActionAsOfSignedAt, checked as of the time the agent
|
|
305
|
+
* claims; `anchored` is false when only the agent says so.
|
|
306
|
+
*/
|
|
307
|
+
asOf:
|
|
308
|
+
| { basis: 'now'; now: string }
|
|
309
|
+
| { basis: 'signed_at'; signedAt: string; anchored: boolean };
|
|
301
310
|
}
|
|
302
311
|
|
|
303
312
|
export type VerifyActionResult =
|