@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,332 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const { requirePolicy } = require("@aporthq/middleware-express");
|
|
3
|
+
|
|
4
|
+
const app = express();
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
// Apply repo policy to PR creation routes
|
|
8
|
+
app.post(
|
|
9
|
+
"/repo/pr",
|
|
10
|
+
requirePolicy("code.repository.merge.v1"),
|
|
11
|
+
async (req, res) => {
|
|
12
|
+
try {
|
|
13
|
+
const { repo, base_branch, head_branch, title, body, files_changed } =
|
|
14
|
+
req.body;
|
|
15
|
+
const passport = req.policyResult.passport;
|
|
16
|
+
|
|
17
|
+
// Check repository allowlist
|
|
18
|
+
const allowedRepos =
|
|
19
|
+
passport.capabilities.find((cap) => cap.id === "repo.pr.create")?.params
|
|
20
|
+
?.allowed_repos || [];
|
|
21
|
+
|
|
22
|
+
if (allowedRepos.length > 0 && !allowedRepos.includes(repo)) {
|
|
23
|
+
return res.status(403).json({
|
|
24
|
+
error: "Repository not allowed",
|
|
25
|
+
allowed_repos: allowedRepos,
|
|
26
|
+
upgrade_instructions:
|
|
27
|
+
"Add repository to your passport's allowed_repos",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check base branch allowlist
|
|
32
|
+
const allowedBranches =
|
|
33
|
+
passport.capabilities.find((cap) => cap.id === "repo.pr.create")?.params
|
|
34
|
+
?.allowed_base_branches || [];
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
allowedBranches.length > 0 &&
|
|
38
|
+
!allowedBranches.includes(base_branch)
|
|
39
|
+
) {
|
|
40
|
+
return res.status(403).json({
|
|
41
|
+
error: "Base branch not allowed",
|
|
42
|
+
allowed_branches: allowedBranches,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check file limits
|
|
47
|
+
const maxFiles =
|
|
48
|
+
passport.capabilities.find((cap) => cap.id === "repo.pr.create")?.params
|
|
49
|
+
?.max_files_changed || Infinity;
|
|
50
|
+
|
|
51
|
+
if (files_changed && files_changed.length > maxFiles) {
|
|
52
|
+
return res.status(403).json({
|
|
53
|
+
error: "Too many files changed",
|
|
54
|
+
max_files: maxFiles,
|
|
55
|
+
requested: files_changed.length,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check total lines added
|
|
60
|
+
const totalLinesAdded =
|
|
61
|
+
files_changed?.reduce(
|
|
62
|
+
(sum, file) => sum + (file.lines_added || 0),
|
|
63
|
+
0
|
|
64
|
+
) || 0;
|
|
65
|
+
const maxLines =
|
|
66
|
+
passport.capabilities.find((cap) => cap.id === "repo.pr.create")?.params
|
|
67
|
+
?.max_total_added_lines || Infinity;
|
|
68
|
+
|
|
69
|
+
if (totalLinesAdded > maxLines) {
|
|
70
|
+
return res.status(403).json({
|
|
71
|
+
error: "Too many lines added",
|
|
72
|
+
max_lines: maxLines,
|
|
73
|
+
requested: totalLinesAdded,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check path allowlist
|
|
78
|
+
const pathAllowlist =
|
|
79
|
+
passport.capabilities.find((cap) => cap.id === "repo.pr.create")?.params
|
|
80
|
+
?.path_allowlist || [];
|
|
81
|
+
|
|
82
|
+
if (pathAllowlist.length > 0 && files_changed) {
|
|
83
|
+
const disallowedFiles = files_changed.filter(
|
|
84
|
+
(file) =>
|
|
85
|
+
!pathAllowlist.some((pattern) =>
|
|
86
|
+
file.path.match(new RegExp(pattern))
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (disallowedFiles.length > 0) {
|
|
91
|
+
return res.status(403).json({
|
|
92
|
+
error: "Files outside allowed paths",
|
|
93
|
+
disallowed_files: disallowedFiles.map((f) => f.path),
|
|
94
|
+
path_allowlist: pathAllowlist,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check daily PR limit
|
|
100
|
+
const dailyUsage = await getDailyPRUsage(passport.agent_id);
|
|
101
|
+
if (dailyUsage >= passport.limits.max_prs_per_day) {
|
|
102
|
+
return res.status(403).json({
|
|
103
|
+
error: "Daily PR limit exceeded",
|
|
104
|
+
limit: passport.limits.max_prs_per_day,
|
|
105
|
+
current_usage: dailyUsage,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create PR using your Git service
|
|
110
|
+
const pr_id = await createPullRequest({
|
|
111
|
+
repo,
|
|
112
|
+
base_branch,
|
|
113
|
+
head_branch,
|
|
114
|
+
title,
|
|
115
|
+
body,
|
|
116
|
+
files_changed,
|
|
117
|
+
agent_id: passport.agent_id,
|
|
118
|
+
agent_name: passport.name,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Record usage
|
|
122
|
+
await recordPRUsage(passport.agent_id);
|
|
123
|
+
|
|
124
|
+
// Log the PR creation
|
|
125
|
+
console.log(
|
|
126
|
+
`PR created: ${pr_id} in ${repo} by agent ${passport.agent_id}`
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
res.json({
|
|
130
|
+
success: true,
|
|
131
|
+
pr_id,
|
|
132
|
+
repo,
|
|
133
|
+
base_branch,
|
|
134
|
+
head_branch,
|
|
135
|
+
files_changed: files_changed?.length || 0,
|
|
136
|
+
lines_added: totalLinesAdded,
|
|
137
|
+
status: "created",
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error("PR creation error:", error);
|
|
141
|
+
res.status(500).json({ error: "Internal server error" });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Apply repo policy to merge routes
|
|
147
|
+
app.post(
|
|
148
|
+
"/repo/merge",
|
|
149
|
+
requirePolicy("code.repository.merge.v1"),
|
|
150
|
+
async (req, res) => {
|
|
151
|
+
try {
|
|
152
|
+
const { repo, pr_id, merge_method, delete_branch } = req.body;
|
|
153
|
+
const passport = req.policyResult.passport;
|
|
154
|
+
|
|
155
|
+
// Get PR details first
|
|
156
|
+
const prDetails = await getPRDetails(repo, pr_id);
|
|
157
|
+
|
|
158
|
+
// Check repository allowlist
|
|
159
|
+
const allowedRepos =
|
|
160
|
+
passport.capabilities.find((cap) => cap.id === "repo.merge")?.params
|
|
161
|
+
?.allowed_repos || [];
|
|
162
|
+
|
|
163
|
+
if (allowedRepos.length > 0 && !allowedRepos.includes(repo)) {
|
|
164
|
+
return res.status(403).json({
|
|
165
|
+
error: "Repository not allowed for merging",
|
|
166
|
+
allowed_repos: allowedRepos,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check base branch allowlist
|
|
171
|
+
const allowedBranches =
|
|
172
|
+
passport.capabilities.find((cap) => cap.id === "repo.merge")?.params
|
|
173
|
+
?.allowed_base_branches || [];
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
allowedBranches.length > 0 &&
|
|
177
|
+
!allowedBranches.includes(prDetails.base_branch)
|
|
178
|
+
) {
|
|
179
|
+
return res.status(403).json({
|
|
180
|
+
error: "Base branch not allowed for merging",
|
|
181
|
+
allowed_branches: allowedBranches,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check required labels
|
|
186
|
+
const requiredLabels =
|
|
187
|
+
passport.capabilities.find((cap) => cap.id === "repo.merge")?.params
|
|
188
|
+
?.required_labels || [];
|
|
189
|
+
|
|
190
|
+
if (requiredLabels.length > 0) {
|
|
191
|
+
const missingLabels = requiredLabels.filter(
|
|
192
|
+
(label) => !prDetails.labels.includes(label)
|
|
193
|
+
);
|
|
194
|
+
if (missingLabels.length > 0) {
|
|
195
|
+
return res.status(403).json({
|
|
196
|
+
error: "Required labels missing",
|
|
197
|
+
missing_labels: missingLabels,
|
|
198
|
+
current_labels: prDetails.labels,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check required reviews
|
|
204
|
+
const requiredReviews =
|
|
205
|
+
passport.capabilities.find((cap) => cap.id === "repo.merge")?.params
|
|
206
|
+
?.required_reviews || 0;
|
|
207
|
+
|
|
208
|
+
if (prDetails.approvals < requiredReviews) {
|
|
209
|
+
return res.status(403).json({
|
|
210
|
+
error: "Insufficient reviews",
|
|
211
|
+
required: requiredReviews,
|
|
212
|
+
current: prDetails.approvals,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Check PR size limit
|
|
217
|
+
const prSizeKB = prDetails.size_kb;
|
|
218
|
+
if (prSizeKB > passport.limits.max_pr_size_kb) {
|
|
219
|
+
return res.status(403).json({
|
|
220
|
+
error: "PR size exceeds limit",
|
|
221
|
+
size_kb: prSizeKB,
|
|
222
|
+
limit_kb: passport.limits.max_pr_size_kb,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check daily merge limit
|
|
227
|
+
const dailyUsage = await getDailyMergeUsage(passport.agent_id);
|
|
228
|
+
if (dailyUsage >= passport.limits.max_merges_per_day) {
|
|
229
|
+
return res.status(403).json({
|
|
230
|
+
error: "Daily merge limit exceeded",
|
|
231
|
+
limit: passport.limits.max_merges_per_day,
|
|
232
|
+
current_usage: dailyUsage,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Merge PR using your Git service
|
|
237
|
+
const merge_result = await mergePullRequest({
|
|
238
|
+
repo,
|
|
239
|
+
pr_id,
|
|
240
|
+
merge_method,
|
|
241
|
+
delete_branch,
|
|
242
|
+
agent_id: passport.agent_id,
|
|
243
|
+
agent_name: passport.name,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Record usage
|
|
247
|
+
await recordMergeUsage(passport.agent_id);
|
|
248
|
+
|
|
249
|
+
// Log the merge
|
|
250
|
+
console.log(
|
|
251
|
+
`PR merged: ${pr_id} in ${repo} by agent ${passport.agent_id}`
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
res.json({
|
|
255
|
+
success: true,
|
|
256
|
+
pr_id,
|
|
257
|
+
repo,
|
|
258
|
+
merge_sha: merge_result.sha,
|
|
259
|
+
merge_method,
|
|
260
|
+
status: "merged",
|
|
261
|
+
});
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error("PR merge error:", error);
|
|
264
|
+
res.status(500).json({ error: "Internal server error" });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Mock functions (implement with your actual Git service)
|
|
270
|
+
async function createPullRequest({
|
|
271
|
+
repo,
|
|
272
|
+
base_branch,
|
|
273
|
+
head_branch,
|
|
274
|
+
title,
|
|
275
|
+
body,
|
|
276
|
+
files_changed,
|
|
277
|
+
agent_id,
|
|
278
|
+
agent_name,
|
|
279
|
+
}) {
|
|
280
|
+
// Simulate PR creation
|
|
281
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
282
|
+
return `pr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function getPRDetails(repo, pr_id) {
|
|
286
|
+
// Mock PR details
|
|
287
|
+
return {
|
|
288
|
+
base_branch: "main",
|
|
289
|
+
labels: ["enhancement", "agent-created"],
|
|
290
|
+
approvals: 2,
|
|
291
|
+
size_kb: 45,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function mergePullRequest({
|
|
296
|
+
repo,
|
|
297
|
+
pr_id,
|
|
298
|
+
merge_method,
|
|
299
|
+
delete_branch,
|
|
300
|
+
agent_id,
|
|
301
|
+
agent_name,
|
|
302
|
+
}) {
|
|
303
|
+
// Simulate PR merge
|
|
304
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
305
|
+
return {
|
|
306
|
+
sha: `sha_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function getDailyPRUsage(agent_id) {
|
|
311
|
+
// Get current daily PR usage
|
|
312
|
+
return 0; // Mock value
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function getDailyMergeUsage(agent_id) {
|
|
316
|
+
// Get current daily merge usage
|
|
317
|
+
return 0; // Mock value
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function recordPRUsage(agent_id) {
|
|
321
|
+
console.log(`Recorded PR creation for agent ${agent_id}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function recordMergeUsage(agent_id) {
|
|
325
|
+
console.log(`Recorded PR merge for agent ${agent_id}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const PORT = process.env.PORT || 3000;
|
|
329
|
+
app.listen(PORT, () => {
|
|
330
|
+
console.log(`Repository service running on port ${PORT}`);
|
|
331
|
+
console.log("Protected by APort code.repository.merge.v1 policy pack");
|
|
332
|
+
});
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from typing import List, Optional, Dict, Any
|
|
4
|
+
from aport.middleware import require_policy
|
|
5
|
+
import asyncio
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
app = FastAPI(title="Repository Service", version="1.0.0")
|
|
9
|
+
|
|
10
|
+
class FileChange(BaseModel):
|
|
11
|
+
path: str
|
|
12
|
+
lines_added: Optional[int] = 0
|
|
13
|
+
lines_removed: Optional[int] = 0
|
|
14
|
+
|
|
15
|
+
class PRRequest(BaseModel):
|
|
16
|
+
repo: str
|
|
17
|
+
base_branch: str
|
|
18
|
+
head_branch: str
|
|
19
|
+
title: str
|
|
20
|
+
body: Optional[str] = ""
|
|
21
|
+
files_changed: Optional[List[FileChange]] = []
|
|
22
|
+
|
|
23
|
+
class MergeRequest(BaseModel):
|
|
24
|
+
repo: str
|
|
25
|
+
pr_id: str
|
|
26
|
+
merge_method: Optional[str] = "merge"
|
|
27
|
+
delete_branch: Optional[bool] = False
|
|
28
|
+
|
|
29
|
+
@app.post("/repo/pr")
|
|
30
|
+
@require_policy("code.repository.merge.v1")
|
|
31
|
+
async def create_pull_request(request: Request, pr_data: PRRequest):
|
|
32
|
+
try:
|
|
33
|
+
passport = request.state.policy_result.passport
|
|
34
|
+
|
|
35
|
+
# Get PR creation capability
|
|
36
|
+
pr_capability = next(
|
|
37
|
+
(cap for cap in passport.capabilities if cap.id == "repo.pr.create"),
|
|
38
|
+
None
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Check repository allowlist
|
|
42
|
+
allowed_repos = (
|
|
43
|
+
pr_capability.params.get("allowed_repos", [])
|
|
44
|
+
if pr_capability and pr_capability.params
|
|
45
|
+
else []
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if allowed_repos and pr_data.repo not in allowed_repos:
|
|
49
|
+
raise HTTPException(
|
|
50
|
+
status_code=403,
|
|
51
|
+
detail={
|
|
52
|
+
"error": "Repository not allowed",
|
|
53
|
+
"allowed_repos": allowed_repos,
|
|
54
|
+
"upgrade_instructions": "Add repository to your passport's allowed_repos"
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Check base branch allowlist
|
|
59
|
+
allowed_branches = (
|
|
60
|
+
pr_capability.params.get("allowed_base_branches", [])
|
|
61
|
+
if pr_capability and pr_capability.params
|
|
62
|
+
else []
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if allowed_branches and pr_data.base_branch not in allowed_branches:
|
|
66
|
+
raise HTTPException(
|
|
67
|
+
status_code=403,
|
|
68
|
+
detail={
|
|
69
|
+
"error": "Base branch not allowed",
|
|
70
|
+
"allowed_branches": allowed_branches
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Check file limits
|
|
75
|
+
max_files = (
|
|
76
|
+
pr_capability.params.get("max_files_changed", float('inf'))
|
|
77
|
+
if pr_capability and pr_capability.params
|
|
78
|
+
else float('inf')
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if pr_data.files_changed and len(pr_data.files_changed) > max_files:
|
|
82
|
+
raise HTTPException(
|
|
83
|
+
status_code=403,
|
|
84
|
+
detail={
|
|
85
|
+
"error": "Too many files changed",
|
|
86
|
+
"max_files": max_files,
|
|
87
|
+
"requested": len(pr_data.files_changed)
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Check total lines added
|
|
92
|
+
total_lines_added = sum(
|
|
93
|
+
file.lines_added or 0 for file in (pr_data.files_changed or [])
|
|
94
|
+
)
|
|
95
|
+
max_lines = (
|
|
96
|
+
pr_capability.params.get("max_total_added_lines", float('inf'))
|
|
97
|
+
if pr_capability and pr_capability.params
|
|
98
|
+
else float('inf')
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if total_lines_added > max_lines:
|
|
102
|
+
raise HTTPException(
|
|
103
|
+
status_code=403,
|
|
104
|
+
detail={
|
|
105
|
+
"error": "Too many lines added",
|
|
106
|
+
"max_lines": max_lines,
|
|
107
|
+
"requested": total_lines_added
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Check path allowlist
|
|
112
|
+
path_allowlist = (
|
|
113
|
+
pr_capability.params.get("path_allowlist", [])
|
|
114
|
+
if pr_capability and pr_capability.params
|
|
115
|
+
else []
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if path_allowlist and pr_data.files_changed:
|
|
119
|
+
disallowed_files = [
|
|
120
|
+
file for file in pr_data.files_changed
|
|
121
|
+
if not any(re.match(pattern, file.path) for pattern in path_allowlist)
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
if disallowed_files:
|
|
125
|
+
raise HTTPException(
|
|
126
|
+
status_code=403,
|
|
127
|
+
detail={
|
|
128
|
+
"error": "Files outside allowed paths",
|
|
129
|
+
"disallowed_files": [f.path for f in disallowed_files],
|
|
130
|
+
"path_allowlist": path_allowlist
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Check daily PR limit
|
|
135
|
+
daily_usage = await get_daily_pr_usage(passport.agent_id)
|
|
136
|
+
pr_limit = passport.limits.get("max_prs_per_day", float('inf'))
|
|
137
|
+
|
|
138
|
+
if daily_usage >= pr_limit:
|
|
139
|
+
raise HTTPException(
|
|
140
|
+
status_code=403,
|
|
141
|
+
detail={
|
|
142
|
+
"error": "Daily PR limit exceeded",
|
|
143
|
+
"limit": pr_limit,
|
|
144
|
+
"current_usage": daily_usage
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Create PR using your Git service
|
|
149
|
+
pr_id = await create_pull_request_service({
|
|
150
|
+
"repo": pr_data.repo,
|
|
151
|
+
"base_branch": pr_data.base_branch,
|
|
152
|
+
"head_branch": pr_data.head_branch,
|
|
153
|
+
"title": pr_data.title,
|
|
154
|
+
"body": pr_data.body,
|
|
155
|
+
"files_changed": pr_data.files_changed,
|
|
156
|
+
"agent_id": passport.agent_id,
|
|
157
|
+
"agent_name": passport.name,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
# Record usage
|
|
161
|
+
await record_pr_usage(passport.agent_id)
|
|
162
|
+
|
|
163
|
+
# Log the PR creation
|
|
164
|
+
print(f"PR created: {pr_id} in {pr_data.repo} by agent {passport.agent_id}")
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"success": True,
|
|
168
|
+
"pr_id": pr_id,
|
|
169
|
+
"repo": pr_data.repo,
|
|
170
|
+
"base_branch": pr_data.base_branch,
|
|
171
|
+
"head_branch": pr_data.head_branch,
|
|
172
|
+
"files_changed": len(pr_data.files_changed or []),
|
|
173
|
+
"lines_added": total_lines_added,
|
|
174
|
+
"status": "created",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
except HTTPException:
|
|
178
|
+
raise
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f"PR creation error: {e}")
|
|
181
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
182
|
+
|
|
183
|
+
@app.post("/repo/merge")
|
|
184
|
+
@require_policy("code.repository.merge.v1")
|
|
185
|
+
async def merge_pull_request(request: Request, merge_data: MergeRequest):
|
|
186
|
+
try:
|
|
187
|
+
passport = request.state.policy_result.passport
|
|
188
|
+
|
|
189
|
+
# Get PR details first
|
|
190
|
+
pr_details = await get_pr_details(merge_data.repo, merge_data.pr_id)
|
|
191
|
+
|
|
192
|
+
# Get merge capability
|
|
193
|
+
merge_capability = next(
|
|
194
|
+
(cap for cap in passport.capabilities if cap.id == "repo.merge"),
|
|
195
|
+
None
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Check repository allowlist
|
|
199
|
+
allowed_repos = (
|
|
200
|
+
merge_capability.params.get("allowed_repos", [])
|
|
201
|
+
if merge_capability and merge_capability.params
|
|
202
|
+
else []
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if allowed_repos and merge_data.repo not in allowed_repos:
|
|
206
|
+
raise HTTPException(
|
|
207
|
+
status_code=403,
|
|
208
|
+
detail={
|
|
209
|
+
"error": "Repository not allowed for merging",
|
|
210
|
+
"allowed_repos": allowed_repos
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Check base branch allowlist
|
|
215
|
+
allowed_branches = (
|
|
216
|
+
merge_capability.params.get("allowed_base_branches", [])
|
|
217
|
+
if merge_capability and merge_capability.params
|
|
218
|
+
else []
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if allowed_branches and pr_details["base_branch"] not in allowed_branches:
|
|
222
|
+
raise HTTPException(
|
|
223
|
+
status_code=403,
|
|
224
|
+
detail={
|
|
225
|
+
"error": "Base branch not allowed for merging",
|
|
226
|
+
"allowed_branches": allowed_branches
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Check required labels
|
|
231
|
+
required_labels = (
|
|
232
|
+
merge_capability.params.get("required_labels", [])
|
|
233
|
+
if merge_capability and merge_capability.params
|
|
234
|
+
else []
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if required_labels:
|
|
238
|
+
missing_labels = [
|
|
239
|
+
label for label in required_labels
|
|
240
|
+
if label not in pr_details["labels"]
|
|
241
|
+
]
|
|
242
|
+
if missing_labels:
|
|
243
|
+
raise HTTPException(
|
|
244
|
+
status_code=403,
|
|
245
|
+
detail={
|
|
246
|
+
"error": "Required labels missing",
|
|
247
|
+
"missing_labels": missing_labels,
|
|
248
|
+
"current_labels": pr_details["labels"]
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Check required reviews
|
|
253
|
+
required_reviews = (
|
|
254
|
+
merge_capability.params.get("required_reviews", 0)
|
|
255
|
+
if merge_capability and merge_capability.params
|
|
256
|
+
else 0
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if pr_details["approvals"] < required_reviews:
|
|
260
|
+
raise HTTPException(
|
|
261
|
+
status_code=403,
|
|
262
|
+
detail={
|
|
263
|
+
"error": "Insufficient reviews",
|
|
264
|
+
"required": required_reviews,
|
|
265
|
+
"current": pr_details["approvals"]
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Check PR size limit
|
|
270
|
+
pr_size_kb = pr_details["size_kb"]
|
|
271
|
+
size_limit = passport.limits.get("max_pr_size_kb", float('inf'))
|
|
272
|
+
|
|
273
|
+
if pr_size_kb > size_limit:
|
|
274
|
+
raise HTTPException(
|
|
275
|
+
status_code=403,
|
|
276
|
+
detail={
|
|
277
|
+
"error": "PR size exceeds limit",
|
|
278
|
+
"size_kb": pr_size_kb,
|
|
279
|
+
"limit_kb": size_limit
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Check daily merge limit
|
|
284
|
+
daily_usage = await get_daily_merge_usage(passport.agent_id)
|
|
285
|
+
merge_limit = passport.limits.get("max_merges_per_day", float('inf'))
|
|
286
|
+
|
|
287
|
+
if daily_usage >= merge_limit:
|
|
288
|
+
raise HTTPException(
|
|
289
|
+
status_code=403,
|
|
290
|
+
detail={
|
|
291
|
+
"error": "Daily merge limit exceeded",
|
|
292
|
+
"limit": merge_limit,
|
|
293
|
+
"current_usage": daily_usage
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Merge PR using your Git service
|
|
298
|
+
merge_result = await merge_pull_request_service({
|
|
299
|
+
"repo": merge_data.repo,
|
|
300
|
+
"pr_id": merge_data.pr_id,
|
|
301
|
+
"merge_method": merge_data.merge_method,
|
|
302
|
+
"delete_branch": merge_data.delete_branch,
|
|
303
|
+
"agent_id": passport.agent_id,
|
|
304
|
+
"agent_name": passport.name,
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
# Record usage
|
|
308
|
+
await record_merge_usage(passport.agent_id)
|
|
309
|
+
|
|
310
|
+
# Log the merge
|
|
311
|
+
print(f"PR merged: {merge_data.pr_id} in {merge_data.repo} by agent {passport.agent_id}")
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
"success": True,
|
|
315
|
+
"pr_id": merge_data.pr_id,
|
|
316
|
+
"repo": merge_data.repo,
|
|
317
|
+
"merge_sha": merge_result["sha"],
|
|
318
|
+
"merge_method": merge_data.merge_method,
|
|
319
|
+
"status": "merged",
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
except HTTPException:
|
|
323
|
+
raise
|
|
324
|
+
except Exception as e:
|
|
325
|
+
print(f"PR merge error: {e}")
|
|
326
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
327
|
+
|
|
328
|
+
# Mock functions (implement with your actual Git service)
|
|
329
|
+
async def create_pull_request_service(pr_data: dict) -> str:
|
|
330
|
+
"""Mock PR creation function"""
|
|
331
|
+
await asyncio.sleep(0.2) # Simulate API call
|
|
332
|
+
return f"pr_{asyncio.get_event_loop().time()}_{hash(str(pr_data)) % 1000000}"
|
|
333
|
+
|
|
334
|
+
async def get_pr_details(repo: str, pr_id: str) -> Dict[str, Any]:
|
|
335
|
+
"""Mock PR details retrieval"""
|
|
336
|
+
return {
|
|
337
|
+
"base_branch": "main",
|
|
338
|
+
"labels": ["enhancement", "agent-created"],
|
|
339
|
+
"approvals": 2,
|
|
340
|
+
"size_kb": 45
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async def merge_pull_request_service(merge_data: dict) -> Dict[str, str]:
|
|
344
|
+
"""Mock PR merge function"""
|
|
345
|
+
await asyncio.sleep(0.3) # Simulate API call
|
|
346
|
+
return {
|
|
347
|
+
"sha": f"sha_{asyncio.get_event_loop().time()}_{hash(str(merge_data)) % 1000000}"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async def get_daily_pr_usage(agent_id: str) -> int:
|
|
351
|
+
"""Get current daily PR usage"""
|
|
352
|
+
return 0 # Mock value
|
|
353
|
+
|
|
354
|
+
async def get_daily_merge_usage(agent_id: str) -> int:
|
|
355
|
+
"""Get current daily merge usage"""
|
|
356
|
+
return 0 # Mock value
|
|
357
|
+
|
|
358
|
+
async def record_pr_usage(agent_id: str) -> None:
|
|
359
|
+
"""Record PR creation usage"""
|
|
360
|
+
print(f"Recorded PR creation for agent {agent_id}")
|
|
361
|
+
|
|
362
|
+
async def record_merge_usage(agent_id: str) -> None:
|
|
363
|
+
"""Record PR merge usage"""
|
|
364
|
+
print(f"Recorded PR merge for agent {agent_id}")
|
|
365
|
+
|
|
366
|
+
if __name__ == "__main__":
|
|
367
|
+
import uvicorn
|
|
368
|
+
print("Repository service starting...")
|
|
369
|
+
print("Protected by APort code.repository.merge.v1 policy pack")
|
|
370
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|