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,161 @@
1
+ """Debate engine — bounded disagreement between positions (§126–133).
2
+
3
+ Shape is small by default: ≤4 participants (typically 2 specialists +
4
+ 1 critic), ≤3 rounds, inside the run envelope. Every position must cite
5
+ evidence — a position without evidence is refused, not debated. The
6
+ referee adjudicates on declared axes and returns a receipt; a resolved
7
+ debate still requires the independent verifier afterward (§133).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ from platformforge.agents.contracts import AgentRefusal, Debate, DebatePosition, refusal
15
+ from platformforge.agents.referee import referee
16
+
17
+ DEFAULT_MAX_PARTICIPANTS = 4
18
+ DEFAULT_MAX_ROUNDS = 3
19
+
20
+
21
+ def _position_errors(p: DebatePosition) -> list[str]:
22
+ errs = []
23
+ if not p.agent:
24
+ errs.append("position missing agent")
25
+ if not p.claim:
26
+ errs.append(f"{p.agent or '?'}: claim is empty")
27
+ if not p.evidence:
28
+ errs.append(f"{p.agent or '?'}: position cites no evidence "
29
+ "(§131 — evidence is required to debate)")
30
+ return errs
31
+
32
+
33
+ def run_debate(question: str, positions: list[dict[str, Any] |
34
+ DebatePosition], *, max_rounds: int = DEFAULT_MAX_ROUNDS,
35
+ max_participants: int = DEFAULT_MAX_PARTICIPANTS,
36
+ envelope_budget: dict[str, Any] | None = None,
37
+ ) -> dict[str, Any]:
38
+ """Adjudicate positions through the referee under explicit bounds.
39
+
40
+ positions: dicts with at least {agent, claim, evidence}. Rounds are
41
+ the positions' declared `round` field — a debate never exceeds
42
+ max_rounds or max_participants; over-bounds input is refused, not
43
+ silently truncated.
44
+ """
45
+ if not question.strip():
46
+ return refusal(AgentRefusal.NO_EVIDENCE,
47
+ "debate has no question",
48
+ "state the contested claim precisely")
49
+ pos = [p if isinstance(p, DebatePosition) else DebatePosition(
50
+ agent=p.get("agent", ""), claim=p.get("claim", ""),
51
+ evidence=tuple(p.get("evidence") or p.get("evidence_fact_ids") or ()),
52
+ evidence_tier=p.get("evidence_tier", 7),
53
+ freshness=p.get("freshness", "unresolved"),
54
+ round=p.get("round", 0)) for p in positions]
55
+ participants = {p.agent for p in pos}
56
+ if len(participants) > max_participants:
57
+ return refusal(AgentRefusal.DEBATE_UNRESOLVED,
58
+ f"{len(participants)} participants exceeds bound "
59
+ f"{max_participants} — debates are small, not swarms",
60
+ "drop to the 2 strongest positions + critic")
61
+ if any(p.round > max_rounds for p in pos):
62
+ return refusal(AgentRefusal.DEBATE_UNRESOLVED,
63
+ f"a position declares a round beyond {max_rounds}",
64
+ "re-run inside the 1–3 round bound")
65
+ errs = [e for p in pos for e in _position_errors(p)]
66
+ if errs:
67
+ return refusal(AgentRefusal.NO_EVIDENCE, "; ".join(errs),
68
+ "every position must cite at least one evidence "
69
+ "ref — unsupported positions are dropped, not "
70
+ "debated")
71
+ if not pos:
72
+ return refusal(AgentRefusal.NO_EVIDENCE,
73
+ "no positions to adjudicate",
74
+ "collect positions first")
75
+
76
+ # §136–137 — information gain / stagnation: group positions by round;
77
+ # a round that adds no new evidence ids signals "stop" — later rounds
78
+ # are trimmed (deterministic bound, no unlimited debate).
79
+ by_round: dict[int, list[DebatePosition]] = {}
80
+ for p in pos:
81
+ by_round.setdefault(p.round, []).append(p)
82
+ seen_evidence: set[str] = set()
83
+ kept: list[DebatePosition] = []
84
+ stagnant_at: int | None = None
85
+ for rnd in sorted(by_round):
86
+ new_ids = {e for p in by_round[rnd] for e in p.evidence}
87
+ gain = new_ids - seen_evidence
88
+ if rnd > 0 and not gain:
89
+ stagnant_at = rnd
90
+ break # §137 — no new evidence → stop
91
+ seen_evidence |= new_ids
92
+ kept.extend(by_round[rnd])
93
+ pos = kept
94
+
95
+ decision = referee([{"agent": p.agent, "claim": p.claim,
96
+ "evidence": list(p.evidence),
97
+ "evidence_tier": p.evidence_tier,
98
+ "freshness": p.freshness, "round": p.round}
99
+ for p in pos])
100
+ outcome = ("unresolved" if decision["unresolved"]
101
+ else "tied" if decision["tied"] else "winner")
102
+ debate = Debate(debate_id=decision["receipt"]["receipt_id"],
103
+ question=question, positions=tuple(pos),
104
+ max_participants=max_participants,
105
+ max_rounds=max_rounds,
106
+ round=max(p.round for p in pos),
107
+ outcome=outcome,
108
+ winner=(decision["winner"] or {}).get("agent", ""),
109
+ axes=tuple(decision["axes"]),
110
+ receipt={**decision["receipt"],
111
+ "positions": decision["positions"],
112
+ "verifier_required": True,
113
+ "note": "referee adjudicates disagreement; "
114
+ "verification is still a separate "
115
+ "independent step (§133)"})
116
+ out = debate.to_dict()
117
+ out["stopped_early"] = stagnant_at is not None # §135–137
118
+ if stagnant_at is not None:
119
+ out["stagnant_round"] = stagnant_at
120
+ out["stop_reason"] = "no information gain — round added no new evidence ids"
121
+ if envelope_budget is not None:
122
+ out["budget"] = envelope_budget
123
+ if outcome == "unresolved":
124
+ return {**refusal(AgentRefusal.DEBATE_UNRESOLVED,
125
+ "no position carried enough evidence to win",
126
+ "gather more evidence or escalate to a human "
127
+ "— the gap is real, not a tie-break problem"),
128
+ "debate": out}
129
+ return {"debate": out, "verifier_required": True}
130
+
131
+
132
+ def build_referee_packet(positions: list[dict[str, Any] | DebatePosition],
133
+ *, budget: dict[str, Any] | None = None
134
+ ) -> dict[str, Any]:
135
+ """§131–133 — RefereePacket: the compressed structured view the
136
+ referee receives. Positions + deltas + shared evidence +
137
+ contradictions + budget — never the full transcript."""
138
+ pos = [p if isinstance(p, DebatePosition) else DebatePosition(
139
+ agent=p.get("agent", ""), claim=p.get("claim", ""),
140
+ evidence=tuple(p.get("evidence") or p.get("evidence_fact_ids") or ()),
141
+ round=p.get("round", 0)) for p in positions]
142
+ evidence_counts: dict[str, int] = {}
143
+ for p in pos:
144
+ for e in p.evidence:
145
+ evidence_counts[e] = evidence_counts.get(e, 0) + 1
146
+ shared = sorted(e for e, n in evidence_counts.items() if n > 1)
147
+ # contradictions = claims citing the same evidence id to opposing ends
148
+ # is unresolvable deterministically here; contradictions are the
149
+ # pairwise claim/delta surface the referee must adjudicate.
150
+ deltas = [{"agent": p.agent, "claim": p.claim,
151
+ "own_evidence": sorted(set(p.evidence) - set(shared))}
152
+ for p in pos]
153
+ return {"schema": "platformforge/referee-packet/v1",
154
+ "positions": [{"agent": p.agent, "claim": p.claim,
155
+ "evidence": list(p.evidence),
156
+ "round": p.round} for p in pos],
157
+ "shared_evidence": shared,
158
+ "deltas": deltas,
159
+ "contradictions": "declared by position evidence overlap",
160
+ "budget": budget or {},
161
+ "note": "no full transcript — referee adjudicates structure"}
@@ -0,0 +1,160 @@
1
+ """pf-* executor subagents — one deterministic job each (§57–66).
2
+
3
+ Executors do ONE thing, return structured output, and never dispatch
4
+ other agents. They are thin wrappers over the deterministic engines —
5
+ collect/analyze/judge/graph/ops — so a caller can bound an executor's
6
+ work inside a run envelope without giving it reasoning authority.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from platformforge.models.base import Fact, stable_id
15
+
16
+ EXECUTORS = ("pf-inventory", "pf-extractor", "pf-judge",
17
+ "pf-graph-builder", "pf-reconciler", "pf-simulator",
18
+ "pf-synthesizer", "pf-verifier")
19
+
20
+
21
+ def pf_inventory(root: str | Path) -> dict[str, Any]:
22
+ """§58 — discover artifacts, identify domains + available evidence,
23
+ emit a coverage map. Does not judge."""
24
+ from platformforge.collect.detect import collect
25
+ out = collect(root)
26
+ detected = out.get("detected", {})
27
+ return {"executor": "pf-inventory",
28
+ "domains": sorted(detected),
29
+ "coverage": {d: len(files) for d, files in detected.items()},
30
+ "undetected": len(out.get("undetected", [])),
31
+ "artifact_count": sum(map(len, detected.values())),
32
+ "artifacts": {d: sorted(files) for d, files in
33
+ detected.items()}}
34
+
35
+
36
+ def pf_extract(artifacts: dict[str, Any] | str | Path) -> dict[str, Any]:
37
+ """§59 — Artifact → Facts. Does not assign severity."""
38
+ from platformforge.collect.detect import collect
39
+ if isinstance(artifacts, (str, Path)):
40
+ out = collect(artifacts)
41
+ else:
42
+ out = {"facts": artifacts.get("facts", [])}
43
+ facts = []
44
+ for f in out.get("facts", []):
45
+ facts.append(f if isinstance(f, dict)
46
+ else (f.to_dict() if hasattr(f, "to_dict")
47
+ else {"value": str(f)}))
48
+ return {"executor": "pf-extractor",
49
+ "fact_ids": [f.get("fact_id", "") for f in facts],
50
+ "facts": facts, "count": len(facts)}
51
+
52
+
53
+ def pf_judge(facts: list[dict[str, Any]],
54
+ rules: Any) -> dict[str, Any]:
55
+ """§60 — Facts → Rules → Findings. Never recommends a change."""
56
+ from platformforge.rules.engine import RuleEngine
57
+ fact_objs = [f if isinstance(f, Fact) else Fact.from_dict(f)
58
+ for f in facts]
59
+ eng = rules if isinstance(rules, RuleEngine) else RuleEngine(rules)
60
+ findings, skipped = eng.evaluate(fact_objs)
61
+ return {"executor": "pf-judge",
62
+ "findings": [f.to_dict() for f in findings],
63
+ "skipped": skipped,
64
+ "count": len(findings)}
65
+
66
+
67
+ def pf_graph_build(facts: list[dict[str, Any]]) -> dict[str, Any]:
68
+ """§61 — Facts → Graphfy. No edge without a contributing fact_id."""
69
+ from platformforge.graph.build import GraphBuilder
70
+ gb = GraphBuilder().from_facts(facts)
71
+ g = gb.graph
72
+ edges = g.edges if isinstance(g.edges, dict) else {}
73
+ no_prov = [k for k, e in edges.items()
74
+ if not getattr(e, "source_fact_ids", ())]
75
+ return {"executor": "pf-graph-builder",
76
+ "nodes": len(g.nodes), "edges": len(edges),
77
+ "unprovenanced_edges": sorted(no_prov),
78
+ "graph": g.to_dict() if hasattr(g, "to_dict") else {}}
79
+
80
+
81
+ def pf_reconcile(*, desired: dict[str, Any],
82
+ planned: dict[str, Any],
83
+ observed: dict[str, Any]) -> dict[str, Any]:
84
+ """§62 — desired/planned/observed reconciliation. All three states
85
+ stay distinct; contradictions name both values."""
86
+ def keys(g): return set((g or {}).keys())
87
+ d, p, o = keys(desired), keys(planned), keys(observed)
88
+ return {"executor": "pf-reconciler",
89
+ "desired_only": sorted(d - p - o),
90
+ "planned_not_observed": sorted(p - o),
91
+ "observed_unplanned": sorted(o - p - d),
92
+ "converged": sorted(d & p & o),
93
+ "drift": sorted((p | d) ^ o),
94
+ "receipt": stable_id("PF-REC", str(sorted(d)),
95
+ str(sorted(p)), str(sorted(o)))}
96
+
97
+
98
+ def pf_simulate(*, observed_graph: dict[str, Any] | None = None,
99
+ planned_graph: dict[str, Any] | None = None,
100
+ plan_hash: str = "") -> dict[str, Any]:
101
+ """§63 — safe simulation: planned graph → expected delta.
102
+ Never executes; output is a delta document, not a change."""
103
+ on = set((observed_graph or {}).get("nodes", {}))
104
+ pn = set((planned_graph or {}).get("nodes", {}))
105
+ oe = set((observed_graph or {}).get("edges", {}))
106
+ pe = set((planned_graph or {}).get("edges", {}))
107
+ return {"executor": "pf-simulator",
108
+ "expected_delta": {
109
+ "adds": {"resources": sorted(pn - on),
110
+ "dependencies": sorted(pe - oe)},
111
+ "removes": {"resources": sorted(on - pn),
112
+ "dependencies": sorted(oe - pe)}},
113
+ "plan_hash": plan_hash,
114
+ "simulation_id": stable_id("PF-SIM", plan_hash,
115
+ str(sorted(pn)))}
116
+
117
+
118
+ def pf_synthesize(*, findings: list[dict[str, Any]],
119
+ graph: dict[str, Any] | None = None,
120
+ reviews: list[dict[str, Any]] | None = None,
121
+ referee: dict[str, Any] | None = None) -> dict[str, Any]:
122
+ """§64 — final composition. References only — mints no new facts."""
123
+ return {"executor": "pf-synthesizer",
124
+ "finding_count": len(findings),
125
+ "statuses": {s: sum(1 for f in findings
126
+ if f.get("status") == s)
127
+ for s in {f.get("status") for f in findings}},
128
+ "graph_nodes": len((graph or {}).get("nodes", {})),
129
+ "reviews": len(reviews or []),
130
+ "referee": bool(referee),
131
+ "evidence": sorted({e for f in findings
132
+ for e in (f.get("evidence") or [])}),
133
+ "unresolved": [f.get("claim", "") for f in findings
134
+ if f.get("status") in ("unresolved",
135
+ "unsupported",
136
+ "not-observed")],
137
+ "synthesis_id": stable_id(
138
+ "PF-SYN", str(len(findings)),
139
+ str(len((graph or {}).get("nodes", {}))))}
140
+
141
+
142
+ def pf_verify(*, doc: dict[str, Any],
143
+ expected_hash: str = "",
144
+ receipt_fields: tuple[str, ...] = ("receipt_id",)
145
+ ) -> dict[str, Any]:
146
+ """§65 — mechanical proof checks used by platform-verifier:
147
+ hash recompute, receipt-field presence, criterion→evidence shape."""
148
+ from platformforge.agents.contracts import doc_hash
149
+ problems = []
150
+ got = doc.get("spec_hash") or doc.get("receipt_id") or ""
151
+ if expected_hash and got != expected_hash:
152
+ problems.append(f"hash mismatch: {got[:24]}… != "
153
+ f"{expected_hash[:24]}…")
154
+ for f in receipt_fields:
155
+ if f not in doc:
156
+ problems.append(f"missing receipt field: {f}")
157
+ return {"executor": "pf-verifier",
158
+ "ok": not problems, "problems": problems,
159
+ "doc_hash": doc_hash(doc),
160
+ "check_id": stable_id("PF-PVF", str(sorted(doc)))}
@@ -0,0 +1,48 @@
1
+ """platform-release-guardian — deterministic closure review (§27).
2
+
3
+ Evaluates gates/tests/evals/lab/docs/package/receipts/versioning/
4
+ compatibility/known-gaps from MEASURED inputs and returns READY or an
5
+ explicit blocker list. The guardian never runs the gates itself — the
6
+ CLI feeds it gate results; it never fudges a missing signal into a pass.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ REQUIRED_SIGNALS = ("tests", "evals", "lab", "docs_drift", "package",
14
+ "gates")
15
+
16
+
17
+ def release_review(signals: dict[str, Any],
18
+ known_gaps: tuple[str, ...] = ()) -> dict[str, Any]:
19
+ """signals: {tests: {"pass": n, "fail": n}, evals: {...}, lab: {...},
20
+ docs_drift: bool(pass), package: bool, gates: {name: ok},
21
+ receipts: {...}, versioning: str, compatibility: [notes]}"""
22
+ blockers: list[str] = []
23
+ missing = [s for s in REQUIRED_SIGNALS if s not in signals]
24
+ for s in missing:
25
+ blockers.append(f"missing signal: {s} — unmeasured is not passed")
26
+ t = signals.get("tests") or {}
27
+ if t.get("fail", 0) or t.get("unresolved", 0):
28
+ blockers.append(f"tests: {t.get('fail', 0)} failed, "
29
+ f"{t.get('unresolved', 0)} unresolved")
30
+ for s in ("evals", "lab"):
31
+ v = signals.get(s) or {}
32
+ if v.get("fail", 0) or v.get("unresolved", 0):
33
+ blockers.append(f"{s}: fail={v.get('fail', 0)} "
34
+ f"unresolved={v.get('unresolved', 0)}")
35
+ if signals.get("docs_drift") is False:
36
+ blockers.append("docs drift gate failing")
37
+ if signals.get("package") is False:
38
+ blockers.append("package gate failing")
39
+ gates = signals.get("gates") or {}
40
+ for name, ok in gates.items():
41
+ if not ok:
42
+ blockers.append(f"gate failed: {name}")
43
+ return {"verdict": "READY" if not blockers else "BLOCKED",
44
+ "blockers": blockers,
45
+ "known_gaps": list(known_gaps),
46
+ "signals_seen": sorted(signals),
47
+ "note": "READY requires every signal measured; absent "
48
+ "signals are blockers, not assumptions"}
@@ -0,0 +1,240 @@
1
+ """Host mirrors — render the canonical roster to host surfaces
2
+ (§92–96): agents/, .agents/agents/, .claude/agents/, .codex/agents/,
3
+ .devin/agents/. The roster is the source of truth; mirrors are OUTPUT.
4
+ Never edit a mirror by hand — `agents check` fails on drift.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ from platformforge.agents.roster import AGENTS, AgentSpec
12
+
13
+ GEN_BANNER = ("# GENERATED from platformforge/agents/roster.py "
14
+ "— do not edit; run `platformforge agents sync`\n")
15
+
16
+ # every host gets the canonical mirror set (§92)
17
+ TARGETS: dict[str, str] = {
18
+ "agents": "md",
19
+ ".agents/agents": "md",
20
+ ".claude/agents": "md",
21
+ ".codex/agents": "toml",
22
+ ".devin/agents": "md",
23
+ }
24
+
25
+ # §106 — host adapter: tier → concrete host tools/model. The roster
26
+ # stays provider-free; only mirrors carry host vocabulary. No access
27
+ # tier maps to Edit/Write: platform state is persisted through the
28
+ # `platformforge` CLI (runs/artifacts), never by editing the tree.
29
+ HOST_TOOLS: dict[str, str] = {
30
+ "read-only": "Read, Grep, Glob, Bash",
31
+ "state-writer": "Read, Grep, Glob, Bash",
32
+ "workspace-writer": "Read, Grep, Glob, Bash, Edit, Write",
33
+ "governed-writer": "Read, Grep, Glob, Bash",
34
+ }
35
+ HOST_MODEL: dict[str, str] = {
36
+ "deterministic": "haiku",
37
+ "fast": "haiku",
38
+ "standard": "sonnet",
39
+ "deep": "opus",
40
+ "critical-review": "opus",
41
+ }
42
+
43
+ # domain → host skill carrying the verb reference for that domain
44
+ DOMAIN_SKILL: dict[str, str] = {
45
+ "graph": "platformforge-graph",
46
+ "change": "platformforge-change", "ops": "platformforge-change",
47
+ "plans": "platformforge-change", "release": "platformforge-change",
48
+ "simulate": "platformforge-change", "drift": "platformforge-change",
49
+ "sre": "platformforge-sre", "slo": "platformforge-sre",
50
+ "incident": "platformforge-sre", "capacity": "platformforge-sre",
51
+ "otel": "platformforge-sre", "reliability": "platformforge-sre",
52
+ "runtime": "platformforge-sre",
53
+ "finops": "platformforge-finops",
54
+ "security": "platformforge-security", "privacy": "platformforge-security",
55
+ "fleet": "platformforge-fleet", "federation": "platformforge-fleet",
56
+ "optimization": "platformforge-fleet", "ai": "platformforge-fleet",
57
+ "gpu": "platformforge-fleet",
58
+ "policy": "platformforge-governance",
59
+ "governance": "platformforge-governance",
60
+ "product": "platformforge-governance", "dx": "platformforge-governance",
61
+ "golden-paths": "platformforge-governance",
62
+ "economy": "platformforge-economy",
63
+ "evidence": "platformforge-agents", "closure": "platformforge-agents",
64
+ "proof": "platformforge-agents", "conflicts": "platformforge-agents",
65
+ "tasks": "platformforge-agents", "compose": "platformforge-agents",
66
+ }
67
+
68
+
69
+ def skills_for(spec: AgentSpec) -> list[str]:
70
+ out: list[str] = []
71
+ for d in spec.domains:
72
+ s = DOMAIN_SKILL.get(d, "platformforge-core")
73
+ if s not in out:
74
+ out.append(s)
75
+ return out
76
+
77
+
78
+ def _description(spec: AgentSpec) -> str:
79
+ text = f"{spec.mission}. Use when: {spec.when_to_enter}."
80
+ if spec.when_not_to_enter:
81
+ text += f" Do NOT use when: {spec.when_not_to_enter}."
82
+ return text
83
+
84
+
85
+ def _yaml_str(text: str) -> str:
86
+ return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"'
87
+
88
+
89
+ def _body(spec: AgentSpec) -> str:
90
+ def bullets(items):
91
+ return "\n".join(f"- {i}" for i in items) or "- (none)"
92
+ verbs = ", ".join(f"`platformforge {v}`" for v in spec.allowed_verbs)
93
+ return f"""# {spec.name}
94
+
95
+ You are `{spec.name}`, a Platform Forge {spec.role}. Mission: {spec.mission}.
96
+
97
+ Role: {spec.role} · Access: {spec.access} · Write: {spec.write_scope}
98
+ Model tier: {spec.model_tier} · Budget: {spec.max_context_budget}B ctx /
99
+ {spec.max_tool_calls} tool calls / fanout ≤{spec.max_parallelism}
100
+ Domains: {', '.join(spec.domains)}
101
+
102
+ ## Enter when
103
+ {spec.when_to_enter}
104
+
105
+ ## Do NOT enter when
106
+ {spec.when_not_to_enter}
107
+
108
+ If the request matches "Do NOT enter when", stop and return a named
109
+ refusal with the agent or skill that should take it — do not stretch.
110
+
111
+ ## Inputs
112
+ {bullets(spec.inputs)}
113
+
114
+ ## How to work
115
+ - Do the work through the `platformforge` CLI (fall back to
116
+ `.venv/bin/platformforge` when it is not on PATH). Verbs you may run:
117
+ {verbs or 'none — read-only inspection of supplied artifacts'}.
118
+ - Verb reference and reading rules live in skill(s):
119
+ {', '.join(skills_for(spec))} — load them before running verbs.
120
+ - Cite `fact_id` / `rule_id` / evidence ids for every claim; what you
121
+ cannot back with evidence goes to `unresolved`, never into prose.
122
+ - Stay inside your budget; when it runs out, report `partial`.
123
+
124
+ Capabilities: {', '.join(spec.allowed_capabilities) or '(none declared)'}
125
+ Required evidence: {', '.join(spec.required_evidence) or '(none)'}
126
+
127
+ ## Output
128
+ {', '.join(spec.outputs)}
129
+
130
+ ## Boundaries
131
+ Delegates to: {', '.join(spec.delegates_to) or 'nobody'}
132
+ Never delegates to: {', '.join(spec.cannot_delegate_to) or '(none)'}
133
+ Reviewed by: {', '.join(spec.reviewers) or '(none)'}
134
+ Verifier: {spec.verifier or '(none)'}
135
+ Escalation: {spec.escalation or 'human operator'}
136
+
137
+ ## Done when
138
+ {spec.done_when}
139
+
140
+ ## Never
141
+ {spec.never}; edit repository files; run `change approve|apply` or any
142
+ `ops`/`live` mutation — those stay host-side behind a human gate.
143
+ """
144
+
145
+
146
+ def _md(spec: AgentSpec) -> str:
147
+ # frontmatter must open on line 1 or hosts ignore the agent; the
148
+ # banner therefore lives inside it as a YAML comment.
149
+ return f"""---
150
+ {GEN_BANNER.rstrip()}
151
+ name: {spec.name}
152
+ description: {_yaml_str(_description(spec))}
153
+ tools: {HOST_TOOLS.get(spec.access, HOST_TOOLS["read-only"])}
154
+ model: {HOST_MODEL.get(spec.model_tier, "inherit")}
155
+ ---
156
+
157
+ {_body(spec)}"""
158
+
159
+
160
+ def _toml_str(text: str) -> str:
161
+ return '"""' + text.replace("\\", "\\\\").replace('"""', '\\"""') + '"""'
162
+
163
+
164
+ def _toml(spec: AgentSpec) -> str:
165
+ lines = [GEN_BANNER.rstrip(),
166
+ f'name = "{spec.name}"',
167
+ f'description = {_toml_str(_description(spec))}',
168
+ f'developer_instructions = {_toml_str(_body(spec))}',
169
+ f'role = "{spec.role}"',
170
+ f'access = "{spec.access}"',
171
+ f'model_tier = "{spec.model_tier}"',
172
+ f'domains = {list(spec.domains)!r}'.replace("'", '"'),
173
+ f'verbs = {list(spec.allowed_verbs)!r}'.replace("'", '"'),
174
+ f'verifier = "{spec.verifier}"',
175
+ f'never = {_toml_str(spec.never)}']
176
+ return "\n".join(lines) + "\n"
177
+
178
+
179
+ def render(spec: AgentSpec, fmt: str) -> str:
180
+ return _toml(spec) if fmt == "toml" else _md(spec)
181
+
182
+
183
+ def expected(root: str | Path) -> dict[str, str]:
184
+ """Mirror path → expected content. The drift gate compares this."""
185
+ root = Path(root)
186
+ out: dict[str, str] = {}
187
+ for rel, fmt in TARGETS.items():
188
+ ext = ".toml" if fmt == "toml" else ".md"
189
+ for spec in AGENTS.values():
190
+ p = root / rel / f"{spec.name}{ext}"
191
+ out[p.relative_to(root).as_posix()] = render(spec, fmt)
192
+ return out
193
+
194
+
195
+ def sync(root: str | Path) -> dict[str, list[str]]:
196
+ root = Path(root)
197
+ want = expected(root)
198
+ written: dict[str, list[str]] = {rel: [] for rel in TARGETS}
199
+ for rel in TARGETS:
200
+ (root / rel).mkdir(parents=True, exist_ok=True)
201
+ for rel_path, content in want.items():
202
+ p = root / rel_path
203
+ p.write_text(content, encoding="utf-8")
204
+ written[Path(rel_path).parent.as_posix()].append(rel_path)
205
+ return {k: sorted(v) for k, v in written.items()}
206
+
207
+
208
+ def check(root: str | Path) -> dict[str, object]:
209
+ """Mirror drift gate — on-disk must equal generated; extra files in
210
+ mirror dirs that aren't generated are drift too."""
211
+ root = Path(root)
212
+ want = expected(root)
213
+ problems = []
214
+ for rel_path, content in want.items():
215
+ p = root / rel_path
216
+ if not p.is_file():
217
+ problems.append(f"missing mirror: {rel_path}")
218
+ elif p.read_text(encoding="utf-8") != content:
219
+ problems.append(f"stale mirror: {rel_path}")
220
+ stale_dirs = []
221
+ for rel in TARGETS:
222
+ d = root / rel
223
+ if d.is_dir():
224
+ for f in sorted(d.iterdir()):
225
+ rel_f = f.relative_to(root).as_posix()
226
+ if f.is_file() and rel_f not in want \
227
+ and f.suffix in (".md", ".toml"):
228
+ stale_dirs.append(rel_f)
229
+ problems += [f"stray mirror file (not generated): {s}"
230
+ for s in stale_dirs]
231
+ return {"ok": not problems, "problems": problems,
232
+ "mirrors": len(want)}
233
+
234
+
235
+ def lint() -> dict[str, object]:
236
+ problems = []
237
+ for a in AGENTS.values():
238
+ problems.extend(f"{a.name}: {e}" for e in a.contract_errors())
239
+ return {"agents": len(AGENTS), "problems": problems,
240
+ "ok": not problems}