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,13 @@
1
+ """Adaptive routing — computed dispatch, never "call all agents"."""
2
+
3
+ from platformforge.routing.router import (
4
+ DOMAIN_SPECIALIST,
5
+ TASK_COORDINATOR,
6
+ TaskSignal,
7
+ load_routes,
8
+ route,
9
+ validate_routing,
10
+ )
11
+
12
+ __all__ = ["DOMAIN_SPECIALIST", "TASK_COORDINATOR", "TaskSignal",
13
+ "load_routes", "route", "validate_routing"]
@@ -0,0 +1,191 @@
1
+ """Routing control plane contracts (§138–160): RoutingRequest,
2
+ RoutingDecision, profiles, RoutingScorecard, receipts, decision plane.
3
+
4
+ Profiles (§142–146): economy | balanced | deep | strict | offline.
5
+ Risk can raise the minimum profile (§147), never lower it. A profile
6
+ change is policy — recorded in the routing receipt (§154) with policy
7
+ version; champion/challenger promotion is human-gated (§153).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import json
14
+ import time
15
+ from dataclasses import asdict, dataclass, field
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ SCHEMA_REQ = "platformforge/routing-request/v1"
20
+ SCHEMA_DEC = "platformforge/routing-decision/v1"
21
+ SCHEMA_SCORE = "platformforge/routing-scorecard/v1"
22
+ ROUTING_POLICY_VERSION = "platformforge/routing/v2"
23
+
24
+ PROFILES = ("economy", "balanced", "deep", "strict", "offline")
25
+ PROFILE_ORDER = {p: i for i, p in enumerate(
26
+ ("offline", "economy", "balanced", "deep", "strict"))}
27
+
28
+ # profile → envelope hints (§143–146). Offline forbids provider calls.
29
+ PROFILE_DEFAULTS: dict[str, dict[str, Any]] = {
30
+ "economy": {"prefer": ["deterministic", "cache", "single-specialist"],
31
+ "context_bytes_soft": 6000, "max_agents": 1,
32
+ "provider_calls_hard": 0, "verification_floor": "V0-static"},
33
+ "balanced": {"prefer": ["deterministic", "single-specialist"],
34
+ "context_bytes_soft": 16000, "max_agents": 3,
35
+ "provider_calls_hard": 20,
36
+ "verification_floor": "V1-contract"},
37
+ "deep": {"prefer": ["multi-specialist", "coordinated"],
38
+ "context_bytes_soft": 30000, "max_agents": 6,
39
+ "provider_calls_hard": 50,
40
+ "verification_floor": "V3-integration"},
41
+ "strict": {"prefer": ["critical-review"],
42
+ "context_bytes_soft": 40000, "max_agents": 8,
43
+ "provider_calls_hard": 100,
44
+ "verification_floor": "V4-runtime"},
45
+ "offline": {"prefer": ["deterministic"],
46
+ "context_bytes_soft": 16000, "max_agents": 1,
47
+ "provider_calls_hard": 0,
48
+ "verification_floor": "V1-contract"},
49
+ }
50
+
51
+ # §147 — risk floors: the cheapest profile a risk may run under.
52
+ RISK_PROFILE_FLOOR = {"low": "economy", "medium": "balanced",
53
+ "high": "deep", "critical": "strict"}
54
+
55
+
56
+ @dataclass
57
+ class RoutingRequest:
58
+ """§139 — what a task asks the routing control plane."""
59
+ schema: str = SCHEMA_REQ
60
+ task: str = ""
61
+ profile: str = "balanced"
62
+ risk: str = "low"
63
+ signal: dict[str, Any] = field(default_factory=dict)
64
+
65
+ def effective_profile(self) -> str:
66
+ """§147 — risk raises the floor; it never lowers it."""
67
+ floor = RISK_PROFILE_FLOOR.get(self.risk, "economy")
68
+ if PROFILE_ORDER[self.profile] < PROFILE_ORDER[floor]:
69
+ return floor
70
+ return self.profile
71
+
72
+
73
+ @dataclass
74
+ class RoutingDecision:
75
+ """§140–141 — the canonical routed decision."""
76
+ schema: str = SCHEMA_DEC
77
+ mode: str = "deterministic"
78
+ agents: list[str] = field(default_factory=list)
79
+ model_tier: str = "none" # none|cheap|strong (host maps)
80
+ context_budget: int | None = None
81
+ tool_budget: int | None = None
82
+ provider_budget: int | None = None
83
+ verification_tier: str = "V0-static"
84
+ reason: str = ""
85
+ risk: str = "low"
86
+ profile: str = "balanced"
87
+ fallback: str = ""
88
+ policy_version: str = ""
89
+
90
+ def to_dict(self) -> dict[str, Any]:
91
+ return asdict(self)
92
+
93
+
94
+ @dataclass
95
+ class RoutingScorecard:
96
+ """§148–149 — measured scorecard per routed run."""
97
+ schema: str = SCHEMA_SCORE
98
+ run_id: str = ""
99
+ mode: str = ""
100
+ correctness: float | None = None
101
+ evidence: float | None = None
102
+ tokens: int | None = None
103
+ tools: int | None = None
104
+ latency_ms: float | None = None
105
+ agents: int = 0
106
+ provider_calls: int = 0
107
+
108
+ def to_dict(self) -> dict[str, Any]:
109
+ return asdict(self)
110
+
111
+
112
+ def decide(request: RoutingRequest, *,
113
+ routes_path: str | Path | None = None) -> RoutingDecision:
114
+ """§139–141 — the ONLY producer of the canonical RoutingDecision.
115
+
116
+ Wraps the deterministic router (`routing/router.py::route`) and
117
+ applies the effective profile (risk raises the floor, never lowers).
118
+ EconomyEngine output is *advice* that may inform `request.signal` —
119
+ it is never a decision and never reaches orchestration. Hosts embed
120
+ `decision.to_dict()`; the orchestrator enforces the schema.
121
+ """
122
+ from platformforge.routing.router import TaskSignal, route
123
+ sig = TaskSignal.from_dict(request.signal or {})
124
+ routed = route(sig, routes_path)
125
+ profile = request.effective_profile()
126
+ defaults = PROFILE_DEFAULTS.get(profile, PROFILE_DEFAULTS["balanced"])
127
+ return RoutingDecision(
128
+ mode=routed.get("mode", "deterministic"),
129
+ agents=list(routed.get("agents")
130
+ or routed.get("specialists") or []),
131
+ model_tier=routed.get("model_tier", "none"),
132
+ context_budget=defaults.get("context_bytes_soft"),
133
+ provider_budget=defaults.get("provider_calls_hard"),
134
+ verification_tier=defaults.get("verification_floor", "V0-static"),
135
+ reason="; ".join(routed.get("reasons", [])),
136
+ risk=request.risk, profile=profile,
137
+ fallback="; ".join(routed.get("fallbacks", [])),
138
+ policy_version=ROUTING_POLICY_VERSION)
139
+
140
+
141
+ def receipt(request: RoutingRequest, decision: RoutingDecision,
142
+ estimated_budget: dict[str, Any]) -> dict[str, Any]:
143
+ """§154 — every routing decision records inputs + policy version +
144
+ decision + reason + estimated budget."""
145
+ body = {"request": request.signal, "profile": request.profile,
146
+ "decision": decision.to_dict()}
147
+ return {"schema": "platformforge/routing-receipt/v1",
148
+ "inputs_hash": hashlib.sha256(json.dumps(
149
+ body, sort_keys=True, default=str).encode()).hexdigest(),
150
+ "policy_version": decision.policy_version,
151
+ "decision": decision.to_dict(),
152
+ "reason": decision.reason,
153
+ "estimated_budget": estimated_budget,
154
+ "ts": time.time()}
155
+
156
+
157
+ def promote_verdict(champion: dict[str, Any], challenger: dict[str, Any],
158
+ *, human_approved: bool = False) -> dict[str, Any]:
159
+ """§150–153 — challenger promotes only when quality floor passes,
160
+ safety is unchanged AND economy is better — and always behind a
161
+ human review. Never self-modifying."""
162
+ quality_ok = challenger.get("quality_pass", False)
163
+ safety_ok = challenger.get("safety_unchanged", False)
164
+ economy_better = challenger.get("economy_better", False)
165
+ if not (quality_ok and safety_ok and economy_better):
166
+ return {"verdict": "rejected",
167
+ "reason": "challenger failed the gate: needs quality floor "
168
+ "+ unchanged safety + better economy",
169
+ "champion_kept": True}
170
+ if not human_approved:
171
+ return {"verdict": "awaiting_human",
172
+ "reason": "challenger passed all gates — promotion still "
173
+ "requires human review (§153)",
174
+ "champion_kept": True}
175
+ return {"verdict": "promotable", "champion": champion.get("id"),
176
+ "challenger": challenger.get("id")}
177
+
178
+
179
+ def decision_receipt_link(cache_decision: dict[str, Any] | None,
180
+ context_ref: str | None,
181
+ routing_receipt: dict[str, Any],
182
+ verification_plan: dict[str, Any] | None,
183
+ budget_verdict: dict[str, Any] | None
184
+ ) -> dict[str, Any]:
185
+ """§155–158 — one decision receipt links all plane decisions."""
186
+ return {"schema": "platformforge/decision-receipt/v1",
187
+ "cache": cache_decision, "context_ref": context_ref,
188
+ "routing": routing_receipt, "verification": verification_plan,
189
+ "budget": budget_verdict,
190
+ "policy_version": routing_receipt.get("policy_version", ""),
191
+ "ts": time.time()}
@@ -0,0 +1,328 @@
1
+ """Router V2 — adaptive routing as a full dispatch decision (§81–91).
2
+
3
+ Route = f(task type, domains, complexity, risk, blast radius, security
4
+ sensitivity, production, evidence completeness, coverage, freshness,
5
+ expected provider cost, expected token cost, mutability, fleet scope,
6
+ incident status). The table lives in rules/catalog/routing.yaml —
7
+ data, not judgment.
8
+
9
+ Output (§82): mode, coordinator, specialists, reviewers, verifier,
10
+ execution DAG, budget, reasons, fallbacks — never just an agent list.
11
+
12
+ Modes (§83): deterministic | single-specialist | multi-specialist |
13
+ coordinated | debate | critical-review. Default is deterministic (§84);
14
+ debate opens only on conflict / high risk+low confidence / explicit
15
+ comparison (§88); critical-review is mandatory for production mutation,
16
+ IAM, security, destructive data changes, large fleet optimization (§89).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ import yaml
26
+
27
+
28
+ @dataclass
29
+ class TaskSignal:
30
+ """§81 — the routing signals. v1 fields kept for compatibility."""
31
+ task_type: str = "analysis" # lint|analysis|review|incident|architecture|change
32
+ domains: list[str] = field(default_factory=list)
33
+ complexity: str = "low" # low|medium|high
34
+ risk: str = "low" # low|medium|high|critical
35
+ blast_radius: str = "local" # local|service|platform|org
36
+ security_sensitive: bool = False
37
+ evidence_available: bool = True
38
+ production: bool = False
39
+ expected_cost: str = "low" # provider cost: low|medium|high
40
+ evidence_completeness: float = 1.0 # 0-1 measured fraction
41
+ # --- v2 signals (§81) ------------------------------------------
42
+ coverage: float = 1.0 # fraction of scope covered
43
+ freshness: str = "current" # current|stale|unknown
44
+ expected_token_cost: str = "low" # low|medium|high
45
+ mutability: str = "read" # read|propose|mutate
46
+ fleet_scope: bool = False
47
+ incident_status: str = "" # ""|active|mitigated
48
+ conflict: bool = False # specialists disagree (§88)
49
+ comparison: bool = False # user asked to compare (§88)
50
+ destructive: bool = False # destructive data change (§89)
51
+ optimization: bool = False # optimization task (§89 fleet)
52
+
53
+ @classmethod
54
+ def from_dict(cls, d: dict[str, Any]) -> TaskSignal:
55
+ return cls(**{k: v for k, v in d.items()
56
+ if k in cls.__dataclass_fields__})
57
+
58
+
59
+ # domain → canonical specialist. Declared data, audit-friendly.
60
+ DOMAIN_SPECIALIST = {
61
+ "iac": "platform-iac-specialist", "terraform": "platform-iac-specialist",
62
+ "k8s": "platform-kubernetes-specialist",
63
+ "kubernetes": "platform-kubernetes-specialist",
64
+ "helm": "platform-kubernetes-specialist",
65
+ "gitops": "platform-gitops-specialist", "cicd": "platform-gitops-specialist",
66
+ "aws": "platform-aws-specialist", "cloud": "platform-aws-specialist",
67
+ "crossplane": "platform-crossplane-specialist",
68
+ "sre": "platform-sre-specialist", "slo": "platform-sre-specialist",
69
+ "incident": "platform-sre-specialist", "otel": "platform-sre-specialist",
70
+ "security": "platform-security-specialist",
71
+ "finops": "platform-finops-specialist", "cost": "platform-finops-specialist",
72
+ "graph": "platform-graph-specialist",
73
+ "fleet": "platform-fleet-specialist",
74
+ "policy": "platform-policy-specialist",
75
+ "governance": "platform-policy-specialist",
76
+ "capacity": "platform-capacity-specialist",
77
+ "reliability": "platform-capacity-specialist",
78
+ "product": "platform-product-specialist", "dx": "platform-product-specialist",
79
+ "ai": "platform-ai-infra-specialist", "gpu": "platform-ai-infra-specialist",
80
+ "federation": "platform-federation-specialist",
81
+ }
82
+
83
+ # coordinator per task shape (coordinators.py COORDINATOR_LOOPS echo)
84
+ TASK_COORDINATOR = {
85
+ "incident": "platform-incident-coordinator",
86
+ "change": "platform-change-coordinator",
87
+ "fleet": "platform-fleet-coordinator",
88
+ "optimization": "platform-optimization-coordinator",
89
+ "product": "platform-product-coordinator",
90
+ "architecture": "platform-orchestrator",
91
+ "analysis": "platform-orchestrator",
92
+ }
93
+
94
+ BUILTIN_ROUTES: list[dict[str, Any]] = [] # table is canonical data now
95
+
96
+
97
+ def _match(sig: TaskSignal, when: dict[str, Any]) -> bool:
98
+ for k, want in when.items():
99
+ have = getattr(sig, k, None)
100
+ if k == "domains": # intersect, not equality
101
+ if not set(have or ()) & set(want or ()):
102
+ return False
103
+ elif isinstance(want, list):
104
+ if have not in want:
105
+ return False
106
+ elif have != want:
107
+ return False
108
+ return True
109
+
110
+
111
+ def load_routes(path: str | Path | None = None) -> list[dict[str, Any]]:
112
+ from platformforge.resources import data_path
113
+ if path and Path(path).is_file():
114
+ doc = yaml.safe_load(Path(path).read_text()) or {}
115
+ return doc.get("routes", []) or []
116
+ default = data_path("rules", "catalog", "routing.yaml")
117
+ if default.is_file():
118
+ doc = yaml.safe_load(default.read_text()) or {}
119
+ return doc.get("routes", []) or []
120
+ return BUILTIN_ROUTES
121
+
122
+
123
+ def _specialists_for(domains: list[str]) -> list[str]:
124
+ return [DOMAIN_SPECIALIST[d] for d in domains
125
+ if d in DOMAIN_SPECIALIST]
126
+
127
+
128
+ def _needs_critical(signal: TaskSignal) -> bool:
129
+ """§89 — critical review is mandatory, not optional."""
130
+ return bool(
131
+ (signal.production and signal.mutability == "mutate")
132
+ or signal.destructive
133
+ or (signal.security_sensitive and signal.task_type == "change")
134
+ or (signal.optimization and signal.fleet_scope))
135
+
136
+
137
+ def _needs_debate(signal: TaskSignal) -> bool:
138
+ """§88 — debate opens only on these; never by default."""
139
+ return bool(signal.conflict or signal.comparison
140
+ or (signal.risk in ("high", "critical")
141
+ and signal.evidence_completeness < 0.5))
142
+
143
+
144
+ def _budget(signal: TaskSignal, mode: str) -> str:
145
+ if mode == "deterministic":
146
+ return "tiny"
147
+ if mode in ("debate", "critical-review") or signal.risk == "critical":
148
+ return "critical" if signal.risk == "critical" else "deep"
149
+ if mode == "coordinated" or signal.fleet_scope:
150
+ return "deep"
151
+ if mode == "multi-specialist" or signal.complexity == "medium":
152
+ return "standard"
153
+ return "small"
154
+
155
+
156
+ def _dag(mode: str, coordinator: str, specialists: list[str],
157
+ reviewers: list[str], verifier: str,
158
+ production: bool) -> list[dict[str, Any]]:
159
+ """Skeleton stage list for the decision — the orchestrator expands
160
+ it into the full loop DAG at prepare() time."""
161
+ if mode == "deterministic":
162
+ return []
163
+ dag: list[dict[str, Any]] = [{"stage": "extract",
164
+ "agent": "pf-extractor"}]
165
+ if coordinator:
166
+ dag.insert(0, {"stage": "dispatch", "agent": coordinator})
167
+ dag += [{"stage": f"specialist:{s}", "agent": s}
168
+ for s in specialists]
169
+ dag += [{"stage": f"review:{r}", "agent": r} for r in reviewers]
170
+ if production:
171
+ dag.append({"stage": "approval", "agent": "human-gate",
172
+ "external": True})
173
+ dag.append({"stage": "verify", "agent": verifier})
174
+ return dag
175
+
176
+
177
+ def route(signal: TaskSignal, routes_path: str | Path | None = None) -> dict[str, Any]:
178
+ """Compute the full dispatch decision (§82)."""
179
+ routes = load_routes(routes_path)
180
+ specialists: list[str] = []
181
+ reviewers: list[str] = []
182
+ coordinator = ""
183
+ reasons: list[str] = []
184
+ fallbacks: list[str] = ["playbook"]
185
+
186
+ # --- table rules (data) ------------------------------------------
187
+ for r in routes:
188
+ if _match(signal, r.get("when", {})):
189
+ for a in r.get("agents", []):
190
+ if a not in specialists:
191
+ specialists.append(a)
192
+ for a in r.get("add_specialists", []):
193
+ if a not in specialists:
194
+ specialists.append(a)
195
+ for a in r.get("add_reviewers", []):
196
+ if a not in reviewers:
197
+ reviewers.append(a)
198
+ if r.get("coordinator") and not coordinator:
199
+ coordinator = r["coordinator"]
200
+ if r.get("reason"):
201
+ reasons.append(r["reason"])
202
+
203
+ # --- domain-derived specialists ----------------------------------
204
+ for s in _specialists_for(signal.domains):
205
+ if s not in specialists:
206
+ specialists.append(s)
207
+
208
+ # --- mode (policy, §83–89) -----------------------------------------
209
+ mode = "deterministic"
210
+ n = len(specialists)
211
+ if _needs_critical(signal):
212
+ mode = "critical-review"
213
+ coordinator = coordinator or "platform-change-coordinator"
214
+ for r in ("platform-operations-safety-reviewer",
215
+ "platform-security-reviewer"):
216
+ if r not in reviewers:
217
+ reviewers.append(r)
218
+ reasons.append("critical-review mandatory: production/IAM/"
219
+ "destructive/fleet-optimization (§89)")
220
+ elif _needs_debate(signal):
221
+ mode = "debate"
222
+ reasons.append("debate opened: conflict/comparison/high-risk "
223
+ "low-confidence (§88)")
224
+ elif signal.fleet_scope or signal.task_type == "fleet":
225
+ mode = "coordinated"
226
+ coordinator = coordinator or "platform-fleet-coordinator"
227
+ elif signal.task_type in ("incident",) or signal.incident_status == "active":
228
+ mode = "coordinated"
229
+ coordinator = coordinator or "platform-incident-coordinator"
230
+ elif signal.complexity == "high" or n >= 3:
231
+ mode = "coordinated"
232
+ coordinator = coordinator or TASK_COORDINATOR.get(
233
+ signal.task_type, "platform-orchestrator")
234
+ elif n == 2:
235
+ mode = "multi-specialist"
236
+ elif n == 1 or (signal.domains and signal.task_type
237
+ in ("analysis", "review")):
238
+ mode = "single-specialist"
239
+
240
+ if signal.task_type == "lint":
241
+ mode = "deterministic"
242
+ specialists, reviewers, coordinator = [], [], ""
243
+
244
+ # mandatory reviewers/verifier whenever agents run (§267)
245
+ verifier = ""
246
+ if mode != "deterministic":
247
+ verifier = "platform-verifier"
248
+ if "platform-evidence-reviewer" not in reviewers:
249
+ reviewers.insert(0, "platform-evidence-reviewer")
250
+ if signal.security_sensitive \
251
+ and "platform-security-reviewer" not in reviewers:
252
+ reviewers.append("platform-security-reviewer")
253
+ reasons.append("security-sensitive → security reviewer")
254
+
255
+ if mode == "debate":
256
+ reviewers.append("platform-debate-referee")
257
+ if not signal.evidence_available or signal.evidence_completeness < 0.5:
258
+ reasons.append("evidence incomplete — expect named unresolved, "
259
+ "not guesses")
260
+ if signal.expected_cost == "high" or signal.expected_token_cost == "high":
261
+ reasons.append("high expected cost — deterministic-first "
262
+ "mandated")
263
+ if signal.freshness in ("stale", "unknown"):
264
+ reasons.append(f"freshness={signal.freshness} — freshness "
265
+ "evidence required")
266
+
267
+ agents = ([coordinator] if coordinator else []) + specialists \
268
+ + reviewers + ([verifier] if verifier else [])
269
+ return {
270
+ "mode": mode,
271
+ "coordinator": coordinator,
272
+ "specialists": specialists,
273
+ "reviewers": reviewers,
274
+ "verifier": verifier,
275
+ "dag": _dag(mode, coordinator, specialists, reviewers, verifier,
276
+ signal.production),
277
+ "budget": _budget(signal, mode),
278
+ "reasons": reasons or [("deterministic-first: no rule forced "
279
+ "an agentic mode")],
280
+ "fallbacks": fallbacks,
281
+ "agents": agents, # §82 compat: flattened dispatch list
282
+ "signal": {k: getattr(signal, k)
283
+ for k in signal.__dataclass_fields__},
284
+ }
285
+
286
+
287
+ def validate_routing(routes_path: str | Path | None = None) -> dict[str, Any]:
288
+ """§91 — every agent referenced by routing.yaml AND by the
289
+ orchestration loops must resolve; unknown refs are drift, not
290
+ missing features. Also lints delegation edges in the roster."""
291
+ from platformforge.agents.orchestrator import load_loops
292
+ from platformforge.agents.roster import AGENTS, resolve
293
+ problems: list[str] = []
294
+ routes = load_routes(routes_path)
295
+ for i, r in enumerate(routes):
296
+ for key in ("agents", "add_specialists", "add_reviewers",
297
+ "coordinator"):
298
+ ref = r.get(key)
299
+ refs = [ref] if isinstance(ref, str) else (ref or [])
300
+ for a in refs:
301
+ if isinstance(a, str) and resolve(a) is None:
302
+ problems.append(
303
+ f"route[{i}] {r.get('name', '?')} references "
304
+ f"missing agent {a!r}")
305
+ when = r.get("when", {})
306
+ for f in when:
307
+ if f not in TaskSignal.__dataclass_fields__:
308
+ problems.append(f"route[{i}] {r.get('name', '?')} uses "
309
+ f"unknown signal {f!r}")
310
+ for lname, loop in load_loops().items():
311
+ for st in loop.get("stages", []):
312
+ a = st.get("agent")
313
+ if a and a != "human-gate" and resolve(a) is None:
314
+ problems.append(f"loop {lname} stage {st.get('id')} "
315
+ f"references missing agent {a!r}")
316
+ for a in AGENTS.values():
317
+ for ref in tuple(a.delegates_to) + tuple(a.reviewers) + \
318
+ ((a.verifier,) if a.verifier else ()):
319
+ if resolve(ref) is None:
320
+ problems.append(f"{a.name} references missing {ref!r}")
321
+ mode_err = a.role not in ("orchestrator", "planner",
322
+ "coordinator", "specialist",
323
+ "executor", "reviewer", "critic",
324
+ "referee", "verifier", "guardian")
325
+ if mode_err:
326
+ problems.append(f"{a.name} bad role {a.role!r}")
327
+ return {"ok": not problems, "problems": problems,
328
+ "routes": len(routes), "agents": len(AGENTS)}
@@ -0,0 +1,9 @@
1
+ """RTK — first-party command-output intelligence.
2
+
3
+ Tools dump thousands of lines; agents consume the compact form. Raw output is
4
+ preserved in the artifact store, expandable on demand.
5
+ """
6
+
7
+ from platformforge.rtk.compact import CompactResult, compact_output, detect_command
8
+
9
+ __all__ = ["CompactResult", "compact_output", "detect_command"]
@@ -0,0 +1,127 @@
1
+ """RTK compaction — turn raw command output into a bounded structure.
2
+
3
+ The agent consumes the compact result; raw output is preserved out-of-context
4
+ in the artifact store and can be expanded by id/section/line range.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import shlex
11
+ from dataclasses import asdict, dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from platformforge.core.store import ArtifactStore, ref
16
+ from platformforge.rtk.parsers import PARSERS, fallback
17
+
18
+ MAX_INTERESTING = 200
19
+ MAX_LINES_INLINE = 400
20
+
21
+
22
+ @dataclass
23
+ class CompactResult:
24
+ command: str
25
+ exit_code: int = 0
26
+ summary: dict[str, Any] = field(default_factory=dict)
27
+ errors: list[Any] = field(default_factory=list)
28
+ warnings: list[Any] = field(default_factory=list)
29
+ changed: list[Any] = field(default_factory=list)
30
+ interesting: list[Any] = field(default_factory=list)
31
+ omitted: int = 0
32
+ raw_artifact: str = ""
33
+ truncated: bool = False
34
+
35
+ def to_dict(self) -> dict[str, Any]:
36
+ return asdict(self)
37
+
38
+
39
+ def detect_command(command: str) -> str:
40
+ """Map an argv string to a parser key — longest prefix wins."""
41
+ toks = shlex.split(command) if isinstance(command, str) else list(command)
42
+ if not toks:
43
+ return ""
44
+ base = Path(toks[0]).name
45
+ candidates = [" ".join([base] + toks[1:i]) for i in
46
+ range(min(3, len(toks)), 0, -1)]
47
+ for cand in candidates:
48
+ if cand in PARSERS:
49
+ return cand
50
+ if base in PARSERS:
51
+ return base
52
+ return ""
53
+
54
+
55
+ def compact_output(command: str, output: str, exit_code: int = 0,
56
+ store: ArtifactStore | None = None,
57
+ max_inline_lines: int = MAX_LINES_INLINE) -> CompactResult:
58
+ lines = output.splitlines()
59
+ raw_ref = ""
60
+ if store is not None:
61
+ from platformforge.core.redaction import redact_text
62
+ sha = store.put_text(redact_text(output),
63
+ meta={"command": command})
64
+ raw_ref = ref(sha)
65
+
66
+ key = detect_command(command)
67
+ parser = PARSERS.get(key, fallback)
68
+ parts = parser(lines)
69
+
70
+ omitted = max(0, len(lines) - max_inline_lines)
71
+ res = CompactResult(
72
+ command=command, exit_code=exit_code,
73
+ summary=parts.get("summary", {}),
74
+ errors=parts.get("errors", [])[:MAX_INTERESTING],
75
+ warnings=parts.get("warnings", [])[:MAX_INTERESTING],
76
+ changed=parts.get("changed", [])[:MAX_INTERESTING],
77
+ interesting=parts.get("interesting", [])[:MAX_INTERESTING],
78
+ omitted=omitted, raw_artifact=raw_ref,
79
+ truncated=len(lines) > max_inline_lines,
80
+ )
81
+ if res.truncated and not res.raw_artifact:
82
+ res.summary["truncated_note"] = (
83
+ "output truncated; rerun with a store to keep raw artifact")
84
+ return res
85
+
86
+
87
+ def tool_economy_receipt(command: str, res: CompactResult,
88
+ raw_bytes: int | None = None,
89
+ expanded_bytes: int = 0) -> dict[str, Any]:
90
+ """§110 — measured tool economy: what the raw was, what the model
91
+ consumed, what expand-on-demand pulled."""
92
+ return {"schema": "platformforge/tool-economy-receipt/v1",
93
+ "command": command,
94
+ "raw_bytes": raw_bytes,
95
+ "compact_bytes": len(compact_json(res).encode()),
96
+ "expanded_bytes": expanded_bytes,
97
+ "raw_artifact": res.raw_artifact or None,
98
+ "truncated": res.truncated,
99
+ "note": "compact is the default surface; raw preserved as "
100
+ "artifact; expand on demand (§107–109)"}
101
+
102
+
103
+ def expand(store: ArtifactStore, raw_artifact: str,
104
+ start: int | None = None, end: int | None = None,
105
+ pattern: str | None = None) -> dict[str, Any]:
106
+ """Lazy expansion of the preserved raw output."""
107
+ sha = raw_artifact.removeprefix("artifact://sha256/")
108
+ text = store.get_text(sha)
109
+ if text is None:
110
+ return {"error": "artifact not found", "artifact": raw_artifact}
111
+ from platformforge.core.redaction import redact_text
112
+ lines = redact_text(text).splitlines() # defense in depth on read-back
113
+ if pattern:
114
+ import re
115
+ rx = re.compile(pattern)
116
+ hits = [i for i, l in enumerate(lines) if rx.search(l)]
117
+ win = sorted({j for i in hits for j in
118
+ range(max(0, i - 2), min(len(lines), i + 3))})
119
+ return {"lines": [f"{j + 1}: {lines[j]}" for j in win],
120
+ "total": len(lines)}
121
+ s, e = start or 0, end if end is not None else len(lines)
122
+ return {"lines": [f"{i + 1}: {l}" for i, l in
123
+ enumerate(lines[s:e], start=s)], "total": len(lines)}
124
+
125
+
126
+ def compact_json(res: CompactResult) -> str:
127
+ return json.dumps(res.to_dict(), indent=2, default=str)