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,394 @@
1
+ """Agent runtime contracts — canonical, versioned, hashable (cycle5.1).
2
+
3
+ Everything crossing an agent boundary is a typed document:
4
+
5
+ PlatformTaskSpec sealed intent (draft|reviewed|sealed|rejected|expired)
6
+ AgentHandoff structured transfer between agents (refs, not bodies)
7
+ AgentRunEnvelope hard budget limits for one run
8
+ AgentRunRecord auditable run receipt
9
+ Debate bounded, evidence-carrying disagreement
10
+
11
+ Schema ids ship in contracts/*.schema.json and are validated by the
12
+ agents-contract gate. None of these documents mutate platform state —
13
+ agents propose; governed ops execute.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import json
20
+ from dataclasses import asdict, dataclass, field
21
+ from datetime import datetime, timezone
22
+ from typing import Any
23
+
24
+ SCHEMA_AGENT_SPEC = "platformforge/agent-spec/v1"
25
+ SCHEMA_TASK_SPEC = "platformforge/task-spec/v1"
26
+ SCHEMA_HANDOFF = "platformforge/agent-handoff/v1"
27
+ SCHEMA_RUN = "platformforge/agent-run/v1"
28
+ SCHEMA_DEBATE = "platformforge/debate/v1"
29
+
30
+ # §9 — never a bare "writer"; scope is part of the tier's meaning.
31
+ ACCESS_TIERS = ("read-only", "state-writer", "workspace-writer",
32
+ "governed-writer")
33
+ # write_scope refines *what* an agent may persist inside the platform:
34
+ # runs/artifacts are its own audit trail; "governed" still routes through
35
+ # the ops pipeline — no tier mints approval.
36
+ WRITE_SCOPES = ("none", "runs", "artifacts", "governed")
37
+
38
+ # §10 — tiers, not providers. A host adapter maps tier → concrete model.
39
+ MODEL_TIERS = ("deterministic", "fast", "standard", "deep",
40
+ "critical-review")
41
+
42
+ ROLES = ("orchestrator", "planner", "coordinator", "specialist",
43
+ "executor", "reviewer", "critic", "referee", "verifier",
44
+ "guardian")
45
+
46
+ TASK_STATES = ("draft", "reviewed", "sealed", "rejected", "expired")
47
+
48
+ # §98 — shared output status vocabulary for every agent-facing document.
49
+ OUTPUT_STATUSES = ("confirmed", "supported", "candidate", "unresolved",
50
+ "refuted", "unsupported", "not-observed")
51
+
52
+ ROUTING_MODES = ("deterministic", "single-specialist", "multi-specialist",
53
+ "coordinated", "debate", "critical-review")
54
+
55
+
56
+ class AgentRefusal:
57
+ """§100–101 — the PF-AGENT-* namespace. A refusal keeps its code and
58
+ an `unlock` instruction; it is never a soft failure."""
59
+
60
+ NO_EVIDENCE = "PF-AGENT-NO-EVIDENCE"
61
+ BUDGET_EXHAUSTED = "PF-AGENT-BUDGET-EXHAUSTED"
62
+ ROUTE_UNRESOLVED = "PF-AGENT-ROUTE-UNRESOLVED"
63
+ CAPABILITY_UNAVAILABLE = "PF-AGENT-CAPABILITY-UNAVAILABLE"
64
+ INDEPENDENCE_FAILED = "PF-AGENT-INDEPENDENCE-FAILED"
65
+ DEBATE_UNRESOLVED = "PF-AGENT-DEBATE-UNRESOLVED"
66
+ CONTEXT_LIMIT = "PF-AGENT-CONTEXT-LIMIT"
67
+ SPEC_UNSEALED = "PF-AGENT-SPEC-UNSEALED"
68
+ SPEC_REJECTED = "PF-AGENT-SPEC-REJECTED"
69
+ HOST_CAPABILITY = "PF-AGENT-HOST-CAPABILITY"
70
+ SELF_VERIFICATION = "PF-AGENT-SELF-VERIFICATION"
71
+ UNKNOWN_AGENT = "PF-AGENT-UNKNOWN-AGENT"
72
+ ALL = (NO_EVIDENCE, BUDGET_EXHAUSTED, ROUTE_UNRESOLVED,
73
+ CAPABILITY_UNAVAILABLE, INDEPENDENCE_FAILED,
74
+ DEBATE_UNRESOLVED, CONTEXT_LIMIT, SPEC_UNSEALED,
75
+ SPEC_REJECTED, HOST_CAPABILITY, SELF_VERIFICATION,
76
+ UNKNOWN_AGENT)
77
+
78
+
79
+ def refusal(code: str, why: str, unlock: str, **extra: Any) -> dict[str, Any]:
80
+ """Uniform agent refusal — code + reason + unlock, plus context."""
81
+ return {"refusal": code, "why": why, "unlock": unlock, **extra}
82
+
83
+
84
+ def doc_hash(doc: dict[str, Any]) -> str:
85
+ """Content hash over a contract doc (schema field excluded)."""
86
+ body = {k: v for k, v in doc.items() if k != "spec_hash"}
87
+ blob = json.dumps(body, sort_keys=True, default=str)
88
+ return "sha256:" + hashlib.sha256(blob.encode()).hexdigest()
89
+
90
+
91
+ def _now() -> str:
92
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
93
+
94
+
95
+ @dataclass
96
+ class AgentRunEnvelope:
97
+ """§76 — hard limits for one agentic run. Exhaustion is an explicit
98
+ result (PF-AGENT-BUDGET-EXHAUSTED + partial), never silent success."""
99
+
100
+ max_model_calls: int = 8
101
+ max_tool_calls: int = 24
102
+ max_agents: int = 6
103
+ max_fanout: int = 4
104
+ max_context_bytes: int = 120_000
105
+ max_runtime_s: float = 600.0
106
+ max_provider_calls: int = 0
107
+
108
+ # measured spend — monotonic, never reset on resume (§141)
109
+ model_calls: int = 0
110
+ tool_calls: int = 0
111
+ agents_used: int = 0
112
+ fanout: int = 0
113
+ context_bytes: int = 0
114
+ provider_calls: int = 0
115
+ elapsed_s: float = 0.0
116
+
117
+ def to_dict(self) -> dict[str, Any]:
118
+ return asdict(self)
119
+
120
+ @classmethod
121
+ def from_dict(cls, d: dict[str, Any]) -> AgentRunEnvelope:
122
+ return cls(**{k: v for k, v in d.items()
123
+ if k in cls.__dataclass_fields__})
124
+
125
+ def charge(self, *, model_calls: int = 0, tool_calls: int = 0,
126
+ agents: int = 0, fanout: int = 0, context_bytes: int = 0,
127
+ provider_calls: int = 0, elapsed_s: float = 0.0) -> None:
128
+ self.model_calls += model_calls
129
+ self.tool_calls += tool_calls
130
+ self.agents_used += agents
131
+ self.fanout += fanout
132
+ self.context_bytes += context_bytes
133
+ self.provider_calls += provider_calls
134
+ self.elapsed_s += elapsed_s
135
+
136
+ def exceeded(self) -> list[str]:
137
+ dims = []
138
+ for limit, spent, name in (
139
+ (self.max_model_calls, self.model_calls, "model_calls"),
140
+ (self.max_tool_calls, self.tool_calls, "tool_calls"),
141
+ (self.max_agents, self.agents_used, "agents"),
142
+ (self.max_fanout, self.fanout, "fanout"),
143
+ (self.max_context_bytes, self.context_bytes,
144
+ "context_bytes"),
145
+ (self.max_runtime_s, self.elapsed_s, "runtime_s"),
146
+ (self.max_provider_calls, self.provider_calls,
147
+ "provider_calls")):
148
+ if spent > limit:
149
+ dims.append(f"{name} {spent}>{limit}")
150
+ return dims
151
+
152
+ def check(self) -> dict[str, Any] | None:
153
+ """None if inside budget; else the §77/§289 partial-result refusal."""
154
+ over = self.exceeded()
155
+ if not over:
156
+ return None
157
+ return refusal(
158
+ AgentRefusal.BUDGET_EXHAUSTED,
159
+ "run envelope exhausted: " + ", ".join(over),
160
+ "raise the budget class or narrow the task scope and resume "
161
+ "the run — spent budget is preserved, never reset",
162
+ exceeded=over, spent=self.to_dict())
163
+
164
+
165
+ # §112 — budget classes are named envelopes, not magic numbers inline.
166
+ BUDGET_CLASSES: dict[str, AgentRunEnvelope] = {
167
+ "tiny": AgentRunEnvelope(max_model_calls=1, max_tool_calls=4,
168
+ max_agents=1, max_fanout=1,
169
+ max_context_bytes=24_000, max_runtime_s=60),
170
+ "small": AgentRunEnvelope(max_model_calls=2, max_tool_calls=8,
171
+ max_agents=2, max_fanout=2,
172
+ max_context_bytes=48_000, max_runtime_s=120),
173
+ "standard": AgentRunEnvelope(max_model_calls=8, max_tool_calls=24,
174
+ max_agents=6, max_fanout=4,
175
+ max_context_bytes=120_000,
176
+ max_runtime_s=600),
177
+ "deep": AgentRunEnvelope(max_model_calls=16, max_tool_calls=48,
178
+ max_agents=10, max_fanout=6,
179
+ max_context_bytes=320_000,
180
+ max_runtime_s=1800),
181
+ "critical": AgentRunEnvelope(max_model_calls=24, max_tool_calls=64,
182
+ max_agents=12, max_fanout=8,
183
+ max_context_bytes=480_000,
184
+ max_runtime_s=3600),
185
+ }
186
+
187
+
188
+ def envelope_for(budget_class: str) -> AgentRunEnvelope:
189
+ """Fresh envelope for a budget class (unknown → standard)."""
190
+ env = BUDGET_CLASSES.get(budget_class) or BUDGET_CLASSES["standard"]
191
+ return AgentRunEnvelope(**{k: getattr(env, k)
192
+ for k in env.__dataclass_fields__})
193
+
194
+
195
+ @dataclass
196
+ class PlatformTaskSpec:
197
+ """§74–75 — sealed task the orchestrator executes. `state` transitions
198
+ only via taskspec.py: draft → reviewed → sealed (or rejected/expired).
199
+ Orchestrators refuse unsealed specs for non-trivial work (§18)."""
200
+
201
+ task_id: str
202
+ intent: str
203
+ scope: str = "repo" # repo | fleet | runtime | change
204
+ domains: tuple[str, ...] = ()
205
+ risk: str = "low" # low | medium | high | critical
206
+ complexity: str = "low" # low | medium | high
207
+ evidence_requirements: tuple[str, ...] = ()
208
+ acceptance_criteria: tuple[str, ...] = ()
209
+ autonomy: str = "read-only" # read-only | propose | simulate
210
+ allowed_capabilities: tuple[str, ...] = ()
211
+ forbidden_capabilities: tuple[str, ...] = ()
212
+ rollback_requirement: str = "none" # none | declared | material-bound
213
+ verification_requirement: str = "independent-verifier"
214
+ budget: str = "standard" # BUDGET_CLASSES key
215
+ state: str = "draft" # TASK_STATES
216
+ reviewer: str = ""
217
+ review_notes: tuple[str, ...] = ()
218
+ created_at: str = ""
219
+ schema: str = SCHEMA_TASK_SPEC
220
+ spec_hash: str = ""
221
+
222
+ def __post_init__(self) -> None:
223
+ if not self.created_at:
224
+ self.created_at = _now()
225
+ if not self.spec_hash:
226
+ self.spec_hash = doc_hash(self.to_dict())
227
+
228
+ def to_dict(self) -> dict[str, Any]:
229
+ d = asdict(self)
230
+ for k in ("domains", "evidence_requirements",
231
+ "acceptance_criteria", "allowed_capabilities",
232
+ "forbidden_capabilities", "review_notes"):
233
+ d[k] = list(d[k])
234
+ return d
235
+
236
+ @classmethod
237
+ def from_dict(cls, d: dict[str, Any]) -> PlatformTaskSpec:
238
+ kw = {k: v for k, v in d.items() if k in cls.__dataclass_fields__}
239
+ for k in ("domains", "evidence_requirements",
240
+ "acceptance_criteria", "allowed_capabilities",
241
+ "forbidden_capabilities", "review_notes"):
242
+ if k in kw and isinstance(kw[k], list):
243
+ kw[k] = tuple(kw[k])
244
+ return cls(**kw)
245
+
246
+ def transition(self, to: str, reviewer: str = "",
247
+ notes: tuple[str, ...] = ()) -> PlatformTaskSpec:
248
+ """Move state; returns a NEW spec (specs are immutable docs).
249
+ Legal: draft→reviewed|rejected; reviewed→sealed|rejected;
250
+ any→expired. Everything else refuses."""
251
+ legal = {"draft": {"reviewed", "rejected", "expired"},
252
+ "reviewed": {"sealed", "rejected", "expired"},
253
+ "sealed": {"expired"},
254
+ "rejected": set(), "expired": set()}
255
+ if to not in legal.get(self.state, set()):
256
+ raise ValueError(
257
+ f"illegal TaskSpec transition {self.state}→{to}")
258
+ d = self.to_dict()
259
+ d["state"] = to
260
+ d["reviewer"] = reviewer or self.reviewer
261
+ d["review_notes"] = list(notes) or list(self.review_notes)
262
+ d["spec_hash"] = ""
263
+ out = PlatformTaskSpec.from_dict(d)
264
+ return out
265
+
266
+
267
+ @dataclass
268
+ class AgentHandoff:
269
+ """§71–73 — every transfer between agents is structured and carries
270
+ refs (artifact/fact/graph/context-pack), never raw context bodies."""
271
+
272
+ sender: str # serialized as `from`
273
+ to: str
274
+ task_id: str
275
+ reason: str = ""
276
+ completed: tuple[str, ...] = ()
277
+ evidence: tuple[str, ...] = () # fact_id / artifact refs only
278
+ findings: tuple[str, ...] = () # finding ids / summaries
279
+ open_questions: tuple[str, ...] = ()
280
+ unresolved: tuple[str, ...] = ()
281
+ budget_spent: dict[str, Any] = field(default_factory=dict)
282
+ budget_remaining: dict[str, Any] = field(default_factory=dict)
283
+ next_required_capability: str = ""
284
+ context_pack_ref: str = "" # §73 — pack hash, not body
285
+ schema: str = SCHEMA_HANDOFF
286
+
287
+ def to_dict(self) -> dict[str, Any]:
288
+ d = asdict(self)
289
+ d["from"] = d.pop("sender")
290
+ for k in ("completed", "evidence", "findings", "open_questions",
291
+ "unresolved"):
292
+ d[k] = list(d[k])
293
+ return d
294
+
295
+ @classmethod
296
+ def from_dict(cls, d: dict[str, Any]) -> AgentHandoff:
297
+ kw = {k: v for k, v in d.items() if k in cls.__dataclass_fields__}
298
+ if "from" in d:
299
+ kw["sender"] = d["from"]
300
+ for k in ("completed", "evidence", "findings", "open_questions",
301
+ "unresolved"):
302
+ if k in kw and isinstance(kw[k], list):
303
+ kw[k] = tuple(kw[k])
304
+ return cls(**kw)
305
+
306
+
307
+ @dataclass
308
+ class AgentRunRecord:
309
+ """§138–139 — the auditable receipt of one agentic run. Persisted
310
+ under .platformforge/runs/ (append-only)."""
311
+
312
+ run_id: str
313
+ task_spec_hash: str
314
+ router_decision: dict[str, Any] = field(default_factory=dict)
315
+ agents: tuple[str, ...] = ()
316
+ handoffs: tuple[dict[str, Any], ...] = ()
317
+ budget: dict[str, Any] = field(default_factory=dict)
318
+ evidence: tuple[str, ...] = ()
319
+ debates: tuple[str, ...] = ()
320
+ gates: tuple[str, ...] = ()
321
+ verifier: str = ""
322
+ verdict: str = "unresolved" # OUTPUT_STATUSES
323
+ checkpoint: dict[str, Any] = field(default_factory=dict)
324
+ started_at: str = ""
325
+ finished_at: str = ""
326
+ schema: str = SCHEMA_RUN
327
+
328
+ def to_dict(self) -> dict[str, Any]:
329
+ d = asdict(self)
330
+ for k in ("agents", "handoffs", "evidence", "debates", "gates"):
331
+ d[k] = list(d[k])
332
+ return d
333
+
334
+ @classmethod
335
+ def from_dict(cls, d: dict[str, Any]) -> AgentRunRecord:
336
+ kw = {k: v for k, v in d.items() if k in cls.__dataclass_fields__}
337
+ for k in ("agents", "evidence", "debates", "gates"):
338
+ if k in kw and isinstance(kw[k], list):
339
+ kw[k] = tuple(kw[k])
340
+ if isinstance(kw.get("handoffs"), list):
341
+ kw["handoffs"] = tuple(kw["handoffs"])
342
+ return cls(**kw)
343
+
344
+
345
+ @dataclass
346
+ class DebatePosition:
347
+ """One participant's position — must cite evidence (§130–131)."""
348
+ agent: str
349
+ claim: str
350
+ evidence: tuple[str, ...] = ()
351
+ evidence_tier: int = 7
352
+ freshness: str = "unresolved"
353
+ round: int = 0
354
+
355
+ def to_dict(self) -> dict[str, Any]:
356
+ d = asdict(self)
357
+ d["evidence"] = list(self.evidence)
358
+ return d
359
+
360
+
361
+ @dataclass
362
+ class Debate:
363
+ """§126–129 — bounded disagreement. Default shape is small:
364
+ 2 specialists + 1 critic + 1 referee, 1–3 rounds, inside the run
365
+ envelope. A position without evidence is refused, not debated."""
366
+
367
+ debate_id: str
368
+ question: str
369
+ positions: tuple[DebatePosition, ...] = ()
370
+ max_participants: int = 4
371
+ max_rounds: int = 3
372
+ round: int = 0
373
+ outcome: str = "open" # open | winner | tied | unresolved
374
+ winner: str = ""
375
+ axes: tuple[str, ...] = ()
376
+ receipt: dict[str, Any] = field(default_factory=dict)
377
+ schema: str = SCHEMA_DEBATE
378
+
379
+ def to_dict(self) -> dict[str, Any]:
380
+ d = asdict(self)
381
+ d["positions"] = [p.to_dict() for p in self.positions]
382
+ d["axes"] = list(self.axes)
383
+ return d
384
+
385
+ @classmethod
386
+ def from_dict(cls, d: dict[str, Any]) -> Debate:
387
+ kw = {k: v for k, v in d.items() if k in cls.__dataclass_fields__}
388
+ kw["positions"] = tuple(
389
+ DebatePosition(**{k: v for k, v in p.items()
390
+ if k in DebatePosition.__dataclass_fields__})
391
+ for p in d.get("positions", []))
392
+ if isinstance(kw.get("axes"), list):
393
+ kw["axes"] = tuple(kw["axes"])
394
+ return cls(**kw)
@@ -0,0 +1,98 @@
1
+ """Coordinator machinery — bounded dispatch + collection (§28–34, §67).
2
+
3
+ Coordinators may dispatch (only orchestrators/coordinators create
4
+ fan-out, §67); specialists may not spawn. A coordinator's dispatch plan
5
+ is deterministic: TaskSpec domains ∩ coordinator delegates_to →
6
+ AgentHandoff list. Collection merges handoffs without minting facts.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from platformforge.agents.contracts import AgentHandoff, AgentRefusal, PlatformTaskSpec, refusal
14
+ from platformforge.agents.roster import resolve
15
+ from platformforge.models.base import stable_id
16
+
17
+ # coordinator → its canonical loop in rules/catalog/orchestration.yaml
18
+ COORDINATOR_LOOPS = {
19
+ "platform-incident-coordinator": "incident",
20
+ "platform-change-coordinator": "change",
21
+ "platform-fleet-coordinator": "fleet",
22
+ "platform-optimization-coordinator": "optimization",
23
+ "platform-product-coordinator": "platform-audit",
24
+ }
25
+
26
+
27
+ def dispatch_plan(coordinator: str, spec: PlatformTaskSpec) \
28
+ -> dict[str, Any]:
29
+ """coordinator + sealed spec → handoffs to delegated specialists.
30
+
31
+ Only delegates whose declared domains intersect the task's are
32
+ dispatched — a coordinator never fans out to its whole delegate
33
+ list 'just in case' (§68 companion)."""
34
+ coord = resolve(coordinator)
35
+ if coord is None:
36
+ return refusal(AgentRefusal.UNKNOWN_AGENT,
37
+ f"no agent named {coordinator!r}",
38
+ "check the canonical roster")
39
+ if coord.role not in ("coordinator", "orchestrator"):
40
+ return refusal(
41
+ AgentRefusal.HOST_CAPABILITY,
42
+ f"{coordinator} is role={coord.role} — only orchestrators "
43
+ "and coordinators may dispatch (§67)",
44
+ "route through platform-orchestrator")
45
+ targets = [d for d in coord.delegates_to
46
+ if resolve(d) is not None
47
+ and (not spec.domains
48
+ or set(resolve(d).domains) & set(spec.domains)
49
+ or "all" in resolve(d).domains)]
50
+ if not targets:
51
+ return refusal(
52
+ AgentRefusal.ROUTE_UNRESOLVED,
53
+ f"{coordinator} has no delegate covering domains "
54
+ f"{sorted(spec.domains)}",
55
+ "widen the spec domains or escalate to the orchestrator")
56
+ # §67/§113 — fanout is bounded: never dispatch past the
57
+ # coordinator's declared parallelism; the remainder stays queued
58
+ capped = targets[:coord.max_parallelism]
59
+ queued = targets[coord.max_parallelism:]
60
+ handoffs = []
61
+ for t in capped:
62
+ handoffs.append(AgentHandoff(
63
+ sender=coordinator, to=t, task_id=spec.task_id,
64
+ reason=f"dispatch: {spec.intent[:80]}",
65
+ open_questions=tuple(c for c in spec.acceptance_criteria),
66
+ budget_spent={},
67
+ next_required_capability="",
68
+ context_pack_ref="", ))
69
+ return {"coordinator": coordinator,
70
+ "loop": COORDINATOR_LOOPS.get(coord.name, "platform-audit"),
71
+ "dispatched": capped,
72
+ "queued": queued,
73
+ "handoffs": [h.to_dict() for h in handoffs],
74
+ "bounded": not queued}
75
+
76
+
77
+ def collect(handoffs: list[dict[str, Any]]) -> dict[str, Any]:
78
+ """Merge specialist handoffs — completed/evidence/unresolved.
79
+ Collecting is bookkeeping, not adjudication: conflicts go to the
80
+ referee, gaps stay unresolved (§98 vocabulary)."""
81
+ done, ev, unresolved, questions = set(), set(), set(), set()
82
+ for h in handoffs:
83
+ done.update(h.get("completed") or ())
84
+ ev.update(h.get("evidence") or ())
85
+ unresolved.update(h.get("unresolved") or ())
86
+ questions.update(h.get("open_questions") or ())
87
+ return {"completed": sorted(done), "evidence": sorted(ev),
88
+ "unresolved": sorted(unresolved),
89
+ "open_questions": sorted(questions),
90
+ "responded": len(handoffs),
91
+ "collection_id": stable_id(
92
+ "PF-COLL", str(len(handoffs)),
93
+ ",".join(sorted(ev)))}
94
+
95
+
96
+ def coordinator_loop(name: str) -> str:
97
+ """Coordinator name → canonical loop; orchestrator → audit."""
98
+ return COORDINATOR_LOOPS.get(name, "platform-audit")
@@ -0,0 +1,113 @@
1
+ """platform-adversarial-critic — attack the plan before acceptance (§22).
2
+
3
+ The critic runs BEFORE execution/ship and asks the §22 questions as
4
+ deterministic checks over the plan + spec. It does not verify (§23):
5
+ it finds ways this could be wrong; the verifier proves or refuses
6
+ closure after work.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from platformforge.agents.contracts import PlatformTaskSpec
14
+ from platformforge.agents.orchestrator import OrchestrationPlan
15
+ from platformforge.agents.roster import resolve
16
+
17
+ _ATTACKS = (
18
+ ("assumption", "What assumption could make this wrong?"),
19
+ ("missing-evidence", "What evidence is missing?"),
20
+ ("fail-open", "Can this fail open?"),
21
+ ("partial-coverage", "Can partial coverage look complete?"),
22
+ ("rollback", "Can rollback fail?"),
23
+ ("source-ownership", "Can source ownership be wrong?"),
24
+ ("regression", "Can this recommendation create regression?"),
25
+ )
26
+
27
+
28
+ def _has_verifier(plan: OrchestrationPlan) -> bool:
29
+ return any(resolve(n.agent) and resolve(n.agent).role == "verifier"
30
+ for n in plan.dag)
31
+
32
+
33
+ def _has_reviewer(plan: OrchestrationPlan) -> bool:
34
+ return any(resolve(n.agent) and resolve(n.agent).role == "reviewer"
35
+ for n in plan.dag)
36
+
37
+
38
+ def adversarial_review(plan: OrchestrationPlan,
39
+ spec: PlatformTaskSpec) -> dict[str, Any]:
40
+ """Return one challenge per attack axis with a status: ok | risk."""
41
+ checks: list[dict[str, Any]] = []
42
+
43
+ def add(axis: str, status: str, detail: str) -> None:
44
+ checks.append({"axis": axis, "status": status,
45
+ "detail": detail})
46
+
47
+ # assumption — no evidence requirements means conclusions ride on air
48
+ add("assumption",
49
+ "risk" if not spec.evidence_requirements else "ok",
50
+ "spec declares evidence_requirements"
51
+ if spec.evidence_requirements
52
+ else "spec declares no evidence_requirements — any conclusion "
53
+ "could be invented")
54
+
55
+ # missing evidence
56
+ add("missing-evidence",
57
+ "risk" if not spec.acceptance_criteria else "ok",
58
+ "acceptance criteria declared" if spec.acceptance_criteria
59
+ else "no acceptance criteria — closure would be uncheckable")
60
+
61
+ # fail-open — is a verifier in the DAG and is it terminal
62
+ add("fail-open",
63
+ "ok" if _has_verifier(plan) else "risk",
64
+ "verifier stage present" if _has_verifier(plan)
65
+ else "no verifier in DAG — run could close unverified")
66
+
67
+ # partial coverage — fleet/coverage-scoped tasks need coverage evidence
68
+ needs_cov = spec.scope == "fleet" or any(
69
+ "coverage" in e for e in spec.evidence_requirements)
70
+ add("partial-coverage",
71
+ "risk" if needs_cov and "coverage"
72
+ not in " ".join(spec.evidence_requirements) else "ok",
73
+ "coverage evidence required and declared"
74
+ if needs_cov else "no coverage requirement inferred")
75
+
76
+ # rollback — risky specs must demand rollback material
77
+ add("rollback",
78
+ "risk" if spec.risk in ("high", "critical")
79
+ and spec.rollback_requirement == "none" else "ok",
80
+ f"rollback_requirement={spec.rollback_requirement}")
81
+
82
+ # source-ownership — mutating caps must be forbidden
83
+ from platformforge.agents.taskspec import MUTATING_CAPS
84
+ missing = [c for c in MUTATING_CAPS
85
+ if c not in spec.forbidden_capabilities]
86
+ add("source-ownership",
87
+ "risk" if missing else "ok",
88
+ "mutating capabilities forbidden"
89
+ if not missing else "forbidden_capabilities missing: "
90
+ + ", ".join(missing))
91
+
92
+ # regression — envelope must bound fanout
93
+ add("regression",
94
+ "risk" if plan.envelope.max_fanout > plan.envelope.max_agents
95
+ else "ok",
96
+ f"fanout≤{plan.envelope.max_fanout} agents≤"
97
+ f"{plan.envelope.max_agents}")
98
+
99
+ risky = [c for c in checks if c["status"] == "risk"]
100
+ return {"verdict": "risk" if risky else "clear",
101
+ "attacks": checks,
102
+ "open_risks": len(risky),
103
+ "note": "critic attacks before acceptance — the verifier "
104
+ "still decides closure"}
105
+
106
+
107
+ def pre_mortem(spec: PlatformTaskSpec) -> dict[str, Any]:
108
+ """Spec-level attack surface, before a plan exists (§22 questions)."""
109
+ return {"question": spec.intent,
110
+ "axes": [{"axis": a, "question": q} for a, q in _ATTACKS],
111
+ "note": "run adversarial_review(plan, spec) once a DAG "
112
+ "exists — a question without a plan is a checklist, "
113
+ "not a finding"}