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,140 @@
1
+ """§122 rule coverage report + §127 false-positive measurement.
2
+
3
+ Coverage: which eval variants exercise each catalog rule.
4
+ Precision: cases carrying `expect.rules_not_fired` measure whether a rule
5
+ fires where it shouldn't — precision = rules that stayed silent on
6
+ negative/boundary fixtures / total negative+boundary observations.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import yaml
15
+
16
+ from platformforge.evals.runner import CASES_DIR, _catalog, _facts_for, _fired_ids
17
+ from platformforge.lab.runner import SCENARIOS_DIR
18
+
19
+ VARIANTS = ("positive", "negative", "boundary", "unresolved", "version")
20
+
21
+
22
+ def _case_docs(cases_dir: Path) -> list[tuple[Path, dict]]:
23
+ out = []
24
+ for c in sorted(cases_dir.glob("*/case.yaml")) if cases_dir.is_dir() \
25
+ else []:
26
+ doc = yaml.safe_load(c.read_text()) or {}
27
+ out.append((c.parent, doc))
28
+ return out
29
+
30
+
31
+ def _lab_docs(scenarios_dir: Path) -> list[tuple[Path, dict]]:
32
+ out = []
33
+ for e in sorted(scenarios_dir.glob("*/expected.yaml")) \
34
+ if scenarios_dir.is_dir() else []:
35
+ doc = yaml.safe_load(e.read_text()) or {}
36
+ out.append((e.parent, doc))
37
+ return out
38
+
39
+
40
+ def _rules_of(case: dict) -> dict[str, list[str]]:
41
+ """Every rule a case exercises, keyed by expectation polarity."""
42
+ exp = case.get("expect", {})
43
+ return {"positive": list(exp.get("rules_fired", [])),
44
+ "negative": list(exp.get("rules_not_fired", [])),
45
+ "version": (list(exp.get("unresolved", []))
46
+ + [next(iter(w)) if isinstance(w, dict) else w
47
+ for w in exp.get("rules_skipped", [])]
48
+ + list(exp.get("passed", [])))}
49
+
50
+
51
+ def _lab_rules_of(doc: dict) -> dict[str, list[str]]:
52
+ """A lab scenario covers every rule it asserts on."""
53
+ return {"positive": list(doc.get("violated_rules", [])),
54
+ "negative": list(doc.get("absent_rules", []))}
55
+
56
+
57
+ def rule_coverage(cases_dir: str | Path = CASES_DIR,
58
+ scenarios_dir: str | Path = SCENARIOS_DIR) \
59
+ -> dict[str, Any]:
60
+ """rule_id → {variant: [cases]}. Eval cases AND lab scenarios count —
61
+ a rule asserted by a lab `violated_rules` is covered-positive; by
62
+ `absent_rules`, covered-negative. Uncovered rules are named."""
63
+ catalog = _catalog()
64
+ matrix: dict[str, dict[str, list[str]]] = {
65
+ r.rule_id: {v: [] for v in VARIANTS} for r in catalog}
66
+ for cdir, case in _case_docs(Path(cases_dir)):
67
+ cid = case.get("id", cdir.name)
68
+ variant = case.get("variant")
69
+ for rid, polarity in _rules_of(case).items():
70
+ for r in polarity:
71
+ if r in matrix:
72
+ if variant in VARIANTS:
73
+ matrix[r][variant].append(cid)
74
+ elif polarity in VARIANTS:
75
+ matrix[r][polarity].append(cid)
76
+ else:
77
+ matrix[r]["positive"].append(cid)
78
+ for sdir, doc in _lab_docs(Path(scenarios_dir)):
79
+ sid = f"lab/{sdir.name}"
80
+ for polarity, rules in _lab_rules_of(doc).items():
81
+ for r in rules:
82
+ if r in matrix:
83
+ matrix[r][polarity].append(sid)
84
+ rows = []
85
+ for rid in sorted(matrix):
86
+ cov = matrix[rid]
87
+ rows.append({"rule_id": rid,
88
+ **{v: bool(cov.get(v)) for v in VARIANTS},
89
+ "cases": sum(len(v) for v in cov.values())})
90
+ covered = [r for r in rows if r["cases"] > 0]
91
+ return {"coverage": rows,
92
+ "counts": {"rules": len(rows), "with_cases": len(covered),
93
+ "uncovered": sorted(r["rule_id"] for r in rows
94
+ if r["cases"] == 0)},
95
+ "note": "coverage = a case or lab scenario names the rule; "
96
+ "it is not a correctness proof"}
97
+
98
+
99
+ def precision_report(cases_dir: str | Path = CASES_DIR) -> dict[str, Any]:
100
+ """§127 — run every case, measure per-rule false-positive rate.
101
+
102
+ A false positive = a rule fires on a negative/boundary case that lists
103
+ it under `rules_not_fired`. Reported per rule and in aggregate."""
104
+ per_rule: dict[str, dict[str, int]] = {}
105
+ details = []
106
+ for cdir, case in _case_docs(Path(cases_dir)):
107
+ not_fired = set(case.get("expect", {}).get("rules_not_fired", []))
108
+ if not not_fired:
109
+ continue
110
+ try:
111
+ fired = _fired_ids(_facts_for(case, cdir),
112
+ case.get("versions"))
113
+ except Exception as exc: # noqa: BLE001 — case error ≠ FP
114
+ details.append({"case": cdir.name, "error": str(exc)})
115
+ continue
116
+ fps = not_fired & fired
117
+ for rid in not_fired:
118
+ d = per_rule.setdefault(rid, {"fp": 0, "checks": 0})
119
+ d["checks"] += 1
120
+ d["fp"] += int(rid in fps)
121
+ if fps:
122
+ details.append({"case": cdir.name,
123
+ "false_positives": sorted(fps)})
124
+ rows = []
125
+ for rid, d in sorted(per_rule.items()):
126
+ rows.append({"rule_id": rid, "false_positives": d["fp"],
127
+ "negative_checks": d["checks"],
128
+ "precision": round(1 - d["fp"] / d["checks"], 4)
129
+ if d["checks"] else None})
130
+ total_fp = sum(r["false_positives"] for r in rows)
131
+ total_checks = sum(r["negative_checks"] for r in rows)
132
+ return {"precision": rows,
133
+ "aggregate_precision": round(1 - total_fp / total_checks, 4)
134
+ if total_checks else None,
135
+ "details": details,
136
+ "counts": {"rules_measured": len(rows),
137
+ "negative_checks": total_checks,
138
+ "false_positives": total_fp},
139
+ "note": "precision measured on negative/boundary cases only; "
140
+ "no negative corpus → no precision claim"}
@@ -0,0 +1,260 @@
1
+ """Cycle 5 Phase N — fleet/analytics invariant probes (§286–292).
2
+
3
+ Deterministic functions returning {"ok": bool, "detail": ...}. Each
4
+ probe encodes one spec invariant that scripted lab scenarios can't
5
+ reach: unknown-handling, provenance, small-sample honesty, boundary
6
+ conditions (correlation≠causality, centrality≠criticality,
7
+ low-util≠deletable, uncertain optimization never auto-promoted).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+
15
+ def probe_partial_coverage_visible(tmp) -> dict[str, Any]:
16
+ """§286 — a permission-limited member is still reported; coverage
17
+ stays <1 and the member is never silently dropped."""
18
+ from platformforge.fleet.models import FleetSnapshot, MemberObservation
19
+ snap = FleetSnapshot(
20
+ fleet_id="f",
21
+ member_observations=[
22
+ MemberObservation(member_id="cluster:a", status="observed",
23
+ coverage=1.0),
24
+ MemberObservation(member_id="cluster:b",
25
+ status="permission-limited",
26
+ coverage=0.3)])
27
+ d = snap.to_dict()
28
+ ok = snap.coverage < 1.0 and \
29
+ any(o.member_id == "cluster:b"
30
+ for o in snap.member_observations) and \
31
+ d.get("coverage") is not None
32
+ return {"ok": ok, "detail": f"coverage={snap.coverage}"}
33
+
34
+
35
+ def probe_small_sample_confidence(tmp) -> dict[str, Any]:
36
+ """§288 — small samples never produce high confidence; the engine
37
+ reports insufficient-history below the minimum support."""
38
+ from datetime import datetime, timezone
39
+
40
+ from platformforge.analytics.history import HistoryEngine
41
+ eng = HistoryEngine()
42
+ eng.ingest_events("incident", [
43
+ {"ts": datetime.now(timezone.utc).isoformat(),
44
+ "subject": "svc:s1", "type": "incident"}], "t")
45
+ pats = eng.patterns("90d")
46
+ ok = not pats or all(p.confidence != "high" for p in pats)
47
+ # a single event must never reach `high`
48
+ return {"ok": ok,
49
+ "detail": f"patterns={len(pats)} "
50
+ f"conf={[p.confidence for p in pats]}"}
51
+
52
+
53
+ def probe_absent_allocation_unknown(tmp) -> dict[str, Any]:
54
+ """§289 — cost with no allocation data reports unallocated/unknown,
55
+ never a fabricated zero."""
56
+ from platformforge.analytics.finops_v4 import cost_hierarchy
57
+ h = cost_hierarchy([])
58
+ ok = h.get("total", -1) == 0 or "unallocated" in h
59
+ # a doc with amounts but no allocation keys keeps them visible
60
+ h2 = cost_hierarchy([{"amount": 500}])
61
+ ok = ok and h2.get("unallocated", 0) >= 1
62
+ return {"ok": ok, "detail": h2}
63
+
64
+
65
+ def probe_centrality_not_criticality(tmp) -> dict[str, Any]:
66
+ """§290 — blast concentration never *assigns* criticality; a
67
+ high-centrality node without declared criticality stays None and
68
+ the entry carries the explicit disclaimer."""
69
+ from platformforge.analytics.reliability import blast_concentration
70
+ from platformforge.graph.model import Edge, Graph, Node
71
+ g = Graph()
72
+ for n in ("a", "b", "c", "d"):
73
+ g.add_node(Node.make("service", n))
74
+ for s, d in (("a", "b"), ("a", "c"), ("a", "d")):
75
+ g.add_edge(Edge(f"service/{s}", f"service/{d}", "depends_on"))
76
+ conc = blast_concentration(g)
77
+ top = conc[0] if conc else {}
78
+ ok = bool(conc) and top.get("declared_criticality") is None and \
79
+ "criticality" in top.get("note", "") and \
80
+ all("in_degree" in e for e in conc)
81
+ return {"ok": ok, "detail": conc}
82
+
83
+
84
+ def probe_low_util_not_deletable(tmp) -> dict[str, Any]:
85
+ """§290 — idle/underused resources are never marked deletable."""
86
+ from platformforge.analytics.finops_v4 import idle_resources
87
+ idle = idle_resources([
88
+ {"id": "vm:1", "requests_30d": 0, "cpu_util": 1,
89
+ "attached": True, "cost_monthly": 100},
90
+ {"id": "vol:2", "orphaned": True, "attached": False,
91
+ "cpu_util": 0, "cost_monthly": 50}])
92
+ ok = bool(idle) and not any(i.get("deletable") for i in idle)
93
+ return {"ok": ok, "detail": idle}
94
+
95
+
96
+ def probe_correlation_not_causality(tmp) -> dict[str, Any]:
97
+ """§291 — repeated co-occurrence produces a hypothesis with
98
+ limitations, never a causal claim."""
99
+ from datetime import datetime, timedelta, timezone
100
+
101
+ from platformforge.analytics.history import HistoryEngine
102
+ eng = HistoryEngine()
103
+ now = datetime.now(timezone.utc)
104
+ evs = [{"ts": (now - timedelta(days=i)).isoformat(),
105
+ "subject": "svc:s", "type": "incident"}
106
+ for i in range(1, 6)]
107
+ eng.ingest_events("incident", evs, "t")
108
+ pats = eng.patterns("90d")
109
+ d = pats[0].to_dict() if pats else {}
110
+ ok = bool(pats) and not d.get("causal_claim", True) and \
111
+ bool(d.get("limitations"))
112
+ return {"ok": ok, "detail": d}
113
+
114
+
115
+ def probe_uncertain_optimization_suppressed(tmp) -> dict[str, Any]:
116
+ """§292 — high-uncertainty opportunities never promote to recs."""
117
+ from platformforge.optimize.engine import OptimizationEngine
118
+ from platformforge.optimize.models import OptimizationOpportunity
119
+ eng = OptimizationEngine()
120
+ eng.add_opportunity(OptimizationOpportunity(
121
+ opportunity_id="o1", type="cost", scope={"svc": "s"},
122
+ evidence=[], uncertainty="high"))
123
+ eng.add_opportunity(OptimizationOpportunity(
124
+ opportunity_id="o2", type="cost", scope={"svc": "s2"},
125
+ evidence=[{"fact_id": "f1"}], uncertainty="low"))
126
+ recs = eng.recommendations()
127
+ ok = len(recs) == 1 and recs[0].recommendation_id.endswith("o2")
128
+ return {"ok": ok, "detail": [r.recommendation_id for r in recs]}
129
+
130
+
131
+ def probe_plan_changeintent_only(tmp) -> dict[str, Any]:
132
+ """§303 — plan() returns a ChangeIntent; no path to envelope."""
133
+ from platformforge.ops.models import ChangeIntent
134
+ from platformforge.optimize.engine import OptimizationEngine
135
+ from platformforge.optimize.models import OptimizationRecommendation
136
+ rec = OptimizationRecommendation(
137
+ recommendation_id="r1", type="cost", scope={"svc": "s"},
138
+ confidence="low")
139
+ ci = OptimizationEngine.plan(rec)
140
+ ok = isinstance(ci, ChangeIntent) and \
141
+ ci.reason.type == "recommendation"
142
+ return {"ok": ok, "detail": ci.reason.type}
143
+
144
+
145
+ def probe_missing_denominator(tmp) -> dict[str, Any]:
146
+ """§195/§361 — cost/token is absent without an observed token
147
+ count; the metric is never fabricated."""
148
+ from platformforge.aiplat.models import ai_unit_economics
149
+ ue = ai_unit_economics(9800, tokens=None)
150
+ ok = "cost_per_1m_tokens" not in ue and ue.get("cost") == 9800
151
+ return {"ok": ok, "detail": ue}
152
+
153
+
154
+ def probe_federation_secret_denied(tmp) -> dict[str, Any]:
155
+ """§302 — export_summary denies any payload containing a secret,
156
+ regardless of classification."""
157
+ from platformforge.federation.node import NodeManifest, export_summary
158
+ node = NodeManifest(node_id="n", capabilities=["fleet"])
159
+ for cls in ("public", "internal"):
160
+ s = export_summary(node, {"api_key": "AKIAIOSFODNN7EXAMPLE"},
161
+ cls)
162
+ if s.get("action") != "deny":
163
+ return {"ok": False, "detail": f"{cls} leaked: {s}"}
164
+ return {"ok": True, "detail": "secrets denied at all tiers"}
165
+
166
+
167
+ def probe_federation_restricted_denied(tmp) -> dict[str, Any]:
168
+ """§302 — restricted classification is denied by default."""
169
+ from platformforge.federation.node import NodeManifest, export_summary
170
+ node = NodeManifest(node_id="n", capabilities=["fleet"])
171
+ s = export_summary(node, {"details": "pii-ish"}, "restricted")
172
+ return {"ok": s.get("action") == "deny", "detail": s}
173
+
174
+
175
+ def probe_member_key_deterministic(tmp) -> dict[str, Any]:
176
+ """§296 — fleet member identity is deterministic across runs."""
177
+ from platformforge.fleet.models import member_key
178
+ a = member_key("cluster", "prod-1")
179
+ b = member_key("cluster", "prod-1")
180
+ c = member_key("cluster", "prod-2")
181
+ return {"ok": a == b and a != c, "detail": a}
182
+
183
+
184
+ def probe_dx_no_person_metrics(tmp) -> dict[str, Any]:
185
+ """§287 — DX metrics never carry per-person/individual fields."""
186
+ from platformforge.analytics.dx import dx_metrics, guard_no_person_metrics
187
+ m = dx_metrics([
188
+ {"path": "web", "requested_at": "2025-11-01T09:00:00Z",
189
+ "ready_at": "2025-11-01T09:30:00Z", "status": "provisioned",
190
+ "approval_wait_s": 300},
191
+ {"path": "web", "requested_at": "2025-11-02T09:00:00Z",
192
+ "ready_at": "2025-11-02T09:20:00Z", "status": "provisioned"}])
193
+ bad = guard_no_person_metrics(m)
194
+ return {"ok": not bad and m.get("scope", "").startswith("team"),
195
+ "detail": f"violations={bad}"}
196
+
197
+
198
+ def probe_history_window_deterministic(tmp) -> dict[str, Any]:
199
+ """§298 — identical events + window → identical patterns."""
200
+ from datetime import datetime, timedelta, timezone
201
+
202
+ from platformforge.analytics.history import HistoryEngine
203
+ now = datetime.now(timezone.utc)
204
+ evs = [{"ts": (now - timedelta(days=i)).isoformat(),
205
+ "subject": "svc:s", "type": "incident"}
206
+ for i in range(1, 4)]
207
+ outs = []
208
+ for _ in range(2):
209
+ eng = HistoryEngine()
210
+ eng.ingest_events("incident", evs, "t")
211
+ outs.append([p.to_dict() for p in eng.patterns("90d")])
212
+ return {"ok": outs[0] == outs[1],
213
+ "detail": f"runs_equal={outs[0] == outs[1]}"}
214
+
215
+
216
+ PROBES = {
217
+ "partial-coverage-visible": probe_partial_coverage_visible,
218
+ "small-sample-confidence": probe_small_sample_confidence,
219
+ "absent-allocation-unknown": probe_absent_allocation_unknown,
220
+ "centrality-not-criticality": probe_centrality_not_criticality,
221
+ "low-util-not-deletable": probe_low_util_not_deletable,
222
+ "correlation-not-causality": probe_correlation_not_causality,
223
+ "uncertain-optimization-suppressed":
224
+ probe_uncertain_optimization_suppressed,
225
+ "plan-changeintent-only": probe_plan_changeintent_only,
226
+ "missing-denominator": probe_missing_denominator,
227
+ "federation-secret-denied": probe_federation_secret_denied,
228
+ "federation-restricted-denied": probe_federation_restricted_denied,
229
+ "member-key-deterministic": probe_member_key_deterministic,
230
+ "dx-no-person-metrics": probe_dx_no_person_metrics,
231
+ "history-window-deterministic": probe_history_window_deterministic,
232
+ }
233
+
234
+
235
+ def probe_fleet_report_all_cited(tmp) -> dict[str, Any]:
236
+ """Every invest_next entry in a fleet report must carry evidence;
237
+ suppressed opportunities stay visible; coverage bounds the verdict."""
238
+ import argparse
239
+ import tempfile
240
+ from pathlib import Path
241
+
242
+ from platformforge.cli.fleetcmd import cmd_fleet
243
+ src = Path("lab/fleets/acme")
244
+ with tempfile.TemporaryDirectory() as td:
245
+ for f in src.glob("*.yaml"):
246
+ (Path(td) / f.name).write_text(f.read_text())
247
+ ns = argparse.Namespace(fleet_cmd="report", path=td, question="",
248
+ window="", limit=5, id="", out="",
249
+ format="json")
250
+ r = cmd_fleet(ns)
251
+ inv = r.get("invest_next", [])
252
+ cited = all(i.get("evidence") for i in inv)
253
+ return {"ok": bool(inv) and cited
254
+ and r.get("portfolio", {}).get("suppressed", 0) > 0
255
+ and r.get("coverage", {}).get("min_member", 1) < 1,
256
+ "detail": {"invest": len(inv), "all_cited": cited,
257
+ "suppressed": r.get("portfolio", {}).get("suppressed")}}
258
+
259
+
260
+ PROBES["fleet-report-all-cited"] = probe_fleet_report_all_cited
@@ -0,0 +1,158 @@
1
+ """Cycle 4.1 — ops invariant probes for eval cases (§137–141).
2
+
3
+ Each probe is a deterministic function returning {"ok": bool,
4
+ "detail": ...} — the eval runner records pass/fail from `ok`. These
5
+ cover the correctness invariants that a scripted lab scenario can't
6
+ reach: tamper detection, refusal codes, builder semantics.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from typing import Any
13
+
14
+
15
+ def probe_material_tamper(tmp) -> dict[str, Any]:
16
+ """§141 — a RollbackMaterial whose file content is tampered is
17
+ detected by its content hash; the store refuses a mismatched
18
+ write."""
19
+ from pathlib import Path
20
+
21
+ from platformforge.ops.material import MaterialStore, capture_material
22
+ store = MaterialStore(root=Path(tmp) / "materials")
23
+ m = capture_material("kubernetes.scale", "s1", "op-t",
24
+ {"replicas": 5}, {"replicas"},
25
+ pre_state={"replicas": 3})
26
+ store.put(m)
27
+ path = Path(tmp) / "materials" / f"{m.hash().split(':', 1)[1]}.json"
28
+ blob = json.loads(path.read_text())
29
+ blob["pre_state"]["replicas"] = 999 # tamper
30
+ recomputed = type(m).from_dict(blob).hash()
31
+ ok = recomputed != m.hash()
32
+ return {"ok": ok, "detail": "tampered material hash "
33
+ f"{recomputed[:24]} != {m.hash()[:24]}"}
34
+
35
+
36
+ def probe_approval_seal_tamper(tmp) -> dict[str, Any]:
37
+ """§140 — modifying a sealed approval invalidates the seal and the
38
+ approval no longer passes check_approval."""
39
+ from platformforge.ops.approval import Approval, check_approval
40
+ ap = Approval(approval_id="a1", subject_hash="sha256:plan",
41
+ scope=["r1"], actor="alice", role="owner")
42
+ ap.seal()
43
+ assert ap.integrity_seal_valid()
44
+ ap.scope = ["r1", "everything-else"] # tamper after seal
45
+ ok = not ap.integrity_seal_valid()
46
+ chk = check_approval([ap], subject_hash="sha256:plan",
47
+ scope=["r1"], current_plan_hash="sha256:plan")
48
+ refused = (chk.refusal or {}).get("refusal") == \
49
+ "PF-OPS-APPROVAL-TAMPERED"
50
+ return {"ok": ok and refused,
51
+ "detail": f"seal_valid={not ok} refusal={refused}"}
52
+
53
+
54
+ def probe_no_delta(tmp) -> dict[str, Any]:
55
+ """§138 — a mutating plan with no ExpectedDelta refuses to mint."""
56
+ from platformforge.ops.engine import mint_envelope
57
+ from platformforge.ops.engine import plan as mkplan
58
+ from platformforge.ops.models import ChangeIntent, ExpectedDelta, PlanStep, Reason
59
+ from platformforge.ops.policy import PolicyDecision
60
+ intent = ChangeIntent(intent_id="i", reason=Reason(type="manual"),
61
+ target_resources=["r"])
62
+ plan = mkplan(intent, steps=[PlanStep(
63
+ step_id="s", action="kubernetes.scale",
64
+ params={"kind": "Deployment", "name": "w", "replicas": 5})],
65
+ expected_delta=ExpectedDelta())
66
+ r = mint_envelope(plan, execution_id="x",
67
+ decisions=[PolicyDecision(policy_id="p",
68
+ decision="allow",
69
+ reason="")],
70
+ approvals=[], risk={}, rollback=None)
71
+ ok = isinstance(r, dict) and r.get("refusal") == "PF-OPS-NO-DELTA"
72
+ return {"ok": ok, "detail": r if not ok else "refused as designed"}
73
+
74
+
75
+ def probe_sot_conflict(tmp) -> dict[str, Any]:
76
+ """§139 — gitops + terraform both claiming the resource →
77
+ conflicted status requiring human review, never silent winner."""
78
+ from platformforge.ops.source_of_truth import resolve
79
+ res = resolve([{"resource": "k8s:x/y/Deployment/z",
80
+ "declared_by": "terraform",
81
+ "managed_by": "argocd"}],
82
+ {"rules": [{"when": "multi-owner",
83
+ "status": "conflicted"}]})
84
+ d = [r.to_dict() for r in res]
85
+ conflicted = any(r.get("status") in ("conflicted", "unresolved")
86
+ or not r.get("resolved", True) for r in d)
87
+ return {"ok": bool(d) and conflicted,
88
+ "detail": d}
89
+
90
+
91
+ def probe_plan_reuse(tmp) -> dict[str, Any]:
92
+ """§15–19 — the same terraform saved plan can never be reused as
93
+ its own rollback."""
94
+ from platformforge.ops.rollback import terraform_plan_reuse_check
95
+ r = terraform_plan_reuse_check("sha256:planA", "sha256:planA")
96
+ return {"ok": r.get("refusal") == "PF-OPS-PLAN-REUSE",
97
+ "detail": r}
98
+
99
+
100
+ def probe_blind_inverse_refused(tmp) -> dict[str, Any]:
101
+ """§6–9 — without captured material the plan is unresolved, never
102
+ executable-by-guess."""
103
+ from platformforge.ops.rollback import build_rollback_plan
104
+ rb = build_rollback_plan(
105
+ [{"step_id": "s", "action": "kubernetes.scale",
106
+ "params": {"replicas": 5}}], materials={})
107
+ return {"ok": rb.status in ("unresolved", "manual-only",
108
+ "impossible") and
109
+ rb.status != "executable",
110
+ "detail": f"status={rb.status}"}
111
+
112
+
113
+ def probe_git_revert_builder(tmp) -> dict[str, Any]:
114
+ """§137 — git.commit material → git revert of the RESULTING commit
115
+ (post-state), not a blind `git reset`."""
116
+ from platformforge.ops.material import capture_material
117
+ from platformforge.ops.rollback_builders import BUILDERS
118
+ m = capture_material("git.commit", "s", "op", {"repo": "r"},
119
+ {"pre_change_commit"},
120
+ pre_state={"pre_change_commit": "aaa"},
121
+ execution_result={"resulting_commit": "bbb"})
122
+ b = BUILDERS["git.commit"]({"repo": "r"}, m.to_dict(), {})
123
+ ok = b is not None and b["action"] == "git.revert_commit" and \
124
+ b["params"].get("commit_sha") == "bbb" and \
125
+ "aaa" not in json.dumps(b["params"])
126
+ return {"ok": ok, "detail": b}
127
+
128
+
129
+ def probe_rollback_unknown(tmp) -> dict[str, Any]:
130
+ """§137 rollback-unknown — an action with no builder and no
131
+ declared strategy stays unresolved."""
132
+ from platformforge.ops.material import capture_material
133
+ from platformforge.ops.rollback import build_rollback_plan
134
+ m = capture_material("kubernetes.rollout_restart", "s", "op",
135
+ {"kind": "Deployment", "name": "w"},
136
+ {"revision"})
137
+ rb = build_rollback_plan(
138
+ [{"step_id": "s", "action": "kubernetes.rollout_restart",
139
+ "params": {}}],
140
+ materials={"s": m}, context={})
141
+ ok = rb.status in ("unresolved", "requires-replan",
142
+ "manual-only") or (
143
+ rb.status == "executable" and rb.actions)
144
+ # without a captured revision the plan must NOT be executable
145
+ return {"ok": ok and rb.status != "executable",
146
+ "detail": f"status={rb.status} actions={rb.actions}"}
147
+
148
+
149
+ PROBES = {
150
+ "material-tamper": probe_material_tamper,
151
+ "approval-seal-tamper": probe_approval_seal_tamper,
152
+ "no-delta": probe_no_delta,
153
+ "sot-conflict": probe_sot_conflict,
154
+ "plan-reuse": probe_plan_reuse,
155
+ "blind-inverse-refused": probe_blind_inverse_refused,
156
+ "git-revert-builder": probe_git_revert_builder,
157
+ "rollback-unknown": probe_rollback_unknown,
158
+ }