@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,609 @@
1
+ /**
2
+ * Test Suite: legal.contract.review.v1 Policy
3
+ *
4
+ * Tests the legal contract review policy with various scenarios
5
+ * including valid reviews, document type violations, jurisdiction controls,
6
+ * attorney review requirements, and privilege protection.
7
+ */
8
+
9
+ const {
10
+ test,
11
+ expect,
12
+ describe,
13
+ beforeEach,
14
+ afterEach,
15
+ } = require("@jest/globals");
16
+
17
+ const fs = require("fs");
18
+ const path = require("path");
19
+
20
+ // Load passport template
21
+ const passportTemplate = JSON.parse(
22
+ fs.readFileSync(
23
+ path.join(__dirname, "passport.template.json"),
24
+ "utf8"
25
+ )
26
+ );
27
+
28
+ // Mock the policy evaluation function
29
+ async function evaluateLegalContractReviewV1(passport, context) {
30
+ const reasons = [];
31
+ let allow = true;
32
+
33
+ // Check agent status
34
+ if (passport.status === "suspended" || passport.status === "revoked") {
35
+ return {
36
+ allow: false,
37
+ reasons: [
38
+ {
39
+ code: "oap.passport_suspended",
40
+ message: `Agent is ${passport.status} and cannot perform operations`,
41
+ severity: "error",
42
+ },
43
+ ],
44
+ };
45
+ }
46
+
47
+ // Check capabilities
48
+ const hasLegalContractReviewCapability = passport.capabilities?.some(
49
+ (cap) => cap.id === "legal.contract.review"
50
+ );
51
+ if (!hasLegalContractReviewCapability) {
52
+ return {
53
+ allow: false,
54
+ reasons: [
55
+ {
56
+ code: "oap.unknown_capability",
57
+ message: "Agent does not have legal.contract.review capability",
58
+ severity: "error",
59
+ },
60
+ ],
61
+ };
62
+ }
63
+
64
+ // Check assurance level
65
+ const requiredAssurance =
66
+ passport.limits?.legal?.contract?.review?.require_assurance_at_least ||
67
+ "L3";
68
+ const assuranceLevels = ["L0", "L1", "L2", "L3", "L4KYC", "L4FIN"];
69
+ const currentLevel = assuranceLevels.indexOf(passport.assurance_level);
70
+ const requiredLevel = assuranceLevels.indexOf(requiredAssurance);
71
+ if (currentLevel < requiredLevel) {
72
+ return {
73
+ allow: false,
74
+ reasons: [
75
+ {
76
+ code: "oap.assurance_insufficient",
77
+ message: `Assurance level ${passport.assurance_level} is insufficient, requires ${requiredAssurance}`,
78
+ severity: "error",
79
+ },
80
+ ],
81
+ };
82
+ }
83
+
84
+ // Check required fields
85
+ const requiredFields = [
86
+ "document_type",
87
+ "client_id",
88
+ "jurisdiction",
89
+ "action_type",
90
+ "idempotency_key",
91
+ ];
92
+ const missingFields = requiredFields.filter((field) => !context[field]);
93
+ if (missingFields.length > 0) {
94
+ return {
95
+ allow: false,
96
+ reasons: [
97
+ {
98
+ code: "oap.invalid_context",
99
+ message: `Missing required fields: ${missingFields.join(", ")}`,
100
+ severity: "error",
101
+ },
102
+ ],
103
+ };
104
+ }
105
+
106
+ // Check document type is allowed
107
+ const allowedDocumentTypes =
108
+ passport.limits?.legal?.contract?.review?.allowed_document_types || [];
109
+ if (
110
+ allowedDocumentTypes.length > 0 &&
111
+ !allowedDocumentTypes.includes(context.document_type)
112
+ ) {
113
+ return {
114
+ allow: false,
115
+ reasons: [
116
+ {
117
+ code: "oap.document_type_forbidden",
118
+ message: `Document type ${context.document_type} is not allowed`,
119
+ severity: "error",
120
+ },
121
+ ],
122
+ };
123
+ }
124
+
125
+ // Check document size limit
126
+ const maxDocumentSizeMb =
127
+ passport.limits?.legal?.contract?.review?.max_document_size_mb;
128
+ if (
129
+ context.document_size_mb &&
130
+ maxDocumentSizeMb &&
131
+ context.document_size_mb > maxDocumentSizeMb
132
+ ) {
133
+ return {
134
+ allow: false,
135
+ reasons: [
136
+ {
137
+ code: "oap.document_size_exceeded",
138
+ message: `Document size ${context.document_size_mb}MB exceeds maximum allowed ${maxDocumentSizeMb}MB`,
139
+ severity: "error",
140
+ },
141
+ ],
142
+ };
143
+ }
144
+
145
+ // Check jurisdiction is authorized
146
+ const allowedJurisdictions =
147
+ passport.limits?.legal?.contract?.review?.allowed_contract_jurisdictions ||
148
+ [];
149
+ if (
150
+ allowedJurisdictions.length > 0 &&
151
+ !allowedJurisdictions.includes(context.jurisdiction)
152
+ ) {
153
+ return {
154
+ allow: false,
155
+ reasons: [
156
+ {
157
+ code: "oap.jurisdiction_blocked",
158
+ message: `Jurisdiction ${context.jurisdiction} is not authorized for contract review`,
159
+ severity: "error",
160
+ },
161
+ ],
162
+ };
163
+ }
164
+
165
+ // Check high-value contract review requirement FIRST (over $10,000 requires attorney review)
166
+ // This check takes precedence to provide specific error code for high-value contracts
167
+ const contractValueUsd = context.contract_value_usd;
168
+ if (contractValueUsd && contractValueUsd >= 1000000 && !context.attorney_reviewer_id) {
169
+ // 1000000 = $10,000 in minor units (cents)
170
+ return {
171
+ allow: false,
172
+ reasons: [
173
+ {
174
+ code: "oap.high_value_review_required",
175
+ message: "High-value contracts (over $10,000) require attorney review",
176
+ severity: "error",
177
+ },
178
+ ],
179
+ };
180
+ }
181
+
182
+ // Check general attorney review requirement (for all contracts when configured)
183
+ const requireAttorneyReview =
184
+ passport.limits?.legal?.contract?.review?.require_attorney_review;
185
+ if (requireAttorneyReview && !context.attorney_reviewer_id) {
186
+ return {
187
+ allow: false,
188
+ reasons: [
189
+ {
190
+ code: "oap.attorney_review_required",
191
+ message: "Attorney review is required but attorney_reviewer_id is missing",
192
+ severity: "error",
193
+ },
194
+ ],
195
+ };
196
+ }
197
+
198
+ // Check privilege protection
199
+ const privilegeProtectionEnabled =
200
+ passport.limits?.legal?.contract?.review?.privilege_protection_enabled;
201
+ if (privilegeProtectionEnabled && !context.privilege_level) {
202
+ return {
203
+ allow: false,
204
+ reasons: [
205
+ {
206
+ code: "oap.privilege_protection_violation",
207
+ message: "Privilege protection is enabled but privilege_level is missing",
208
+ severity: "error",
209
+ },
210
+ ],
211
+ };
212
+ }
213
+
214
+ // Check client tier authorization
215
+ const allowedClientTiers =
216
+ passport.limits?.legal?.contract?.review?.allowed_client_tiers || [];
217
+ if (
218
+ context.client_tier &&
219
+ allowedClientTiers.length > 0 &&
220
+ !allowedClientTiers.includes(context.client_tier)
221
+ ) {
222
+ return {
223
+ allow: false,
224
+ reasons: [
225
+ {
226
+ code: "oap.client_tier_forbidden",
227
+ message: `Client tier ${context.client_tier} is not authorized`,
228
+ severity: "error",
229
+ },
230
+ ],
231
+ };
232
+ }
233
+
234
+ // Check idempotency
235
+ const idempotencyRequired =
236
+ passport.limits?.legal?.contract?.review?.idempotency_required ?? false;
237
+ if (idempotencyRequired && !context.idempotency_key) {
238
+ return {
239
+ allow: false,
240
+ reasons: [
241
+ {
242
+ code: "oap.idempotency_conflict",
243
+ message: "Idempotency key is required",
244
+ severity: "error",
245
+ },
246
+ ],
247
+ };
248
+ }
249
+
250
+ return {
251
+ allow: true,
252
+ reasons: [],
253
+ };
254
+ }
255
+
256
+ // Test cases
257
+ describe("Legal Contract Review v1 Policy - Core Functionality", () => {
258
+ describe("Required Fields Validation", () => {
259
+ test("should allow contract review with all required fields", async () => {
260
+ const validContext = {
261
+ document_type: "contract",
262
+ client_id: "client_abc123",
263
+ jurisdiction: "US",
264
+ action_type: "review",
265
+ idempotency_key: "unique-key-12345",
266
+ attorney_reviewer_id: "attorney_xyz789",
267
+ privilege_level: "privileged",
268
+ };
269
+
270
+ const result = await evaluateLegalContractReviewV1(
271
+ passportTemplate,
272
+ validContext
273
+ );
274
+
275
+ expect(result.allow).toBe(true);
276
+ expect(result.reasons).toHaveLength(0);
277
+ });
278
+
279
+ test("should deny contract review missing required fields", async () => {
280
+ const invalidContext = {
281
+ document_type: "contract",
282
+ // Missing client_id, jurisdiction, action_type, idempotency_key
283
+ };
284
+
285
+ const result = await evaluateLegalContractReviewV1(
286
+ passportTemplate,
287
+ invalidContext
288
+ );
289
+
290
+ expect(result.allow).toBe(false);
291
+ expect(result.reasons[0].code).toBe("oap.invalid_context");
292
+ });
293
+ });
294
+
295
+ describe("Document Type Validation", () => {
296
+ test("should allow allowed document types", async () => {
297
+ const validContext = {
298
+ document_type: "contract",
299
+ client_id: "client_abc123",
300
+ jurisdiction: "US",
301
+ action_type: "review",
302
+ idempotency_key: "unique-key-12345",
303
+ attorney_reviewer_id: "attorney_xyz789",
304
+ privilege_level: "privileged",
305
+ };
306
+
307
+ const result = await evaluateLegalContractReviewV1(
308
+ passportTemplate,
309
+ validContext
310
+ );
311
+
312
+ expect(result.allow).toBe(true);
313
+ });
314
+
315
+ test("should deny forbidden document types", async () => {
316
+ const invalidContext = {
317
+ document_type: "forbidden_type",
318
+ client_id: "client_abc123",
319
+ jurisdiction: "US",
320
+ action_type: "review",
321
+ idempotency_key: "unique-key-12345",
322
+ attorney_reviewer_id: "attorney_xyz789",
323
+ privilege_level: "privileged",
324
+ };
325
+
326
+ const result = await evaluateLegalContractReviewV1(
327
+ passportTemplate,
328
+ invalidContext
329
+ );
330
+
331
+ expect(result.allow).toBe(false);
332
+ expect(result.reasons[0].code).toBe("oap.document_type_forbidden");
333
+ });
334
+ });
335
+
336
+ describe("Document Size Limits", () => {
337
+ test("should allow documents within size limit", async () => {
338
+ const validContext = {
339
+ document_type: "contract",
340
+ client_id: "client_abc123",
341
+ jurisdiction: "US",
342
+ action_type: "review",
343
+ idempotency_key: "unique-key-12345",
344
+ document_size_mb: 5,
345
+ attorney_reviewer_id: "attorney_xyz789",
346
+ privilege_level: "privileged",
347
+ };
348
+
349
+ const result = await evaluateLegalContractReviewV1(
350
+ passportTemplate,
351
+ validContext
352
+ );
353
+
354
+ expect(result.allow).toBe(true);
355
+ });
356
+
357
+ test("should deny documents exceeding size limit", async () => {
358
+ const invalidContext = {
359
+ document_type: "contract",
360
+ client_id: "client_abc123",
361
+ jurisdiction: "US",
362
+ action_type: "review",
363
+ idempotency_key: "unique-key-12345",
364
+ document_size_mb: 15, // Exceeds 10MB limit
365
+ attorney_reviewer_id: "attorney_xyz789",
366
+ privilege_level: "privileged",
367
+ };
368
+
369
+ const result = await evaluateLegalContractReviewV1(
370
+ passportTemplate,
371
+ invalidContext
372
+ );
373
+
374
+ expect(result.allow).toBe(false);
375
+ expect(result.reasons[0].code).toBe("oap.document_size_exceeded");
376
+ });
377
+ });
378
+
379
+ describe("Jurisdiction Controls", () => {
380
+ test("should allow authorized jurisdictions", async () => {
381
+ const validContext = {
382
+ document_type: "contract",
383
+ client_id: "client_abc123",
384
+ jurisdiction: "US",
385
+ action_type: "review",
386
+ idempotency_key: "unique-key-12345",
387
+ attorney_reviewer_id: "attorney_xyz789",
388
+ privilege_level: "privileged",
389
+ };
390
+
391
+ const result = await evaluateLegalContractReviewV1(
392
+ passportTemplate,
393
+ validContext
394
+ );
395
+
396
+ expect(result.allow).toBe(true);
397
+ });
398
+
399
+ test("should deny unauthorized jurisdictions", async () => {
400
+ const invalidContext = {
401
+ document_type: "contract",
402
+ client_id: "client_abc123",
403
+ jurisdiction: "FR", // Not in allowed list
404
+ action_type: "review",
405
+ idempotency_key: "unique-key-12345",
406
+ attorney_reviewer_id: "attorney_xyz789",
407
+ privilege_level: "privileged",
408
+ };
409
+
410
+ const result = await evaluateLegalContractReviewV1(
411
+ passportTemplate,
412
+ invalidContext
413
+ );
414
+
415
+ expect(result.allow).toBe(false);
416
+ expect(result.reasons[0].code).toBe("oap.jurisdiction_blocked");
417
+ });
418
+ });
419
+
420
+ describe("Attorney Review Requirements", () => {
421
+ test("should require attorney review when configured", async () => {
422
+ const invalidContext = {
423
+ document_type: "contract",
424
+ client_id: "client_abc123",
425
+ jurisdiction: "US",
426
+ action_type: "review",
427
+ idempotency_key: "unique-key-12345",
428
+ // Missing attorney_reviewer_id
429
+ privilege_level: "privileged",
430
+ };
431
+
432
+ const result = await evaluateLegalContractReviewV1(
433
+ passportTemplate,
434
+ invalidContext
435
+ );
436
+
437
+ expect(result.allow).toBe(false);
438
+ expect(result.reasons[0].code).toBe("oap.attorney_review_required");
439
+ });
440
+
441
+ test("should require attorney review for high-value contracts", async () => {
442
+ const invalidContext = {
443
+ document_type: "contract",
444
+ client_id: "client_abc123",
445
+ jurisdiction: "US",
446
+ action_type: "review",
447
+ idempotency_key: "unique-key-12345",
448
+ contract_value_usd: 1500000, // $15,000 - over $10,000 threshold
449
+ privilege_level: "privileged",
450
+ // Missing attorney_reviewer_id
451
+ };
452
+
453
+ const result = await evaluateLegalContractReviewV1(
454
+ passportTemplate,
455
+ invalidContext
456
+ );
457
+
458
+ expect(result.allow).toBe(false);
459
+ expect(result.reasons[0].code).toBe("oap.high_value_review_required");
460
+ });
461
+ });
462
+
463
+ describe("Privilege Protection", () => {
464
+ test("should require privilege level when protection is enabled", async () => {
465
+ const invalidContext = {
466
+ document_type: "contract",
467
+ client_id: "client_abc123",
468
+ jurisdiction: "US",
469
+ action_type: "review",
470
+ idempotency_key: "unique-key-12345",
471
+ attorney_reviewer_id: "attorney_xyz789",
472
+ // Missing privilege_level
473
+ };
474
+
475
+ const result = await evaluateLegalContractReviewV1(
476
+ passportTemplate,
477
+ invalidContext
478
+ );
479
+
480
+ expect(result.allow).toBe(false);
481
+ expect(result.reasons[0].code).toBe("oap.privilege_protection_violation");
482
+ });
483
+ });
484
+
485
+ describe("Client Tier Authorization", () => {
486
+ test("should allow authorized client tiers", async () => {
487
+ const validContext = {
488
+ document_type: "contract",
489
+ client_id: "client_abc123",
490
+ jurisdiction: "US",
491
+ action_type: "review",
492
+ idempotency_key: "unique-key-12345",
493
+ client_tier: "tier1",
494
+ attorney_reviewer_id: "attorney_xyz789",
495
+ privilege_level: "privileged",
496
+ };
497
+
498
+ const result = await evaluateLegalContractReviewV1(
499
+ passportTemplate,
500
+ validContext
501
+ );
502
+
503
+ expect(result.allow).toBe(true);
504
+ });
505
+
506
+ test("should deny unauthorized client tiers", async () => {
507
+ const invalidContext = {
508
+ document_type: "contract",
509
+ client_id: "client_abc123",
510
+ jurisdiction: "US",
511
+ action_type: "review",
512
+ idempotency_key: "unique-key-12345",
513
+ client_tier: "tier4", // Not in allowed list
514
+ attorney_reviewer_id: "attorney_xyz789",
515
+ privilege_level: "privileged",
516
+ };
517
+
518
+ const result = await evaluateLegalContractReviewV1(
519
+ passportTemplate,
520
+ invalidContext
521
+ );
522
+
523
+ expect(result.allow).toBe(false);
524
+ expect(result.reasons[0].code).toBe("oap.client_tier_forbidden");
525
+ });
526
+ });
527
+
528
+ describe("Capability Checks", () => {
529
+ test("should deny if agent lacks legal.contract.review capability", async () => {
530
+ const passportWithoutCapability = {
531
+ ...passportTemplate,
532
+ capabilities: [],
533
+ };
534
+
535
+ const validContext = {
536
+ document_type: "contract",
537
+ client_id: "client_abc123",
538
+ jurisdiction: "US",
539
+ action_type: "review",
540
+ idempotency_key: "unique-key-12345",
541
+ attorney_reviewer_id: "attorney_xyz789",
542
+ privilege_level: "privileged",
543
+ };
544
+
545
+ const result = await evaluateLegalContractReviewV1(
546
+ passportWithoutCapability,
547
+ validContext
548
+ );
549
+
550
+ expect(result.allow).toBe(false);
551
+ expect(result.reasons[0].code).toBe("oap.unknown_capability");
552
+ });
553
+ });
554
+
555
+ describe("Assurance Level Checks", () => {
556
+ test("should deny if assurance level is insufficient", async () => {
557
+ const passportWithLowAssurance = {
558
+ ...passportTemplate,
559
+ assurance_level: "L2", // Below required L3
560
+ };
561
+
562
+ const validContext = {
563
+ document_type: "contract",
564
+ client_id: "client_abc123",
565
+ jurisdiction: "US",
566
+ action_type: "review",
567
+ idempotency_key: "unique-key-12345",
568
+ attorney_reviewer_id: "attorney_xyz789",
569
+ privilege_level: "privileged",
570
+ };
571
+
572
+ const result = await evaluateLegalContractReviewV1(
573
+ passportWithLowAssurance,
574
+ validContext
575
+ );
576
+
577
+ expect(result.allow).toBe(false);
578
+ expect(result.reasons[0].code).toBe("oap.assurance_insufficient");
579
+ });
580
+ });
581
+
582
+ describe("Agent Status Checks", () => {
583
+ test("should deny if agent is suspended", async () => {
584
+ const suspendedPassport = {
585
+ ...passportTemplate,
586
+ status: "suspended",
587
+ };
588
+
589
+ const validContext = {
590
+ document_type: "contract",
591
+ client_id: "client_abc123",
592
+ jurisdiction: "US",
593
+ action_type: "review",
594
+ idempotency_key: "unique-key-12345",
595
+ attorney_reviewer_id: "attorney_xyz789",
596
+ privilege_level: "privileged",
597
+ };
598
+
599
+ const result = await evaluateLegalContractReviewV1(
600
+ suspendedPassport,
601
+ validContext
602
+ );
603
+
604
+ expect(result.allow).toBe(false);
605
+ expect(result.reasons[0].code).toBe("oap.passport_suspended");
606
+ });
607
+ });
608
+ });
609
+
@@ -0,0 +1,49 @@
1
+ {
2
+ "agent_id": "ap_legal_contract_review_template",
3
+ "slug": "legal-contract-review-template",
4
+ "name": "Legal Contract Review Template",
5
+ "kind": "template",
6
+ "spec_version": "oap/1.0",
7
+ "owner_id": "org_law_firm_demo",
8
+ "owner_type": "org",
9
+ "owner_display": "Demo Law Firm",
10
+ "controller_type": "org",
11
+ "claimed": true,
12
+ "assurance_level": "L3",
13
+ "status": "active",
14
+ "capabilities": [
15
+ {
16
+ "id": "legal.contract.review",
17
+ "params": {}
18
+ }
19
+ ],
20
+ "limits": {
21
+ "legal": {
22
+ "contract": {
23
+ "review": {
24
+ "allowed_document_types": [
25
+ "contract",
26
+ "nda",
27
+ "msa",
28
+ "sla",
29
+ "employment_agreement"
30
+ ],
31
+ "max_document_size_mb": 10,
32
+ "allowed_contract_jurisdictions": ["US", "CA", "GB"],
33
+ "require_attorney_review": true,
34
+ "privilege_protection_enabled": true,
35
+ "max_contracts_per_day": 50,
36
+ "allowed_client_tiers": ["tier1", "tier2", "enterprise"],
37
+ "require_assurance_at_least": "L3",
38
+ "idempotency_required": true
39
+ }
40
+ }
41
+ }
42
+ },
43
+ "regions": ["US", "CA", "GB"],
44
+ "role": "legal_assistant",
45
+ "description": "AI agent for legal contract review operations",
46
+ "created_at": "2025-01-30T00:00:00Z",
47
+ "updated_at": "2025-01-30T00:00:00Z",
48
+ "version": "1.0.0"
49
+ }