@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,533 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * OAP Conformance Test Runner
5
+ *
6
+ * Validates OAP implementations against the specification by:
7
+ * - Validating JSON against schemas
8
+ * - Evaluating policy pack logic
9
+ * - Verifying Ed25519 signatures over JCS payloads
10
+ * - Producing PASS/FAIL reports
11
+ */
12
+
13
+ import { Command } from "commander";
14
+ import chalk from "chalk";
15
+ import ora from "ora";
16
+ import {
17
+ readFileSync,
18
+ writeFileSync,
19
+ mkdirSync,
20
+ existsSync,
21
+ readdirSync,
22
+ } from "fs";
23
+ import { join, dirname } from "path";
24
+ import { fileURLToPath } from "url";
25
+
26
+ import { SchemaValidator } from "./validators.js";
27
+ import { JCS } from "./jcs.js";
28
+ import { Ed25519 } from "./ed25519.js";
29
+ import { TestCase, TestResult, ConformanceReport } from "./cases.js";
30
+
31
+ const __filename = fileURLToPath(import.meta.url);
32
+ const __dirname = dirname(__filename);
33
+
34
+ interface RunnerOptions {
35
+ pack?: string;
36
+ verbose?: boolean;
37
+ report?: boolean;
38
+ }
39
+
40
+ class ConformanceRunner {
41
+ private validator: SchemaValidator;
42
+ private jcs: JCS;
43
+ private ed25519: Ed25519;
44
+ private options: RunnerOptions;
45
+
46
+ constructor(options: RunnerOptions = {}) {
47
+ this.options = options;
48
+ this.validator = new SchemaValidator();
49
+ this.jcs = new JCS();
50
+ this.ed25519 = new Ed25519();
51
+ }
52
+
53
+ async run(): Promise<void> {
54
+ console.log(chalk.blue.bold("\n🔍 OAP Conformance Test Runner v1.0.0\n"));
55
+
56
+ const spinner = ora("Loading test cases...").start();
57
+
58
+ try {
59
+ // Load test cases
60
+ const testCases = await this.loadTestCases();
61
+ spinner.succeed(`Loaded ${testCases.length} test cases`);
62
+
63
+ // Run tests
64
+ const results = await this.runTests(testCases);
65
+
66
+ // Generate report
67
+ const report = this.generateReport(results);
68
+
69
+ // Display results
70
+ this.displayResults(report);
71
+
72
+ // Save report if requested
73
+ if (this.options.report) {
74
+ await this.saveReport(report);
75
+ }
76
+
77
+ // Exit with appropriate code
78
+ process.exit(report.summary.failed > 0 ? 1 : 0);
79
+ } catch (error) {
80
+ spinner.fail("Test execution failed");
81
+ console.error(chalk.red("Error:"), error);
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ private async loadTestCases(): Promise<TestCase[]> {
87
+ const casesDir = join(__dirname, "..", "cases");
88
+ const testCases: TestCase[] = [];
89
+
90
+ // Load policy pack test cases
91
+ const packDirs = this.getPackDirectories(casesDir);
92
+
93
+ for (const packDir of packDirs) {
94
+ const packId = packDir.split("/").pop()!;
95
+
96
+ // Skip if specific pack requested and doesn't match
97
+ if (this.options.pack && !packId.includes(this.options.pack)) {
98
+ continue;
99
+ }
100
+
101
+ const packCases = await this.loadPackTestCases(packDir, packId);
102
+ testCases.push(...packCases);
103
+ }
104
+
105
+ return testCases;
106
+ }
107
+
108
+ private getPackDirectories(casesDir: string): string[] {
109
+ if (!existsSync(casesDir)) {
110
+ return [];
111
+ }
112
+
113
+ return readdirSync(casesDir, { withFileTypes: true })
114
+ .filter((dirent: any) => dirent.isDirectory())
115
+ .map((dirent: any) => join(casesDir, dirent.name));
116
+ }
117
+
118
+ private async loadPackTestCases(
119
+ packDir: string,
120
+ packId: string
121
+ ): Promise<TestCase[]> {
122
+ const testCases: TestCase[] = [];
123
+
124
+ try {
125
+ // Load passports
126
+ const passportsDir = join(packDir, "passports");
127
+ const contextsDir = join(packDir, "contexts");
128
+ const expectedDir = join(packDir, "expected");
129
+ const receiptsDir = join(packDir, "receipts");
130
+
131
+ if (
132
+ !existsSync(passportsDir) ||
133
+ !existsSync(contextsDir) ||
134
+ !existsSync(expectedDir)
135
+ ) {
136
+ console.warn(
137
+ chalk.yellow(`⚠️ Skipping ${packId}: missing required directories`)
138
+ );
139
+ return testCases;
140
+ }
141
+
142
+ // Load passport files
143
+ const passportFiles = this.getJsonFiles(passportsDir);
144
+ const contextFiles = this.getJsonFiles(contextsDir);
145
+ const expectedFiles = this.getJsonFiles(expectedDir);
146
+ const receiptFiles = existsSync(receiptsDir)
147
+ ? this.getJsonFiles(receiptsDir)
148
+ : [];
149
+
150
+ // Create test cases
151
+ for (const contextFile of contextFiles) {
152
+ const contextName = contextFile.replace(".json", "");
153
+ const expectedFile = expectedFiles.find((f) => f.includes(contextName));
154
+
155
+ if (!expectedFile) {
156
+ console.warn(
157
+ chalk.yellow(`⚠️ No expected result for context: ${contextName}`)
158
+ );
159
+ continue;
160
+ }
161
+
162
+ const testCase: TestCase = {
163
+ id: `${packId}:${contextName}`,
164
+ packId,
165
+ contextName,
166
+ passport: this.loadJsonFile(join(passportsDir, passportFiles[0])), // Use first passport
167
+ context: this.loadJsonFile(join(contextsDir, contextFile)),
168
+ expected: this.loadJsonFile(join(expectedDir, expectedFile)),
169
+ receipt: receiptFiles.find((f) => f.includes(contextName))
170
+ ? this.loadJsonFile(
171
+ join(
172
+ receiptsDir,
173
+ receiptFiles.find((f) => f.includes(contextName))!
174
+ )
175
+ )
176
+ : undefined,
177
+ };
178
+
179
+ testCases.push(testCase);
180
+ }
181
+ } catch (error) {
182
+ console.warn(chalk.yellow(`⚠️ Error loading ${packId}: ${error}`));
183
+ }
184
+
185
+ return testCases;
186
+ }
187
+
188
+ private getJsonFiles(dir: string): string[] {
189
+ return readdirSync(dir).filter((file: string) => file.endsWith(".json"));
190
+ }
191
+
192
+ private loadJsonFile(filePath: string): any {
193
+ return JSON.parse(readFileSync(filePath, "utf-8"));
194
+ }
195
+
196
+ private async runTests(testCases: TestCase[]): Promise<TestResult[]> {
197
+ const results: TestResult[] = [];
198
+
199
+ for (const testCase of testCases) {
200
+ const spinner = ora(`Running ${testCase.id}...`).start();
201
+
202
+ try {
203
+ const result = await this.runTestCase(testCase);
204
+ results.push(result);
205
+
206
+ if (result.passed) {
207
+ spinner.succeed(`${testCase.id}: PASS`);
208
+ } else {
209
+ spinner.fail(`${testCase.id}: FAIL`);
210
+ }
211
+
212
+ if (this.options.verbose && !result.passed) {
213
+ console.log(chalk.red(" Errors:"), result.errors);
214
+ }
215
+ } catch (error) {
216
+ spinner.fail(`${testCase.id}: ERROR`);
217
+ results.push({
218
+ testCase,
219
+ passed: false,
220
+ errors: [`Test execution failed: ${error}`],
221
+ warnings: [],
222
+ });
223
+ }
224
+ }
225
+
226
+ return results;
227
+ }
228
+
229
+ private async runTestCase(testCase: TestCase): Promise<TestResult> {
230
+ const errors: string[] = [];
231
+ const warnings: string[] = [];
232
+
233
+ // 1. Validate passport against schema
234
+ const passportValidation = await this.validator.validatePassport(
235
+ testCase.passport
236
+ );
237
+ if (!passportValidation.valid) {
238
+ errors.push(
239
+ `Passport validation failed: ${passportValidation.errors.join(", ")}`
240
+ );
241
+ }
242
+
243
+ // 2. Validate context against policy requirements
244
+ const contextValidation = await this.validator.validateContext(
245
+ testCase.packId,
246
+ testCase.context
247
+ );
248
+ if (!contextValidation.valid) {
249
+ errors.push(
250
+ `Context validation failed: ${contextValidation.errors.join(", ")}`
251
+ );
252
+ }
253
+
254
+ // 3. Evaluate policy logic (simplified - in real implementation this would call the policy evaluator)
255
+ const policyResult = await this.evaluatePolicy(testCase);
256
+ if (!policyResult.valid) {
257
+ errors.push(
258
+ `Policy evaluation failed: ${policyResult.errors.join(", ")}`
259
+ );
260
+ }
261
+
262
+ // 4. Validate expected decision against schema
263
+ const decisionValidation = await this.validator.validateDecision(
264
+ testCase.expected
265
+ );
266
+ if (!decisionValidation.valid) {
267
+ errors.push(
268
+ `Expected decision validation failed: ${decisionValidation.errors.join(
269
+ ", "
270
+ )}`
271
+ );
272
+ }
273
+
274
+ // 5. Verify signature if receipt provided
275
+ if (testCase.receipt) {
276
+ const signatureValidation = await this.verifySignature(testCase.receipt);
277
+ if (!signatureValidation.valid) {
278
+ errors.push(
279
+ `Signature verification failed: ${signatureValidation.errors.join(
280
+ ", "
281
+ )}`
282
+ );
283
+ }
284
+ }
285
+
286
+ // 6. Compare actual vs expected (simplified)
287
+ const comparison = this.compareResults(
288
+ policyResult.decision,
289
+ testCase.expected
290
+ );
291
+ if (!comparison.matches) {
292
+ errors.push(`Result mismatch: ${comparison.differences.join(", ")}`);
293
+ }
294
+
295
+ return {
296
+ testCase,
297
+ passed: errors.length === 0,
298
+ errors,
299
+ warnings,
300
+ };
301
+ }
302
+
303
+ private async evaluatePolicy(
304
+ testCase: TestCase
305
+ ): Promise<{ valid: boolean; errors: string[]; decision: any }> {
306
+ // This is a simplified policy evaluation
307
+ // In a real implementation, this would call the actual policy evaluator
308
+
309
+ const { packId, passport, context } = testCase;
310
+
311
+ // Basic policy logic based on pack ID
312
+ let allow = true;
313
+ const reasons: any[] = [];
314
+
315
+ if (packId === "finance.payment.refund.v1") {
316
+ const amount = context.amount || 0;
317
+ const currency = context.currency || "USD";
318
+
319
+ // Check currency limits
320
+ const limits =
321
+ passport.limits?.["finance.payment.refund"]?.currency_limits?.[
322
+ currency
323
+ ];
324
+ if (limits) {
325
+ if (amount > limits.max_per_tx) {
326
+ allow = false;
327
+ reasons.push({
328
+ code: "oap.limit_exceeded",
329
+ message: `Amount ${amount} exceeds max per transaction ${limits.max_per_tx}`,
330
+ });
331
+ }
332
+ } else {
333
+ // Currency not supported
334
+ allow = false;
335
+ reasons.push({
336
+ code: "oap.currency_unsupported",
337
+ message: `Currency ${currency} not supported for this passport`,
338
+ });
339
+ }
340
+ }
341
+
342
+ if (packId === "data.export.create.v1") {
343
+ const includePii = context.include_pii || false;
344
+ const allowPii = passport.limits?.["data.export"]?.allow_pii || false;
345
+
346
+ if (includePii && !allowPii) {
347
+ allow = false;
348
+ reasons.push({
349
+ code: "oap.pii_blocked",
350
+ message: "PII export not allowed for this passport",
351
+ });
352
+ }
353
+ }
354
+
355
+ // If no reasons and allow is true, add success reason
356
+ if (allow && reasons.length === 0) {
357
+ reasons.push({
358
+ code: "oap.allowed",
359
+ message: "Transaction within limits and policy requirements",
360
+ });
361
+ }
362
+
363
+ const decision = {
364
+ decision_id: `test_${Date.now()}`,
365
+ policy_id: packId,
366
+ agent_id: passport.agent_id || passport.passport_id,
367
+ owner_id: passport.owner_id,
368
+ assurance_level: passport.assurance_level,
369
+ allow,
370
+ reasons,
371
+ created_at: new Date().toISOString(),
372
+ expires_in: 3600,
373
+ passport_digest: await this.computePassportDigest(passport),
374
+ signature:
375
+ "ed25519:test_signature_placeholder_64_chars_long_for_conformance_testing",
376
+ kid: "oap:registry:test-key",
377
+ };
378
+
379
+ return {
380
+ valid: true,
381
+ errors: [],
382
+ decision,
383
+ };
384
+ }
385
+
386
+ private async computePassportDigest(passport: any): Promise<string> {
387
+ const canonical = this.jcs.canonicalize(passport);
388
+ const hash = await crypto.subtle.digest(
389
+ "SHA-256",
390
+ new TextEncoder().encode(canonical)
391
+ );
392
+ const hashArray = Array.from(new Uint8Array(hash));
393
+ const hashHex = hashArray
394
+ .map((b) => b.toString(16).padStart(2, "0"))
395
+ .join("");
396
+ return `sha256:${hashHex}`;
397
+ }
398
+
399
+ private async verifySignature(
400
+ receipt: any
401
+ ): Promise<{ valid: boolean; errors: string[] }> {
402
+ // Simplified signature verification for conformance testing
403
+ // In a real implementation, this would verify Ed25519 signatures
404
+
405
+ if (!receipt.signature) {
406
+ return { valid: false, errors: ["No signature provided"] };
407
+ }
408
+
409
+ // For conformance testing, we'll just check that signature exists and has correct format
410
+ if (!receipt.signature.startsWith("ed25519:")) {
411
+ return { valid: false, errors: ["Invalid signature format"] };
412
+ }
413
+
414
+ return { valid: true, errors: [] };
415
+ }
416
+
417
+ private compareResults(
418
+ actual: any,
419
+ expected: any
420
+ ): { matches: boolean; differences: string[] } {
421
+ const differences: string[] = [];
422
+
423
+ // Compare key fields
424
+ if (actual.allow !== expected.allow) {
425
+ differences.push(
426
+ `allow: expected ${expected.allow}, got ${actual.allow}`
427
+ );
428
+ }
429
+
430
+ if (actual.assurance_level !== expected.assurance_level) {
431
+ differences.push(
432
+ `assurance_level: expected ${expected.assurance_level}, got ${actual.assurance_level}`
433
+ );
434
+ }
435
+
436
+ // Compare reasons (simplified)
437
+ if (actual.reasons?.length !== expected.reasons?.length) {
438
+ differences.push(
439
+ `reasons: expected ${expected.reasons?.length} reasons, got ${actual.reasons?.length}`
440
+ );
441
+ }
442
+
443
+ return {
444
+ matches: differences.length === 0,
445
+ differences,
446
+ };
447
+ }
448
+
449
+ private generateReport(results: TestResult[]): ConformanceReport {
450
+ const passed = results.filter((r) => r.passed).length;
451
+ const failed = results.filter((r) => !r.passed).length;
452
+ const total = results.length;
453
+
454
+ const summary = {
455
+ total,
456
+ passed,
457
+ failed,
458
+ successRate: total > 0 ? (passed / total) * 100 : 0,
459
+ };
460
+
461
+ const details = results.map((result) => ({
462
+ testCase: result.testCase.id,
463
+ passed: result.passed,
464
+ errors: result.errors,
465
+ warnings: result.warnings,
466
+ }));
467
+
468
+ return {
469
+ timestamp: new Date().toISOString(),
470
+ summary,
471
+ details,
472
+ };
473
+ }
474
+
475
+ private displayResults(report: ConformanceReport): void {
476
+ console.log("\n" + chalk.blue.bold("📊 Conformance Test Results\n"));
477
+
478
+ const { summary } = report;
479
+
480
+ console.log(chalk.green(`✅ Passed: ${summary.passed}`));
481
+ console.log(chalk.red(`❌ Failed: ${summary.failed}`));
482
+ console.log(
483
+ chalk.blue(`📈 Success Rate: ${summary.successRate.toFixed(1)}%`)
484
+ );
485
+
486
+ if (summary.failed > 0) {
487
+ console.log("\n" + chalk.red.bold("Failed Tests:"));
488
+ report.details
489
+ .filter((d: any) => !d.passed)
490
+ .forEach((detail: any) => {
491
+ console.log(chalk.red(` • ${detail.testCase}`));
492
+ detail.errors.forEach((error: any) => {
493
+ console.log(chalk.gray(` - ${error}`));
494
+ });
495
+ });
496
+ }
497
+
498
+ console.log("\n" + chalk.blue("🎯 Conformance testing complete!\n"));
499
+ }
500
+
501
+ private async saveReport(report: ConformanceReport): Promise<void> {
502
+ const reportsDir = join(__dirname, "..", "reports");
503
+
504
+ if (!existsSync(reportsDir)) {
505
+ mkdirSync(reportsDir, { recursive: true });
506
+ }
507
+
508
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
509
+ const reportFile = join(reportsDir, `conformance-${timestamp}.json`);
510
+
511
+ writeFileSync(reportFile, JSON.stringify(report, null, 2));
512
+ console.log(chalk.green(`📄 Report saved to: ${reportFile}`));
513
+ }
514
+ }
515
+
516
+ // CLI setup
517
+ const program = new Command();
518
+
519
+ program
520
+ .name("oap-conformance")
521
+ .description("Open Agent Passport conformance test runner")
522
+ .version("1.0.0");
523
+
524
+ program
525
+ .option("-p, --pack <pack>", "Run tests for specific policy pack")
526
+ .option("-v, --verbose", "Verbose output")
527
+ .option("-r, --report", "Generate detailed report")
528
+ .action(async (options: any) => {
529
+ const runner = new ConformanceRunner(options);
530
+ await runner.run();
531
+ });
532
+
533
+ program.parse();
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Schema validators for OAP conformance testing
3
+ */
4
+
5
+ import Ajv from "ajv";
6
+ import addFormats from "ajv-formats";
7
+
8
+ export class SchemaValidator {
9
+ private ajv: Ajv;
10
+ private passportSchema: any;
11
+ private decisionSchema: any;
12
+
13
+ constructor() {
14
+ this.ajv = new Ajv({ allErrors: true });
15
+ addFormats(this.ajv);
16
+ // Use fallback schemas for now
17
+ this.passportSchema = this.getFallbackPassportSchema();
18
+ this.decisionSchema = this.getFallbackDecisionSchema();
19
+ }
20
+
21
+ async validatePassport(
22
+ passport: any
23
+ ): Promise<{ valid: boolean; errors: string[] }> {
24
+ const validate = this.ajv.compile(this.passportSchema);
25
+ const valid = validate(passport);
26
+
27
+ if (!valid) {
28
+ const errors = validate.errors?.map(
29
+ (err) => `${err.instancePath || "root"}: ${err.message}`
30
+ ) || ["Unknown validation error"];
31
+ return { valid: false, errors };
32
+ }
33
+
34
+ return { valid: true, errors: [] };
35
+ }
36
+
37
+ async validateDecision(
38
+ decision: any
39
+ ): Promise<{ valid: boolean; errors: string[] }> {
40
+ const validate = this.ajv.compile(this.decisionSchema);
41
+ const valid = validate(decision);
42
+
43
+ if (!valid) {
44
+ const errors = validate.errors?.map(
45
+ (err) => `${err.instancePath || "root"}: ${err.message}`
46
+ ) || ["Unknown validation error"];
47
+ return { valid: false, errors };
48
+ }
49
+
50
+ return { valid: true, errors: [] };
51
+ }
52
+
53
+ async validateContext(
54
+ packId: string,
55
+ context: any
56
+ ): Promise<{ valid: boolean; errors: string[] }> {
57
+ const errors: string[] = [];
58
+
59
+ // Basic context validation based on pack ID
60
+ switch (packId) {
61
+ case "finance.payment.refund.v1":
62
+ if (!context.amount || typeof context.amount !== "number") {
63
+ errors.push("amount is required and must be a number");
64
+ }
65
+ if (!context.currency || typeof context.currency !== "string") {
66
+ errors.push("currency is required and must be a string");
67
+ }
68
+ if (!context.order_id || typeof context.order_id !== "string") {
69
+ errors.push("order_id is required and must be a string");
70
+ }
71
+ break;
72
+
73
+ case "data.export.create.v1":
74
+ if (!context.collection || typeof context.collection !== "string") {
75
+ errors.push("collection is required and must be a string");
76
+ }
77
+ if (
78
+ context.estimated_rows &&
79
+ typeof context.estimated_rows !== "number"
80
+ ) {
81
+ errors.push("estimated_rows must be a number");
82
+ }
83
+ break;
84
+
85
+ case "repo.release.publish.v1":
86
+ if (!context.repo || typeof context.repo !== "string") {
87
+ errors.push("repo is required and must be a string");
88
+ }
89
+ if (!context.branch || typeof context.branch !== "string") {
90
+ errors.push("branch is required and must be a string");
91
+ }
92
+ if (!context.tag || typeof context.tag !== "string") {
93
+ errors.push("tag is required and must be a string");
94
+ }
95
+ break;
96
+
97
+ default:
98
+ errors.push(`Unknown policy pack: ${packId}`);
99
+ }
100
+
101
+ return {
102
+ valid: errors.length === 0,
103
+ errors,
104
+ };
105
+ }
106
+
107
+ private getFallbackPassportSchema(): any {
108
+ return {
109
+ type: "object",
110
+ required: [
111
+ "passport_id",
112
+ "kind",
113
+ "spec_version",
114
+ "owner_id",
115
+ "owner_type",
116
+ "status",
117
+ "assurance_level",
118
+ "capabilities",
119
+ "limits",
120
+ "regions",
121
+ "created_at",
122
+ "updated_at",
123
+ "version",
124
+ ],
125
+ properties: {
126
+ passport_id: { type: "string", format: "uuid" },
127
+ kind: { type: "string", enum: ["template", "instance"] },
128
+ spec_version: { type: "string", const: "oap/1.0" },
129
+ owner_id: { type: "string" },
130
+ owner_type: { type: "string", enum: ["org", "user"] },
131
+ assurance_level: {
132
+ type: "string",
133
+ enum: ["L0", "L1", "L2", "L3", "L4KYC", "L4FIN"],
134
+ },
135
+ status: {
136
+ type: "string",
137
+ enum: ["draft", "active", "suspended", "revoked"],
138
+ },
139
+ capabilities: { type: "array" },
140
+ limits: { type: "object" },
141
+ regions: { type: "array" },
142
+ created_at: { type: "string", format: "date-time" },
143
+ updated_at: { type: "string", format: "date-time" },
144
+ version: { type: "string" },
145
+ },
146
+ };
147
+ }
148
+
149
+ private getFallbackDecisionSchema(): any {
150
+ return {
151
+ type: "object",
152
+ required: [
153
+ "decision_id",
154
+ "policy_id",
155
+ "agent_id",
156
+ "owner_id",
157
+ "assurance_level",
158
+ "allow",
159
+ "reasons",
160
+ "created_at",
161
+ "expires_in",
162
+ "passport_digest",
163
+ "signature",
164
+ "kid",
165
+ ],
166
+ properties: {
167
+ decision_id: { type: "string", format: "uuid" },
168
+ policy_id: { type: "string" },
169
+ agent_id: { type: "string", format: "uuid" },
170
+ owner_id: { type: "string" },
171
+ assurance_level: {
172
+ type: "string",
173
+ enum: ["L0", "L1", "L2", "L3", "L4KYC", "L4FIN"],
174
+ },
175
+ allow: { type: "boolean" },
176
+ reasons: { type: "array" },
177
+ created_at: { type: "string", format: "date-time" },
178
+ expires_in: { type: "integer", minimum: 0 },
179
+ passport_digest: { type: "string", pattern: "^sha256:[a-f0-9]{64}$" },
180
+ signature: { type: "string", pattern: "^ed25519:[A-Za-z0-9+/=]+$" },
181
+ kid: { type: "string" },
182
+ },
183
+ };
184
+ }
185
+ }