@aporthq/aport-agent-guardrails 1.0.8

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.
Files changed (237) hide show
  1. package/LICENSE +217 -0
  2. package/README.md +481 -0
  3. package/bin/agent-guardrails +133 -0
  4. package/bin/aport-create-passport.sh +444 -0
  5. package/bin/aport-cursor-hook.sh +90 -0
  6. package/bin/aport-guardrail-api.sh +108 -0
  7. package/bin/aport-guardrail-bash.sh +394 -0
  8. package/bin/aport-guardrail-v2.sh +5 -0
  9. package/bin/aport-guardrail.sh +5 -0
  10. package/bin/aport-resolve-paths.sh +71 -0
  11. package/bin/aport-status.sh +276 -0
  12. package/bin/frameworks/crewai.sh +49 -0
  13. package/bin/frameworks/cursor.sh +95 -0
  14. package/bin/frameworks/langchain.sh +48 -0
  15. package/bin/frameworks/n8n.sh +36 -0
  16. package/bin/frameworks/openclaw.sh +19 -0
  17. package/bin/lib/allowlist.sh +18 -0
  18. package/bin/lib/common.sh +28 -0
  19. package/bin/lib/config.sh +46 -0
  20. package/bin/lib/constants.sh +232 -0
  21. package/bin/lib/detect.sh +65 -0
  22. package/bin/lib/error.sh +269 -0
  23. package/bin/lib/passport.sh +19 -0
  24. package/bin/lib/templates/.gitkeep +1 -0
  25. package/bin/lib/templates/config.yaml +6 -0
  26. package/bin/lib/validation.sh +206 -0
  27. package/bin/openclaw +660 -0
  28. package/docs/ADDING_A_FRAMEWORK.md +87 -0
  29. package/docs/AGENTS.md.example +40 -0
  30. package/docs/CODE_REVIEW.md +192 -0
  31. package/docs/DEPLOYMENT_READINESS.md +81 -0
  32. package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
  33. package/docs/FRAMEWORK_ROADMAP.md +41 -0
  34. package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
  35. package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
  36. package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
  37. package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
  38. package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
  39. package/docs/QUICKSTART.md +470 -0
  40. package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
  41. package/docs/README.md +28 -0
  42. package/docs/RELEASE.md +87 -0
  43. package/docs/REPO_LAYOUT.md +47 -0
  44. package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
  45. package/docs/TOOL_POLICY_MAPPING.md +46 -0
  46. package/docs/UPGRADE.md +46 -0
  47. package/docs/VERIFICATION_METHODS.md +97 -0
  48. package/docs/assets/README.md +8 -0
  49. package/docs/assets/porter.svg +54 -0
  50. package/docs/development/ERROR_CODES.md +616 -0
  51. package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
  52. package/docs/frameworks/crewai.md +114 -0
  53. package/docs/frameworks/cursor.md +159 -0
  54. package/docs/frameworks/langchain.md +72 -0
  55. package/docs/frameworks/n8n.md +40 -0
  56. package/docs/frameworks/openclaw.md +40 -0
  57. package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
  58. package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
  59. package/docs/launch/AWESOME_REPOS.md +53 -0
  60. package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
  61. package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
  62. package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
  63. package/docs/launch/EVIDENCE_README.md +61 -0
  64. package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
  65. package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
  66. package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
  67. package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
  68. package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
  69. package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
  70. package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
  71. package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
  72. package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
  73. package/docs/launch/READINESS_SUMMARY.md +262 -0
  74. package/docs/launch/README.md +68 -0
  75. package/docs/launch/USER_STORIES.md +327 -0
  76. package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
  77. package/docs/operations/MONITORING.md +588 -0
  78. package/docs/reviews/2026-02-18-staff-review.md +268 -0
  79. package/extensions/openclaw-aport/README.md +415 -0
  80. package/extensions/openclaw-aport/index.js +625 -0
  81. package/extensions/openclaw-aport/openclaw-aport.js +7 -0
  82. package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
  83. package/extensions/openclaw-aport/package.json +36 -0
  84. package/extensions/openclaw-aport/test.js +307 -0
  85. package/external/aport-policies/README.md +363 -0
  86. package/external/aport-policies/agent.session.create.v1/README.md +345 -0
  87. package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
  88. package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
  89. package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
  90. package/external/aport-policies/code.release.publish.v1/README.md +51 -0
  91. package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
  92. package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
  93. package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
  94. package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
  95. package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
  96. package/external/aport-policies/data.export.create.v1/README.md +226 -0
  97. package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
  98. package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
  99. package/external/aport-policies/data.export.create.v1/policy.json +133 -0
  100. package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
  101. package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
  102. package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
  103. package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
  104. package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
  105. package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
  106. package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
  107. package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
  108. package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
  109. package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
  110. package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
  111. package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
  112. package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
  113. package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
  114. package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
  115. package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
  116. package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
  117. package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
  118. package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
  119. package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
  120. package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
  121. package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
  122. package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
  123. package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
  124. package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
  125. package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
  126. package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
  127. package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
  128. package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
  129. package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
  130. package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
  131. package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
  132. package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
  133. package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
  134. package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
  135. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
  136. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
  137. package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
  138. package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
  139. package/external/aport-policies/governance.data.access.v1/README.md +292 -0
  140. package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
  141. package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
  142. package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
  143. package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
  144. package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
  145. package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
  146. package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
  147. package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
  148. package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
  149. package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
  150. package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
  151. package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
  152. package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
  153. package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
  154. package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
  155. package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
  156. package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
  157. package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
  158. package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
  159. package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
  160. package/external/aport-policies/policy-template.json +107 -0
  161. package/external/aport-policies/system.command.execute.v1/README.md +275 -0
  162. package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
  163. package/external/aport-spec/CONTRIBUTING.md +273 -0
  164. package/external/aport-spec/LICENSE +21 -0
  165. package/external/aport-spec/README.md +168 -0
  166. package/external/aport-spec/conformance/README.md +294 -0
  167. package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
  168. package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
  169. package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
  170. package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
  171. package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
  172. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
  173. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
  174. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
  175. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
  176. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
  177. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
  178. package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
  179. package/external/aport-spec/conformance/package.json +44 -0
  180. package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
  181. package/external/aport-spec/conformance/src/cases.ts +371 -0
  182. package/external/aport-spec/conformance/src/ed25519.ts +167 -0
  183. package/external/aport-spec/conformance/src/jcs.ts +85 -0
  184. package/external/aport-spec/conformance/src/runner.ts +533 -0
  185. package/external/aport-spec/conformance/src/validators.ts +185 -0
  186. package/external/aport-spec/conformance/test-runner.js +315 -0
  187. package/external/aport-spec/conformance/tsconfig.json +21 -0
  188. package/external/aport-spec/error-schema.json +192 -0
  189. package/external/aport-spec/index.json +12 -0
  190. package/external/aport-spec/integrations/clawmoat/README.md +12 -0
  191. package/external/aport-spec/integrations/shield/README.md +245 -0
  192. package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
  193. package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
  194. package/external/aport-spec/integrations/shield/test/README.md +58 -0
  195. package/external/aport-spec/integrations/shield/test/shield.md +40 -0
  196. package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
  197. package/external/aport-spec/metrics-schema.json +504 -0
  198. package/external/aport-spec/oap/CHANGELOG.md +54 -0
  199. package/external/aport-spec/oap/VERSION.md +40 -0
  200. package/external/aport-spec/oap/capability-registry.md +229 -0
  201. package/external/aport-spec/oap/conformance.md +257 -0
  202. package/external/aport-spec/oap/decision-schema.json +114 -0
  203. package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
  204. package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
  205. package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
  206. package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
  207. package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
  208. package/external/aport-spec/oap/oap-spec.md +426 -0
  209. package/external/aport-spec/oap/passport-schema.json +396 -0
  210. package/external/aport-spec/oap/security.md +213 -0
  211. package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
  212. package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
  213. package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
  214. package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
  215. package/external/aport-spec/oap/vc/tools/README.md +278 -0
  216. package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
  217. package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
  218. package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
  219. package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
  220. package/external/aport-spec/oap/vc/tools/package.json +38 -0
  221. package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
  222. package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
  223. package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
  224. package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
  225. package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
  226. package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
  227. package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
  228. package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
  229. package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
  230. package/external/aport-spec/passport-schema.json +586 -0
  231. package/external/aport-spec/rate-limiting.md +136 -0
  232. package/external/aport-spec/transport-profile.md +325 -0
  233. package/external/aport-spec/webhook-spec.md +314 -0
  234. package/package.json +70 -0
  235. package/skills/aport-agent-guardrail/SKILL.md +314 -0
  236. package/src/evaluator.js +252 -0
  237. package/src/server/index.js +72 -0
@@ -0,0 +1,653 @@
1
+ /**
2
+ * Open Agent Passport VC Conversion Tools
3
+ *
4
+ * Provides functions to convert between OAP objects and Verifiable Credentials
5
+ * for interoperability with VC/DID ecosystems.
6
+ *
7
+ * Note: This module is designed to work in both Node.js and Cloudflare Workers environments.
8
+ * File system operations are skipped in Workers since they're not available there.
9
+ */
10
+
11
+ import {
12
+ canonicalizeJsonLd,
13
+ signEd25519,
14
+ verifyEd25519,
15
+ bytesToBase64url,
16
+ publicKeyToBytes,
17
+ } from "./crypto-utils";
18
+
19
+ /**
20
+ * Type Definitions
21
+ *
22
+ * These interfaces define the structure of OAP objects and W3C Verifiable Credentials.
23
+ */
24
+
25
+ /**
26
+ * Open Agent Passport (OAP) structure
27
+ *
28
+ * Represents an agent's identity, capabilities, and authorization metadata.
29
+ */
30
+ export interface OAPPassport {
31
+ agent_id: string;
32
+ kind: "template" | "instance";
33
+ spec_version: string;
34
+ parent_agent_id?: string;
35
+ owner_id: string;
36
+ owner_type: "org" | "user";
37
+ assurance_level: "L0" | "L1" | "L2" | "L3" | "L4KYC" | "L4FIN";
38
+ status: "draft" | "active" | "suspended" | "revoked";
39
+ capabilities: Array<{ id: string; params?: Record<string, any> }>;
40
+ limits: Record<string, any>;
41
+ regions: string[];
42
+ metadata: Record<string, any>;
43
+ created_at: string;
44
+ updated_at: string;
45
+ version: string;
46
+ }
47
+
48
+ /**
49
+ * Open Agent Passport (OAP) Decision structure
50
+ *
51
+ * Represents a policy verification decision with authorization result and metadata.
52
+ */
53
+ export interface OAPDecision {
54
+ decision_id: string;
55
+ policy_id: string;
56
+ agent_id: string;
57
+ owner_id: string;
58
+ assurance_level: string;
59
+ allow: boolean;
60
+ reasons: Array<{ code: string; message?: string }>;
61
+ created_at: string;
62
+ expires_in: number;
63
+ passport_digest: string;
64
+ signature: string;
65
+ kid: string;
66
+ decision_token?: string;
67
+ remaining_daily_cap?: Record<string, number>;
68
+ }
69
+
70
+ /**
71
+ * W3C Verifiable Credential structure
72
+ *
73
+ * Compliant with W3C VC Data Model 1.1. The proof contains the cryptographic
74
+ * signature in JWS format.
75
+ */
76
+ export interface VerifiableCredential {
77
+ "@context": string[];
78
+ type: string[];
79
+ credentialSubject: any;
80
+ issuer: string;
81
+ issuanceDate: string;
82
+ expirationDate: string;
83
+ proof: {
84
+ type: string;
85
+ created: string;
86
+ verificationMethod: string;
87
+ proofPurpose: string;
88
+ jws: string;
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Registry key configuration for signing Verifiable Credentials
94
+ *
95
+ * Contains issuer information and cryptographic keys for signing VCs.
96
+ */
97
+ export interface RegistryKey {
98
+ issuer: string;
99
+ kid: string;
100
+ privateKey?: string;
101
+ publicKey?: string;
102
+ }
103
+
104
+ // Note: Schema validation is not currently implemented but may be added in the future
105
+ // for additional validation beyond the basic structure checks in isValidOAPPassport/isValidOAPDecision
106
+
107
+ /**
108
+ * Convert OAP Passport to Verifiable Credential
109
+ *
110
+ * Exports an Open Agent Passport (OAP) as a W3C Verifiable Credential (VC),
111
+ * compliant with W3C VC Data Model 1.1. The credential is cryptographically
112
+ * signed using Ed25519 and includes a DID-based verification method.
113
+ *
114
+ * @param passport - The OAP Passport to convert
115
+ * @param registryKey - Registry key containing issuer info and private key for signing
116
+ * @returns Promise resolving to a Verifiable Credential
117
+ * @throws Error if passport structure is invalid or signing fails
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * const vc = await exportPassportToVC(passport, {
122
+ * issuer: "https://aport.io",
123
+ * kid: "ap_registry_ed25519_2024",
124
+ * privateKey: process.env.REGISTRY_PRIVATE_KEY
125
+ * });
126
+ * ```
127
+ *
128
+ * @see https://www.w3.org/TR/vc-data-model/
129
+ */
130
+ export async function exportPassportToVC(
131
+ passport: OAPPassport,
132
+ registryKey: RegistryKey
133
+ ): Promise<VerifiableCredential> {
134
+ // Validate passport structure
135
+ if (!isValidOAPPassport(passport)) {
136
+ throw new Error("Invalid OAP passport structure");
137
+ }
138
+
139
+ // Generate DID if not present in passport
140
+ // DID format: did:web:aport.io:api:agents:{agent_id}
141
+ let did: string;
142
+ if ((passport as any).did) {
143
+ did = (passport as any).did;
144
+ } else {
145
+ // Generate DID from agent_id
146
+ // This matches the DID resolution endpoint: /api/agents/{agent_id}/did.json
147
+ const baseUrl = registryKey.issuer
148
+ .replace(/^https?:\/\//, "")
149
+ .replace(/\/$/, "");
150
+ did = `did:web:${baseUrl}:api:agents:${passport.agent_id}`;
151
+ }
152
+
153
+ // Use DID as issuer (W3C VC spec prefers DIDs over URLs)
154
+ const issuer = did;
155
+
156
+ // Construct verification method using DID (W3C VC best practice)
157
+ // DID-based verificationMethod resolves via DID Document
158
+ // DID Document is at: https://aport.io/api/agents/{agent_id}/did.json
159
+ // Public key is in the DID Document's verificationMethod array with id: {did}#key-1
160
+ const verificationMethod = `${did}#key-1`;
161
+
162
+ // Create VC structure (without proof first, proof is added after signing)
163
+ const vcWithoutProof: Omit<VerifiableCredential, "proof"> = {
164
+ "@context": [
165
+ "https://www.w3.org/2018/credentials/v1",
166
+ "https://raw.githubusercontent.com/aporthq/aport-spec/refs/heads/main/oap/vc/context-oap-v1.jsonld",
167
+ ],
168
+ type: ["VerifiableCredential", "OAPPassportCredential"],
169
+ credentialSubject: {
170
+ // Map all OAP passport fields
171
+ agent_id: passport.agent_id,
172
+ kind: passport.kind,
173
+ spec_version: passport.spec_version,
174
+ parent_agent_id: passport.parent_agent_id,
175
+ owner_id: passport.owner_id,
176
+ owner_type: passport.owner_type,
177
+ assurance_level: passport.assurance_level,
178
+ status: passport.status,
179
+ capabilities: passport.capabilities,
180
+ limits: passport.limits,
181
+ regions: passport.regions,
182
+ metadata: passport.metadata,
183
+ created_at: passport.created_at,
184
+ updated_at: passport.updated_at,
185
+ version: passport.version,
186
+ did: did, // Include DID in credential subject
187
+ },
188
+ issuer: issuer,
189
+ issuanceDate: passport.created_at,
190
+ expirationDate: computeExpirationDate(passport),
191
+ };
192
+
193
+ // Sign the credential (signs the credential without proof)
194
+ const jws = await signCredential(
195
+ vcWithoutProof as VerifiableCredential,
196
+ registryKey
197
+ );
198
+
199
+ // Add proof to complete the VC
200
+ const vc: VerifiableCredential = {
201
+ ...vcWithoutProof,
202
+ proof: {
203
+ type: "Ed25519Signature2020",
204
+ created: passport.created_at,
205
+ verificationMethod: verificationMethod,
206
+ proofPurpose: "assertionMethod",
207
+ jws: jws,
208
+ },
209
+ };
210
+
211
+ return vc;
212
+ }
213
+
214
+ /**
215
+ * Convert OAP Decision to Verifiable Credential
216
+ *
217
+ * Exports an OAP Decision as a W3C Verifiable Credential, representing a
218
+ * policy verification decision receipt. The credential is cryptographically
219
+ * signed using Ed25519.
220
+ *
221
+ * @param decision - The OAP Decision to convert
222
+ * @param registryKey - Registry key containing issuer info and private key for signing
223
+ * @returns Promise resolving to a Verifiable Credential
224
+ * @throws Error if decision structure is invalid or signing fails
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * const vc = await exportDecisionToVC(decision, {
229
+ * issuer: "https://aport.io",
230
+ * kid: "ap_registry_ed25519_2024",
231
+ * privateKey: process.env.REGISTRY_PRIVATE_KEY
232
+ * });
233
+ * ```
234
+ */
235
+ export async function exportDecisionToVC(
236
+ decision: OAPDecision,
237
+ registryKey: RegistryKey
238
+ ): Promise<VerifiableCredential> {
239
+ // Validate decision structure
240
+ if (!isValidOAPDecision(decision)) {
241
+ throw new Error("Invalid OAP decision structure");
242
+ }
243
+
244
+ // Create VC structure (without proof first)
245
+ const vcWithoutProof: Omit<VerifiableCredential, "proof"> = {
246
+ "@context": [
247
+ "https://www.w3.org/2018/credentials/v1",
248
+ "https://raw.githubusercontent.com/aporthq/aport-spec/refs/heads/main/oap/vc/context-oap-v1.jsonld",
249
+ ],
250
+ type: ["VerifiableCredential", "OAPDecisionReceipt"],
251
+ credentialSubject: {
252
+ // Map all OAP decision fields
253
+ decision_id: decision.decision_id,
254
+ policy_id: decision.policy_id,
255
+ agent_id: decision.agent_id,
256
+ owner_id: decision.owner_id,
257
+ assurance_level: decision.assurance_level,
258
+ allow: decision.allow,
259
+ reasons: decision.reasons,
260
+ created_at: decision.created_at,
261
+ expires_in: decision.expires_in,
262
+ passport_digest: decision.passport_digest,
263
+ signature: decision.signature,
264
+ kid: decision.kid,
265
+ decision_token: decision.decision_token,
266
+ remaining_daily_cap: decision.remaining_daily_cap,
267
+ },
268
+ issuer: registryKey.issuer,
269
+ issuanceDate: decision.created_at,
270
+ expirationDate: new Date(
271
+ new Date(decision.created_at).getTime() + decision.expires_in * 1000
272
+ ).toISOString(),
273
+ };
274
+
275
+ // Sign the credential
276
+ const jws = await signCredential(
277
+ vcWithoutProof as VerifiableCredential,
278
+ registryKey
279
+ );
280
+
281
+ // Add proof to complete the VC
282
+ const vc: VerifiableCredential = {
283
+ ...vcWithoutProof,
284
+ proof: {
285
+ type: "Ed25519Signature2020",
286
+ created: decision.created_at,
287
+ // For decisions, use registry's well-known endpoint
288
+ // This should resolve to: https://aport.io/.well-known/oap/keys.json
289
+ verificationMethod: `${registryKey.issuer}/.well-known/oap/keys.json#${registryKey.kid}`,
290
+ proofPurpose: "assertionMethod",
291
+ jws: jws,
292
+ },
293
+ };
294
+
295
+ return vc;
296
+ }
297
+
298
+ /**
299
+ * Convert Verifiable Credential to OAP Passport
300
+ *
301
+ * Imports a W3C Verifiable Credential back to an OAP Passport format.
302
+ * Verifies the credential's signature before conversion.
303
+ *
304
+ * @param vc - The Verifiable Credential to convert
305
+ * @param publicKey - Optional public key for signature verification. If not provided,
306
+ * verification will fail unless DID resolution is implemented.
307
+ * @returns Promise resolving to an OAP Passport
308
+ * @throws Error if VC structure is invalid, signature is invalid, or passport structure is invalid
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * const passport = await importVCToPassport(vc, publicKey);
313
+ * ```
314
+ */
315
+ export async function importVCToPassport(
316
+ vc: VerifiableCredential,
317
+ publicKey?: string | Uint8Array
318
+ ): Promise<OAPPassport> {
319
+ // Validate VC structure
320
+ if (!isValidVC(vc)) {
321
+ throw new Error("Invalid VC structure");
322
+ }
323
+
324
+ // Verify signature
325
+ const isValid = await verifyCredentialSignature(vc, publicKey);
326
+ if (!isValid) {
327
+ throw new Error("Invalid VC signature");
328
+ }
329
+
330
+ // Extract passport from credential subject
331
+ const passport = vc.credentialSubject;
332
+
333
+ // Validate OAP passport structure
334
+ if (!isValidOAPPassport(passport)) {
335
+ throw new Error("Invalid OAP passport structure");
336
+ }
337
+
338
+ return passport;
339
+ }
340
+
341
+ /**
342
+ * Convert Verifiable Credential to OAP Decision
343
+ *
344
+ * Imports a W3C Verifiable Credential back to an OAP Decision format.
345
+ * Verifies the credential's signature before conversion.
346
+ *
347
+ * @param vc - The Verifiable Credential to convert
348
+ * @param publicKey - Optional public key for signature verification. If not provided,
349
+ * verification will fail unless DID resolution is implemented.
350
+ * @returns Promise resolving to an OAP Decision
351
+ * @throws Error if VC structure is invalid, signature is invalid, or decision structure is invalid
352
+ *
353
+ * @example
354
+ * ```typescript
355
+ * const decision = await importVCToDecision(vc, publicKey);
356
+ * ```
357
+ */
358
+ export async function importVCToDecision(
359
+ vc: VerifiableCredential,
360
+ publicKey?: string | Uint8Array
361
+ ): Promise<OAPDecision> {
362
+ // Validate VC structure
363
+ if (!isValidVC(vc)) {
364
+ throw new Error("Invalid VC structure");
365
+ }
366
+
367
+ // Verify signature
368
+ const isValid = await verifyCredentialSignature(vc, publicKey);
369
+ if (!isValid) {
370
+ throw new Error("Invalid VC signature");
371
+ }
372
+
373
+ // Extract decision from credential subject
374
+ const decision = vc.credentialSubject;
375
+
376
+ // Validate OAP decision structure
377
+ if (!isValidOAPDecision(decision)) {
378
+ throw new Error("Invalid OAP decision structure");
379
+ }
380
+
381
+ return decision;
382
+ }
383
+
384
+ /**
385
+ * Validation Functions
386
+ */
387
+
388
+ /**
389
+ * Validates that an object conforms to the Verifiable Credential structure
390
+ *
391
+ * @param vc - Object to validate
392
+ * @returns true if the object has all required VC fields
393
+ */
394
+ export function isValidVC(vc: any): boolean {
395
+ return (
396
+ vc &&
397
+ vc["@context"] &&
398
+ vc.type &&
399
+ vc.credentialSubject &&
400
+ vc.issuer &&
401
+ vc.issuanceDate &&
402
+ vc.proof
403
+ );
404
+ }
405
+
406
+ /**
407
+ * Validates that an object conforms to the OAP Passport structure
408
+ *
409
+ * @param passport - Object to validate
410
+ * @returns true if the object has all required passport fields
411
+ */
412
+ export function isValidOAPPassport(passport: any): boolean {
413
+ return (
414
+ passport &&
415
+ (passport.agent_id || passport.id) &&
416
+ passport.kind &&
417
+ (passport.spec_version || passport.version) &&
418
+ passport.owner_id &&
419
+ passport.owner_type &&
420
+ passport.assurance_level &&
421
+ passport.status &&
422
+ passport.capabilities &&
423
+ passport.limits &&
424
+ passport.regions &&
425
+ passport.created_at &&
426
+ passport.updated_at
427
+ );
428
+ }
429
+
430
+ /**
431
+ * Validates that an object conforms to the OAP Decision structure
432
+ *
433
+ * @param decision - Object to validate
434
+ * @returns true if the object has all required decision fields
435
+ */
436
+ export function isValidOAPDecision(decision: any): boolean {
437
+ return (
438
+ decision &&
439
+ decision.decision_id &&
440
+ decision.policy_id &&
441
+ decision.agent_id &&
442
+ decision.owner_id &&
443
+ decision.assurance_level &&
444
+ typeof decision.allow === "boolean" &&
445
+ decision.reasons &&
446
+ decision.created_at &&
447
+ decision.expires_in &&
448
+ decision.passport_digest &&
449
+ decision.signature &&
450
+ decision.kid
451
+ );
452
+ }
453
+
454
+ /**
455
+ * Helper Functions
456
+ */
457
+
458
+ /**
459
+ * Computes the expiration date for a passport-based VC
460
+ *
461
+ * Uses passport.expires_at if set, otherwise checks never_expires flag,
462
+ * or defaults to 1 year from creation.
463
+ *
464
+ * @param passport - Passport object with expiration metadata
465
+ * @returns ISO 8601 timestamp string
466
+ */
467
+ function computeExpirationDate(passport: OAPPassport | any): string {
468
+ // Use native expiry if set
469
+ if (passport.expires_at) {
470
+ return passport.expires_at;
471
+ }
472
+
473
+ // Check never_expires flag
474
+ if (passport.never_expires) {
475
+ // W3C VC spec requires expirationDate, use far future for perpetual credentials
476
+ return new Date("2999-12-31T23:59:59Z").toISOString();
477
+ }
478
+
479
+ // Default: 1 year from creation
480
+ const created = new Date(passport.created_at);
481
+ const expiration = new Date(created.getTime() + 365 * 24 * 60 * 60 * 1000);
482
+ return expiration.toISOString();
483
+ }
484
+
485
+ /**
486
+ * Sign a credential using Ed25519
487
+ *
488
+ * @param vc - The Verifiable Credential to sign (without proof)
489
+ * @param registryKey - Registry key containing private key
490
+ * @returns JWS in compact format: <header>.<payload>.<signature>
491
+ *
492
+ * Note: JWS is safe to expose publicly - it's a cryptographic signature meant for verification.
493
+ * Anyone can verify it using the public key from verificationMethod.
494
+ *
495
+ * According to W3C VC spec, we sign the credential WITHOUT the proof, then add the proof.
496
+ *
497
+ * @see https://www.w3.org/TR/vc-data-model/#proofs-signatures
498
+ * @see https://w3c-ccg.github.io/lds-ed25519-2020/#ed25519signature2020
499
+ */
500
+ async function signCredential(
501
+ vc: VerifiableCredential,
502
+ registryKey: RegistryKey
503
+ ): Promise<string> {
504
+ if (!registryKey.privateKey) {
505
+ throw new Error(
506
+ "REGISTRY_PRIVATE_KEY is required for VC signing. Set it in environment variables."
507
+ );
508
+ }
509
+
510
+ // Step 1: Create credential without proof (proof is added after signing)
511
+ const credentialWithoutProof = {
512
+ "@context": vc["@context"],
513
+ type: vc.type,
514
+ credentialSubject: vc.credentialSubject,
515
+ issuer: vc.issuer,
516
+ issuanceDate: vc.issuanceDate,
517
+ expirationDate: vc.expirationDate,
518
+ };
519
+
520
+ // Step 2: Canonicalize the JSON-LD representation
521
+ const canonicalMessage = await canonicalizeJsonLd(credentialWithoutProof);
522
+
523
+ // Step 3: Sign using Ed25519
524
+ const signatureBytes = await signEd25519(
525
+ canonicalMessage,
526
+ registryKey.privateKey
527
+ );
528
+
529
+ // Step 4: Format as JWS (compact format)
530
+ // JWS header for Ed25519
531
+ const header = {
532
+ alg: "EdDSA",
533
+ b64: false,
534
+ crit: ["b64"],
535
+ };
536
+ const headerB64 = bytesToBase64url(
537
+ new TextEncoder().encode(JSON.stringify(header))
538
+ );
539
+
540
+ // For Ed25519Signature2020, we use detached payload
541
+ // The signature is over the canonicalized credential
542
+ const signatureB64 = bytesToBase64url(signatureBytes);
543
+
544
+ // Return compact JWS format: header..signature (detached payload)
545
+ // Note: For Ed25519Signature2020, the payload is the canonicalized credential
546
+ return `${headerB64}..${signatureB64}`;
547
+ }
548
+
549
+ /**
550
+ * Verify a Verifiable Credential's Ed25519 signature
551
+ *
552
+ * @param vc - The Verifiable Credential to verify
553
+ * @param publicKey - Optional public key for signature verification. If not provided,
554
+ * verification will fail unless DID resolution is implemented.
555
+ * @returns Promise<boolean> - true if signature is valid, false otherwise
556
+ *
557
+ * @see https://www.w3.org/TR/vc-data-model/#proofs-signatures
558
+ * @see https://w3c-ccg.github.io/lds-ed25519-2020/#ed25519signature2020
559
+ */
560
+ export async function verifyCredentialSignature(
561
+ vc: VerifiableCredential,
562
+ publicKey?: string | Uint8Array
563
+ ): Promise<boolean> {
564
+ if (!vc.proof || !vc.proof.jws) {
565
+ return false;
566
+ }
567
+
568
+ try {
569
+ // Step 1: Recreate credential without proof
570
+ const credentialWithoutProof = {
571
+ "@context": vc["@context"],
572
+ type: vc.type,
573
+ credentialSubject: vc.credentialSubject,
574
+ issuer: vc.issuer,
575
+ issuanceDate: vc.issuanceDate,
576
+ expirationDate: vc.expirationDate,
577
+ };
578
+
579
+ // Step 2: Canonicalize the JSON-LD representation (same as signing)
580
+ const canonicalMessage = await canonicalizeJsonLd(credentialWithoutProof);
581
+
582
+ // Step 3: Extract signature from JWS
583
+ const jwsParts = vc.proof.jws.split(".");
584
+ if (jwsParts.length !== 3) {
585
+ // Invalid JWS format - expected header..signature (detached payload)
586
+ return false;
587
+ }
588
+
589
+ const signatureB64 = jwsParts[2];
590
+ const signatureBytes = base64urlToBytes(signatureB64);
591
+
592
+ // Step 4: Get public key
593
+ let publicKeyBytes: Uint8Array;
594
+
595
+ if (publicKey) {
596
+ // Use provided public key
597
+ publicKeyBytes = publicKeyToBytes(publicKey);
598
+ } else {
599
+ // Public key resolution from verificationMethod is not yet implemented
600
+ // This would require DID Document resolution, which is environment-dependent
601
+ // For now, require public key to be provided explicitly
602
+ throw new Error(
603
+ "Public key is required for verification. Provide it explicitly or implement DID resolution to fetch from verificationMethod."
604
+ );
605
+ }
606
+
607
+ // Step 5: Verify signature
608
+ return await verifyEd25519(
609
+ canonicalMessage,
610
+ signatureBytes,
611
+ publicKeyBytes
612
+ );
613
+ } catch (error) {
614
+ // Return false on any verification error (invalid signature, malformed data, etc.)
615
+ // Error details are not exposed to prevent information leakage
616
+ return false;
617
+ }
618
+ }
619
+
620
+ /**
621
+ * Convert base64url string to Uint8Array
622
+ *
623
+ * Handles both Node.js (Buffer) and browser/Workers (atob) environments.
624
+ *
625
+ * @param base64url - Base64url-encoded string
626
+ * @returns Decoded bytes
627
+ * @internal
628
+ */
629
+ function base64urlToBytes(base64url: string): Uint8Array {
630
+ // Convert base64url to base64
631
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
632
+
633
+ // Add padding if needed
634
+ while (base64.length % 4) {
635
+ base64 += "=";
636
+ }
637
+
638
+ // Decode base64
639
+ if (typeof Buffer !== "undefined") {
640
+ return Buffer.from(base64, "base64");
641
+ } else {
642
+ // For Cloudflare Workers, use atob
643
+ const binary = atob(base64);
644
+ const bytes = new Uint8Array(binary.length);
645
+ for (let i = 0; i < binary.length; i++) {
646
+ bytes[i] = binary.charCodeAt(i);
647
+ }
648
+ return bytes;
649
+ }
650
+ }
651
+
652
+ // Export Verifiable Presentation functions
653
+ export * from "./vp";