@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,308 @@
1
+ /**
2
+ * Test Suite: governance.data.access.v1 Policy
3
+ *
4
+ * Tests the data access governance policy with various scenarios
5
+ * including valid access, classification violations, and security controls.
6
+ */
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+
11
+ // Mock the policy evaluation function
12
+ async function evaluateGovernanceDataAccessV1(passport, context) {
13
+ // Mock implementation based on the actual policy logic
14
+ const reasons = [];
15
+ let allow = true;
16
+
17
+ // Check agent status
18
+ if (passport.status === "suspended" || passport.status === "revoked") {
19
+ return {
20
+ allow: false,
21
+ reasons: [
22
+ {
23
+ code: "oap.passport_suspended",
24
+ message: `Agent is ${passport.status} and cannot perform operations`,
25
+ severity: "error",
26
+ },
27
+ ],
28
+ };
29
+ }
30
+
31
+ // Check capabilities
32
+ const hasDataAccessCapability = passport.capabilities?.some(
33
+ (cap) => cap.id === "data.access"
34
+ );
35
+ if (!hasDataAccessCapability) {
36
+ return {
37
+ allow: false,
38
+ reasons: [
39
+ {
40
+ code: "oap.unknown_capability",
41
+ message: "Agent does not have data.access capability",
42
+ severity: "error",
43
+ },
44
+ ],
45
+ };
46
+ }
47
+
48
+ // Check assurance level
49
+ const requiredAssurance =
50
+ passport.limits?.data?.access?.require_assurance_at_least || "L3";
51
+ if (
52
+ passport.assurance_level !== requiredAssurance &&
53
+ passport.assurance_level !== "L4KYC" &&
54
+ passport.assurance_level !== "L4FIN"
55
+ ) {
56
+ return {
57
+ allow: false,
58
+ reasons: [
59
+ {
60
+ code: "oap.assurance_insufficient",
61
+ message: `Assurance level ${passport.assurance_level} is insufficient, requires ${requiredAssurance}`,
62
+ severity: "error",
63
+ },
64
+ ],
65
+ };
66
+ }
67
+
68
+ // Check required fields
69
+ const requiredFields = [
70
+ "data_classification",
71
+ "accessing_entity_id",
72
+ "accessing_entity_type",
73
+ "resource_id",
74
+ ];
75
+ const missingFields = requiredFields.filter((field) => !context[field]);
76
+ if (missingFields.length > 0) {
77
+ return {
78
+ allow: false,
79
+ reasons: [
80
+ {
81
+ code: "oap.invalid_context",
82
+ message: `Missing required fields: ${missingFields.join(", ")}`,
83
+ severity: "error",
84
+ },
85
+ ],
86
+ };
87
+ }
88
+
89
+ // Check data classification is allowed
90
+ const allowedClassifications =
91
+ passport.limits?.data?.access?.allowed_classifications || [];
92
+ if (
93
+ allowedClassifications.length > 0 &&
94
+ !allowedClassifications.includes(context.data_classification)
95
+ ) {
96
+ return {
97
+ allow: false,
98
+ reasons: [
99
+ {
100
+ code: "oap.classification_forbidden",
101
+ message: `Data classification ${context.data_classification} is not allowed`,
102
+ severity: "error",
103
+ },
104
+ ],
105
+ };
106
+ }
107
+
108
+ // Check entity type is allowed for the data classification
109
+ const permissions =
110
+ passport.limits?.data?.access?.permissions?.[context.data_classification];
111
+ if (
112
+ permissions?.allowed_entity_types &&
113
+ !permissions.allowed_entity_types.includes(context.accessing_entity_type)
114
+ ) {
115
+ return {
116
+ allow: false,
117
+ reasons: [
118
+ {
119
+ code: "oap.entity_type_forbidden",
120
+ message: `Entity type ${context.accessing_entity_type} is not allowed for ${context.data_classification} data`,
121
+ severity: "error",
122
+ },
123
+ ],
124
+ };
125
+ }
126
+
127
+ // Check jurisdiction is allowed
128
+ const allowedJurisdictions =
129
+ passport.limits?.data?.access?.allowed_jurisdictions || [];
130
+ if (
131
+ context.jurisdiction &&
132
+ allowedJurisdictions.length > 0 &&
133
+ !allowedJurisdictions.includes(context.jurisdiction)
134
+ ) {
135
+ return {
136
+ allow: false,
137
+ reasons: [
138
+ {
139
+ code: "oap.jurisdiction_blocked",
140
+ message: `Jurisdiction ${context.jurisdiction} is not allowed`,
141
+ severity: "error",
142
+ },
143
+ ],
144
+ };
145
+ }
146
+
147
+ // Check row limit for exports
148
+ const maxRowsPerExport = passport.limits?.data?.access?.max_rows_per_export;
149
+ if (
150
+ context.row_count &&
151
+ maxRowsPerExport &&
152
+ context.row_count > maxRowsPerExport
153
+ ) {
154
+ return {
155
+ allow: false,
156
+ reasons: [
157
+ {
158
+ code: "oap.row_limit_exceeded",
159
+ message: `Row count ${context.row_count} exceeds maximum allowed ${maxRowsPerExport}`,
160
+ severity: "error",
161
+ },
162
+ ],
163
+ };
164
+ }
165
+
166
+ // Check data locality (destination jurisdiction)
167
+ const allowedDestinationJurisdictions =
168
+ passport.limits?.data?.access?.allowed_destination_jurisdictions || [];
169
+ if (
170
+ context.destination_jurisdiction &&
171
+ allowedDestinationJurisdictions.length > 0 &&
172
+ !allowedDestinationJurisdictions.includes(context.destination_jurisdiction)
173
+ ) {
174
+ return {
175
+ allow: false,
176
+ reasons: [
177
+ {
178
+ code: "oap.jurisdiction_blocked",
179
+ message: `Destination jurisdiction ${context.destination_jurisdiction} is not allowed`,
180
+ severity: "error",
181
+ },
182
+ ],
183
+ };
184
+ }
185
+
186
+ // Check balance inquiry limit
187
+ const balanceInquiryCap =
188
+ passport.limits?.data?.access?.balance_inquiry_cap_usd;
189
+ if (
190
+ context.resource_attributes?.account_balance_usd &&
191
+ balanceInquiryCap &&
192
+ context.resource_attributes.account_balance_usd >= balanceInquiryCap
193
+ ) {
194
+ return {
195
+ allow: false,
196
+ reasons: [
197
+ {
198
+ code: "oap.balance_inquiry_forbidden",
199
+ message: `Account balance ${context.resource_attributes.account_balance_usd} exceeds inquiry cap ${balanceInquiryCap}`,
200
+ severity: "error",
201
+ },
202
+ ],
203
+ };
204
+ }
205
+
206
+ // Check action type is allowed
207
+ if (
208
+ context.action_type &&
209
+ permissions?.allowed_actions &&
210
+ !permissions.allowed_actions.includes(context.action_type)
211
+ ) {
212
+ return {
213
+ allow: false,
214
+ reasons: [
215
+ {
216
+ code: "oap.action_forbidden",
217
+ message: `Action type ${context.action_type} is not allowed for ${context.data_classification} data`,
218
+ severity: "error",
219
+ },
220
+ ],
221
+ };
222
+ }
223
+
224
+ // If all checks pass, allow the data access
225
+ return {
226
+ allow: true,
227
+ reasons: [
228
+ {
229
+ code: "oap.allowed",
230
+ message: "Data access within limits and policy requirements",
231
+ severity: "info",
232
+ },
233
+ ],
234
+ };
235
+ }
236
+
237
+ // Test runner
238
+ async function runTests() {
239
+ console.log("๐Ÿงช Running governance.data.access.v1 Policy Tests\n");
240
+
241
+ // Load test data
242
+ const passportPath = path.join(__dirname, "passport.instance.json");
243
+ const contextsPath = path.join(__dirname, "contexts.jsonl");
244
+ const expectedPath = path.join(__dirname, "expected.jsonl");
245
+
246
+ const passport = JSON.parse(fs.readFileSync(passportPath, "utf8"));
247
+ const contexts = fs
248
+ .readFileSync(contextsPath, "utf8")
249
+ .trim()
250
+ .split("\n")
251
+ .map((line) => JSON.parse(line));
252
+ const expected = fs
253
+ .readFileSync(expectedPath, "utf8")
254
+ .trim()
255
+ .split("\n")
256
+ .map((line) => JSON.parse(line));
257
+
258
+ let passed = 0;
259
+ let failed = 0;
260
+
261
+ for (let i = 0; i < contexts.length; i++) {
262
+ const testCase = contexts[i];
263
+ const expectedResult = expected[i];
264
+
265
+ try {
266
+ const result = await evaluateGovernanceDataAccessV1(
267
+ passport,
268
+ testCase.context
269
+ );
270
+
271
+ // Compare results
272
+ const allowMatch = result.allow === expectedResult.expected.allow;
273
+ const reasonsMatch =
274
+ JSON.stringify(result.reasons) ===
275
+ JSON.stringify(expectedResult.expected.reasons);
276
+
277
+ if (allowMatch && reasonsMatch) {
278
+ console.log(`โœ… ${testCase.name}: PASS`);
279
+ passed++;
280
+ } else {
281
+ console.log(`โŒ ${testCase.name}: FAIL`);
282
+ console.log(` Expected: ${JSON.stringify(expectedResult.expected)}`);
283
+ console.log(` Got: ${JSON.stringify(result)}`);
284
+ failed++;
285
+ }
286
+ } catch (error) {
287
+ console.log(`โŒ ${testCase.name}: ERROR - ${error.message}`);
288
+ failed++;
289
+ }
290
+ }
291
+
292
+ console.log(`\n๐Ÿ“Š Test Results: ${passed} passed, ${failed} failed`);
293
+
294
+ if (failed === 0) {
295
+ console.log("๐ŸŽ‰ All tests passed!");
296
+ process.exit(0);
297
+ } else {
298
+ console.log("๐Ÿ’ฅ Some tests failed!");
299
+ process.exit(1);
300
+ }
301
+ }
302
+
303
+ // Run tests if this file is executed directly
304
+ if (require.main === module) {
305
+ runTests().catch(console.error);
306
+ }
307
+
308
+ module.exports = { evaluateGovernanceDataAccessV1, runTests };
@@ -0,0 +1,12 @@
1
+ {"name":"allow_pii_read_employee","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Data access within limits and policy requirements","severity":"info"}]}}
2
+ {"name":"deny_classification_forbidden","expected":{"allow":false,"reasons":[{"code":"oap.classification_forbidden","message":"Data classification Sensitive is not allowed","severity":"error"}]}}
3
+ {"name":"deny_entity_type_forbidden","expected":{"allow":false,"reasons":[{"code":"oap.entity_type_forbidden","message":"Entity type client is not allowed for HR data","severity":"error"}]}}
4
+ {"name":"deny_jurisdiction_blocked","expected":{"allow":false,"reasons":[{"code":"oap.jurisdiction_blocked","message":"Jurisdiction CN is not allowed","severity":"error"}]}}
5
+ {"name":"deny_row_limit_exceeded","expected":{"allow":false,"reasons":[{"code":"oap.row_limit_exceeded","message":"Row count 15000 exceeds maximum allowed 10000","severity":"error"}]}}
6
+ {"name":"deny_destination_jurisdiction_blocked","expected":{"allow":false,"reasons":[{"code":"oap.jurisdiction_blocked","message":"Destination jurisdiction CN is not allowed","severity":"error"}]}}
7
+ {"name":"deny_balance_inquiry_forbidden","expected":{"allow":false,"reasons":[{"code":"oap.balance_inquiry_forbidden","message":"Account balance 150000 exceeds inquiry cap 100000","severity":"error"}]}}
8
+ {"name":"allow_financial_export_system_agent","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Data access within limits and policy requirements","severity":"info"}]}}
9
+ {"name":"allow_client_tier1_read","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Data access within limits and policy requirements","severity":"info"}]}}
10
+ {"name":"deny_action_forbidden","expected":{"allow":false,"reasons":[{"code":"oap.action_forbidden","message":"Action type delete is not allowed for HR data","severity":"error"}]}}
11
+ {"name":"allow_hr_read_employee","expected":{"allow":true,"reasons":[{"code":"oap.allowed","message":"Data access within limits and policy requirements","severity":"info"}]}}
12
+ {"name":"deny_missing_required_fields","expected":{"allow":false,"reasons":[{"code":"oap.invalid_context","message":"Missing required fields: accessing_entity_type, resource_id","severity":"error"}]}}
@@ -0,0 +1,56 @@
1
+ {
2
+ "passport_id": "ap_a2d10232c6534523812423eec8a1425c45679",
3
+ "kind": "instance",
4
+ "spec_version": "oap/1.0",
5
+ "owner_id": "org_demo_co",
6
+ "owner_type": "org",
7
+ "assurance_level": "L3",
8
+ "status": "active",
9
+ "capabilities": [
10
+ {
11
+ "id": "data.access",
12
+ "params": {
13
+ "max_classifications": 5,
14
+ "max_rows_per_export": 10000
15
+ }
16
+ }
17
+ ],
18
+ "limits": {
19
+ "data.access": {
20
+ "allowed_classifications": ["PII", "Financial", "HR", "ClientTier1"],
21
+ "permissions": {
22
+ "PII": {
23
+ "allowed_entity_types": ["employee", "system_agent"],
24
+ "allowed_actions": ["read", "export"]
25
+ },
26
+ "Financial": {
27
+ "allowed_entity_types": ["employee", "system_agent"],
28
+ "allowed_actions": ["read", "export"]
29
+ },
30
+ "HR": {
31
+ "allowed_entity_types": ["employee"],
32
+ "allowed_actions": ["read"]
33
+ },
34
+ "ClientTier1": {
35
+ "allowed_entity_types": ["employee", "client"],
36
+ "allowed_actions": ["read", "export"]
37
+ }
38
+ },
39
+ "allowed_jurisdictions": ["US", "CA", "EU"],
40
+ "max_rows_per_export": 10000,
41
+ "allowed_destination_jurisdictions": ["US", "CA", "EU"],
42
+ "balance_inquiry_cap_usd": 100000,
43
+ "max_access_attempts_per_hour": 100,
44
+ "max_data_age_seconds": 86400,
45
+ "require_assurance_at_least": "L3"
46
+ }
47
+ },
48
+ "regions": ["US", "CA", "EU"],
49
+ "metadata": {
50
+ "template_name": "Demo Data Access Agent",
51
+ "description": "Instance for data access governance operations"
52
+ },
53
+ "created_at": "2025-01-30T00:00:00Z",
54
+ "updated_at": "2025-01-30T00:00:00Z",
55
+ "version": "1.0.0"
56
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "passport_id": "550e8400-e29b-41d4-a716-446655440002",
3
+ "kind": "template",
4
+ "spec_version": "oap/1.0",
5
+ "owner_id": "org_demo_co",
6
+ "owner_type": "org",
7
+ "assurance_level": "L3",
8
+ "status": "active",
9
+ "capabilities": [
10
+ {
11
+ "id": "data.access",
12
+ "params": {
13
+ "max_classifications": 5,
14
+ "max_rows_per_export": 10000
15
+ }
16
+ }
17
+ ],
18
+ "limits": {
19
+ "data.access": {
20
+ "allowed_classifications": ["PII", "Financial", "HR", "ClientTier1"],
21
+ "permissions": {
22
+ "PII": {
23
+ "allowed_entity_types": ["employee", "system_agent"],
24
+ "allowed_actions": ["read", "export"]
25
+ },
26
+ "Financial": {
27
+ "allowed_entity_types": ["employee", "system_agent"],
28
+ "allowed_actions": ["read", "export"]
29
+ },
30
+ "HR": {
31
+ "allowed_entity_types": ["employee"],
32
+ "allowed_actions": ["read"]
33
+ },
34
+ "ClientTier1": {
35
+ "allowed_entity_types": ["employee", "client"],
36
+ "allowed_actions": ["read", "export"]
37
+ }
38
+ },
39
+ "allowed_jurisdictions": ["US", "CA", "EU"],
40
+ "max_rows_per_export": 10000,
41
+ "allowed_destination_jurisdictions": ["US", "CA", "EU"],
42
+ "balance_inquiry_cap_usd": 100000,
43
+ "max_access_attempts_per_hour": 100,
44
+ "max_data_age_seconds": 86400,
45
+ "require_assurance_at_least": "L3"
46
+ }
47
+ },
48
+ "regions": ["US", "CA", "EU"],
49
+ "metadata": {
50
+ "template_name": "Demo Data Access Template",
51
+ "description": "Template for data access governance operations"
52
+ },
53
+ "created_at": "2025-01-30T00:00:00Z",
54
+ "updated_at": "2025-01-30T00:00:00Z",
55
+ "version": "1.0.0"
56
+ }
@@ -0,0 +1,214 @@
1
+ """
2
+ Test Suite: governance.data.access.v1 Policy
3
+
4
+ Tests the data access governance policy with various scenarios
5
+ including valid access, classification violations, and security controls.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from typing import Dict, Any, List
11
+
12
+ def evaluate_governance_data_access_v1(passport: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
13
+ """Mock implementation of the governance.data.access.v1 policy evaluation"""
14
+ reasons = []
15
+ allow = True
16
+
17
+ # Check agent status
18
+ if passport.get("status") in ["suspended", "revoked"]:
19
+ return {
20
+ "allow": False,
21
+ "reasons": [{
22
+ "code": "oap.passport_suspended",
23
+ "message": f"Agent is {passport.get('status')} and cannot perform operations",
24
+ "severity": "error"
25
+ }]
26
+ }
27
+
28
+ # Check capabilities
29
+ capabilities = passport.get("capabilities", [])
30
+ has_data_access_capability = any(
31
+ cap.get("id") == "data.access" for cap in capabilities
32
+ )
33
+ if not has_data_access_capability:
34
+ return {
35
+ "allow": False,
36
+ "reasons": [{
37
+ "code": "oap.unknown_capability",
38
+ "message": "Agent does not have data.access capability",
39
+ "severity": "error"
40
+ }]
41
+ }
42
+
43
+ # Check assurance level
44
+ required_assurance = passport.get("limits", {}).get("data", {}).get("access", {}).get("require_assurance_at_least", "L3")
45
+ assurance_level = passport.get("assurance_level")
46
+ if assurance_level not in [required_assurance, "L4KYC", "L4FIN"]:
47
+ return {
48
+ "allow": False,
49
+ "reasons": [{
50
+ "code": "oap.assurance_insufficient",
51
+ "message": f"Assurance level {assurance_level} is insufficient, requires {required_assurance}",
52
+ "severity": "error"
53
+ }]
54
+ }
55
+
56
+ # Check required fields
57
+ required_fields = ["data_classification", "accessing_entity_id", "accessing_entity_type", "resource_id"]
58
+ missing_fields = [field for field in required_fields if not context.get(field)]
59
+ if missing_fields:
60
+ return {
61
+ "allow": False,
62
+ "reasons": [{
63
+ "code": "oap.invalid_context",
64
+ "message": f"Missing required fields: {', '.join(missing_fields)}",
65
+ "severity": "error"
66
+ }]
67
+ }
68
+
69
+ # Check data classification is allowed
70
+ allowed_classifications = passport.get("limits", {}).get("data", {}).get("access", {}).get("allowed_classifications", [])
71
+ if allowed_classifications and context.get("data_classification") not in allowed_classifications:
72
+ return {
73
+ "allow": False,
74
+ "reasons": [{
75
+ "code": "oap.classification_forbidden",
76
+ "message": f"Data classification {context.get('data_classification')} is not allowed",
77
+ "severity": "error"
78
+ }]
79
+ }
80
+
81
+ # Check entity type is allowed for the data classification
82
+ permissions = passport.get("limits", {}).get("data", {}).get("access", {}).get("permissions", {}).get(context.get("data_classification"), {})
83
+ if permissions.get("allowed_entity_types") and context.get("accessing_entity_type") not in permissions["allowed_entity_types"]:
84
+ return {
85
+ "allow": False,
86
+ "reasons": [{
87
+ "code": "oap.entity_type_forbidden",
88
+ "message": f"Entity type {context.get('accessing_entity_type')} is not allowed for {context.get('data_classification')} data",
89
+ "severity": "error"
90
+ }]
91
+ }
92
+
93
+ # Check jurisdiction is allowed
94
+ allowed_jurisdictions = passport.get("limits", {}).get("data", {}).get("access", {}).get("allowed_jurisdictions", [])
95
+ if context.get("jurisdiction") and allowed_jurisdictions and context.get("jurisdiction") not in allowed_jurisdictions:
96
+ return {
97
+ "allow": False,
98
+ "reasons": [{
99
+ "code": "oap.jurisdiction_blocked",
100
+ "message": f"Jurisdiction {context.get('jurisdiction')} is not allowed",
101
+ "severity": "error"
102
+ }]
103
+ }
104
+
105
+ # Check row limit for exports
106
+ max_rows_per_export = passport.get("limits", {}).get("data", {}).get("access", {}).get("max_rows_per_export")
107
+ if context.get("row_count") and max_rows_per_export and context.get("row_count") > max_rows_per_export:
108
+ return {
109
+ "allow": False,
110
+ "reasons": [{
111
+ "code": "oap.row_limit_exceeded",
112
+ "message": f"Row count {context.get('row_count')} exceeds maximum allowed {max_rows_per_export}",
113
+ "severity": "error"
114
+ }]
115
+ }
116
+
117
+ # Check data locality (destination jurisdiction)
118
+ allowed_destination_jurisdictions = passport.get("limits", {}).get("data", {}).get("access", {}).get("allowed_destination_jurisdictions", [])
119
+ if context.get("destination_jurisdiction") and allowed_destination_jurisdictions and context.get("destination_jurisdiction") not in allowed_destination_jurisdictions:
120
+ return {
121
+ "allow": False,
122
+ "reasons": [{
123
+ "code": "oap.jurisdiction_blocked",
124
+ "message": f"Destination jurisdiction {context.get('destination_jurisdiction')} is not allowed",
125
+ "severity": "error"
126
+ }]
127
+ }
128
+
129
+ # Check balance inquiry limit
130
+ balance_inquiry_cap = passport.get("limits", {}).get("data", {}).get("access", {}).get("balance_inquiry_cap_usd")
131
+ if context.get("resource_attributes", {}).get("account_balance_usd") and balance_inquiry_cap and context.get("resource_attributes", {}).get("account_balance_usd") >= balance_inquiry_cap:
132
+ return {
133
+ "allow": False,
134
+ "reasons": [{
135
+ "code": "oap.balance_inquiry_forbidden",
136
+ "message": f"Account balance {context.get('resource_attributes', {}).get('account_balance_usd')} exceeds inquiry cap {balance_inquiry_cap}",
137
+ "severity": "error"
138
+ }]
139
+ }
140
+
141
+ # Check action type is allowed
142
+ if context.get("action_type") and permissions.get("allowed_actions") and context.get("action_type") not in permissions["allowed_actions"]:
143
+ return {
144
+ "allow": False,
145
+ "reasons": [{
146
+ "code": "oap.action_forbidden",
147
+ "message": f"Action type {context.get('action_type')} is not allowed for {context.get('data_classification')} data",
148
+ "severity": "error"
149
+ }]
150
+ }
151
+
152
+ # If all checks pass, allow the data access
153
+ return {
154
+ "allow": True,
155
+ "reasons": [{
156
+ "code": "oap.allowed",
157
+ "message": "Data access within limits and policy requirements",
158
+ "severity": "info"
159
+ }]
160
+ }
161
+
162
+ def run_tests():
163
+ """Run the test suite"""
164
+ print("๐Ÿงช Running governance.data.access.v1 Policy Tests\n")
165
+
166
+ # Load test data
167
+ test_dir = os.path.dirname(os.path.abspath(__file__))
168
+
169
+ with open(os.path.join(test_dir, "passport.instance.json"), "r") as f:
170
+ passport = json.load(f)
171
+
172
+ with open(os.path.join(test_dir, "contexts.jsonl"), "r") as f:
173
+ contexts = [json.loads(line) for line in f.read().strip().split("\n")]
174
+
175
+ with open(os.path.join(test_dir, "expected.jsonl"), "r") as f:
176
+ expected = [json.loads(line) for line in f.read().strip().split("\n")]
177
+
178
+ passed = 0
179
+ failed = 0
180
+
181
+ for i, test_case in enumerate(contexts):
182
+ expected_result = expected[i]
183
+
184
+ try:
185
+ result = evaluate_governance_data_access_v1(passport, test_case["context"])
186
+
187
+ # Compare results
188
+ allow_match = result["allow"] == expected_result["expected"]["allow"]
189
+ reasons_match = json.dumps(result["reasons"]) == json.dumps(expected_result["expected"]["reasons"])
190
+
191
+ if allow_match and reasons_match:
192
+ print(f"โœ… {test_case['name']}: PASS")
193
+ passed += 1
194
+ else:
195
+ print(f"โŒ {test_case['name']}: FAIL")
196
+ print(f" Expected: {json.dumps(expected_result['expected'])}")
197
+ print(f" Got: {json.dumps(result)}")
198
+ failed += 1
199
+ except Exception as error:
200
+ print(f"โŒ {test_case['name']}: ERROR - {str(error)}")
201
+ failed += 1
202
+
203
+ print(f"\n๐Ÿ“Š Test Results: {passed} passed, {failed} failed")
204
+
205
+ if failed == 0:
206
+ print("๐ŸŽ‰ All tests passed!")
207
+ return True
208
+ else:
209
+ print("๐Ÿ’ฅ Some tests failed!")
210
+ return False
211
+
212
+ if __name__ == "__main__":
213
+ success = run_tests()
214
+ exit(0 if success else 1)