platformforge 0.1.0__py3-none-any.whl

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 (659) hide show
  1. platformforge/__init__.py +7 -0
  2. platformforge/__main__.py +5 -0
  3. platformforge/_forge_portal.py +913 -0
  4. platformforge/_graphstudio.py +376 -0
  5. platformforge/_graphview.py +181 -0
  6. platformforge/_installkit.py +1706 -0
  7. platformforge/agents/__init__.py +156 -0
  8. platformforge/agents/bench.py +61 -0
  9. platformforge/agents/contextpack.py +137 -0
  10. platformforge/agents/contracts.py +394 -0
  11. platformforge/agents/coordinators.py +98 -0
  12. platformforge/agents/critic.py +113 -0
  13. platformforge/agents/debate.py +161 -0
  14. platformforge/agents/executors.py +160 -0
  15. platformforge/agents/guardian.py +48 -0
  16. platformforge/agents/mirrors.py +240 -0
  17. platformforge/agents/orchestrator.py +295 -0
  18. platformforge/agents/planner.py +116 -0
  19. platformforge/agents/playbook.py +51 -0
  20. platformforge/agents/referee.py +132 -0
  21. platformforge/agents/reviewers.py +113 -0
  22. platformforge/agents/roster.py +893 -0
  23. platformforge/agents/runledger.py +85 -0
  24. platformforge/agents/runstore.py +74 -0
  25. platformforge/agents/specialists.py +73 -0
  26. platformforge/agents/taskspec.py +94 -0
  27. platformforge/agents/uniqueness.py +115 -0
  28. platformforge/agents/verifier.py +106 -0
  29. platformforge/aiplat/__init__.py +4 -0
  30. platformforge/aiplat/models.py +175 -0
  31. platformforge/analytics/__init__.py +6 -0
  32. platformforge/analytics/capacity.py +109 -0
  33. platformforge/analytics/debt.py +92 -0
  34. platformforge/analytics/dx.py +69 -0
  35. platformforge/analytics/finops_v4.py +171 -0
  36. platformforge/analytics/goldenpath.py +131 -0
  37. platformforge/analytics/history.py +233 -0
  38. platformforge/analytics/measurement.py +149 -0
  39. platformforge/analytics/models.py +127 -0
  40. platformforge/analytics/opsanalytics.py +119 -0
  41. platformforge/analytics/policyintel.py +139 -0
  42. platformforge/analytics/reliability.py +106 -0
  43. platformforge/analytics/soak.py +135 -0
  44. platformforge/analytics/store.py +254 -0
  45. platformforge/bench.py +174 -0
  46. platformforge/cases/__init__.py +13 -0
  47. platformforge/cases/casefile.py +106 -0
  48. platformforge/cases/context_audit.py +134 -0
  49. platformforge/cases/ledgers.py +174 -0
  50. platformforge/cases/replay.py +142 -0
  51. platformforge/cases/routing_audit.py +162 -0
  52. platformforge/caveman/__init__.py +11 -0
  53. platformforge/caveman/compress.py +156 -0
  54. platformforge/cicd/__init__.py +16 -0
  55. platformforge/cicd/backstage.py +51 -0
  56. platformforge/cicd/dockerfile.py +64 -0
  57. platformforge/cicd/github_actions.py +122 -0
  58. platformforge/cicd/gitlab_ci.py +56 -0
  59. platformforge/cicd/gitops.py +130 -0
  60. platformforge/cli/__init__.py +0 -0
  61. platformforge/cli/fleetcmd.py +457 -0
  62. platformforge/cli/main.py +3832 -0
  63. platformforge/cloud/__init__.py +8 -0
  64. platformforge/cloud/aws.py +254 -0
  65. platformforge/cloud/common.py +69 -0
  66. platformforge/cloud/multicloud.py +143 -0
  67. platformforge/collect/__init__.py +11 -0
  68. platformforge/collect/detect.py +254 -0
  69. platformforge/context/__init__.py +10 -0
  70. platformforge/context/capsule.py +60 -0
  71. platformforge/context/gateway.py +227 -0
  72. platformforge/context/refs.py +49 -0
  73. platformforge/context/sufficiency.py +50 -0
  74. platformforge/core/__init__.py +1 -0
  75. platformforge/core/hashing.py +39 -0
  76. platformforge/core/ownership.py +220 -0
  77. platformforge/core/receipts.py +74 -0
  78. platformforge/core/redaction.py +97 -0
  79. platformforge/core/store.py +119 -0
  80. platformforge/core/workspace.py +89 -0
  81. platformforge/data/contracts/agent-handoff.schema.json +26 -0
  82. platformforge/data/contracts/agent-run.schema.json +27 -0
  83. platformforge/data/contracts/agent-spec.schema.json +43 -0
  84. platformforge/data/contracts/capability.schema.json +23 -0
  85. platformforge/data/contracts/change-event.schema.json +22 -0
  86. platformforge/data/contracts/change-intent.schema.json +62 -0
  87. platformforge/data/contracts/change-plan.schema.json +43 -0
  88. platformforge/data/contracts/collector-receipt.schema.json +36 -0
  89. platformforge/data/contracts/context-pack.schema.json +29 -0
  90. platformforge/data/contracts/debate.schema.json +31 -0
  91. platformforge/data/contracts/distribution-manifest-v1.schema.json +81 -0
  92. platformforge/data/contracts/drift-event.schema.json +21 -0
  93. platformforge/data/contracts/fact.schema.json +35 -0
  94. platformforge/data/contracts/finding.schema.json +20 -0
  95. platformforge/data/contracts/graph-v2.schema.json +67 -0
  96. platformforge/data/contracts/graph.schema.json +40 -0
  97. platformforge/data/contracts/install-receipt-v1.schema.json +49 -0
  98. platformforge/data/contracts/observation.schema.json +93 -0
  99. platformforge/data/contracts/receipt.schema.json +21 -0
  100. platformforge/data/contracts/recommendation.schema.json +32 -0
  101. platformforge/data/contracts/refusal.schema.json +16 -0
  102. platformforge/data/contracts/slo-contract.schema.json +25 -0
  103. platformforge/data/contracts/task-spec.schema.json +37 -0
  104. platformforge/data/contracts/workspace-manifest-v1.schema.json +62 -0
  105. platformforge/data/evals/cases/agent-budget-exhaustion/case.yaml +6 -0
  106. platformforge/data/evals/cases/agent-conflicting-specialists/case.yaml +11 -0
  107. platformforge/data/evals/cases/agent-fleet-optimization/case.yaml +13 -0
  108. platformforge/data/evals/cases/agent-host-no-subagents/case.yaml +6 -0
  109. platformforge/data/evals/cases/agent-incident-multi/case.yaml +13 -0
  110. platformforge/data/evals/cases/agent-missing-evidence/case.yaml +6 -0
  111. platformforge/data/evals/cases/agent-multi-domain-audit/case.yaml +14 -0
  112. platformforge/data/evals/cases/agent-production-change/case.yaml +15 -0
  113. platformforge/data/evals/cases/agent-security-mandatory/case.yaml +12 -0
  114. platformforge/data/evals/cases/agent-simple-iac/case.yaml +14 -0
  115. platformforge/data/evals/cases/approval-integrity-seal/case.yaml +6 -0
  116. platformforge/data/evals/cases/capacity-hot/case.yaml +7 -0
  117. platformforge/data/evals/cases/capacity-hot/fixture/capacity.json +5 -0
  118. platformforge/data/evals/cases/catalog-noowner/case.yaml +7 -0
  119. platformforge/data/evals/cases/catalog-noowner/fixture/catalog-info.yaml +6 -0
  120. platformforge/data/evals/cases/contract-fact/case.yaml +5 -0
  121. platformforge/data/evals/cases/contract-fact/fixture/doc.json +2 -0
  122. platformforge/data/evals/cases/cosign-unverified/case.yaml +8 -0
  123. platformforge/data/evals/cases/cosign-unverified/fixture/sig.json +33 -0
  124. platformforge/data/evals/cases/expected-delta-refusal/case.yaml +6 -0
  125. platformforge/data/evals/cases/finops-noattr/case.yaml +9 -0
  126. platformforge/data/evals/cases/finops-noattr/fixture/costs.json +7 -0
  127. platformforge/data/evals/cases/fleet-inv-absent-allocation-unknown/case.yaml +7 -0
  128. platformforge/data/evals/cases/fleet-inv-centrality-not-criticality/case.yaml +7 -0
  129. platformforge/data/evals/cases/fleet-inv-correlation-not-causality/case.yaml +7 -0
  130. platformforge/data/evals/cases/fleet-inv-dx-no-person-metrics/case.yaml +7 -0
  131. platformforge/data/evals/cases/fleet-inv-federation-restricted-denied/case.yaml +7 -0
  132. platformforge/data/evals/cases/fleet-inv-federation-secret-denied/case.yaml +7 -0
  133. platformforge/data/evals/cases/fleet-inv-history-window-deterministic/case.yaml +7 -0
  134. platformforge/data/evals/cases/fleet-inv-low-util-not-deletable/case.yaml +7 -0
  135. platformforge/data/evals/cases/fleet-inv-member-key-deterministic/case.yaml +7 -0
  136. platformforge/data/evals/cases/fleet-inv-missing-denominator/case.yaml +7 -0
  137. platformforge/data/evals/cases/fleet-inv-partial-coverage-visible/case.yaml +7 -0
  138. platformforge/data/evals/cases/fleet-inv-plan-changeintent-only/case.yaml +7 -0
  139. platformforge/data/evals/cases/fleet-inv-report-all-cited/case.yaml +7 -0
  140. platformforge/data/evals/cases/fleet-inv-small-sample-confidence/case.yaml +7 -0
  141. platformforge/data/evals/cases/fleet-inv-uncertain-optimization-suppressed/case.yaml +7 -0
  142. platformforge/data/evals/cases/gha-dangerous/case.yaml +12 -0
  143. platformforge/data/evals/cases/gha-dangerous/fixture/.github/workflows/ci.yaml +11 -0
  144. platformforge/data/evals/cases/gitops-v2/case.yaml +11 -0
  145. platformforge/data/evals/cases/gitops-v2/fixture/argo.yaml +41 -0
  146. platformforge/data/evals/cases/graph-k8s-blast/case.yaml +7 -0
  147. platformforge/data/evals/cases/graph-k8s-blast/fixture/deploy.yaml +15 -0
  148. platformforge/data/evals/cases/iac-module-nopin/case.yaml +8 -0
  149. platformforge/data/evals/cases/iac-module-nopin/fixture/main.tf +3 -0
  150. platformforge/data/evals/cases/iac-plan-replace/case.yaml +8 -0
  151. platformforge/data/evals/cases/iac-plan-replace/fixture/plan.json +12 -0
  152. platformforge/data/evals/cases/iac-sg-open/case.yaml +8 -0
  153. platformforge/data/evals/cases/iac-sg-open/fixture/main.tf +9 -0
  154. platformforge/data/evals/cases/iam-oidc/case.yaml +8 -0
  155. platformforge/data/evals/cases/iam-oidc/fixture/policy.json +11 -0
  156. platformforge/data/evals/cases/iam-wildcard-positive/case.yaml +8 -0
  157. platformforge/data/evals/cases/iam-wildcard-positive/fixture/policy.json +3 -0
  158. platformforge/data/evals/cases/k8s-bad-actor/case.yaml +19 -0
  159. platformforge/data/evals/cases/k8s-bad-actor/fixture/app.yaml +97 -0
  160. platformforge/data/evals/cases/k8s-beta-api/case.yaml +8 -0
  161. platformforge/data/evals/cases/k8s-beta-api/fixture/cr.yaml +7 -0
  162. platformforge/data/evals/cases/k8s-dep-api-boundary/case.yaml +15 -0
  163. platformforge/data/evals/cases/k8s-dep-api-boundary/fixture/app.yaml +25 -0
  164. platformforge/data/evals/cases/k8s-dep-api-clean/case.yaml +12 -0
  165. platformforge/data/evals/cases/k8s-dep-api-clean/fixture/app.yaml +29 -0
  166. platformforge/data/evals/cases/k8s-dep-api-unknown/case.yaml +11 -0
  167. platformforge/data/evals/cases/k8s-dep-api-unknown/fixture/app.yaml +25 -0
  168. platformforge/data/evals/cases/k8s-dep-api-v120/case.yaml +12 -0
  169. platformforge/data/evals/cases/k8s-dep-api-v120/fixture/app.yaml +25 -0
  170. platformforge/data/evals/cases/k8s-dep-api-v129/case.yaml +9 -0
  171. platformforge/data/evals/cases/k8s-dep-api-v129/fixture/app.yaml +25 -0
  172. platformforge/data/evals/cases/k8s-hpa-noreq/case.yaml +8 -0
  173. platformforge/data/evals/cases/k8s-hpa-noreq/fixture/app.yaml +28 -0
  174. platformforge/data/evals/cases/k8s-limits-boundary/case.yaml +10 -0
  175. platformforge/data/evals/cases/k8s-limits-boundary/fixture/app.yaml +15 -0
  176. platformforge/data/evals/cases/k8s-limits-negative/case.yaml +8 -0
  177. platformforge/data/evals/cases/k8s-limits-negative/fixture/deploy.yaml +15 -0
  178. platformforge/data/evals/cases/k8s-limits-positive/case.yaml +8 -0
  179. platformforge/data/evals/cases/k8s-limits-positive/fixture/deploy.yaml +14 -0
  180. platformforge/data/evals/cases/k8s-nopdb/case.yaml +7 -0
  181. platformforge/data/evals/cases/k8s-nopdb/fixture/app.yaml +34 -0
  182. platformforge/data/evals/cases/knowledge-registry/case.yaml +10 -0
  183. platformforge/data/evals/cases/kyverno-dep/case.yaml +10 -0
  184. platformforge/data/evals/cases/kyverno-dep/fixture/pol.yaml +18 -0
  185. platformforge/data/evals/cases/kyverno-legacy/case.yaml +8 -0
  186. platformforge/data/evals/cases/kyverno-legacy/fixture/pol.yaml +7 -0
  187. platformforge/data/evals/cases/live-aws-collect/case.yaml +11 -0
  188. platformforge/data/evals/cases/live-aws-collect/fixture/aws/ec2_describe-vpcs.json +15 -0
  189. platformforge/data/evals/cases/live-aws-collect/fixture/aws/iam_list-users.denied.txt +1 -0
  190. platformforge/data/evals/cases/live-aws-collect/fixture/aws/sts_get-caller-identity.json +5 -0
  191. platformforge/data/evals/cases/live-drift/case.yaml +8 -0
  192. platformforge/data/evals/cases/live-drift/fixture/after.json +22 -0
  193. platformforge/data/evals/cases/live-drift/fixture/before.json +22 -0
  194. platformforge/data/evals/cases/live-incident/case.yaml +7 -0
  195. platformforge/data/evals/cases/live-incident/fixture/changes.json +8 -0
  196. platformforge/data/evals/cases/live-incident/fixture/incident.json +7 -0
  197. platformforge/data/evals/cases/live-k8s-collect/case.yaml +10 -0
  198. platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_configmaps.denied.txt +1 -0
  199. platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_deployments.apps.json +25 -0
  200. platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_pods.json +17 -0
  201. platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_secrets.json +13 -0
  202. platformforge/data/evals/cases/live-multicluster/case.yaml +7 -0
  203. platformforge/data/evals/cases/live-multicluster/fixture/clusters.json +6 -0
  204. platformforge/data/evals/cases/live-plan/case.yaml +7 -0
  205. platformforge/data/evals/cases/live-plan/fixture/drift.json +8 -0
  206. platformforge/data/evals/cases/live-reconcile/case.yaml +7 -0
  207. platformforge/data/evals/cases/live-reconcile/fixture/desired.json +7 -0
  208. platformforge/data/evals/cases/live-reconcile/fixture/observed.json +18 -0
  209. platformforge/data/evals/cases/live-secrets-boundary/case.yaml +8 -0
  210. platformforge/data/evals/cases/live-secrets-boundary/fixture/envelope.json +16 -0
  211. platformforge/data/evals/cases/live-temporal-graph/case.yaml +10 -0
  212. platformforge/data/evals/cases/live-temporal-graph/fixture/graph.json +22 -0
  213. platformforge/data/evals/cases/ops-approval/case.yaml +6 -0
  214. platformforge/data/evals/cases/ops-convergence/case.yaml +6 -0
  215. platformforge/data/evals/cases/ops-execution-safety/case.yaml +6 -0
  216. platformforge/data/evals/cases/ops-intent/case.yaml +6 -0
  217. platformforge/data/evals/cases/ops-planning/case.yaml +6 -0
  218. platformforge/data/evals/cases/ops-policy/case.yaml +6 -0
  219. platformforge/data/evals/cases/ops-rollback/case.yaml +6 -0
  220. platformforge/data/evals/cases/ops-verification/case.yaml +6 -0
  221. platformforge/data/evals/cases/rollback-argocd-revision/case.yaml +6 -0
  222. platformforge/data/evals/cases/rollback-blind-inverse/case.yaml +6 -0
  223. platformforge/data/evals/cases/rollback-git-commit/case.yaml +6 -0
  224. platformforge/data/evals/cases/rollback-impossible/case.yaml +6 -0
  225. platformforge/data/evals/cases/rollback-k8s-annotation-restore/case.yaml +6 -0
  226. platformforge/data/evals/cases/rollback-k8s-scale/case.yaml +6 -0
  227. platformforge/data/evals/cases/rollback-material-tamper/case.yaml +6 -0
  228. platformforge/data/evals/cases/rollback-terraform-replan/case.yaml +6 -0
  229. platformforge/data/evals/cases/rollback-unknown/case.yaml +6 -0
  230. platformforge/data/evals/cases/routing-specialist/case.yaml +9 -0
  231. platformforge/data/evals/cases/rtk-roundtrip/case.yaml +5 -0
  232. platformforge/data/evals/cases/rtk-roundtrip/fixture/verbose.txt +300 -0
  233. platformforge/data/evals/cases/sbom-vuln/case.yaml +9 -0
  234. platformforge/data/evals/cases/sbom-vuln/fixture/sbom.json +9 -0
  235. platformforge/data/evals/cases/secret-prose-mixed/fixture/app.env +1 -0
  236. platformforge/data/evals/cases/secret-prose-mixed/fixture/policy-notes.md +7 -0
  237. platformforge/data/evals/cases/secrets-noleak/case.yaml +5 -0
  238. platformforge/data/evals/cases/secrets-noleak/fixture/app.env +2 -0
  239. platformforge/data/evals/cases/slo-fastburn/case.yaml +8 -0
  240. platformforge/data/evals/cases/slo-fastburn/fixture/events.yaml +2 -0
  241. platformforge/data/evals/cases/slo-fastburn/fixture/slo.yaml +4 -0
  242. platformforge/data/evals/cases/slo-noinput/case.yaml +8 -0
  243. platformforge/data/evals/cases/slo-noinput/fixture/events.yaml +1 -0
  244. platformforge/data/evals/cases/slo-noinput/fixture/slo.yaml +4 -0
  245. platformforge/data/evals/cases/sot-conflict/case.yaml +6 -0
  246. platformforge/data/evals/cases/token-budget/case.yaml +6 -0
  247. platformforge/data/evals/cases/token-budget/fixture/big.py +50 -0
  248. platformforge/data/evals/cases/xr-delete/case.yaml +8 -0
  249. platformforge/data/evals/cases/xr-delete/fixture/xr.yaml +10 -0
  250. platformforge/data/evals/cases/xr-graph-chain/case.yaml +8 -0
  251. platformforge/data/evals/cases/xr-graph-chain/fixture/xrd.yaml +90 -0
  252. platformforge/data/evals/cases/xr-negative-crd/case.yaml +8 -0
  253. platformforge/data/evals/cases/xr-negative-crd/fixture/widget.yaml +19 -0
  254. platformforge/data/evals/cases/xr-upbound-mr/case.yaml +8 -0
  255. platformforge/data/evals/cases/xr-upbound-mr/fixture/bucket.yaml +12 -0
  256. platformforge/data/evals/qpt-bench/change-review/case.yaml +5 -0
  257. platformforge/data/evals/qpt-bench/change-review/fixture/plan.json +9 -0
  258. platformforge/data/evals/qpt-bench/gha-security/case.yaml +5 -0
  259. platformforge/data/evals/qpt-bench/gha-security/fixture/ci.yml +10 -0
  260. platformforge/data/evals/qpt-bench/iam-investigate/case.yaml +5 -0
  261. platformforge/data/evals/qpt-bench/iam-investigate/fixture/policy.json +6 -0
  262. platformforge/data/evals/qpt-bench/incident-analysis/case.yaml +5 -0
  263. platformforge/data/evals/qpt-bench/incident-analysis/fixture/query.json +5 -0
  264. platformforge/data/evals/qpt-bench/incident-analysis/fixture/rules.yaml +6 -0
  265. platformforge/data/evals/qpt-bench/k8s-review/case.yaml +6 -0
  266. platformforge/data/evals/qpt-bench/k8s-review/fixture/deploy.yaml +15 -0
  267. platformforge/data/evals/qpt-bench/tf-security/case.yaml +5 -0
  268. platformforge/data/evals/qpt-bench/tf-security/fixture/main.tf +17 -0
  269. platformforge/data/knowledge/aws/eks.yaml +20 -0
  270. platformforge/data/knowledge/aws/iam.yaml +28 -0
  271. platformforge/data/knowledge/aws/network-exposure.yaml +19 -0
  272. platformforge/data/knowledge/aws/organizations.yaml +19 -0
  273. platformforge/data/knowledge/crossplane/composition-providers.yaml +20 -0
  274. platformforge/data/knowledge/crossplane/managed-resources.yaml +26 -0
  275. platformforge/data/knowledge/crossplane/v1-v2.yaml +25 -0
  276. platformforge/data/knowledge/kubernetes/api-deprecations.yaml +33 -0
  277. platformforge/data/knowledge/kubernetes/autoscaling-rbac.yaml +27 -0
  278. platformforge/data/knowledge/kubernetes/gateway-api.yaml +20 -0
  279. platformforge/data/knowledge/kubernetes/pod-security.yaml +26 -0
  280. platformforge/data/knowledge/sources.yaml +516 -0
  281. platformforge/data/knowledge/terraform/lifecycle.yaml +18 -0
  282. platformforge/data/knowledge/terraform/plan-semantics.yaml +26 -0
  283. platformforge/data/lab/chaos-pod-kill/chaos.yaml +3 -0
  284. platformforge/data/lab/chaos-pod-kill/facts.json +13 -0
  285. platformforge/data/lab/fleets/acme/capacity.yaml +35 -0
  286. platformforge/data/lab/fleets/acme/costs.yaml +25 -0
  287. platformforge/data/lab/fleets/acme/events.yaml +41 -0
  288. platformforge/data/lab/fleets/acme/fleet.yaml +51 -0
  289. platformforge/data/lab/fleets/acme/graph.yaml +45 -0
  290. platformforge/data/lab/fleets/acme/policies.yaml +28 -0
  291. platformforge/data/lab/fleets/acme/requests.yaml +24 -0
  292. platformforge/data/lab/scenarios/argo-bad-rollout/expected.yaml +6 -0
  293. platformforge/data/lab/scenarios/argo-bad-rollout/fixture/app.yaml +9 -0
  294. platformforge/data/lab/scenarios/aws-dump/expected.yaml +19 -0
  295. platformforge/data/lab/scenarios/aws-dump/fixture/compute.json +4 -0
  296. platformforge/data/lab/scenarios/aws-dump/fixture/iam.json +7 -0
  297. platformforge/data/lab/scenarios/aws-dump/fixture/network.json +6 -0
  298. platformforge/data/lab/scenarios/aws-dump/fixture/org.json +4 -0
  299. platformforge/data/lab/scenarios/aws-dump/fixture/storage.json +4 -0
  300. platformforge/data/lab/scenarios/container-unsigned/expected.yaml +5 -0
  301. platformforge/data/lab/scenarios/container-unsigned/fixture/supply.json +11 -0
  302. platformforge/data/lab/scenarios/cost-idle-workload/expected.yaml +5 -0
  303. platformforge/data/lab/scenarios/cost-idle-workload/fixture/costs.json +12 -0
  304. platformforge/data/lab/scenarios/crossplane-platform/expected.yaml +16 -0
  305. platformforge/data/lab/scenarios/crossplane-platform/fixture/negative.yaml +18 -0
  306. platformforge/data/lab/scenarios/crossplane-platform/fixture/xrd.yaml +90 -0
  307. platformforge/data/lab/scenarios/demo-platform/expected.yaml +16 -0
  308. platformforge/data/lab/scenarios/demo-platform/fixture/.github/workflows/ci.yml +10 -0
  309. platformforge/data/lab/scenarios/demo-platform/fixture/deploy.yaml +16 -0
  310. platformforge/data/lab/scenarios/demo-platform/fixture/infra.tf +5 -0
  311. platformforge/data/lab/scenarios/fleet-ai/expected.yaml +12 -0
  312. platformforge/data/lab/scenarios/fleet-capacity/expected.yaml +12 -0
  313. platformforge/data/lab/scenarios/fleet-cost/expected.yaml +18 -0
  314. platformforge/data/lab/scenarios/fleet-federation/expected.yaml +11 -0
  315. platformforge/data/lab/scenarios/fleet-golden-path/expected.yaml +18 -0
  316. platformforge/data/lab/scenarios/fleet-op-hotspot/expected.yaml +11 -0
  317. platformforge/data/lab/scenarios/fleet-ownership/expected.yaml +17 -0
  318. platformforge/data/lab/scenarios/fleet-partial/expected.yaml +10 -0
  319. platformforge/data/lab/scenarios/fleet-policy-friction/expected.yaml +10 -0
  320. platformforge/data/lab/scenarios/fleet-recurring-incident/expected.yaml +12 -0
  321. platformforge/data/lab/scenarios/fleet-report/expected.yaml +13 -0
  322. platformforge/data/lab/scenarios/fleet-risk/expected.yaml +18 -0
  323. platformforge/data/lab/scenarios/gitops-drift/expected.yaml +6 -0
  324. platformforge/data/lab/scenarios/gitops-drift/fixture/app.yaml +12 -0
  325. platformforge/data/lab/scenarios/k8s-bad-readiness/expected.yaml +6 -0
  326. platformforge/data/lab/scenarios/k8s-bad-readiness/fixture/deploy.yaml +23 -0
  327. platformforge/data/lab/scenarios/k8s-bad-readiness/fixture/netpol.yaml +9 -0
  328. platformforge/data/lab/scenarios/k8s-missing-limits/expected.yaml +6 -0
  329. platformforge/data/lab/scenarios/k8s-missing-limits/fixture/deploy.yaml +26 -0
  330. platformforge/data/lab/scenarios/k8s-missing-limits/fixture/netpol.yaml +9 -0
  331. platformforge/data/lab/scenarios/k8s-single-replica/expected.yaml +5 -0
  332. platformforge/data/lab/scenarios/k8s-single-replica/fixture/deploy.yaml +26 -0
  333. platformforge/data/lab/scenarios/k8s-single-replica/fixture/netpol.yaml +4 -0
  334. platformforge/data/lab/scenarios/live-aws-collect/expected.yaml +4 -0
  335. platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/ec2_describe-vpcs.json +15 -0
  336. platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/iam_list-users.denied.txt +1 -0
  337. platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/sts_get-caller-identity.json +5 -0
  338. platformforge/data/lab/scenarios/live-k8s-collect/expected.yaml +4 -0
  339. platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_configmaps.denied.txt +1 -0
  340. platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_deployments.apps.json +25 -0
  341. platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_pods.json +17 -0
  342. platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_secrets.json +13 -0
  343. platformforge/data/lab/scenarios/live-kind/expected.yaml +14 -0
  344. platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_configmaps.denied.txt +1 -0
  345. platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_deployments.apps.json +25 -0
  346. platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_pods.json +17 -0
  347. platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_secrets.json +13 -0
  348. platformforge/data/lab/scenarios/network-policy-missing/expected.yaml +6 -0
  349. platformforge/data/lab/scenarios/network-policy-missing/fixture/deploy.yaml +26 -0
  350. platformforge/data/lab/scenarios/ops-bad-rollout/expected.yaml +3 -0
  351. platformforge/data/lab/scenarios/ops-bad-rollout/fixture/ops.yaml +33 -0
  352. platformforge/data/lab/scenarios/ops-break-glass/expected.yaml +3 -0
  353. platformforge/data/lab/scenarios/ops-break-glass/fixture/ops.yaml +21 -0
  354. platformforge/data/lab/scenarios/ops-broken-selector/expected.yaml +3 -0
  355. platformforge/data/lab/scenarios/ops-broken-selector/fixture/ops.yaml +21 -0
  356. platformforge/data/lab/scenarios/ops-changed-plan-hash/expected.yaml +3 -0
  357. platformforge/data/lab/scenarios/ops-changed-plan-hash/fixture/ops.yaml +20 -0
  358. platformforge/data/lab/scenarios/ops-conflicting-operation/expected.yaml +3 -0
  359. platformforge/data/lab/scenarios/ops-conflicting-operation/fixture/ops.yaml +21 -0
  360. platformforge/data/lab/scenarios/ops-expired-approval/expected.yaml +3 -0
  361. platformforge/data/lab/scenarios/ops-expired-approval/fixture/ops.yaml +20 -0
  362. platformforge/data/lab/scenarios/ops-partial-failure/expected.yaml +3 -0
  363. platformforge/data/lab/scenarios/ops-partial-failure/fixture/ops.yaml +24 -0
  364. platformforge/data/lab/scenarios/ops-policy-denial/expected.yaml +3 -0
  365. platformforge/data/lab/scenarios/ops-policy-denial/fixture/ops.yaml +17 -0
  366. platformforge/data/lab/scenarios/ops-replica-drift/expected.yaml +3 -0
  367. platformforge/data/lab/scenarios/ops-replica-drift/fixture/ops.yaml +28 -0
  368. platformforge/data/lab/scenarios/ops-rollback-annotate/expected.yaml +3 -0
  369. platformforge/data/lab/scenarios/ops-rollback-annotate/fixture/ops.yaml +41 -0
  370. platformforge/data/lab/scenarios/ops-rollback-gitops/expected.yaml +3 -0
  371. platformforge/data/lab/scenarios/ops-rollback-gitops/fixture/ops.yaml +38 -0
  372. platformforge/data/lab/scenarios/ops-rollback-impossible/expected.yaml +3 -0
  373. platformforge/data/lab/scenarios/ops-rollback-impossible/fixture/ops.yaml +34 -0
  374. platformforge/data/lab/scenarios/ops-rollback-scale/expected.yaml +3 -0
  375. platformforge/data/lab/scenarios/ops-rollback-scale/fixture/ops.yaml +40 -0
  376. platformforge/data/lab/scenarios/ops-rollback-terraform/expected.yaml +3 -0
  377. platformforge/data/lab/scenarios/ops-rollback-terraform/fixture/ops.yaml +38 -0
  378. platformforge/data/lab/scenarios/ops-rollback-terraform/fixture/tf/tfplan.bin +1 -0
  379. platformforge/data/lab/scenarios/ops-stale-observation/expected.yaml +3 -0
  380. platformforge/data/lab/scenarios/ops-stale-observation/fixture/ops.yaml +20 -0
  381. platformforge/data/lab/scenarios/secret-exposure/expected.yaml +5 -0
  382. platformforge/data/lab/scenarios/secret-exposure/fixture/app.env +2 -0
  383. platformforge/data/lab/scenarios/slo-burn/expected.yaml +5 -0
  384. platformforge/data/lab/scenarios/slo-burn/fixture/events.yaml +2 -0
  385. platformforge/data/lab/scenarios/slo-burn/fixture/slo.yaml +4 -0
  386. platformforge/data/lab/scenarios/terraform-iam-wildcard/expected.yaml +5 -0
  387. platformforge/data/lab/scenarios/terraform-iam-wildcard/fixture/policy.json +14 -0
  388. platformforge/data/lab/scenarios/terraform-public-bucket/expected.yaml +6 -0
  389. platformforge/data/lab/scenarios/terraform-public-bucket/fixture/main.tf +5 -0
  390. platformforge/data/portable_skills/platformforge-agents/SKILL.md +47 -0
  391. platformforge/data/portable_skills/platformforge-change/SKILL.md +41 -0
  392. platformforge/data/portable_skills/platformforge-core/SKILL.md +62 -0
  393. platformforge/data/portable_skills/platformforge-economy/SKILL.md +42 -0
  394. platformforge/data/portable_skills/platformforge-finops/SKILL.md +34 -0
  395. platformforge/data/portable_skills/platformforge-fleet/SKILL.md +39 -0
  396. platformforge/data/portable_skills/platformforge-governance/SKILL.md +76 -0
  397. platformforge/data/portable_skills/platformforge-graph/SKILL.md +46 -0
  398. platformforge/data/portable_skills/platformforge-lab/SKILL.md +39 -0
  399. platformforge/data/portable_skills/platformforge-sdd/SKILL.md +39 -0
  400. platformforge/data/portable_skills/platformforge-security/SKILL.md +38 -0
  401. platformforge/data/portable_skills/platformforge-sre/SKILL.md +41 -0
  402. platformforge/data/rules/catalog/finops.yaml +26 -0
  403. platformforge/data/rules/catalog/gitops.yaml +161 -0
  404. platformforge/data/rules/catalog/iac.yaml +84 -0
  405. platformforge/data/rules/catalog/k8s.yaml +322 -0
  406. platformforge/data/rules/catalog/orchestration.yaml +99 -0
  407. platformforge/data/rules/catalog/product.yaml +42 -0
  408. platformforge/data/rules/catalog/routing.yaml +122 -0
  409. platformforge/data/rules/catalog/security.yaml +159 -0
  410. platformforge/data/rules/catalog/sre.yaml +67 -0
  411. platformforge/diagnose/__init__.py +75 -0
  412. platformforge/distribution/__init__.py +18 -0
  413. platformforge/distribution/bundle.py +127 -0
  414. platformforge/distribution/hostflow.py +247 -0
  415. platformforge/distribution/models.py +80 -0
  416. platformforge/distribution/service.py +225 -0
  417. platformforge/economy/__init__.py +27 -0
  418. platformforge/economy/bench.py +66 -0
  419. platformforge/economy/budget.py +198 -0
  420. platformforge/economy/cache.py +287 -0
  421. platformforge/economy/checkpoint.py +112 -0
  422. platformforge/economy/collection.py +142 -0
  423. platformforge/economy/doctor.py +207 -0
  424. platformforge/economy/engine.py +158 -0
  425. platformforge/economy/envelope.py +61 -0
  426. platformforge/economy/fleetpack.py +86 -0
  427. platformforge/economy/ledger.py +232 -0
  428. platformforge/economy/plan.py +131 -0
  429. platformforge/economy/pricing.py +101 -0
  430. platformforge/economy/qpt.py +286 -0
  431. platformforge/economy/reconcile.py +86 -0
  432. platformforge/economy/verifyplan.py +107 -0
  433. platformforge/economy/waste.py +123 -0
  434. platformforge/evals/__init__.py +16 -0
  435. platformforge/evals/coverage.py +140 -0
  436. platformforge/evals/fleet_invariants.py +260 -0
  437. platformforge/evals/ops_invariants.py +158 -0
  438. platformforge/evals/runner.py +541 -0
  439. platformforge/federation/__init__.py +4 -0
  440. platformforge/federation/node.py +135 -0
  441. platformforge/finops/__init__.py +14 -0
  442. platformforge/finops/alloc.py +31 -0
  443. platformforge/finops/costs.py +79 -0
  444. platformforge/finops/focus.py +100 -0
  445. platformforge/finops/graph_cost.py +51 -0
  446. platformforge/finops/ingest.py +144 -0
  447. platformforge/finops/insights.py +193 -0
  448. platformforge/finops/unit.py +36 -0
  449. platformforge/fleet/__init__.py +5 -0
  450. platformforge/fleet/loader.py +73 -0
  451. platformforge/fleet/models.py +155 -0
  452. platformforge/fleet/orggraph.py +172 -0
  453. platformforge/fleet/query.py +154 -0
  454. platformforge/forge/__init__.py +9 -0
  455. platformforge/forge/collect.py +88 -0
  456. platformforge/forge/delegate.py +77 -0
  457. platformforge/forge/manifest.py +106 -0
  458. platformforge/freeze/__init__.py +22 -0
  459. platformforge/freeze/exceptions.py +56 -0
  460. platformforge/freeze/manifest.py +167 -0
  461. platformforge/freeze/snapshots.py +108 -0
  462. platformforge/graph/__init__.py +39 -0
  463. platformforge/graph/backend.py +174 -0
  464. platformforge/graph/bench.py +245 -0
  465. platformforge/graph/build.py +130 -0
  466. platformforge/graph/delivery.py +183 -0
  467. platformforge/graph/diff.py +292 -0
  468. platformforge/graph/events.py +116 -0
  469. platformforge/graph/migrate.py +44 -0
  470. platformforge/graph/model.py +264 -0
  471. platformforge/graph/persist.py +97 -0
  472. platformforge/graph/query.py +290 -0
  473. platformforge/graph/temporal.py +78 -0
  474. platformforge/graph/vocab.py +63 -0
  475. platformforge/graphview.py +94 -0
  476. platformforge/iac/__init__.py +7 -0
  477. platformforge/iac/drift.py +53 -0
  478. platformforge/iac/plan.py +84 -0
  479. platformforge/iac/terraform.py +123 -0
  480. platformforge/install/__init__.py +1 -0
  481. platformforge/install/service.py +421 -0
  482. platformforge/k8s/__init__.py +8 -0
  483. platformforge/k8s/helm.py +152 -0
  484. platformforge/k8s/hubble.py +87 -0
  485. platformforge/k8s/manifests.py +513 -0
  486. platformforge/knowledge/__init__.py +1 -0
  487. platformforge/knowledge/packs.py +157 -0
  488. platformforge/knowledge/registry.py +249 -0
  489. platformforge/knowledge/select.py +127 -0
  490. platformforge/lab/__init__.py +5 -0
  491. platformforge/lab/chaos.py +71 -0
  492. platformforge/lab/fleetlab.py +351 -0
  493. platformforge/lab/opslab.py +271 -0
  494. platformforge/lab/runner.py +219 -0
  495. platformforge/live/__init__.py +36 -0
  496. platformforge/live/budget.py +92 -0
  497. platformforge/live/capability.py +80 -0
  498. platformforge/live/collectors/__init__.py +6 -0
  499. platformforge/live/collectors/aws.py +370 -0
  500. platformforge/live/collectors/aws_transport.py +98 -0
  501. platformforge/live/collectors/k8s.py +389 -0
  502. platformforge/live/collectors/k8s_transport.py +85 -0
  503. platformforge/live/cursors.py +48 -0
  504. platformforge/live/drift.py +130 -0
  505. platformforge/live/envelope.py +85 -0
  506. platformforge/live/federation.py +113 -0
  507. platformforge/live/fixtures.py +168 -0
  508. platformforge/live/identity.py +258 -0
  509. platformforge/live/incident.py +200 -0
  510. platformforge/live/models.py +435 -0
  511. platformforge/live/reconcile.py +382 -0
  512. platformforge/live/remediate.py +169 -0
  513. platformforge/live/store.py +256 -0
  514. platformforge/live/topology.py +249 -0
  515. platformforge/mcp/__init__.py +7 -0
  516. platformforge/mcp/parity.py +69 -0
  517. platformforge/mcp/registry.py +427 -0
  518. platformforge/mcp/server.py +84 -0
  519. platformforge/mcp/tools.py +732 -0
  520. platformforge/models/__init__.py +21 -0
  521. platformforge/models/base.py +214 -0
  522. platformforge/observe/__init__.py +26 -0
  523. platformforge/observe/capacity.py +39 -0
  524. platformforge/observe/dr.py +67 -0
  525. platformforge/observe/grafana.py +76 -0
  526. platformforge/observe/incident.py +58 -0
  527. platformforge/observe/otel.py +135 -0
  528. platformforge/observe/postmortem.py +72 -0
  529. platformforge/observe/prometheus.py +88 -0
  530. platformforge/observe/slo.py +125 -0
  531. platformforge/ops/__init__.py +8 -0
  532. platformforge/ops/actions.py +215 -0
  533. platformforge/ops/analytics.py +83 -0
  534. platformforge/ops/approval.py +275 -0
  535. platformforge/ops/autorem.py +101 -0
  536. platformforge/ops/config.py +175 -0
  537. platformforge/ops/engine.py +758 -0
  538. platformforge/ops/envelope.py +135 -0
  539. platformforge/ops/executors/__init__.py +1 -0
  540. platformforge/ops/executors/base.py +211 -0
  541. platformforge/ops/executors/git.py +77 -0
  542. platformforge/ops/executors/gitops.py +134 -0
  543. platformforge/ops/executors/terraform.py +84 -0
  544. platformforge/ops/gates.py +135 -0
  545. platformforge/ops/goldenpath.py +180 -0
  546. platformforge/ops/material.py +181 -0
  547. platformforge/ops/models.py +421 -0
  548. platformforge/ops/operation.py +335 -0
  549. platformforge/ops/opgraph.py +233 -0
  550. platformforge/ops/policy.py +275 -0
  551. platformforge/ops/preconditions.py +109 -0
  552. platformforge/ops/registry.py +101 -0
  553. platformforge/ops/risk.py +216 -0
  554. platformforge/ops/rollback.py +287 -0
  555. platformforge/ops/rollback_builders.py +163 -0
  556. platformforge/ops/runbook.py +269 -0
  557. platformforge/ops/simulate.py +125 -0
  558. platformforge/ops/source_of_truth.py +198 -0
  559. platformforge/ops/store.py +157 -0
  560. platformforge/ops/verify.py +190 -0
  561. platformforge/optimize/__init__.py +4 -0
  562. platformforge/optimize/engine.py +109 -0
  563. platformforge/optimize/models.py +68 -0
  564. platformforge/optimize/scan.py +196 -0
  565. platformforge/plan/__init__.py +107 -0
  566. platformforge/portable_cli.py +199 -0
  567. platformforge/portal_adapter.py +740 -0
  568. platformforge/portal_assets/chunk-7_l-b4Tr.js +1 -0
  569. platformforge/portal_assets/chunk-B0Iad6pD.js +1 -0
  570. platformforge/portal_assets/chunk-B605XnCF.js +1 -0
  571. platformforge/portal_assets/chunk-B7N3TXcn.js +1 -0
  572. platformforge/portal_assets/chunk-BAj6yGem.js +1 -0
  573. platformforge/portal_assets/chunk-BGUitn2r.js +1 -0
  574. platformforge/portal_assets/chunk-BVKnCv85.js +1 -0
  575. platformforge/portal_assets/chunk-BYyM9V_w.js +1 -0
  576. platformforge/portal_assets/chunk-BaQMhq-G.js +1 -0
  577. platformforge/portal_assets/chunk-BavOrxVm.js +1 -0
  578. platformforge/portal_assets/chunk-Bb0C24YV.js +1 -0
  579. platformforge/portal_assets/chunk-BfCFGPzs.js +1 -0
  580. platformforge/portal_assets/chunk-C2t4OUVu.js +1 -0
  581. platformforge/portal_assets/chunk-C3pI4QjU.js +1 -0
  582. platformforge/portal_assets/chunk-C4gpbtbP.js +1 -0
  583. platformforge/portal_assets/chunk-CGWeHhKY.js +1 -0
  584. platformforge/portal_assets/chunk-CJCT86Dr.js +1 -0
  585. platformforge/portal_assets/chunk-CXcsLDPJ.js +1 -0
  586. platformforge/portal_assets/chunk-CcjxoLxw.js +1 -0
  587. platformforge/portal_assets/chunk-D-gBPXHa.js +1 -0
  588. platformforge/portal_assets/chunk-D3eZogIe.js +1 -0
  589. platformforge/portal_assets/chunk-DQU0VyoQ.js +1 -0
  590. platformforge/portal_assets/chunk-DqWNkF1O.js +1 -0
  591. platformforge/portal_assets/chunk-DuPPkSIE.js +3 -0
  592. platformforge/portal_assets/chunk-DyIHOW_a.js +1 -0
  593. platformforge/portal_assets/chunk-Fx4zSGL5.js +1 -0
  594. platformforge/portal_assets/chunk-UBKt05Rg.js +1 -0
  595. platformforge/portal_assets/chunk-htnJDQEY.js +1 -0
  596. platformforge/portal_assets/chunk-kXRFMGqe.js +1 -0
  597. platformforge/portal_assets/chunk-qe3u763a.js +1 -0
  598. platformforge/portal_assets/favicon.ico +0 -0
  599. platformforge/portal_assets/index.html +13 -0
  600. platformforge/portal_assets/main-QDFVZK3T.js +5 -0
  601. platformforge/portal_assets/styles-MQEBDFDX.css +1 -0
  602. platformforge/product/__init__.py +10 -0
  603. platformforge/product/backstage.py +49 -0
  604. platformforge/product/catalog.py +74 -0
  605. platformforge/product/crossplane.py +341 -0
  606. platformforge/product/golden_paths/__init__.py +5 -0
  607. platformforge/product/golden_paths/engine.py +97 -0
  608. platformforge/product/golden_paths/library.yaml +198 -0
  609. platformforge/product/golden_paths/model.py +84 -0
  610. platformforge/product/maturity.py +107 -0
  611. platformforge/product/scorecards.py +84 -0
  612. platformforge/resources.py +36 -0
  613. platformforge/risk/__init__.py +5 -0
  614. platformforge/risk/engine.py +95 -0
  615. platformforge/risk/signals.py +49 -0
  616. platformforge/routing/__init__.py +13 -0
  617. platformforge/routing/decision.py +191 -0
  618. platformforge/routing/router.py +328 -0
  619. platformforge/rtk/__init__.py +9 -0
  620. platformforge/rtk/compact.py +127 -0
  621. platformforge/rtk/parsers.py +199 -0
  622. platformforge/rules/__init__.py +5 -0
  623. platformforge/rules/engine.py +345 -0
  624. platformforge/sandbox/__init__.py +7 -0
  625. platformforge/sandbox/env.py +151 -0
  626. platformforge/sandbox/review.py +92 -0
  627. platformforge/sdd/__init__.py +8 -0
  628. platformforge/sdd/lifecycle.py +239 -0
  629. platformforge/security/__init__.py +19 -0
  630. platformforge/security/cosign.py +67 -0
  631. platformforge/security/iam.py +112 -0
  632. platformforge/security/identity.py +84 -0
  633. platformforge/security/kyverno.py +106 -0
  634. platformforge/security/sbom.py +59 -0
  635. platformforge/security/scan.py +63 -0
  636. platformforge/security/slsa.py +86 -0
  637. platformforge/security/supply.py +62 -0
  638. platformforge/tokensave/__init__.py +20 -0
  639. platformforge/tokensave/budget.py +62 -0
  640. platformforge/tokensave/estimate.py +21 -0
  641. platformforge/tokensave/index.py +172 -0
  642. platformforge/tokensave/ledger.py +121 -0
  643. platformforge/tokensave/packs.py +285 -0
  644. platformforge/ui/__init__.py +28 -0
  645. platformforge/ui/app.py +465 -0
  646. platformforge/ui/home.py +113 -0
  647. platformforge/ui/i18n.py +143 -0
  648. platformforge/ui/kit.py +689 -0
  649. platformforge/ui/screen.py +628 -0
  650. platformforge/ui/tui.py +94 -0
  651. platformforge/ui/wizard.py +309 -0
  652. platformforge/workspace/__init__.py +13 -0
  653. platformforge/workspace/models.py +35 -0
  654. platformforge/workspace/service.py +69 -0
  655. platformforge-0.1.0.dist-info/METADATA +212 -0
  656. platformforge-0.1.0.dist-info/RECORD +659 -0
  657. platformforge-0.1.0.dist-info/WHEEL +4 -0
  658. platformforge-0.1.0.dist-info/entry_points.txt +3 -0
  659. platformforge-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,732 @@
1
+ """Tool dispatch — same core as the CLI, output bounded by capability.
2
+
3
+ Oversized results are written to the artifact store and returned as a
4
+ reference + summary instead of a wall of text (MCP output stays bounded)."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from platformforge.core.store import ArtifactStore
13
+ from platformforge.mcp.registry import CAPABILITIES
14
+
15
+
16
+ def _dispatch(handler: str, inp: dict[str, Any], repo: str) -> Any:
17
+ """Resolve handler to the same functions cmd_* use."""
18
+ if handler == "cli:inspect":
19
+ import argparse
20
+
21
+ from platformforge.cli.main import cmd_inspect
22
+ ns = argparse.Namespace(repo=inp.get("repo", repo), format="json",
23
+ strict=False)
24
+ import contextlib
25
+ import io
26
+ buf = io.StringIO()
27
+ with contextlib.redirect_stdout(buf):
28
+ cmd_inspect(ns)
29
+ return json.loads(buf.getvalue())
30
+ if handler == "cli:analyze":
31
+ return _analyze(inp.get("domain"), inp.get("path"))
32
+ if handler == "cli:judge":
33
+ from platformforge.models import Fact
34
+ from platformforge.rules import RuleEngine, load_catalog
35
+ doc = json.loads(Path(inp["facts_path"]).read_text())
36
+ facts = [Fact.from_dict(f) for f in doc.get("facts", doc)]
37
+ rules = load_catalog(Path(repo).parent / "rules" / "catalog"
38
+ if (Path(repo).parent / "rules").exists()
39
+ else _default_catalog())
40
+ findings, skipped = RuleEngine(rules).evaluate(facts)
41
+ return {"findings": [f.to_dict() for f in findings],
42
+ "skipped": skipped}
43
+ if handler == "cli:graph":
44
+ return _graph(inp, repo)
45
+ if handler == "cli:observe_slo":
46
+ from platformforge.observe import SloContract, error_budget
47
+ c = SloContract.load(inp["contract"])
48
+ return error_budget(c, **(inp.get("sli") or {}))
49
+ if handler == "cli:economy":
50
+ from platformforge.economy import EconomyEngine
51
+ return EconomyEngine(repo).report()
52
+ if handler == "cli:economy_explain":
53
+ from platformforge.economy.checkpoint import CheckpointStore
54
+ from platformforge.economy.ledger import unified_view
55
+ st = CheckpointStore(
56
+ Path(repo) / ".platformforge" / "checkpoints")
57
+ cp = st.load(inp["run_id"])
58
+ if cp is None:
59
+ return {"refusal": "PF-CHECKPOINT-MISSING",
60
+ "run_id": inp["run_id"]}
61
+ return {"run_id": inp["run_id"], "checkpoint": cp.to_dict(),
62
+ "ledger": unified_view(repo)}
63
+ if handler == "cli:context_inspect":
64
+ from platformforge.context.gateway import ContextGateway
65
+ return ContextGateway(repo).inspect(inp["ref"])
66
+ if handler == "cli:context_expand":
67
+ from platformforge.context.gateway import ContextGateway
68
+ return ContextGateway(repo).expand(inp["ref"],
69
+ inp.get("section", ""))
70
+ if handler == "cli:routing_explain":
71
+ from platformforge.routing.decision import RoutingRequest, decide, receipt
72
+ req = RoutingRequest(task=inp.get("task", ""),
73
+ profile=inp.get("profile", "balanced"),
74
+ risk=inp.get("risk", "low"),
75
+ signal=inp.get("signal") or {})
76
+ dec = decide(req)
77
+ return {"request": {"task": req.task,
78
+ "effective_profile": req.effective_profile(),
79
+ "risk": req.risk},
80
+ "receipt": receipt(req, dec, {})}
81
+ if handler == "cli:product_maturity":
82
+ from platformforge.product import maturity
83
+ return maturity(inp["signals"])
84
+ if handler == "cli:agents_list":
85
+ from platformforge.agents import AGENTS
86
+ return {"agents": [a.to_dict() for a in AGENTS.values()]}
87
+ if handler == "cli:collect":
88
+ from platformforge.collect import collect
89
+ return collect(inp["path"])
90
+ if handler == "cli:diagnose":
91
+ from platformforge import graph as G
92
+ from platformforge.diagnose import diagnose
93
+ g = G.load(repo)
94
+ findings = json.loads(Path(inp["findings"]).read_text()) \
95
+ if inp.get("findings") else []
96
+ facts = json.loads(Path(inp["facts"]).read_text()) \
97
+ if inp.get("facts") else []
98
+ return diagnose(g, inp["node"],
99
+ findings.get("findings", findings),
100
+ facts.get("facts", facts))
101
+ if handler == "cli:plan":
102
+ from platformforge.plan import remediation_plan
103
+ doc = json.loads(Path(inp["findings_path"]).read_text())
104
+ findings = doc.get("findings", doc if isinstance(doc, list) else [])
105
+ facts = doc.get("facts", [])
106
+ try:
107
+ from platformforge import graph as G
108
+ g = G.load(repo)
109
+ except FileNotFoundError:
110
+ g = None
111
+ return remediation_plan(findings, g, facts)
112
+ if handler == "cli:risk":
113
+ from platformforge.risk.engine import assess_change
114
+ signals = dict(inp.get("signals") or {})
115
+ if inp.get("node"):
116
+ from platformforge import graph as G
117
+ from platformforge.graph.query import blast_radius
118
+ from platformforge.risk.signals import signals_from_graph
119
+ g = G.load(repo)
120
+ blast = blast_radius(g, inp["node"])
121
+ signals = {**signals_from_graph(g, [inp["node"]], blast),
122
+ **signals}
123
+ return assess_change(signals)
124
+ if handler == "cli:explain":
125
+ doc = json.loads(Path(inp["findings_path"]).read_text())
126
+ findings = doc.get("findings", doc if isinstance(doc, list) else [])
127
+ f = next((x for x in findings
128
+ if x.get("finding_id") == inp["name"]
129
+ or x.get("rule_id") == inp["name"]), None)
130
+ if not f:
131
+ return {"refusal": "platform.finding.unresolved",
132
+ "name": inp["name"]}
133
+ facts = json.loads(Path(inp["facts_path"]).read_text()) \
134
+ if inp.get("facts_path") else doc.get("facts", [])
135
+ by_id = {x.get("fact_id"): x
136
+ for x in (facts.get("facts", facts)
137
+ if isinstance(facts, dict) else facts)}
138
+ return {"finding": f,
139
+ "evidence_chain": [{"fact_id": e,
140
+ "fact": by_id.get(e, "unresolved")}
141
+ for e in f.get("evidence", [])]}
142
+ if handler == "cli:recommend":
143
+ import argparse
144
+ import contextlib
145
+ import io
146
+
147
+ from platformforge.cli.main import cmd_recommend
148
+ ns = argparse.Namespace(path=inp["findings_path"], format="json",
149
+ strict=False, repo=repo, output=None,
150
+ detail_level="normal", offline=False)
151
+ buf = io.StringIO()
152
+ with contextlib.redirect_stdout(buf):
153
+ cmd_recommend(ns)
154
+ return json.loads(buf.getvalue())
155
+ if handler == "cli:security":
156
+ import argparse
157
+ import contextlib
158
+ import io
159
+
160
+ from platformforge.cli.main import cmd_security
161
+ ns = argparse.Namespace(path=inp["path"], repo=repo, format="json",
162
+ strict=False, output=None,
163
+ detail_level="normal", offline=False)
164
+ buf = io.StringIO()
165
+ with contextlib.redirect_stdout(buf):
166
+ cmd_security(ns)
167
+ return json.loads(buf.getvalue())
168
+ if handler == "cli:reliability":
169
+ import argparse
170
+ import contextlib
171
+ import io
172
+
173
+ from platformforge.cli.main import cmd_reliability
174
+ ns = argparse.Namespace(path=inp["facts_path"], repo=repo,
175
+ format="json", strict=False, output=None,
176
+ detail_level="normal", offline=False)
177
+ buf = io.StringIO()
178
+ with contextlib.redirect_stdout(buf):
179
+ cmd_reliability(ns)
180
+ return json.loads(buf.getvalue())
181
+ if handler == "cli:drift":
182
+ from platformforge.iac import analyze_hcl, analyze_state, drift
183
+ return drift(analyze_hcl(inp["config"])["facts"],
184
+ analyze_state(inp["state"])["facts"])
185
+ if handler == "cli:change_verify":
186
+ from platformforge.sandbox import sandbox_analyze
187
+ return sandbox_analyze(inp["root"], patch=inp.get("patch"),
188
+ files=inp.get("files"))
189
+ if handler == "cli:change_review":
190
+ from platformforge.sandbox import review_change
191
+ return review_change(inp["root"], patch=inp.get("patch"),
192
+ files=inp.get("files"),
193
+ signals=inp.get("signals"))
194
+ if handler == "cli:finops":
195
+ from platformforge import finops as F
196
+ sub = inp.get("op", "costs")
197
+ if sub == "costs":
198
+ doc = F.cost_facts(inp["path"])
199
+ return {**doc, "summary": F.cost_summary(doc["facts"])}
200
+ if sub == "allocate":
201
+ return F.allocate(F.cost_facts(inp["path"])["facts"],
202
+ by=inp.get("by", "cost_center"))
203
+ if sub == "focus":
204
+ rows = json.loads(Path(inp["path"]).read_text())
205
+ return F.to_focus(rows if isinstance(rows, list)
206
+ else rows.get("rows", []))
207
+ if sub == "focus-validate":
208
+ rows = json.loads(Path(inp["path"]).read_text())
209
+ return F.validate_focus(rows if isinstance(rows, list)
210
+ else rows.get("rows", []))
211
+ if sub == "unit":
212
+ doc = F.cost_facts(inp["path"])
213
+ return F.unit_economics(doc["facts"],
214
+ inp.get("denominators") or {})
215
+ if sub == "ingest":
216
+ return F.ingest_billing(inp["path"])
217
+ if sub == "report":
218
+ doc = F.cost_facts(inp["path"])
219
+ extra = inp.get("denominators") or {}
220
+ return F.finops_report(
221
+ doc["facts"], utilization=extra.get("utilization"),
222
+ requested_vs_used=extra.get("requested_vs_used"),
223
+ committed=extra.get("committed"),
224
+ split=extra.get("split"))
225
+ raise ValueError(f"unknown finops op {sub}")
226
+ if handler == "cli:observe":
227
+ return _observe(inp)
228
+ if handler == "cli:evals":
229
+ from platformforge.evals import run_all
230
+ return run_all(inp["cases"] if inp.get("cases") else
231
+ __import__("platformforge.evals.runner",
232
+ fromlist=["CASES_DIR"]).CASES_DIR,
233
+ type_filter=inp.get("type") or None)
234
+ if handler == "cli:lab_chaos":
235
+ from platformforge.lab.chaos import run_scenario
236
+ return run_scenario(inp["scenario"],
237
+ allow_prod=bool(inp.get("allow_prod")))
238
+ if handler == "cli:correlate":
239
+ from platformforge import observe as O
240
+ return O.correlate_spans(inp["path"])
241
+ if handler == "cli:policy":
242
+ from platformforge.rules import load_catalog
243
+ rules = load_catalog(_default_catalog())
244
+ if inp["op"] == "list":
245
+ return {"rules": [{"rule_id": r.rule_id, "domain": r.domain,
246
+ "severity": r.severity} for r in rules]}
247
+ from platformforge.models import Fact
248
+ from platformforge.rules import RuleEngine
249
+ doc = json.loads(Path(inp["facts_path"]).read_text())
250
+ facts = [Fact.from_dict(f)
251
+ for f in doc.get("facts", doc if isinstance(doc, list)
252
+ else [])]
253
+ findings, skipped = RuleEngine(rules).evaluate(facts)
254
+ return {"findings": [f.to_dict() for f in findings],
255
+ "skipped": skipped,
256
+ "violated": [f.rule_id for f in findings
257
+ if f.status == "violated"]}
258
+ if handler == "cli:integrate":
259
+ from platformforge.mcp.parity import detach, integrate
260
+ return detach(inp["host"], repo) if inp.get("detach") \
261
+ else integrate(inp["host"], repo)
262
+ if handler == "cli:hosts":
263
+ from platformforge.distribution import hostflow
264
+ return hostflow.hosts_flow(
265
+ inp.get("host") or "all", repo=Path(repo), launch=False)
266
+ if handler == "cli:capability":
267
+ op = inp.get("op", "manifest")
268
+ if op == "list":
269
+ return {"capabilities": [c.contract()
270
+ for c in CAPABILITIES.values()]}
271
+ if op == "check":
272
+ dom = inp.get("domain", "")
273
+ matches = [c for c in CAPABILITIES.values()
274
+ if c.domain == dom or dom in c.description.lower()
275
+ or dom in c.name]
276
+ if not matches:
277
+ return {"supported": False,
278
+ "refusal": "platform.capability.unresolved",
279
+ "domain": dom}
280
+ return {"supported": True, "domain": dom,
281
+ "version": inp.get("version"),
282
+ "capabilities": [c.contract() for c in matches]}
283
+ if op == "describe":
284
+ c = CAPABILITIES.get(inp.get("name", ""))
285
+ return c.contract() if c else {
286
+ "refusal": "platform.capability.unresolved",
287
+ "name": inp.get("name")}
288
+ from platformforge.forge import capability_manifest
289
+ return capability_manifest()
290
+ if handler == "cli:live":
291
+ return _live(inp, repo)
292
+ if handler == "cli:ops":
293
+ return _ops_tool(inp, repo)
294
+ if handler in ("cli:fleet", "cli:analytics", "cli:optimize",
295
+ "cli:ai", "cli:federation"):
296
+ return _cycle5(handler, inp)
297
+ raise ValueError(f"no handler {handler}")
298
+
299
+
300
+ def _ops_tool(inp: dict[str, Any], repo: str) -> Any:
301
+ """Cycle 4 ops surface for MCP — *prepare* side of the governed
302
+ pipeline plus read views over the operation store.
303
+
304
+ The delegation contract applies: this tool can plan, simulate,
305
+ assess risk, evaluate gates/policies and mint envelopes — it can
306
+ NEVER execute (`--execute` is not reachable from this surface)."""
307
+ import argparse # noqa: F401
308
+ import contextlib
309
+ import io
310
+
311
+ from platformforge.cli.main import build_parser, cmd_ops
312
+ op = inp.get("op", "capabilities")
313
+ argv: list[str] = ["ops", op, "--repo", repo]
314
+ if op in ("run", "prepare"):
315
+ if not inp.get("spec"):
316
+ return {"refusal": "PF-OPS-PREPARE-NEED-SPEC",
317
+ "unlock": "pass spec <ops.yaml>"}
318
+ argv = ["ops", "run", "--spec", inp["spec"], "--repo", repo]
319
+ # never --execute: MCP delegates preparation, not mutation
320
+ elif op in ("status", "history", "rollback", "store-verify",
321
+ "graph"):
322
+ if inp.get("operation_id"):
323
+ argv += ["--operation-id", inp["operation_id"]]
324
+ if op == "rollback":
325
+ return {"refusal": "PF-OPS-MCP-NO-EXECUTE",
326
+ "unlock": "rollback executes via CLI with "
327
+ "--execute + hash-bound approval"}
328
+ elif op == "autorem-eval":
329
+ if inp.get("plan"):
330
+ argv += ["--plan", inp["plan"]]
331
+ if inp.get("environment"):
332
+ argv += ["--environment", inp["environment"]]
333
+ if inp.get("evidence_tier"):
334
+ argv += ["--evidence-tier", inp["evidence_tier"]]
335
+ elif op in ("intent", "plan", "simulate", "risk", "policy-eval",
336
+ "runbook", "approve", "delegate", "config",
337
+ "capabilities", "analytics", "store-list"):
338
+ for flag, key in (("--intent", "intent"), ("--plan", "plan"),
339
+ ("--spec", "spec"), ("--policies", "policies"),
340
+ ("--environment", "environment"),
341
+ ("--level", "level"), ("--request", "request"),
342
+ ("--bind", "bind")):
343
+ if inp.get(key):
344
+ argv += [flag, str(inp[key])]
345
+ if op == "approve":
346
+ return {"refusal": "PF-OPS-MCP-NO-APPROVAL",
347
+ "unlock": "approvals are minted host-side by a "
348
+ "human actor; MCP cannot sign"}
349
+ else:
350
+ return {"refusal": "PF-OPS-MCP-UNKNOWN-OP",
351
+ "unlock": "ops: capabilities|prepare|status|history|"
352
+ "graph|analytics|autorem-eval|store-list|"
353
+ "store-verify"}
354
+ ns = build_parser().parse_args(argv)
355
+ buf = io.StringIO()
356
+ with contextlib.redirect_stdout(buf):
357
+ rc = cmd_ops(ns)
358
+ try:
359
+ out = json.loads(buf.getvalue())
360
+ except json.JSONDecodeError:
361
+ out = {"raw": buf.getvalue()[-2000:]}
362
+ out.setdefault("exit_code", rc)
363
+ return out
364
+
365
+
366
+ def _live(inp: dict[str, Any], repo: str) -> Any:
367
+ """Live surface ops — same functions cmd_live calls. All ops are
368
+ read-only; snapshot uses host transports (kubectl/aws CLI)."""
369
+ op = inp.get("op", "status")
370
+ from platformforge.live.store import ObservationStore
371
+ if op == "status":
372
+ return ObservationStore(repo).status()
373
+ if op == "capability":
374
+ from platformforge.live.capability import availability
375
+ return availability(repo)
376
+ if op == "drift":
377
+ from platformforge.live.drift import diff_observations
378
+ store = ObservationStore(repo)
379
+ def _env(ref: str):
380
+ p = Path(ref)
381
+ if p.exists():
382
+ from platformforge.live.envelope import loads
383
+ return loads(p.read_text())
384
+ return store.get(ref)
385
+ b, a = inp.get("before"), inp.get("after")
386
+ if not (b and a):
387
+ rows = store.list(limit=2)
388
+ if len(rows) < 2:
389
+ return {"refusal": "PF-LIVE-NEED-2-OBS",
390
+ "unlock": "collect ≥2 observations or pass "
391
+ "before/after"}
392
+ a, b = rows[0]["observation_id"], rows[1]["observation_id"]
393
+ return diff_observations(_env(b), _env(a))
394
+ if op == "plan":
395
+ from platformforge.live.remediate import remediate
396
+ if inp.get("drift_events"):
397
+ events = json.loads(Path(inp["drift_events"]).read_text())
398
+ else:
399
+ from platformforge.live.drift import diff_observations
400
+ store = ObservationStore(repo)
401
+ rows = store.list(limit=2)
402
+ if len(rows) < 2:
403
+ return {"refusal": "PF-LIVE-NO-DRIFT-INPUT",
404
+ "unlock": "pass drift_events or collect "
405
+ "≥2 observations"}
406
+ events = diff_observations(
407
+ store.get(rows[1]["observation_id"]),
408
+ store.get(rows[0]["observation_id"]))["drift"]
409
+ return remediate(events)
410
+ if op == "incident":
411
+ if not inp.get("incident"):
412
+ return {"refusal": "PF-LIVE-NO-INCIDENT",
413
+ "unlock": "pass incident <incident.json>"}
414
+ from platformforge.live.incident import build_timeline, postmortem_v3, score_candidates
415
+ inc = json.loads(Path(inp["incident"]).read_text())
416
+ cands = json.loads(Path(inp["changes"]).read_text()) \
417
+ if inp.get("changes") else []
418
+ events = json.loads(Path(inp["events"]).read_text()) \
419
+ if inp.get("events") else []
420
+ try:
421
+ from platformforge import graph as G
422
+ g = G.load(repo)
423
+ except FileNotFoundError:
424
+ g = None
425
+ tl = build_timeline(events + [inc])
426
+ ranked = score_candidates(inc, cands,
427
+ window_s=float(inp.get("window", 3600)),
428
+ graph=g)
429
+ return {**ranked,
430
+ "postmortem": postmortem_v3(inc, tl, ranked)["postmortem"]}
431
+ if op == "clusters":
432
+ from platformforge.live.federation import ClusterRegistry
433
+ return {"clusters": ClusterRegistry(repo).list()}
434
+ if op == "topology":
435
+ from platformforge.live.topology import (
436
+ classify_behavior,
437
+ edges_from_endpointslices,
438
+ edges_from_hubble,
439
+ edges_from_otel,
440
+ )
441
+ edges: list[dict] = []
442
+ for key, fn in (("otel", edges_from_otel),
443
+ ("hubble", edges_from_hubble),
444
+ ("slices", edges_from_endpointslices)):
445
+ if inp.get(key):
446
+ doc = json.loads(Path(inp[key]).read_text())
447
+ edges += fn(doc if isinstance(doc, list) else [doc])
448
+ declared = set()
449
+ try:
450
+ from platformforge import graph as G
451
+ declared = set(G.load(repo).edges)
452
+ except FileNotFoundError:
453
+ pass
454
+ return {"edges": classify_behavior(edges, declared)}
455
+ if op == "snapshot":
456
+ return _live_snapshot(inp, repo)
457
+ raise ValueError(f"unknown live op {op}")
458
+
459
+
460
+ def _live_snapshot(inp: dict[str, Any], repo: str) -> Any:
461
+ provider = inp.get("provider", "kubernetes")
462
+ from platformforge.live.budget import ObservationBudget
463
+ from platformforge.live.models import ObservationScope
464
+ from platformforge.live.store import ObservationStore
465
+ budget = ObservationBudget()
466
+ scope = ObservationScope(
467
+ namespaces=[inp["namespace"]] if inp.get("namespace") else [])
468
+ if provider == "kubernetes":
469
+ from platformforge.live.collectors import k8s_transport
470
+ from platformforge.live.collectors.k8s import K8sCollector
471
+ if not k8s_transport.kubectl_available():
472
+ return {"refusal": "PF-LIVE-NO-KUBECTL",
473
+ "unlock": "install kubectl + kubeconfig"}
474
+ env = K8sCollector(
475
+ transport=k8s_transport.make_transport(
476
+ inp.get("context", "")),
477
+ context=inp.get("context", "")).collect(scope, budget)
478
+ elif provider == "aws":
479
+ from platformforge.live.collectors import aws_transport
480
+ from platformforge.live.collectors.aws import AwsCollector
481
+ if not aws_transport.aws_available():
482
+ return {"refusal": "PF-LIVE-NO-AWSCLI",
483
+ "unlock": "install aws CLI + credentials"}
484
+ env = AwsCollector(transport=aws_transport.make_transport()
485
+ ).collect(scope, budget)
486
+ else:
487
+ return {"refusal": "PF-LIVE-UNKNOWN-PROVIDER",
488
+ "unlock": "provider kubernetes|aws"}
489
+ res = ObservationStore(repo).put(env)
490
+ return {**res, "provider": env.provider,
491
+ "coverage": env.coverage.get("status")}
492
+
493
+
494
+ def _observe(inp: dict[str, Any]) -> Any:
495
+ """Observe sub-verbs — same functions cmd_observe calls."""
496
+ from platformforge import observe as O
497
+ sub = inp.get("op", "slo-burn")
498
+ if sub == "slo":
499
+ c = O.SloContract.load(inp["contract"])
500
+ return O.error_budget(c, **(inp.get("sli") or {}))
501
+ if sub == "slo-burn":
502
+ c = O.SloContract.load(inp["contract"])
503
+ return O.multi_window_burn(c, inp.get("windows") or {})
504
+ if sub == "otel":
505
+ return O.correlate_spans(inp["path"])
506
+ if sub == "semconv":
507
+ return O.analyze_semconv(inp["path"])
508
+ if sub == "timeline":
509
+ return O.timeline(json.loads(Path(inp["path"]).read_text()))
510
+ if sub == "postmortem":
511
+ inc = json.loads(Path(inp["incident"]).read_text())
512
+ return O.postmortem(inc, inc.get("hypotheses"))
513
+ if sub == "dr":
514
+ doc = json.loads(Path(inp["facts"]).read_text())
515
+ return O.dr_model(doc.get("facts", doc))
516
+ if sub == "prometheus":
517
+ return O.analyze_prometheus(inp["path"])
518
+ if sub == "grafana":
519
+ return O.analyze_grafana(inp["path"])
520
+ if sub == "incident":
521
+ alerts = json.loads(Path(inp["alerts"]).read_text())
522
+ changes = json.loads(Path(inp["changes"]).read_text())
523
+ return O.correlate(alerts, changes,
524
+ window_s=int(inp.get("window", 3600)))
525
+ if sub == "capacity":
526
+ items = json.loads(Path(inp["path"]).read_text())
527
+ return O.capacity(items)
528
+ raise ValueError(f"unknown observe op {sub}")
529
+
530
+
531
+ def _default_catalog() -> Path:
532
+ from platformforge.resources import data_path
533
+ return data_path("rules", "catalog")
534
+
535
+
536
+ def _analyze(domain: str, path: str) -> Any:
537
+ if domain in ("iac",):
538
+ from platformforge.iac import analyze_hcl
539
+ return analyze_hcl(path)
540
+ if domain == "plan":
541
+ from platformforge.iac import analyze_plan
542
+ return analyze_plan(path)
543
+ if domain == "state":
544
+ from platformforge.iac import analyze_state
545
+ return analyze_state(path)
546
+ if domain == "k8s":
547
+ from platformforge.k8s import analyze_k8s
548
+ return analyze_k8s(path)
549
+ if domain == "gitops":
550
+ from platformforge.cicd import analyze_gitops
551
+ return analyze_gitops(path)
552
+ if domain == "gha":
553
+ from platformforge.cicd import analyze_gha
554
+ return analyze_gha(path)
555
+ if domain == "iam":
556
+ from platformforge.security import analyze_iam_policy
557
+ return analyze_iam_policy(path)
558
+ if domain == "sbom":
559
+ from platformforge.security import analyze_sbom
560
+ return analyze_sbom(path)
561
+ if domain == "secrets":
562
+ from platformforge.security import scan_secrets
563
+ return scan_secrets(path)
564
+ if domain == "supply":
565
+ from platformforge.security import analyze_supply
566
+ return analyze_supply(path)
567
+ if domain == "catalog":
568
+ from platformforge.product import analyze_catalog
569
+ return analyze_catalog(path)
570
+ if domain == "crossplane":
571
+ from platformforge.product import analyze_crossplane
572
+ return analyze_crossplane(path)
573
+ if domain == "helm":
574
+ from platformforge.k8s.helm import analyze_helm
575
+ return analyze_helm(path)
576
+ if domain == "kustomize":
577
+ from platformforge.k8s.helm import analyze_kustomize
578
+ return analyze_kustomize(path)
579
+ if domain == "hubble":
580
+ from platformforge.k8s.hubble import analyze_hubble
581
+ return analyze_hubble(path)
582
+ if domain == "kyverno":
583
+ from platformforge.security.kyverno import analyze_kyverno
584
+ return analyze_kyverno(path)
585
+ if domain == "cosign":
586
+ from platformforge.security.cosign import analyze_cosign
587
+ return analyze_cosign(path)
588
+ if domain == "slsa":
589
+ from platformforge.security.slsa import slsa_assess
590
+ doc = json.loads(Path(path).read_text())
591
+ return slsa_assess(doc.get("provenance", doc),
592
+ evidence=doc.get("evidence")
593
+ if isinstance(doc, dict) else None)
594
+ if domain == "cloud-aws":
595
+ from platformforge.cloud import analyze_aws_dump
596
+ return analyze_aws_dump(path)
597
+ if domain == "cloud-azure":
598
+ from platformforge.cloud import analyze_azure_dump
599
+ return analyze_azure_dump(path)
600
+ if domain == "cloud-gcp":
601
+ from platformforge.cloud import analyze_gcp_dump
602
+ return analyze_gcp_dump(path)
603
+ if domain == "ownership":
604
+ from platformforge.core.ownership import analyze_ownership
605
+ return analyze_ownership(path)
606
+ if domain == "contradictions":
607
+ from platformforge.core.ownership import detect_contradictions
608
+ doc = json.loads(Path(path).read_text())
609
+ facts = doc.get("facts", doc if isinstance(doc, list) else [])
610
+ out = detect_contradictions(facts)
611
+ return {"facts": out, "contradictions": len(out)}
612
+ raise ValueError(f"unknown domain {domain}")
613
+
614
+
615
+ def _graph(inp: dict[str, Any], repo: str) -> Any:
616
+ from platformforge import graph as G
617
+ g = G.load(repo)
618
+ q = inp["query"]
619
+ if q == "deps":
620
+ return G.dependencies(g, inp["node"])
621
+ if q == "dependents":
622
+ return G.dependents(g, inp["node"])
623
+ if q == "blast":
624
+ return G.blast_radius(g, inp["node"])
625
+ if q == "paths":
626
+ return G.paths(g, inp["src"], inp["dst"])
627
+ if q == "gaps":
628
+ return G.gaps(g)
629
+ if q == "cycles":
630
+ return {"cycles": G.cycles(g)}
631
+ if q == "stats":
632
+ return g.stats()
633
+ if q.startswith("identity"):
634
+ from platformforge.security.identity import (
635
+ compromise_blast,
636
+ who_can_access,
637
+ who_can_become,
638
+ workloads_using_identity,
639
+ )
640
+ target = inp.get("node") or inp.get("dst") or inp.get("src")
641
+ fn = {"identity-become": who_can_become,
642
+ "identity-access": who_can_access,
643
+ "identity-workloads": workloads_using_identity,
644
+ "identity-blast": compromise_blast}.get(q)
645
+ if not fn:
646
+ raise ValueError(f"unknown graph query {q}")
647
+ return fn(g, target)
648
+ raise ValueError(f"unknown graph query {q}")
649
+
650
+
651
+ def _cycle5(handler: str, inp: dict[str, Any]) -> Any:
652
+ """Cycle 5 read-only surfaces — same cmd_* functions the CLI calls,
653
+ driven by a Namespace built from the tool input. `optimize plan` is
654
+ allowed but `out` is forced off — MCP never writes files."""
655
+ import argparse
656
+
657
+ from platformforge.cli import fleetcmd
658
+ op = inp.get("op", "")
659
+ common = {"path": inp.get("path", ""), "id": inp.get("id", ""),
660
+ "question": inp.get("question", ""),
661
+ "window": inp.get("window", ""),
662
+ "limit": inp.get("limit") or 0, "out": "",
663
+ "format": "json"}
664
+ if handler == "cli:fleet":
665
+ ns = argparse.Namespace(fleet_cmd=op, **common)
666
+ return fleetcmd.cmd_fleet(ns)
667
+ if handler == "cli:analytics":
668
+ ns = argparse.Namespace(analytics_cmd=op, **common)
669
+ return fleetcmd.cmd_analytics(ns)
670
+ if handler == "cli:optimize":
671
+ ns = argparse.Namespace(optimize_cmd=op, **common)
672
+ return fleetcmd.cmd_optimize(ns)
673
+ if handler == "cli:ai":
674
+ ns = argparse.Namespace(
675
+ ai_cmd=op, cost=inp.get("cost"),
676
+ denominators=inp.get("denominators", ""), **common)
677
+ return fleetcmd.cmd_ai(ns)
678
+ ns = argparse.Namespace(
679
+ federation_cmd=op, nodes=list(inp.get("nodes") or []),
680
+ classification=inp.get("classification", ""),
681
+ node_id=inp.get("node_id", ""),
682
+ capabilities=inp.get("capabilities", ""),
683
+ freshness=inp.get("freshness", ""),
684
+ manifest=inp.get("manifest", ""), **common)
685
+ return fleetcmd.cmd_federation(ns)
686
+
687
+
688
+ def _bound(result: Any, cap, repo: str) -> dict[str, Any]:
689
+ """Enforce output bounds; oversize → artifact ref."""
690
+ text = json.dumps(result, default=str)
691
+ if len(text.encode()) <= cap.max_bytes:
692
+ return {"result": result, "bounded": False}
693
+ # Secrets are redacted BEFORE persistence — the artifact store is a
694
+ # file on disk, not a trusted vault, and rtk expand reads it back.
695
+ from platformforge.core.redaction import redact_obj, redact_text
696
+ store = ArtifactStore(repo)
697
+ sha = store.put(redact_text(text).encode(), meta={"kind": "mcp-result"})
698
+ ref = f"artifact://sha256/{sha}"
699
+ summary = result
700
+ if isinstance(result, dict):
701
+ summary = {k: v for k, v in result.items()
702
+ if not isinstance(v, (list, dict)) or len(
703
+ json.dumps(v, default=str)) < 2000}
704
+ summary = redact_obj(summary)
705
+ return {"bounded": True,
706
+ "artifact_ref": ref,
707
+ "summary": summary,
708
+ "note": f"result exceeded {cap.max_bytes}B — full output "
709
+ f"in artifact store"}
710
+
711
+
712
+ def call_tool(name: str, arguments: dict[str, Any] | None = None,
713
+ repo: str = ".") -> dict[str, Any]:
714
+ cap = CAPABILITIES.get(name)
715
+ if not cap:
716
+ return {"error": "platform.tool.unresolved",
717
+ "unlock": "tools/list — unknown tool", "tool": name}
718
+ inp = dict(arguments or {})
719
+ detail = inp.pop("detail_level", "normal")
720
+ if detail not in cap.detail_levels:
721
+ detail = "normal"
722
+ try:
723
+ result = _dispatch(cap.handler, inp, repo)
724
+ except FileNotFoundError as e:
725
+ return {"refusal": "platform.evidence.unresolved",
726
+ "detail": str(e), "tool": name}
727
+ except Exception as e: # noqa: BLE001 — bounded error, never traceback over MCP
728
+ return {"error": type(e).__name__, "detail": str(e)[:500],
729
+ "tool": name}
730
+ out = _bound(result, cap, repo)
731
+ out["detail_level"] = detail
732
+ return out