@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,371 @@
1
+ /**
2
+ * Test case definitions and types for OAP conformance testing
3
+ */
4
+
5
+ export interface TestCase {
6
+ id: string;
7
+ packId: string;
8
+ contextName: string;
9
+ passport: any;
10
+ context: any;
11
+ expected: any;
12
+ receipt?: any;
13
+ }
14
+
15
+ export interface TestResult {
16
+ testCase: TestCase;
17
+ passed: boolean;
18
+ errors: string[];
19
+ warnings: string[];
20
+ }
21
+
22
+ export interface ConformanceReport {
23
+ timestamp: string;
24
+ summary: {
25
+ total: number;
26
+ passed: number;
27
+ failed: number;
28
+ successRate: number;
29
+ };
30
+ details: Array<{
31
+ testCase: string;
32
+ passed: boolean;
33
+ errors: string[];
34
+ warnings: string[];
35
+ }>;
36
+ }
37
+
38
+ export interface PolicyPackTestCase {
39
+ packId: string;
40
+ name: string;
41
+ description: string;
42
+ passports: any[];
43
+ contexts: any[];
44
+ expected: any[];
45
+ receipts?: any[];
46
+ }
47
+
48
+ export interface SignatureVerificationResult {
49
+ valid: boolean;
50
+ errors: string[];
51
+ warnings: string[];
52
+ }
53
+
54
+ export interface PolicyEvaluationResult {
55
+ valid: boolean;
56
+ decision: any;
57
+ errors: string[];
58
+ warnings: string[];
59
+ }
60
+
61
+ /**
62
+ * Standard test cases for OAP conformance
63
+ */
64
+ export const STANDARD_TEST_CASES: PolicyPackTestCase[] = [
65
+ {
66
+ packId: "finance.payment.refund.v1",
67
+ name: "Refunds Policy Pack",
68
+ description: "Tests for finance.payment.refund.v1 policy pack",
69
+ passports: [],
70
+ contexts: [],
71
+ expected: [],
72
+ receipts: [],
73
+ },
74
+ {
75
+ packId: "data.export.create.v1",
76
+ name: "Data Export Policy Pack",
77
+ description: "Tests for data.export.create.v1 policy pack",
78
+ passports: [],
79
+ contexts: [],
80
+ expected: [],
81
+ receipts: [],
82
+ },
83
+ {
84
+ packId: "repo.release.publish.v1",
85
+ name: "Repository Release Policy Pack",
86
+ description: "Tests for repo.release.publish.v1 policy pack",
87
+ passports: [],
88
+ contexts: [],
89
+ expected: [],
90
+ receipts: [],
91
+ },
92
+ ];
93
+
94
+ /**
95
+ * Error codes for OAP conformance testing
96
+ */
97
+ export const OAP_ERROR_CODES = {
98
+ INVALID_CONTEXT: "oap.invalid_context",
99
+ UNKNOWN_CAPABILITY: "oap.unknown_capability",
100
+ LIMIT_EXCEEDED: "oap.limit_exceeded",
101
+ CURRENCY_UNSUPPORTED: "oap.currency_unsupported",
102
+ REGION_BLOCKED: "oap.region_blocked",
103
+ ASSURANCE_INSUFFICIENT: "oap.assurance_insufficient",
104
+ PASSPORT_SUSPENDED: "oap.passport_suspended",
105
+ IDEMPOTENCY_CONFLICT: "oap.idempotency_conflict",
106
+ POLICY_ERROR: "oap.policy_error",
107
+ PII_BLOCKED: "oap.pii_blocked",
108
+ COLLECTION_FORBIDDEN: "oap.collection_forbidden",
109
+ BRANCH_FORBIDDEN: "oap.branch_forbidden",
110
+ REPO_FORBIDDEN: "oap.repo_forbidden",
111
+ UNSIGNED_ARTIFACT: "oap.unsigned_artifact",
112
+ } as const;
113
+
114
+ /**
115
+ * Assurance levels for OAP conformance testing
116
+ */
117
+ export const ASSURANCE_LEVELS = {
118
+ L0: "L0",
119
+ L1: "L1",
120
+ L2: "L2",
121
+ L3: "L3",
122
+ L4KYC: "L4KYC",
123
+ L4FIN: "L4FIN",
124
+ } as const;
125
+
126
+ /**
127
+ * Policy pack requirements for conformance testing
128
+ */
129
+ export const POLICY_PACK_REQUIREMENTS = {
130
+ "finance.payment.refund.v1": {
131
+ requiredCapabilities: ["finance.payment.refund"],
132
+ minAssurance: "L2",
133
+ requiredContextFields: ["amount", "currency", "order_id"],
134
+ optionalContextFields: [
135
+ "customer_id",
136
+ "reason_code",
137
+ "region",
138
+ "idempotency_key",
139
+ ],
140
+ },
141
+ "data.export.create.v1": {
142
+ requiredCapabilities: ["data.export"],
143
+ minAssurance: "L1",
144
+ requiredContextFields: ["collection"],
145
+ optionalContextFields: ["estimated_rows", "include_pii", "region"],
146
+ },
147
+ "repo.release.publish.v1": {
148
+ requiredCapabilities: ["repo.release.publish"],
149
+ minAssurance: "L2",
150
+ requiredContextFields: ["repo", "branch", "tag"],
151
+ optionalContextFields: ["artifact_sha", "signer"],
152
+ },
153
+ } as const;
154
+
155
+ /**
156
+ * Test case validation utilities
157
+ */
158
+ export class TestCaseValidator {
159
+ /**
160
+ * Validate a test case structure
161
+ */
162
+ static validateTestCase(testCase: TestCase): {
163
+ valid: boolean;
164
+ errors: string[];
165
+ } {
166
+ const errors: string[] = [];
167
+
168
+ if (!testCase.id) {
169
+ errors.push("Test case ID is required");
170
+ }
171
+
172
+ if (!testCase.packId) {
173
+ errors.push("Policy pack ID is required");
174
+ }
175
+
176
+ if (!testCase.passport) {
177
+ errors.push("Passport is required");
178
+ }
179
+
180
+ if (!testCase.context) {
181
+ errors.push("Context is required");
182
+ }
183
+
184
+ if (!testCase.expected) {
185
+ errors.push("Expected result is required");
186
+ }
187
+
188
+ return {
189
+ valid: errors.length === 0,
190
+ errors,
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Validate a policy pack test case
196
+ */
197
+ static validatePolicyPackTestCase(testCase: PolicyPackTestCase): {
198
+ valid: boolean;
199
+ errors: string[];
200
+ } {
201
+ const errors: string[] = [];
202
+
203
+ if (!testCase.packId) {
204
+ errors.push("Policy pack ID is required");
205
+ }
206
+
207
+ if (!testCase.name) {
208
+ errors.push("Test case name is required");
209
+ }
210
+
211
+ if (!Array.isArray(testCase.passports)) {
212
+ errors.push("Passports must be an array");
213
+ }
214
+
215
+ if (!Array.isArray(testCase.contexts)) {
216
+ errors.push("Contexts must be an array");
217
+ }
218
+
219
+ if (!Array.isArray(testCase.expected)) {
220
+ errors.push("Expected results must be an array");
221
+ }
222
+
223
+ if (testCase.passports.length !== testCase.contexts.length) {
224
+ errors.push("Number of passports must match number of contexts");
225
+ }
226
+
227
+ if (testCase.contexts.length !== testCase.expected.length) {
228
+ errors.push("Number of contexts must match number of expected results");
229
+ }
230
+
231
+ return {
232
+ valid: errors.length === 0,
233
+ errors,
234
+ };
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Test case generators for common scenarios
240
+ */
241
+ export class TestCaseGenerator {
242
+ /**
243
+ * Generate a basic refund test case
244
+ */
245
+ static generateRefundTestCase(
246
+ amount: number,
247
+ currency: string,
248
+ shouldPass: boolean
249
+ ): TestCase {
250
+ return {
251
+ id: `finance.payment.refund.v1:${amount}${currency}_${
252
+ shouldPass ? "allow" : "deny"
253
+ }`,
254
+ packId: "finance.payment.refund.v1",
255
+ contextName: `${amount}${currency}_${shouldPass ? "allow" : "deny"}`,
256
+ passport: {
257
+ passport_id: "550e8400-e29b-41d4-a716-446655440000",
258
+ kind: "template",
259
+ spec_version: "oap/1.0",
260
+ owner_id: "org_12345678",
261
+ owner_type: "org",
262
+ assurance_level: "L2",
263
+ status: "active",
264
+ capabilities: [{ id: "finance.payment.refund" }],
265
+ limits: {
266
+ "finance.payment.refund": {
267
+ currency_limits: {
268
+ [currency]: {
269
+ max_per_tx: shouldPass ? amount * 2 : amount / 2,
270
+ daily_cap: amount * 10,
271
+ },
272
+ },
273
+ },
274
+ },
275
+ regions: ["US"],
276
+ created_at: "2024-01-01T00:00:00Z",
277
+ updated_at: "2024-01-15T10:30:00Z",
278
+ version: "1.0.0",
279
+ },
280
+ context: {
281
+ amount,
282
+ currency,
283
+ order_id: `order_${Date.now()}`,
284
+ customer_id: "cust_123",
285
+ reason_code: "customer_request",
286
+ region: "US",
287
+ },
288
+ expected: {
289
+ decision_id: "test_decision_id",
290
+ policy_id: "finance.payment.refund.v1",
291
+ agent_id: "550e8400-e29b-41d4-a716-446655440000",
292
+ owner_id: "org_12345678",
293
+ assurance_level: "L2",
294
+ allow: shouldPass,
295
+ reasons: shouldPass
296
+ ? [{ code: "oap.allowed", message: "Transaction within limits" }]
297
+ : [{ code: "oap.limit_exceeded", message: "Amount exceeds limit" }],
298
+ created_at: "2024-01-15T10:30:00Z",
299
+ expires_in: 3600,
300
+ passport_digest: "sha256:test_digest",
301
+ signature: "ed25519:test_signature",
302
+ kid: "oap:registry:test-key",
303
+ },
304
+ };
305
+ }
306
+
307
+ /**
308
+ * Generate a data export test case
309
+ */
310
+ static generateDataExportTestCase(
311
+ collection: string,
312
+ estimatedRows: number,
313
+ includePii: boolean,
314
+ shouldPass: boolean
315
+ ): TestCase {
316
+ return {
317
+ id: `data.export.create.v1:${collection}_${estimatedRows}_${
318
+ includePii ? "pii" : "no_pii"
319
+ }_${shouldPass ? "allow" : "deny"}`,
320
+ packId: "data.export.create.v1",
321
+ contextName: `${collection}_${estimatedRows}_${
322
+ includePii ? "pii" : "no_pii"
323
+ }_${shouldPass ? "allow" : "deny"}`,
324
+ passport: {
325
+ passport_id: "550e8400-e29b-41d4-a716-446655440001",
326
+ kind: "template",
327
+ spec_version: "oap/1.0",
328
+ owner_id: "org_12345678",
329
+ owner_type: "org",
330
+ assurance_level: "L1",
331
+ status: "active",
332
+ capabilities: [{ id: "data.export" }],
333
+ limits: {
334
+ "data.export": {
335
+ max_rows: shouldPass ? estimatedRows * 2 : estimatedRows / 2,
336
+ allow_pii: includePii,
337
+ allowed_collections: shouldPass
338
+ ? [collection]
339
+ : ["other_collection"],
340
+ },
341
+ },
342
+ regions: ["US"],
343
+ created_at: "2024-01-01T00:00:00Z",
344
+ updated_at: "2024-01-15T10:30:00Z",
345
+ version: "1.0.0",
346
+ },
347
+ context: {
348
+ collection,
349
+ estimated_rows: estimatedRows,
350
+ include_pii: includePii,
351
+ region: "US",
352
+ },
353
+ expected: {
354
+ decision_id: "test_decision_id",
355
+ policy_id: "data.export.create.v1",
356
+ agent_id: "550e8400-e29b-41d4-a716-446655440001",
357
+ owner_id: "org_12345678",
358
+ assurance_level: "L1",
359
+ allow: shouldPass,
360
+ reasons: shouldPass
361
+ ? [{ code: "oap.allowed", message: "Export within limits" }]
362
+ : [{ code: "oap.limit_exceeded", message: "Row limit exceeded" }],
363
+ created_at: "2024-01-15T10:30:00Z",
364
+ expires_in: 3600,
365
+ passport_digest: "sha256:test_digest",
366
+ signature: "ed25519:test_signature",
367
+ kid: "oap:registry:test-key",
368
+ },
369
+ };
370
+ }
371
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Ed25519 signature verification for OAP conformance testing
3
+ *
4
+ * Note: This is a simplified implementation for conformance testing.
5
+ * In production, use a proper Ed25519 library like @noble/ed25519
6
+ */
7
+
8
+ export class Ed25519 {
9
+ /**
10
+ * Verify an Ed25519 signature
11
+ *
12
+ * @param message - The message that was signed
13
+ * @param signature - The signature in base64 format
14
+ * @param publicKey - The public key in base64 format
15
+ * @returns Promise<boolean> - True if signature is valid
16
+ */
17
+ async verify(
18
+ message: Uint8Array,
19
+ signature: string,
20
+ publicKey: string
21
+ ): Promise<boolean> {
22
+ try {
23
+ // For conformance testing, we'll implement a simplified verification
24
+ // In production, this would use a proper Ed25519 library
25
+
26
+ // Remove the 'ed25519:' prefix if present
27
+ const cleanSignature = signature.replace(/^ed25519:/, "");
28
+ const cleanPublicKey = publicKey.replace(/^ed25519:/, "");
29
+
30
+ // Basic format validation
31
+ if (
32
+ !this.isValidBase64(cleanSignature) ||
33
+ !this.isValidBase64(cleanPublicKey)
34
+ ) {
35
+ return false;
36
+ }
37
+
38
+ // For conformance testing, we'll accept any properly formatted signature
39
+ // In production, this would perform actual cryptographic verification
40
+ return cleanSignature.length === 88 && cleanPublicKey.length === 44; // Expected lengths for base64 encoded Ed25519
41
+ } catch (error) {
42
+ console.warn("Ed25519 verification failed:", error);
43
+ return false;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Verify a decision signature
49
+ *
50
+ * @param decision - The decision object
51
+ * @param signature - The signature string
52
+ * @param publicKey - The public key
53
+ * @returns Promise<boolean> - True if signature is valid
54
+ */
55
+ async verifyDecisionSignature(
56
+ decision: any,
57
+ signature: string,
58
+ publicKey: string
59
+ ): Promise<boolean> {
60
+ try {
61
+ // Create the message that was signed (JCS canonicalized decision)
62
+ const jcs = new (await import("./jcs.js")).JCS();
63
+ const canonical = jcs.canonicalize(decision);
64
+ const message = new TextEncoder().encode(canonical);
65
+
66
+ return await this.verify(message, signature, publicKey);
67
+ } catch (error) {
68
+ console.warn("Decision signature verification failed:", error);
69
+ return false;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Verify a passport digest signature
75
+ *
76
+ * @param passport - The passport object
77
+ * @param digest - The passport digest
78
+ * @returns Promise<boolean> - True if digest is valid
79
+ */
80
+ async verifyPassportDigest(passport: any, digest: string): Promise<boolean> {
81
+ try {
82
+ const jcs = new (await import("./jcs.js")).JCS();
83
+ const computedDigest = await jcs.computePassportDigest(passport);
84
+ return computedDigest === digest;
85
+ } catch (error) {
86
+ console.warn("Passport digest verification failed:", error);
87
+ return false;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Generate a test key pair for conformance testing
93
+ *
94
+ * @returns Promise<{publicKey: string, privateKey: string}>
95
+ */
96
+ async generateTestKeyPair(): Promise<{
97
+ publicKey: string;
98
+ privateKey: string;
99
+ }> {
100
+ // For conformance testing, generate deterministic test keys
101
+ // In production, use proper key generation
102
+ const publicKey = "ed25519:" + "A".repeat(44); // 44 chars for base64 encoded 32-byte key
103
+ const privateKey = "ed25519:" + "B".repeat(44);
104
+
105
+ return { publicKey, privateKey };
106
+ }
107
+
108
+ /**
109
+ * Sign a message with a private key (for testing)
110
+ *
111
+ * @param message - The message to sign
112
+ * @param privateKey - The private key
113
+ * @returns Promise<string> - The signature
114
+ */
115
+ async sign(message: Uint8Array, privateKey: string): Promise<string> {
116
+ // For conformance testing, generate a deterministic signature
117
+ // In production, use proper Ed25519 signing
118
+ const messageStr = new TextDecoder().decode(message);
119
+ const hash = await crypto.subtle.digest("SHA-256", new Uint8Array(message));
120
+ const hashArray = Array.from(new Uint8Array(hash));
121
+ const hashHex = hashArray
122
+ .map((b) => b.toString(16).padStart(2, "0"))
123
+ .join("");
124
+ return "ed25519:" + hashHex.substring(0, 88); // 88 chars for base64 encoded 64-byte signature
125
+ }
126
+
127
+ /**
128
+ * Check if a string is valid base64
129
+ */
130
+ private isValidBase64(str: string): boolean {
131
+ try {
132
+ return btoa(atob(str)) === str;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Resolve a key ID to a public key
140
+ *
141
+ * @param kid - The key identifier
142
+ * @returns Promise<string | null> - The public key or null if not found
143
+ */
144
+ async resolveKey(kid: string): Promise<string | null> {
145
+ try {
146
+ // For conformance testing, we'll use test keys
147
+ // In production, this would resolve keys from /.well-known/oap/keys.json
148
+
149
+ if (kid.startsWith("oap:registry:test-key")) {
150
+ const { publicKey } = await this.generateTestKeyPair();
151
+ return publicKey;
152
+ }
153
+
154
+ if (kid.startsWith("oap:owner:")) {
155
+ // For owner keys, we'd fetch from the owner's domain
156
+ // For conformance testing, return a test key
157
+ const { publicKey } = await this.generateTestKeyPair();
158
+ return publicKey;
159
+ }
160
+
161
+ return null;
162
+ } catch (error) {
163
+ console.warn("Key resolution failed:", error);
164
+ return null;
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * JSON Canonicalization Scheme (JCS) implementation
3
+ *
4
+ * Implements RFC 8785 for deterministic JSON serialization
5
+ */
6
+
7
+ export class JCS {
8
+ /**
9
+ * Canonicalize a JSON object according to RFC 8785
10
+ */
11
+ canonicalize(obj: any): string {
12
+ return this.canonicalizeValue(obj);
13
+ }
14
+
15
+ private canonicalizeValue(value: any): string {
16
+ if (value === null) {
17
+ return "null";
18
+ }
19
+
20
+ if (typeof value === "boolean") {
21
+ return value ? "true" : "false";
22
+ }
23
+
24
+ if (typeof value === "number") {
25
+ if (Number.isInteger(value)) {
26
+ return value.toString();
27
+ }
28
+ return value.toString();
29
+ }
30
+
31
+ if (typeof value === "string") {
32
+ return JSON.stringify(value);
33
+ }
34
+
35
+ if (Array.isArray(value)) {
36
+ return this.canonicalizeArray(value);
37
+ }
38
+
39
+ if (typeof value === "object") {
40
+ return this.canonicalizeObject(value);
41
+ }
42
+
43
+ throw new Error(`Unsupported value type: ${typeof value}`);
44
+ }
45
+
46
+ private canonicalizeArray(arr: any[]): string {
47
+ const elements = arr.map((item) => this.canonicalizeValue(item));
48
+ return "[" + elements.join(",") + "]";
49
+ }
50
+
51
+ private canonicalizeObject(obj: any): string {
52
+ // Get all keys and sort them
53
+ const keys = Object.keys(obj).sort();
54
+
55
+ const pairs = keys.map((key) => {
56
+ const value = this.canonicalizeValue(obj[key]);
57
+ return JSON.stringify(key) + ":" + value;
58
+ });
59
+
60
+ return "{" + pairs.join(",") + "}";
61
+ }
62
+
63
+ /**
64
+ * Compute SHA-256 hash of canonicalized JSON
65
+ */
66
+ async computeHash(obj: any): Promise<string> {
67
+ const canonical = this.canonicalize(obj);
68
+ const encoder = new TextEncoder();
69
+ const data = encoder.encode(canonical);
70
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
71
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
72
+ const hashHex = hashArray
73
+ .map((b) => b.toString(16).padStart(2, "0"))
74
+ .join("");
75
+ return hashHex;
76
+ }
77
+
78
+ /**
79
+ * Compute passport digest as specified in OAP
80
+ */
81
+ async computePassportDigest(passport: any): Promise<string> {
82
+ const hash = await this.computeHash(passport);
83
+ return `sha256:${hash}`;
84
+ }
85
+ }