@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.
- package/LICENSE +217 -0
- package/README.md +481 -0
- package/bin/agent-guardrails +133 -0
- package/bin/aport-create-passport.sh +444 -0
- package/bin/aport-cursor-hook.sh +90 -0
- package/bin/aport-guardrail-api.sh +108 -0
- package/bin/aport-guardrail-bash.sh +394 -0
- package/bin/aport-guardrail-v2.sh +5 -0
- package/bin/aport-guardrail.sh +5 -0
- package/bin/aport-resolve-paths.sh +71 -0
- package/bin/aport-status.sh +276 -0
- package/bin/frameworks/crewai.sh +49 -0
- package/bin/frameworks/cursor.sh +95 -0
- package/bin/frameworks/langchain.sh +48 -0
- package/bin/frameworks/n8n.sh +36 -0
- package/bin/frameworks/openclaw.sh +19 -0
- package/bin/lib/allowlist.sh +18 -0
- package/bin/lib/common.sh +28 -0
- package/bin/lib/config.sh +46 -0
- package/bin/lib/constants.sh +232 -0
- package/bin/lib/detect.sh +65 -0
- package/bin/lib/error.sh +269 -0
- package/bin/lib/passport.sh +19 -0
- package/bin/lib/templates/.gitkeep +1 -0
- package/bin/lib/templates/config.yaml +6 -0
- package/bin/lib/validation.sh +206 -0
- package/bin/openclaw +660 -0
- package/docs/ADDING_A_FRAMEWORK.md +87 -0
- package/docs/AGENTS.md.example +40 -0
- package/docs/CODE_REVIEW.md +192 -0
- package/docs/DEPLOYMENT_READINESS.md +81 -0
- package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
- package/docs/FRAMEWORK_ROADMAP.md +41 -0
- package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
- package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
- package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
- package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
- package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
- package/docs/QUICKSTART.md +470 -0
- package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE.md +87 -0
- package/docs/REPO_LAYOUT.md +47 -0
- package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
- package/docs/TOOL_POLICY_MAPPING.md +46 -0
- package/docs/UPGRADE.md +46 -0
- package/docs/VERIFICATION_METHODS.md +97 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/porter.svg +54 -0
- package/docs/development/ERROR_CODES.md +616 -0
- package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
- package/docs/frameworks/crewai.md +114 -0
- package/docs/frameworks/cursor.md +159 -0
- package/docs/frameworks/langchain.md +72 -0
- package/docs/frameworks/n8n.md +40 -0
- package/docs/frameworks/openclaw.md +40 -0
- package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
- package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
- package/docs/launch/AWESOME_REPOS.md +53 -0
- package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
- package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
- package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
- package/docs/launch/EVIDENCE_README.md +61 -0
- package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
- package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
- package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
- package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
- package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
- package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
- package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
- package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
- package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
- package/docs/launch/READINESS_SUMMARY.md +262 -0
- package/docs/launch/README.md +68 -0
- package/docs/launch/USER_STORIES.md +327 -0
- package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
- package/docs/operations/MONITORING.md +588 -0
- package/docs/reviews/2026-02-18-staff-review.md +268 -0
- package/extensions/openclaw-aport/README.md +415 -0
- package/extensions/openclaw-aport/index.js +625 -0
- package/extensions/openclaw-aport/openclaw-aport.js +7 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
- package/extensions/openclaw-aport/package.json +36 -0
- package/extensions/openclaw-aport/test.js +307 -0
- package/external/aport-policies/README.md +363 -0
- package/external/aport-policies/agent.session.create.v1/README.md +345 -0
- package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
- package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
- package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
- package/external/aport-policies/code.release.publish.v1/README.md +51 -0
- package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
- package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
- package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
- package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
- package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
- package/external/aport-policies/data.export.create.v1/README.md +226 -0
- package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
- package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
- package/external/aport-policies/data.export.create.v1/policy.json +133 -0
- package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
- package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
- package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
- package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
- package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
- package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
- package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
- package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
- package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
- package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
- package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
- package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
- package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
- package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
- package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
- package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
- package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
- package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
- package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
- package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
- package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
- package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
- package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
- package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
- package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
- package/external/aport-policies/governance.data.access.v1/README.md +292 -0
- package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
- package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
- package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
- package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
- package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
- package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
- package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
- package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
- package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
- package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
- package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
- package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
- package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
- package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
- package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
- package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
- package/external/aport-policies/policy-template.json +107 -0
- package/external/aport-policies/system.command.execute.v1/README.md +275 -0
- package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
- package/external/aport-spec/CONTRIBUTING.md +273 -0
- package/external/aport-spec/LICENSE +21 -0
- package/external/aport-spec/README.md +168 -0
- package/external/aport-spec/conformance/README.md +294 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
- package/external/aport-spec/conformance/package.json +44 -0
- package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
- package/external/aport-spec/conformance/src/cases.ts +371 -0
- package/external/aport-spec/conformance/src/ed25519.ts +167 -0
- package/external/aport-spec/conformance/src/jcs.ts +85 -0
- package/external/aport-spec/conformance/src/runner.ts +533 -0
- package/external/aport-spec/conformance/src/validators.ts +185 -0
- package/external/aport-spec/conformance/test-runner.js +315 -0
- package/external/aport-spec/conformance/tsconfig.json +21 -0
- package/external/aport-spec/error-schema.json +192 -0
- package/external/aport-spec/index.json +12 -0
- package/external/aport-spec/integrations/clawmoat/README.md +12 -0
- package/external/aport-spec/integrations/shield/README.md +245 -0
- package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
- package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
- package/external/aport-spec/integrations/shield/test/README.md +58 -0
- package/external/aport-spec/integrations/shield/test/shield.md +40 -0
- package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
- package/external/aport-spec/metrics-schema.json +504 -0
- package/external/aport-spec/oap/CHANGELOG.md +54 -0
- package/external/aport-spec/oap/VERSION.md +40 -0
- package/external/aport-spec/oap/capability-registry.md +229 -0
- package/external/aport-spec/oap/conformance.md +257 -0
- package/external/aport-spec/oap/decision-schema.json +114 -0
- package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
- package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
- package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
- package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
- package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
- package/external/aport-spec/oap/oap-spec.md +426 -0
- package/external/aport-spec/oap/passport-schema.json +396 -0
- package/external/aport-spec/oap/security.md +213 -0
- package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
- package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
- package/external/aport-spec/oap/vc/tools/README.md +278 -0
- package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
- package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
- package/external/aport-spec/oap/vc/tools/package.json +38 -0
- package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
- package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
- package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
- package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
- package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
- package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
- package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
- package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
- package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
- package/external/aport-spec/passport-schema.json +586 -0
- package/external/aport-spec/rate-limiting.md +136 -0
- package/external/aport-spec/transport-profile.md +325 -0
- package/external/aport-spec/webhook-spec.md +314 -0
- package/package.json +70 -0
- package/skills/aport-agent-guardrail/SKILL.md +314 -0
- package/src/evaluator.js +252 -0
- 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)
|