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,286 @@
1
+ """Quality-per-token (§30–§32 + cycle-2.1 §34–50) — measured, not claimed.
2
+
3
+ Methodology: build a `ContextEnvelope` for the full context and one for the
4
+ TokenSave-packed context, serialize BOTH with the same serializer, estimate
5
+ tokens on exactly those bytes, then hand each envelope to the same judge.
6
+ What is measured is what is evaluated — no off-envelope facts.
7
+
8
+ Deterministic facts the local engine reads are the *working set*, not model
9
+ spend: they land in `deterministic_input_bytes`/`deterministic_fact_count`,
10
+ while `model_context_*` counts only bytes a model/agent would receive.
11
+
12
+ Verdicts: `beneficial` (reduction > 0 AND all quality floors pass),
13
+ `optimization_not_beneficial` (reduction exists but quality dropped below a
14
+ floor — quality wins over economy), `no_reduction` (pack didn't shrink).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import time
20
+ from typing import Any
21
+
22
+ from platformforge.economy.envelope import ContextEnvelope
23
+ from platformforge.tokensave.budget import Budget
24
+ from platformforge.tokensave.index import SearchIndex
25
+ from platformforge.tokensave.packs import ContextPackBuilder
26
+
27
+ DEFAULT_FLOORS = {"finding_recall": 0.98, "evidence_recall": 0.98,
28
+ "precision": 0.9, "unresolved_recall": 1.0}
29
+
30
+
31
+ def _pr(actual: set[str], predicted: set[str]) -> dict[str, Any]:
32
+ tp = len(actual & predicted)
33
+ return {
34
+ "recall": tp / len(actual) if actual else 1.0,
35
+ "precision": tp / len(predicted) if predicted else 1.0,
36
+ "tp": tp, "fn": len(actual - predicted), "fp": len(predicted - actual),
37
+ }
38
+
39
+
40
+ def _default_judge(versions: dict[str, str] | None):
41
+ from platformforge.models import Fact
42
+ from platformforge.resources import data_path
43
+ from platformforge.rules import RuleEngine, load_catalog
44
+ eng = RuleEngine(load_catalog(data_path("rules", "catalog")),
45
+ versions=versions)
46
+
47
+ def judge(env: ContextEnvelope) -> list[dict[str, Any]]:
48
+ out, _ = eng.evaluate([Fact.from_dict(f) for f in env.facts])
49
+ return [{"rule_id": f.rule_id, "status": f.status,
50
+ "fact": ev} for f in out for ev in f.evidence]
51
+ return judge
52
+
53
+
54
+ def quality_per_token(*, facts_full: list[dict[str, Any]],
55
+ findings_full: list[dict[str, Any]],
56
+ index: SearchIndex, task: str,
57
+ budget: Budget | None = None,
58
+ judge=None,
59
+ floors: dict[str, float] | None = None,
60
+ versions: dict[str, str] | None = None) -> dict[str, Any]:
61
+ """Compare full-context judgment vs packed-context judgment.
62
+
63
+ `judge(ContextEnvelope) -> list[finding-dicts]` — defaults to the rule
64
+ catalog evaluated over `env.facts` (versions-aware).
65
+ """
66
+ floors = floors or DEFAULT_FLOORS
67
+ judge_is_default = judge is None
68
+ judge = judge or _default_judge(versions)
69
+
70
+ # deterministic working set — local facts are not model spend (§38)
71
+ det = ContextEnvelope(kind="deterministic", facts=facts_full,
72
+ metadata={"task": task, "variant": "working-set"})
73
+
74
+ # baseline = everything the pack could have received: every indexed file
75
+ # body + facts + findings (no duplication: files are raw content, facts
76
+ # are the extracted contract a pack would carry anyway)
77
+ all_files = [{"path": p, "content": index.read(p) or ""}
78
+ for p in index.by_path("*")]
79
+ full = ContextEnvelope(kind="model", files=all_files,
80
+ facts=facts_full, findings=findings_full,
81
+ metadata={"task": task, "variant": "full"})
82
+ builder = ContextPackBuilder(index)
83
+ # retain_content → the envelope carries the EXACT bytes the pack
84
+ # delivers (truncated bodies, not re-read full files)
85
+ pack = builder.build(task, budget=budget, facts=facts_full,
86
+ findings=findings_full, retain_content=True)
87
+ if pack["budget_decision"] == "refuse":
88
+ return {"task": task, "verdict": "refused",
89
+ "budget_decision": "refuse",
90
+ "reason": pack["budget_reason"],
91
+ "refusals": pack.get("refusals", [])}
92
+
93
+ # packed = what the pack actually delivers: delivered file bodies +
94
+ # facts/findings/rules/symbols/graph neighborhood it carries
95
+ packed = ContextEnvelope(
96
+ kind="model",
97
+ files=[{"path": f["path"], "content": f.get("content", "")}
98
+ for f in pack["relevant_files"]],
99
+ facts=list(pack.get("facts") or []),
100
+ findings=list(pack.get("findings") or []),
101
+ rules=list(pack.get("rules") or []),
102
+ graph={"neighborhood": pack.get("graph_neighborhood") or []},
103
+ metadata={"task": task, "variant": "tokensave",
104
+ "symbols": pack.get("relevant_symbols") or [],
105
+ "sources": pack.get("sources") or [],
106
+ "pack_budget": pack.get("budget"),
107
+ "budget_decision": pack.get("budget_decision")})
108
+
109
+ # measure BEFORE judging — a mutating judge must not shift the numbers
110
+ tokens_full, bytes_full = full.estimated_tokens, full.byte_size
111
+ tokens_pack, bytes_pack = packed.estimated_tokens, packed.byte_size
112
+
113
+ # same serializer + estimator for both sides; judge sees exactly the
114
+ # envelope that was measured
115
+ t0 = time.perf_counter()
116
+ full_res = judge(full)
117
+ t_full = time.perf_counter() - t0
118
+ t0 = time.perf_counter()
119
+ pack_res = judge(packed)
120
+ t_pack = time.perf_counter() - t0
121
+
122
+ def viol(rs): return {f["rule_id"] for f in rs if f.get("status") == "violated"}
123
+ def ev(rs): return {f["fact"] for f in rs if f.get("status") == "violated"}
124
+ def unr(rs): return {f["rule_id"] for f in rs if f.get("status") == "unresolved"}
125
+ def kept(rs): return {f["rule_id"] for f in rs
126
+ if f.get("status") == "unresolved"}
127
+
128
+ fr = _pr(viol(full_res), viol(pack_res))
129
+ evr = _pr(ev(full_res), ev(pack_res))
130
+ # unresolved correctness: a baseline-unresolved rule must STAY
131
+ # unresolved — flipping to `violated` on less context is an overclaim,
132
+ # counted as lost (and as a false positive by `fr`).
133
+ unres_lost = sorted(unr(full_res) - kept(pack_res))
134
+ unres_recall = 1.0 - (len(unres_lost) / len(unr(full_res))
135
+ if unr(full_res) else 0.0)
136
+
137
+ reduction = (1 - tokens_pack / tokens_full) if tokens_full else 0.0
138
+
139
+ floors_ok = {
140
+ "finding_recall": fr["recall"] >= floors["finding_recall"],
141
+ "evidence_recall": evr["recall"] >= floors["evidence_recall"],
142
+ "precision": fr["precision"] >= floors["precision"],
143
+ "unresolved_recall": unres_recall >= floors["unresolved_recall"],
144
+ }
145
+ if reduction <= 0:
146
+ verdict = "no_reduction"
147
+ elif all(floors_ok.values()):
148
+ verdict = "beneficial"
149
+ else:
150
+ verdict = "optimization_not_beneficial"
151
+
152
+ return {
153
+ "task": task,
154
+ "baseline": {
155
+ "model_context_bytes": bytes_full,
156
+ "estimated_tokens": tokens_full,
157
+ "facts": len(full.facts),
158
+ "judge_seconds": round(t_full, 6),
159
+ "findings_violated": sorted(viol(full_res)),
160
+ "findings_unresolved": sorted(unr(full_res)),
161
+ },
162
+ "optimized": {
163
+ "model_context_bytes": bytes_pack,
164
+ "estimated_tokens": tokens_pack,
165
+ "facts": len(packed.facts),
166
+ "judge_seconds": round(t_pack, 6),
167
+ "findings_violated": sorted(viol(pack_res)),
168
+ "findings_unresolved": sorted(unr(pack_res)),
169
+ "pack_refusals": pack.get("refusals") or [],
170
+ },
171
+ "deterministic": det.ledger_fields(),
172
+ "quality": {
173
+ "finding_recall": fr["recall"], "finding_precision": fr["precision"],
174
+ "evidence_recall": evr["recall"], "evidence_precision": evr["precision"],
175
+ "false_negatives": fr["fn"], "false_positives": fr["fp"],
176
+ "unresolved_recall": round(unres_recall, 4),
177
+ "unresolved_lost": unres_lost,
178
+ "floors": floors, "floors_ok": floors_ok,
179
+ },
180
+ "economy": {
181
+ "token_reduction": round(reduction, 4),
182
+ "byte_reduction": round(
183
+ 1 - bytes_pack / bytes_full, 4) if bytes_full else 0.0,
184
+ },
185
+ # honesty label: the shipped judge reads env.facts only, and the
186
+ # pack carries facts as essential-complete — quality floors then
187
+ # measure payload integrity, not file-dependent judgment. A
188
+ # file-consuming judge (inject `judge=`) exercises the rest.
189
+ "quality_measurement": ("facts-only (files not judged)"
190
+ if judge_is_default
191
+ else "custom judge"),
192
+ "verdict": verdict,
193
+ # compat with pre-2.1 readers
194
+ "tokens_full_context": tokens_full, "tokens_packed": tokens_pack,
195
+ "token_reduction": round(reduction, 4),
196
+ "finding_recall": fr["recall"], "finding_precision": fr["precision"],
197
+ "evidence_recall": evr["recall"], "evidence_precision": evr["precision"],
198
+ "facts_full": len(facts_full), "facts_packed": len(packed.facts),
199
+ "floors": floors,
200
+ "quality_gate": "pass" if verdict == "beneficial" else "fail",
201
+ }
202
+
203
+
204
+ # --- QPT v3: generalized quality-per-cost (economy spec §161–165) --------
205
+
206
+ COST_DIMENSIONS = ("tokens", "bytes", "tool_calls", "model_calls",
207
+ "agents", "provider_calls", "wall_time_s", "money")
208
+
209
+
210
+ def quality_per_cost(qpt_result: dict[str, Any], *,
211
+ optimized_usage: dict[str, Any] | None = None,
212
+ baseline_usage: dict[str, Any] | None = None,
213
+ money: dict[str, Any] | None = None) -> dict[str, Any]:
214
+ """QPT v3 — the same quality floors measured against every cost axis.
215
+
216
+ `qpt_result` is the output of `quality_per_token` (floors + verdict).
217
+ `baseline_usage`/`optimized_usage` carry the measured per-mode costs:
218
+ tokens, tool_calls, model_calls, agents, provider_calls, wall_time_s.
219
+ `money` is a ProviderCost dict from economy.pricing — optional, and
220
+ `unresolved` when pricing is missing (never a guessed dollar).
221
+
222
+ Every dimension is reported individually; the composite score is a
223
+ decomposable mean of per-dimension reduction ratios — it summarizes,
224
+ it never hides a dimension (§164). Quality floors still gate: no
225
+ cost win counts when a floor failed.
226
+ """
227
+ opt = optimized_usage or {}
228
+ base = baseline_usage or {}
229
+ dims: dict[str, Any] = {}
230
+
231
+ # tokens/bytes come from the measured envelopes inside qpt_result
232
+ bt = base.get("estimated_tokens",
233
+ qpt_result.get("baseline", {}).get("estimated_tokens"))
234
+ ot = opt.get("estimated_tokens",
235
+ qpt_result.get("optimized", {}).get("estimated_tokens"))
236
+ bb = base.get("bytes",
237
+ qpt_result.get("baseline", {}).get("model_context_bytes"))
238
+ ob = opt.get("bytes",
239
+ qpt_result.get("optimized", {}).get("model_context_bytes"))
240
+ for name, b, o in (("tokens", bt, ot), ("bytes", bb, ob)):
241
+ if b is not None and o is not None:
242
+ dims[name] = {"baseline": b, "optimized": o,
243
+ "reduction": round(1 - o / b, 4) if b else 0.0}
244
+
245
+ for name in ("tool_calls", "model_calls", "agents",
246
+ "provider_calls", "wall_time_s"):
247
+ b, o = base.get(name), opt.get(name)
248
+ if b is None and o is None:
249
+ dims[name] = {"state": "unresolved",
250
+ "reason": "not measured"}
251
+ elif b is None or o is None:
252
+ dims[name] = {"state": "partial",
253
+ "baseline": b, "optimized": o}
254
+ else:
255
+ dims[name] = {"baseline": b, "optimized": o,
256
+ "reduction": round(1 - o / b, 4) if b else 0.0}
257
+
258
+ dims["money"] = money if money is not None else {
259
+ "state": "unresolved",
260
+ "reason": "PF-ECONOMY-PRICING-MISSING — no declared rate, "
261
+ "no dollar claim"}
262
+
263
+ measured = [d["reduction"] for d in dims.values()
264
+ if isinstance(d, dict) and "reduction" in d]
265
+ quality = qpt_result.get("quality", {})
266
+ floors_ok = all(quality.get("floors_ok", {}).values()) \
267
+ if quality.get("floors_ok") else qpt_result.get("verdict") == "beneficial"
268
+
269
+ return {
270
+ "task": qpt_result.get("task"),
271
+ "verdict": qpt_result.get("verdict"),
272
+ "quality_floors_ok": floors_ok,
273
+ "dimensions": dims,
274
+ "composite": {
275
+ "cost_reduction_mean": (round(sum(measured) / len(measured), 4)
276
+ if measured else 0.0),
277
+ "decomposable": True,
278
+ "note": "mean over measured dims only; unresolved dims are "
279
+ "excluded, not zeroed",
280
+ },
281
+ "honest_claim": ("no savings claim — quality floor failed"
282
+ if not floors_ok else
283
+ ("measured reduction across "
284
+ f"{len(measured)} dimensions"
285
+ if measured else "nothing measured")),
286
+ }
@@ -0,0 +1,86 @@
1
+ """BudgetReconciliation (§81–88) — planned vs observed, per axis.
2
+
3
+ Observed absent → `unresolved` (§85), never a fake zero. Savings claims
4
+ require observed usage + declared pricing — "saved $X" without both is
5
+ refused (§86, §293 north star). Historical mismatch feeds routing signal
6
+ (§88) as data, never self-modifying policy (§153).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import asdict, dataclass, field
12
+ from typing import Any
13
+
14
+ SCHEMA = "platformforge/reconciliation/v1"
15
+
16
+ AXES = ("tokens", "context", "tools", "agents", "provider_calls",
17
+ "duration", "money")
18
+
19
+
20
+ @dataclass
21
+ class AxisRecon:
22
+ planned: float | None
23
+ observed: float | None
24
+ state: str # ok|over|under|unresolved
25
+ error_pct: float | None = None # §84 calibration error
26
+
27
+ def to_dict(self) -> dict[str, Any]:
28
+ return asdict(self)
29
+
30
+
31
+ @dataclass
32
+ class BudgetReconciliation:
33
+ schema: str = SCHEMA
34
+ run_id: str = ""
35
+ axes: dict[str, AxisRecon] = field(default_factory=dict)
36
+
37
+ def to_dict(self) -> dict[str, Any]:
38
+ return {"schema": self.schema, "run_id": self.run_id,
39
+ "axes": {k: v.to_dict() for k, v in self.axes.items()},
40
+ "calibrated_axes": [k for k, v in self.axes.items()
41
+ if v.state != "unresolved"],
42
+ "unresolved_axes": [k for k, v in self.axes.items()
43
+ if v.state == "unresolved"]}
44
+
45
+
46
+ def reconcile(planned: dict[str, float | None],
47
+ observed: dict[str, float | None],
48
+ run_id: str = "", *,
49
+ tolerance_pct: float = 20.0) -> BudgetReconciliation:
50
+ """§82–87 — per-axis compare. `observed=None` means the axis was not
51
+ measured → unresolved, never treated as 0."""
52
+ rec = BudgetReconciliation(run_id=run_id)
53
+ for axis in AXES:
54
+ p = planned.get(axis)
55
+ o = observed.get(axis)
56
+ if p is None and o is None:
57
+ continue
58
+ if o is None:
59
+ rec.axes[axis] = AxisRecon(p, None, "unresolved")
60
+ continue
61
+ if p is None:
62
+ rec.axes[axis] = AxisRecon(None, o, "unresolved")
63
+ continue
64
+ err = ((o - p) / p * 100.0) if p else (100.0 if o else 0.0)
65
+ state = "ok" if abs(err) <= tolerance_pct else \
66
+ ("over" if err > 0 else "under")
67
+ rec.axes[axis] = AxisRecon(p, o, state, round(err, 1))
68
+ return rec
69
+
70
+
71
+ def savings_claim(observed_spend: float | None,
72
+ declared_pricing: bool) -> dict[str, Any]:
73
+ """§86/§293 — a $-savings claim needs observed spend AND declared
74
+ pricing; otherwise the honest answer is unresolved."""
75
+ if observed_spend is None:
76
+ return {"claim": "unresolved",
77
+ "reason": "no observed usage — cannot claim savings",
78
+ "code": "PF-ECONOMY-USAGE-UNOBSERVED"}
79
+ if not declared_pricing:
80
+ return {"claim": "unresolved",
81
+ "reason": "tokens observed but pricing absent — cost "
82
+ "unresolved, not $0",
83
+ "code": "PF-ECONOMY-PRICING-MISSING",
84
+ "observed_spend": observed_spend}
85
+ return {"claim": "supported",
86
+ "observed_spend": observed_spend}
@@ -0,0 +1,107 @@
1
+ """Selective verification (§97–104) — VerificationPlanner.
2
+
3
+ Impact-based, not test-skipping (§103): select the verification tiers the
4
+ change's scope/risk/graph-impact/security require, record selected AND
5
+ skipped with reasons in the verification ledger (§104). Risk floors
6
+ (§100) cap how low a risky change may verify; security changes always
7
+ include the security check (§101); the verification floor cannot be
8
+ lowered by budget pressure (§19/§246).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
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 = "platformforge/verification-plan/v1"
20
+
21
+ TIERS = ("V0-static", "V1-contract", "V2-unit", "V3-integration",
22
+ "V4-runtime", "V5-production-evidence")
23
+
24
+ TIER_ORDER = {t: i for i, t in enumerate(TIERS)}
25
+
26
+ # §100 — risk floors: the cheapest tier a risk class may stop at.
27
+ RISK_FLOORS = {"low": "V0-static", "medium": "V1-contract",
28
+ "high": "V3-integration", "critical": "V4-runtime"}
29
+
30
+ SEC_CHECK = "security-verification" # §101 — always present if touched
31
+
32
+
33
+ @dataclass
34
+ class VerificationPlan:
35
+ schema: str = SCHEMA
36
+ risk: str = "low"
37
+ security_sensitive: bool = False
38
+ floor: str = "V0-static"
39
+ selected: list[str] = field(default_factory=list)
40
+ skipped: list[str] = field(default_factory=list)
41
+ checks: list[str] = field(default_factory=list)
42
+ reasons: dict[str, str] = field(default_factory=dict)
43
+
44
+ def to_dict(self) -> dict[str, Any]:
45
+ return asdict(self)
46
+
47
+
48
+ class VerificationPlanner:
49
+ """Select tiers + checks from change signal; write the ledger."""
50
+
51
+ def __init__(self, root: str | Path | None = None):
52
+ self.root = Path(root) if root else None
53
+
54
+ def plan(self, *, risk: str = "low", security_sensitive: bool = False,
55
+ change_scope: str = "local", impacted_nodes: int = 0,
56
+ touches_security: bool = False,
57
+ runtime_change: bool = False,
58
+ available_checks: list[str] | None = None,
59
+ budget_pressure: bool = False) -> VerificationPlan:
60
+ floor = RISK_FLOORS.get(risk, "V0-static")
61
+
62
+ # graph-aware selection (§102): large blast raises the floor
63
+ if impacted_nodes > 100 and TIER_ORDER[floor] < TIER_ORDER[
64
+ "V2-unit"]:
65
+ floor = "V2-unit"
66
+ if change_scope in ("platform", "org") and TIER_ORDER[floor] < \
67
+ TIER_ORDER["V3-integration"]:
68
+ floor = "V3-integration"
69
+ if runtime_change and TIER_ORDER[floor] < TIER_ORDER[
70
+ "V4-runtime"]:
71
+ floor = "V4-runtime"
72
+
73
+ # budget may never lower the floor (§246 property)
74
+ selected = TIERS[:TIER_ORDER[floor] + 1]
75
+ skipped = TIERS[TIER_ORDER[floor] + 1:]
76
+
77
+ checks: list[str] = []
78
+ reasons: dict[str, str] = {
79
+ "floor": f"risk={risk} → floor {floor}"}
80
+ if touches_security or security_sensitive:
81
+ checks.append(SEC_CHECK)
82
+ reasons[SEC_CHECK] = "§101 — security change always verifies security"
83
+ if budget_pressure:
84
+ reasons["budget"] = ("budget pressure recorded — floor "
85
+ "unchanged (verification floor is "
86
+ "protected)")
87
+
88
+ plan = VerificationPlan(risk=risk,
89
+ security_sensitive=security_sensitive
90
+ or touches_security,
91
+ floor=floor, selected=list(selected),
92
+ skipped=list(skipped), checks=checks,
93
+ reasons=reasons)
94
+ self._record(plan)
95
+ return plan
96
+
97
+ def _record(self, plan: VerificationPlan) -> None:
98
+ """§104 — verification ledger: selected, skipped, why."""
99
+ if not self.root:
100
+ return
101
+ d = self.root / ".platformforge" / "ledger"
102
+ d.mkdir(parents=True, exist_ok=True)
103
+ row = {"schema": SCHEMA, "ts": time.time(),
104
+ "selected": plan.selected, "skipped": plan.skipped,
105
+ "checks": plan.checks, "reasons": plan.reasons}
106
+ with open(d / "verification.jsonl", "a") as fh:
107
+ fh.write(json.dumps(row, sort_keys=True) + "\n")
@@ -0,0 +1,123 @@
1
+ """EconomyWasteDetector (§112–116) — reads the ledgers and reports waste
2
+ as findings. Recommendations only — never auto-fixes routing (§115)."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import asdict, dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ WASTE_TYPES = ("duplicate_context", "duplicate_tool_call",
11
+ "repeated_graph_build", "unused_agent", "overrouting",
12
+ "repeated_provider_call", "unused_expansion",
13
+ "cache_bypass", "oversized_context",
14
+ "unnecessary_verification")
15
+
16
+
17
+ @dataclass
18
+ class WasteFinding:
19
+ """§114 — the output shape."""
20
+ waste_type: str
21
+ estimated_impact: str # low|medium|high — never fake bytes
22
+ evidence: dict[str, Any] = field(default_factory=dict)
23
+ confidence: str = "medium"
24
+ recommended_fix: str = ""
25
+
26
+ def to_dict(self) -> dict[str, Any]:
27
+ return asdict(self)
28
+
29
+
30
+ class EconomyWasteDetector:
31
+ """Scans `.platformforge/ledger/*.jsonl` and reports waste."""
32
+
33
+ def __init__(self, root: str | Path):
34
+ self.root = Path(root)
35
+
36
+ def _read(self, name: str) -> list[dict[str, Any]]:
37
+ import json
38
+ p = self.root / ".platformforge" / "ledger" / name
39
+ if not p.exists():
40
+ return []
41
+ return [json.loads(l) for l in p.read_text().splitlines()
42
+ if l.strip()]
43
+
44
+ def detect(self) -> dict[str, Any]:
45
+ findings: list[WasteFinding] = []
46
+
47
+ tokens = self._read("tokens.jsonl")
48
+ econ = self._read("economy.jsonl")
49
+ runs = self._read("agent-runs.jsonl")
50
+
51
+ # duplicate tool call: same tool+input_bytes hash row repeated
52
+ tool_rows = [e for e in econ if e.get("entry_type") == "tool"]
53
+ seen_tool: dict[str, int] = {}
54
+ for e in tool_rows:
55
+ k = f"{e.get('tool')}:{e.get('input_bytes')}:{e.get('run_id')}"
56
+ seen_tool[k] = seen_tool.get(k, 0) + 1
57
+ dups = {k: n for k, n in seen_tool.items() if n > 1}
58
+ if dups:
59
+ findings.append(WasteFinding(
60
+ "duplicate_tool_call", "medium",
61
+ {"calls": dups}, "high",
62
+ "route through the cache layer or dedup identical calls"))
63
+
64
+ # repeated provider call
65
+ prov = [e for e in econ if e.get("entry_type") == "provider"]
66
+ by_coll: dict[str, int] = {}
67
+ for e in prov:
68
+ k = f"{e.get('collector')}:{e.get('service')}"
69
+ by_coll[k] = by_coll.get(k, 0) + e.get("api_calls", 0)
70
+ if prov and all(e.get("cache_hits", 0) == 0 for e in prov) \
71
+ and any(n > 50 for n in by_coll.values()):
72
+ findings.append(WasteFinding(
73
+ "repeated_provider_call", "high",
74
+ {"calls_by_service": by_coll}, "medium",
75
+ "enable provider call caching or widen observation reuse"))
76
+
77
+ # cache bypass: token rows that delivered but never reused
78
+ delivered = sum(e.get("context_delivered", 0) for e in tokens)
79
+ reused = sum(e.get("context_reused", 0) for e in tokens)
80
+ if len(tokens) >= 5 and delivered and reused == 0:
81
+ findings.append(WasteFinding(
82
+ "cache_bypass", "high",
83
+ {"operations": len(tokens), "delivered": delivered,
84
+ "reused": 0}, "medium",
85
+ "route context builds through CacheStore"))
86
+
87
+ # overrouting: runs with fanout>1 for deterministic task types
88
+ over = [r for r in runs
89
+ if r.get("mode") in ("multi-specialist", "coordinated")
90
+ and r.get("extra", {}).get("task_type") in
91
+ ("lint", "inventory", "policy", "diff")]
92
+ if over:
93
+ findings.append(WasteFinding(
94
+ "overrouting", "medium",
95
+ {"runs": [r.get("run_id") for r in over]}, "medium",
96
+ "simple tasks route deterministic/single-specialist "
97
+ "(§123)"))
98
+
99
+ # oversized context: delivered > 32k est tokens repeatedly
100
+ big = [e for e in tokens
101
+ if (e.get("context_delivered") or 0) > 32000]
102
+ if len(big) >= 3:
103
+ findings.append(WasteFinding(
104
+ "oversized_context", "medium",
105
+ {"deliveries_over_32k": len(big)}, "medium",
106
+ "tighten context budget or use delta context (§74)"))
107
+
108
+ # unused expansion: tool expanded_bytes>0 but output small
109
+ unused_exp = [e for e in tool_rows
110
+ if e.get("expanded_bytes", 0) > 0
111
+ and e.get("output_bytes", 0) < 200]
112
+ if unused_exp:
113
+ findings.append(WasteFinding(
114
+ "unused_expansion", "low",
115
+ {"entries": len(unused_exp)}, "low",
116
+ "expand-on-demand may have fetched unneeded detail"))
117
+
118
+ return {"waste_findings": [f.to_dict() for f in findings],
119
+ "scanned": {"token_rows": len(tokens),
120
+ "economy_rows": len(econ),
121
+ "agent_runs": len(runs)},
122
+ "note": "recommendations only — routing is never "
123
+ "auto-modified (§115)"}
@@ -0,0 +1,16 @@
1
+ """Eval framework (§94–95) — typed cases over fixtures, graded offline.
2
+
3
+ Case types (§94): unit, integration, golden, contract, property,
4
+ metamorphic, regression, recall, precision, token_economy,
5
+ graph_correctness, routing, security.
6
+
7
+ §95 variants per finding: positive | negative | boundary | unresolved |
8
+ version — declared in `variant` and checked against the expectation kind
9
+ (positive must fire, negative must not, boundary states the edge,
10
+ unresolved expects the finding absent AND the fact unresolved, version
11
+ pairs a rule constraint with a fixture version).
12
+ """
13
+
14
+ from platformforge.evals.runner import EVAL_TYPES, VARIANTS, run_all, run_case
15
+
16
+ __all__ = ["EVAL_TYPES", "VARIANTS", "run_all", "run_case"]