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.
- platformforge/__init__.py +7 -0
- platformforge/__main__.py +5 -0
- platformforge/_forge_portal.py +913 -0
- platformforge/_graphstudio.py +376 -0
- platformforge/_graphview.py +181 -0
- platformforge/_installkit.py +1706 -0
- platformforge/agents/__init__.py +156 -0
- platformforge/agents/bench.py +61 -0
- platformforge/agents/contextpack.py +137 -0
- platformforge/agents/contracts.py +394 -0
- platformforge/agents/coordinators.py +98 -0
- platformforge/agents/critic.py +113 -0
- platformforge/agents/debate.py +161 -0
- platformforge/agents/executors.py +160 -0
- platformforge/agents/guardian.py +48 -0
- platformforge/agents/mirrors.py +240 -0
- platformforge/agents/orchestrator.py +295 -0
- platformforge/agents/planner.py +116 -0
- platformforge/agents/playbook.py +51 -0
- platformforge/agents/referee.py +132 -0
- platformforge/agents/reviewers.py +113 -0
- platformforge/agents/roster.py +893 -0
- platformforge/agents/runledger.py +85 -0
- platformforge/agents/runstore.py +74 -0
- platformforge/agents/specialists.py +73 -0
- platformforge/agents/taskspec.py +94 -0
- platformforge/agents/uniqueness.py +115 -0
- platformforge/agents/verifier.py +106 -0
- platformforge/aiplat/__init__.py +4 -0
- platformforge/aiplat/models.py +175 -0
- platformforge/analytics/__init__.py +6 -0
- platformforge/analytics/capacity.py +109 -0
- platformforge/analytics/debt.py +92 -0
- platformforge/analytics/dx.py +69 -0
- platformforge/analytics/finops_v4.py +171 -0
- platformforge/analytics/goldenpath.py +131 -0
- platformforge/analytics/history.py +233 -0
- platformforge/analytics/measurement.py +149 -0
- platformforge/analytics/models.py +127 -0
- platformforge/analytics/opsanalytics.py +119 -0
- platformforge/analytics/policyintel.py +139 -0
- platformforge/analytics/reliability.py +106 -0
- platformforge/analytics/soak.py +135 -0
- platformforge/analytics/store.py +254 -0
- platformforge/bench.py +174 -0
- platformforge/cases/__init__.py +13 -0
- platformforge/cases/casefile.py +106 -0
- platformforge/cases/context_audit.py +134 -0
- platformforge/cases/ledgers.py +174 -0
- platformforge/cases/replay.py +142 -0
- platformforge/cases/routing_audit.py +162 -0
- platformforge/caveman/__init__.py +11 -0
- platformforge/caveman/compress.py +156 -0
- platformforge/cicd/__init__.py +16 -0
- platformforge/cicd/backstage.py +51 -0
- platformforge/cicd/dockerfile.py +64 -0
- platformforge/cicd/github_actions.py +122 -0
- platformforge/cicd/gitlab_ci.py +56 -0
- platformforge/cicd/gitops.py +130 -0
- platformforge/cli/__init__.py +0 -0
- platformforge/cli/fleetcmd.py +457 -0
- platformforge/cli/main.py +3832 -0
- platformforge/cloud/__init__.py +8 -0
- platformforge/cloud/aws.py +254 -0
- platformforge/cloud/common.py +69 -0
- platformforge/cloud/multicloud.py +143 -0
- platformforge/collect/__init__.py +11 -0
- platformforge/collect/detect.py +254 -0
- platformforge/context/__init__.py +10 -0
- platformforge/context/capsule.py +60 -0
- platformforge/context/gateway.py +227 -0
- platformforge/context/refs.py +49 -0
- platformforge/context/sufficiency.py +50 -0
- platformforge/core/__init__.py +1 -0
- platformforge/core/hashing.py +39 -0
- platformforge/core/ownership.py +220 -0
- platformforge/core/receipts.py +74 -0
- platformforge/core/redaction.py +97 -0
- platformforge/core/store.py +119 -0
- platformforge/core/workspace.py +89 -0
- platformforge/data/contracts/agent-handoff.schema.json +26 -0
- platformforge/data/contracts/agent-run.schema.json +27 -0
- platformforge/data/contracts/agent-spec.schema.json +43 -0
- platformforge/data/contracts/capability.schema.json +23 -0
- platformforge/data/contracts/change-event.schema.json +22 -0
- platformforge/data/contracts/change-intent.schema.json +62 -0
- platformforge/data/contracts/change-plan.schema.json +43 -0
- platformforge/data/contracts/collector-receipt.schema.json +36 -0
- platformforge/data/contracts/context-pack.schema.json +29 -0
- platformforge/data/contracts/debate.schema.json +31 -0
- platformforge/data/contracts/distribution-manifest-v1.schema.json +81 -0
- platformforge/data/contracts/drift-event.schema.json +21 -0
- platformforge/data/contracts/fact.schema.json +35 -0
- platformforge/data/contracts/finding.schema.json +20 -0
- platformforge/data/contracts/graph-v2.schema.json +67 -0
- platformforge/data/contracts/graph.schema.json +40 -0
- platformforge/data/contracts/install-receipt-v1.schema.json +49 -0
- platformforge/data/contracts/observation.schema.json +93 -0
- platformforge/data/contracts/receipt.schema.json +21 -0
- platformforge/data/contracts/recommendation.schema.json +32 -0
- platformforge/data/contracts/refusal.schema.json +16 -0
- platformforge/data/contracts/slo-contract.schema.json +25 -0
- platformforge/data/contracts/task-spec.schema.json +37 -0
- platformforge/data/contracts/workspace-manifest-v1.schema.json +62 -0
- platformforge/data/evals/cases/agent-budget-exhaustion/case.yaml +6 -0
- platformforge/data/evals/cases/agent-conflicting-specialists/case.yaml +11 -0
- platformforge/data/evals/cases/agent-fleet-optimization/case.yaml +13 -0
- platformforge/data/evals/cases/agent-host-no-subagents/case.yaml +6 -0
- platformforge/data/evals/cases/agent-incident-multi/case.yaml +13 -0
- platformforge/data/evals/cases/agent-missing-evidence/case.yaml +6 -0
- platformforge/data/evals/cases/agent-multi-domain-audit/case.yaml +14 -0
- platformforge/data/evals/cases/agent-production-change/case.yaml +15 -0
- platformforge/data/evals/cases/agent-security-mandatory/case.yaml +12 -0
- platformforge/data/evals/cases/agent-simple-iac/case.yaml +14 -0
- platformforge/data/evals/cases/approval-integrity-seal/case.yaml +6 -0
- platformforge/data/evals/cases/capacity-hot/case.yaml +7 -0
- platformforge/data/evals/cases/capacity-hot/fixture/capacity.json +5 -0
- platformforge/data/evals/cases/catalog-noowner/case.yaml +7 -0
- platformforge/data/evals/cases/catalog-noowner/fixture/catalog-info.yaml +6 -0
- platformforge/data/evals/cases/contract-fact/case.yaml +5 -0
- platformforge/data/evals/cases/contract-fact/fixture/doc.json +2 -0
- platformforge/data/evals/cases/cosign-unverified/case.yaml +8 -0
- platformforge/data/evals/cases/cosign-unverified/fixture/sig.json +33 -0
- platformforge/data/evals/cases/expected-delta-refusal/case.yaml +6 -0
- platformforge/data/evals/cases/finops-noattr/case.yaml +9 -0
- platformforge/data/evals/cases/finops-noattr/fixture/costs.json +7 -0
- platformforge/data/evals/cases/fleet-inv-absent-allocation-unknown/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-centrality-not-criticality/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-correlation-not-causality/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-dx-no-person-metrics/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-federation-restricted-denied/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-federation-secret-denied/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-history-window-deterministic/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-low-util-not-deletable/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-member-key-deterministic/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-missing-denominator/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-partial-coverage-visible/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-plan-changeintent-only/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-report-all-cited/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-small-sample-confidence/case.yaml +7 -0
- platformforge/data/evals/cases/fleet-inv-uncertain-optimization-suppressed/case.yaml +7 -0
- platformforge/data/evals/cases/gha-dangerous/case.yaml +12 -0
- platformforge/data/evals/cases/gha-dangerous/fixture/.github/workflows/ci.yaml +11 -0
- platformforge/data/evals/cases/gitops-v2/case.yaml +11 -0
- platformforge/data/evals/cases/gitops-v2/fixture/argo.yaml +41 -0
- platformforge/data/evals/cases/graph-k8s-blast/case.yaml +7 -0
- platformforge/data/evals/cases/graph-k8s-blast/fixture/deploy.yaml +15 -0
- platformforge/data/evals/cases/iac-module-nopin/case.yaml +8 -0
- platformforge/data/evals/cases/iac-module-nopin/fixture/main.tf +3 -0
- platformforge/data/evals/cases/iac-plan-replace/case.yaml +8 -0
- platformforge/data/evals/cases/iac-plan-replace/fixture/plan.json +12 -0
- platformforge/data/evals/cases/iac-sg-open/case.yaml +8 -0
- platformforge/data/evals/cases/iac-sg-open/fixture/main.tf +9 -0
- platformforge/data/evals/cases/iam-oidc/case.yaml +8 -0
- platformforge/data/evals/cases/iam-oidc/fixture/policy.json +11 -0
- platformforge/data/evals/cases/iam-wildcard-positive/case.yaml +8 -0
- platformforge/data/evals/cases/iam-wildcard-positive/fixture/policy.json +3 -0
- platformforge/data/evals/cases/k8s-bad-actor/case.yaml +19 -0
- platformforge/data/evals/cases/k8s-bad-actor/fixture/app.yaml +97 -0
- platformforge/data/evals/cases/k8s-beta-api/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-beta-api/fixture/cr.yaml +7 -0
- platformforge/data/evals/cases/k8s-dep-api-boundary/case.yaml +15 -0
- platformforge/data/evals/cases/k8s-dep-api-boundary/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-dep-api-clean/case.yaml +12 -0
- platformforge/data/evals/cases/k8s-dep-api-clean/fixture/app.yaml +29 -0
- platformforge/data/evals/cases/k8s-dep-api-unknown/case.yaml +11 -0
- platformforge/data/evals/cases/k8s-dep-api-unknown/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-dep-api-v120/case.yaml +12 -0
- platformforge/data/evals/cases/k8s-dep-api-v120/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-dep-api-v129/case.yaml +9 -0
- platformforge/data/evals/cases/k8s-dep-api-v129/fixture/app.yaml +25 -0
- platformforge/data/evals/cases/k8s-hpa-noreq/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-hpa-noreq/fixture/app.yaml +28 -0
- platformforge/data/evals/cases/k8s-limits-boundary/case.yaml +10 -0
- platformforge/data/evals/cases/k8s-limits-boundary/fixture/app.yaml +15 -0
- platformforge/data/evals/cases/k8s-limits-negative/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-limits-negative/fixture/deploy.yaml +15 -0
- platformforge/data/evals/cases/k8s-limits-positive/case.yaml +8 -0
- platformforge/data/evals/cases/k8s-limits-positive/fixture/deploy.yaml +14 -0
- platformforge/data/evals/cases/k8s-nopdb/case.yaml +7 -0
- platformforge/data/evals/cases/k8s-nopdb/fixture/app.yaml +34 -0
- platformforge/data/evals/cases/knowledge-registry/case.yaml +10 -0
- platformforge/data/evals/cases/kyverno-dep/case.yaml +10 -0
- platformforge/data/evals/cases/kyverno-dep/fixture/pol.yaml +18 -0
- platformforge/data/evals/cases/kyverno-legacy/case.yaml +8 -0
- platformforge/data/evals/cases/kyverno-legacy/fixture/pol.yaml +7 -0
- platformforge/data/evals/cases/live-aws-collect/case.yaml +11 -0
- platformforge/data/evals/cases/live-aws-collect/fixture/aws/ec2_describe-vpcs.json +15 -0
- platformforge/data/evals/cases/live-aws-collect/fixture/aws/iam_list-users.denied.txt +1 -0
- platformforge/data/evals/cases/live-aws-collect/fixture/aws/sts_get-caller-identity.json +5 -0
- platformforge/data/evals/cases/live-drift/case.yaml +8 -0
- platformforge/data/evals/cases/live-drift/fixture/after.json +22 -0
- platformforge/data/evals/cases/live-drift/fixture/before.json +22 -0
- platformforge/data/evals/cases/live-incident/case.yaml +7 -0
- platformforge/data/evals/cases/live-incident/fixture/changes.json +8 -0
- platformforge/data/evals/cases/live-incident/fixture/incident.json +7 -0
- platformforge/data/evals/cases/live-k8s-collect/case.yaml +10 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_configmaps.denied.txt +1 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_deployments.apps.json +25 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_pods.json +17 -0
- platformforge/data/evals/cases/live-k8s-collect/fixture/kubectl/get_secrets.json +13 -0
- platformforge/data/evals/cases/live-multicluster/case.yaml +7 -0
- platformforge/data/evals/cases/live-multicluster/fixture/clusters.json +6 -0
- platformforge/data/evals/cases/live-plan/case.yaml +7 -0
- platformforge/data/evals/cases/live-plan/fixture/drift.json +8 -0
- platformforge/data/evals/cases/live-reconcile/case.yaml +7 -0
- platformforge/data/evals/cases/live-reconcile/fixture/desired.json +7 -0
- platformforge/data/evals/cases/live-reconcile/fixture/observed.json +18 -0
- platformforge/data/evals/cases/live-secrets-boundary/case.yaml +8 -0
- platformforge/data/evals/cases/live-secrets-boundary/fixture/envelope.json +16 -0
- platformforge/data/evals/cases/live-temporal-graph/case.yaml +10 -0
- platformforge/data/evals/cases/live-temporal-graph/fixture/graph.json +22 -0
- platformforge/data/evals/cases/ops-approval/case.yaml +6 -0
- platformforge/data/evals/cases/ops-convergence/case.yaml +6 -0
- platformforge/data/evals/cases/ops-execution-safety/case.yaml +6 -0
- platformforge/data/evals/cases/ops-intent/case.yaml +6 -0
- platformforge/data/evals/cases/ops-planning/case.yaml +6 -0
- platformforge/data/evals/cases/ops-policy/case.yaml +6 -0
- platformforge/data/evals/cases/ops-rollback/case.yaml +6 -0
- platformforge/data/evals/cases/ops-verification/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-argocd-revision/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-blind-inverse/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-git-commit/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-impossible/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-k8s-annotation-restore/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-k8s-scale/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-material-tamper/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-terraform-replan/case.yaml +6 -0
- platformforge/data/evals/cases/rollback-unknown/case.yaml +6 -0
- platformforge/data/evals/cases/routing-specialist/case.yaml +9 -0
- platformforge/data/evals/cases/rtk-roundtrip/case.yaml +5 -0
- platformforge/data/evals/cases/rtk-roundtrip/fixture/verbose.txt +300 -0
- platformforge/data/evals/cases/sbom-vuln/case.yaml +9 -0
- platformforge/data/evals/cases/sbom-vuln/fixture/sbom.json +9 -0
- platformforge/data/evals/cases/secret-prose-mixed/fixture/app.env +1 -0
- platformforge/data/evals/cases/secret-prose-mixed/fixture/policy-notes.md +7 -0
- platformforge/data/evals/cases/secrets-noleak/case.yaml +5 -0
- platformforge/data/evals/cases/secrets-noleak/fixture/app.env +2 -0
- platformforge/data/evals/cases/slo-fastburn/case.yaml +8 -0
- platformforge/data/evals/cases/slo-fastburn/fixture/events.yaml +2 -0
- platformforge/data/evals/cases/slo-fastburn/fixture/slo.yaml +4 -0
- platformforge/data/evals/cases/slo-noinput/case.yaml +8 -0
- platformforge/data/evals/cases/slo-noinput/fixture/events.yaml +1 -0
- platformforge/data/evals/cases/slo-noinput/fixture/slo.yaml +4 -0
- platformforge/data/evals/cases/sot-conflict/case.yaml +6 -0
- platformforge/data/evals/cases/token-budget/case.yaml +6 -0
- platformforge/data/evals/cases/token-budget/fixture/big.py +50 -0
- platformforge/data/evals/cases/xr-delete/case.yaml +8 -0
- platformforge/data/evals/cases/xr-delete/fixture/xr.yaml +10 -0
- platformforge/data/evals/cases/xr-graph-chain/case.yaml +8 -0
- platformforge/data/evals/cases/xr-graph-chain/fixture/xrd.yaml +90 -0
- platformforge/data/evals/cases/xr-negative-crd/case.yaml +8 -0
- platformforge/data/evals/cases/xr-negative-crd/fixture/widget.yaml +19 -0
- platformforge/data/evals/cases/xr-upbound-mr/case.yaml +8 -0
- platformforge/data/evals/cases/xr-upbound-mr/fixture/bucket.yaml +12 -0
- platformforge/data/evals/qpt-bench/change-review/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/change-review/fixture/plan.json +9 -0
- platformforge/data/evals/qpt-bench/gha-security/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/gha-security/fixture/ci.yml +10 -0
- platformforge/data/evals/qpt-bench/iam-investigate/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/iam-investigate/fixture/policy.json +6 -0
- platformforge/data/evals/qpt-bench/incident-analysis/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/incident-analysis/fixture/query.json +5 -0
- platformforge/data/evals/qpt-bench/incident-analysis/fixture/rules.yaml +6 -0
- platformforge/data/evals/qpt-bench/k8s-review/case.yaml +6 -0
- platformforge/data/evals/qpt-bench/k8s-review/fixture/deploy.yaml +15 -0
- platformforge/data/evals/qpt-bench/tf-security/case.yaml +5 -0
- platformforge/data/evals/qpt-bench/tf-security/fixture/main.tf +17 -0
- platformforge/data/knowledge/aws/eks.yaml +20 -0
- platformforge/data/knowledge/aws/iam.yaml +28 -0
- platformforge/data/knowledge/aws/network-exposure.yaml +19 -0
- platformforge/data/knowledge/aws/organizations.yaml +19 -0
- platformforge/data/knowledge/crossplane/composition-providers.yaml +20 -0
- platformforge/data/knowledge/crossplane/managed-resources.yaml +26 -0
- platformforge/data/knowledge/crossplane/v1-v2.yaml +25 -0
- platformforge/data/knowledge/kubernetes/api-deprecations.yaml +33 -0
- platformforge/data/knowledge/kubernetes/autoscaling-rbac.yaml +27 -0
- platformforge/data/knowledge/kubernetes/gateway-api.yaml +20 -0
- platformforge/data/knowledge/kubernetes/pod-security.yaml +26 -0
- platformforge/data/knowledge/sources.yaml +516 -0
- platformforge/data/knowledge/terraform/lifecycle.yaml +18 -0
- platformforge/data/knowledge/terraform/plan-semantics.yaml +26 -0
- platformforge/data/lab/chaos-pod-kill/chaos.yaml +3 -0
- platformforge/data/lab/chaos-pod-kill/facts.json +13 -0
- platformforge/data/lab/fleets/acme/capacity.yaml +35 -0
- platformforge/data/lab/fleets/acme/costs.yaml +25 -0
- platformforge/data/lab/fleets/acme/events.yaml +41 -0
- platformforge/data/lab/fleets/acme/fleet.yaml +51 -0
- platformforge/data/lab/fleets/acme/graph.yaml +45 -0
- platformforge/data/lab/fleets/acme/policies.yaml +28 -0
- platformforge/data/lab/fleets/acme/requests.yaml +24 -0
- platformforge/data/lab/scenarios/argo-bad-rollout/expected.yaml +6 -0
- platformforge/data/lab/scenarios/argo-bad-rollout/fixture/app.yaml +9 -0
- platformforge/data/lab/scenarios/aws-dump/expected.yaml +19 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/compute.json +4 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/iam.json +7 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/network.json +6 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/org.json +4 -0
- platformforge/data/lab/scenarios/aws-dump/fixture/storage.json +4 -0
- platformforge/data/lab/scenarios/container-unsigned/expected.yaml +5 -0
- platformforge/data/lab/scenarios/container-unsigned/fixture/supply.json +11 -0
- platformforge/data/lab/scenarios/cost-idle-workload/expected.yaml +5 -0
- platformforge/data/lab/scenarios/cost-idle-workload/fixture/costs.json +12 -0
- platformforge/data/lab/scenarios/crossplane-platform/expected.yaml +16 -0
- platformforge/data/lab/scenarios/crossplane-platform/fixture/negative.yaml +18 -0
- platformforge/data/lab/scenarios/crossplane-platform/fixture/xrd.yaml +90 -0
- platformforge/data/lab/scenarios/demo-platform/expected.yaml +16 -0
- platformforge/data/lab/scenarios/demo-platform/fixture/.github/workflows/ci.yml +10 -0
- platformforge/data/lab/scenarios/demo-platform/fixture/deploy.yaml +16 -0
- platformforge/data/lab/scenarios/demo-platform/fixture/infra.tf +5 -0
- platformforge/data/lab/scenarios/fleet-ai/expected.yaml +12 -0
- platformforge/data/lab/scenarios/fleet-capacity/expected.yaml +12 -0
- platformforge/data/lab/scenarios/fleet-cost/expected.yaml +18 -0
- platformforge/data/lab/scenarios/fleet-federation/expected.yaml +11 -0
- platformforge/data/lab/scenarios/fleet-golden-path/expected.yaml +18 -0
- platformforge/data/lab/scenarios/fleet-op-hotspot/expected.yaml +11 -0
- platformforge/data/lab/scenarios/fleet-ownership/expected.yaml +17 -0
- platformforge/data/lab/scenarios/fleet-partial/expected.yaml +10 -0
- platformforge/data/lab/scenarios/fleet-policy-friction/expected.yaml +10 -0
- platformforge/data/lab/scenarios/fleet-recurring-incident/expected.yaml +12 -0
- platformforge/data/lab/scenarios/fleet-report/expected.yaml +13 -0
- platformforge/data/lab/scenarios/fleet-risk/expected.yaml +18 -0
- platformforge/data/lab/scenarios/gitops-drift/expected.yaml +6 -0
- platformforge/data/lab/scenarios/gitops-drift/fixture/app.yaml +12 -0
- platformforge/data/lab/scenarios/k8s-bad-readiness/expected.yaml +6 -0
- platformforge/data/lab/scenarios/k8s-bad-readiness/fixture/deploy.yaml +23 -0
- platformforge/data/lab/scenarios/k8s-bad-readiness/fixture/netpol.yaml +9 -0
- platformforge/data/lab/scenarios/k8s-missing-limits/expected.yaml +6 -0
- platformforge/data/lab/scenarios/k8s-missing-limits/fixture/deploy.yaml +26 -0
- platformforge/data/lab/scenarios/k8s-missing-limits/fixture/netpol.yaml +9 -0
- platformforge/data/lab/scenarios/k8s-single-replica/expected.yaml +5 -0
- platformforge/data/lab/scenarios/k8s-single-replica/fixture/deploy.yaml +26 -0
- platformforge/data/lab/scenarios/k8s-single-replica/fixture/netpol.yaml +4 -0
- platformforge/data/lab/scenarios/live-aws-collect/expected.yaml +4 -0
- platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/ec2_describe-vpcs.json +15 -0
- platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/iam_list-users.denied.txt +1 -0
- platformforge/data/lab/scenarios/live-aws-collect/fixture/aws/sts_get-caller-identity.json +5 -0
- platformforge/data/lab/scenarios/live-k8s-collect/expected.yaml +4 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_configmaps.denied.txt +1 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_deployments.apps.json +25 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_pods.json +17 -0
- platformforge/data/lab/scenarios/live-k8s-collect/fixture/kubectl/get_secrets.json +13 -0
- platformforge/data/lab/scenarios/live-kind/expected.yaml +14 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_configmaps.denied.txt +1 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_deployments.apps.json +25 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_pods.json +17 -0
- platformforge/data/lab/scenarios/live-kind/fixture/kubectl/get_secrets.json +13 -0
- platformforge/data/lab/scenarios/network-policy-missing/expected.yaml +6 -0
- platformforge/data/lab/scenarios/network-policy-missing/fixture/deploy.yaml +26 -0
- platformforge/data/lab/scenarios/ops-bad-rollout/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-bad-rollout/fixture/ops.yaml +33 -0
- platformforge/data/lab/scenarios/ops-break-glass/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-break-glass/fixture/ops.yaml +21 -0
- platformforge/data/lab/scenarios/ops-broken-selector/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-broken-selector/fixture/ops.yaml +21 -0
- platformforge/data/lab/scenarios/ops-changed-plan-hash/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-changed-plan-hash/fixture/ops.yaml +20 -0
- platformforge/data/lab/scenarios/ops-conflicting-operation/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-conflicting-operation/fixture/ops.yaml +21 -0
- platformforge/data/lab/scenarios/ops-expired-approval/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-expired-approval/fixture/ops.yaml +20 -0
- platformforge/data/lab/scenarios/ops-partial-failure/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-partial-failure/fixture/ops.yaml +24 -0
- platformforge/data/lab/scenarios/ops-policy-denial/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-policy-denial/fixture/ops.yaml +17 -0
- platformforge/data/lab/scenarios/ops-replica-drift/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-replica-drift/fixture/ops.yaml +28 -0
- platformforge/data/lab/scenarios/ops-rollback-annotate/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-annotate/fixture/ops.yaml +41 -0
- platformforge/data/lab/scenarios/ops-rollback-gitops/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-gitops/fixture/ops.yaml +38 -0
- platformforge/data/lab/scenarios/ops-rollback-impossible/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-impossible/fixture/ops.yaml +34 -0
- platformforge/data/lab/scenarios/ops-rollback-scale/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-scale/fixture/ops.yaml +40 -0
- platformforge/data/lab/scenarios/ops-rollback-terraform/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-rollback-terraform/fixture/ops.yaml +38 -0
- platformforge/data/lab/scenarios/ops-rollback-terraform/fixture/tf/tfplan.bin +1 -0
- platformforge/data/lab/scenarios/ops-stale-observation/expected.yaml +3 -0
- platformforge/data/lab/scenarios/ops-stale-observation/fixture/ops.yaml +20 -0
- platformforge/data/lab/scenarios/secret-exposure/expected.yaml +5 -0
- platformforge/data/lab/scenarios/secret-exposure/fixture/app.env +2 -0
- platformforge/data/lab/scenarios/slo-burn/expected.yaml +5 -0
- platformforge/data/lab/scenarios/slo-burn/fixture/events.yaml +2 -0
- platformforge/data/lab/scenarios/slo-burn/fixture/slo.yaml +4 -0
- platformforge/data/lab/scenarios/terraform-iam-wildcard/expected.yaml +5 -0
- platformforge/data/lab/scenarios/terraform-iam-wildcard/fixture/policy.json +14 -0
- platformforge/data/lab/scenarios/terraform-public-bucket/expected.yaml +6 -0
- platformforge/data/lab/scenarios/terraform-public-bucket/fixture/main.tf +5 -0
- platformforge/data/portable_skills/platformforge-agents/SKILL.md +47 -0
- platformforge/data/portable_skills/platformforge-change/SKILL.md +41 -0
- platformforge/data/portable_skills/platformforge-core/SKILL.md +62 -0
- platformforge/data/portable_skills/platformforge-economy/SKILL.md +42 -0
- platformforge/data/portable_skills/platformforge-finops/SKILL.md +34 -0
- platformforge/data/portable_skills/platformforge-fleet/SKILL.md +39 -0
- platformforge/data/portable_skills/platformforge-governance/SKILL.md +76 -0
- platformforge/data/portable_skills/platformforge-graph/SKILL.md +46 -0
- platformforge/data/portable_skills/platformforge-lab/SKILL.md +39 -0
- platformforge/data/portable_skills/platformforge-sdd/SKILL.md +39 -0
- platformforge/data/portable_skills/platformforge-security/SKILL.md +38 -0
- platformforge/data/portable_skills/platformforge-sre/SKILL.md +41 -0
- platformforge/data/rules/catalog/finops.yaml +26 -0
- platformforge/data/rules/catalog/gitops.yaml +161 -0
- platformforge/data/rules/catalog/iac.yaml +84 -0
- platformforge/data/rules/catalog/k8s.yaml +322 -0
- platformforge/data/rules/catalog/orchestration.yaml +99 -0
- platformforge/data/rules/catalog/product.yaml +42 -0
- platformforge/data/rules/catalog/routing.yaml +122 -0
- platformforge/data/rules/catalog/security.yaml +159 -0
- platformforge/data/rules/catalog/sre.yaml +67 -0
- platformforge/diagnose/__init__.py +75 -0
- platformforge/distribution/__init__.py +18 -0
- platformforge/distribution/bundle.py +127 -0
- platformforge/distribution/hostflow.py +247 -0
- platformforge/distribution/models.py +80 -0
- platformforge/distribution/service.py +225 -0
- platformforge/economy/__init__.py +27 -0
- platformforge/economy/bench.py +66 -0
- platformforge/economy/budget.py +198 -0
- platformforge/economy/cache.py +287 -0
- platformforge/economy/checkpoint.py +112 -0
- platformforge/economy/collection.py +142 -0
- platformforge/economy/doctor.py +207 -0
- platformforge/economy/engine.py +158 -0
- platformforge/economy/envelope.py +61 -0
- platformforge/economy/fleetpack.py +86 -0
- platformforge/economy/ledger.py +232 -0
- platformforge/economy/plan.py +131 -0
- platformforge/economy/pricing.py +101 -0
- platformforge/economy/qpt.py +286 -0
- platformforge/economy/reconcile.py +86 -0
- platformforge/economy/verifyplan.py +107 -0
- platformforge/economy/waste.py +123 -0
- platformforge/evals/__init__.py +16 -0
- platformforge/evals/coverage.py +140 -0
- platformforge/evals/fleet_invariants.py +260 -0
- platformforge/evals/ops_invariants.py +158 -0
- platformforge/evals/runner.py +541 -0
- platformforge/federation/__init__.py +4 -0
- platformforge/federation/node.py +135 -0
- platformforge/finops/__init__.py +14 -0
- platformforge/finops/alloc.py +31 -0
- platformforge/finops/costs.py +79 -0
- platformforge/finops/focus.py +100 -0
- platformforge/finops/graph_cost.py +51 -0
- platformforge/finops/ingest.py +144 -0
- platformforge/finops/insights.py +193 -0
- platformforge/finops/unit.py +36 -0
- platformforge/fleet/__init__.py +5 -0
- platformforge/fleet/loader.py +73 -0
- platformforge/fleet/models.py +155 -0
- platformforge/fleet/orggraph.py +172 -0
- platformforge/fleet/query.py +154 -0
- platformforge/forge/__init__.py +9 -0
- platformforge/forge/collect.py +88 -0
- platformforge/forge/delegate.py +77 -0
- platformforge/forge/manifest.py +106 -0
- platformforge/freeze/__init__.py +22 -0
- platformforge/freeze/exceptions.py +56 -0
- platformforge/freeze/manifest.py +167 -0
- platformforge/freeze/snapshots.py +108 -0
- platformforge/graph/__init__.py +39 -0
- platformforge/graph/backend.py +174 -0
- platformforge/graph/bench.py +245 -0
- platformforge/graph/build.py +130 -0
- platformforge/graph/delivery.py +183 -0
- platformforge/graph/diff.py +292 -0
- platformforge/graph/events.py +116 -0
- platformforge/graph/migrate.py +44 -0
- platformforge/graph/model.py +264 -0
- platformforge/graph/persist.py +97 -0
- platformforge/graph/query.py +290 -0
- platformforge/graph/temporal.py +78 -0
- platformforge/graph/vocab.py +63 -0
- platformforge/graphview.py +94 -0
- platformforge/iac/__init__.py +7 -0
- platformforge/iac/drift.py +53 -0
- platformforge/iac/plan.py +84 -0
- platformforge/iac/terraform.py +123 -0
- platformforge/install/__init__.py +1 -0
- platformforge/install/service.py +421 -0
- platformforge/k8s/__init__.py +8 -0
- platformforge/k8s/helm.py +152 -0
- platformforge/k8s/hubble.py +87 -0
- platformforge/k8s/manifests.py +513 -0
- platformforge/knowledge/__init__.py +1 -0
- platformforge/knowledge/packs.py +157 -0
- platformforge/knowledge/registry.py +249 -0
- platformforge/knowledge/select.py +127 -0
- platformforge/lab/__init__.py +5 -0
- platformforge/lab/chaos.py +71 -0
- platformforge/lab/fleetlab.py +351 -0
- platformforge/lab/opslab.py +271 -0
- platformforge/lab/runner.py +219 -0
- platformforge/live/__init__.py +36 -0
- platformforge/live/budget.py +92 -0
- platformforge/live/capability.py +80 -0
- platformforge/live/collectors/__init__.py +6 -0
- platformforge/live/collectors/aws.py +370 -0
- platformforge/live/collectors/aws_transport.py +98 -0
- platformforge/live/collectors/k8s.py +389 -0
- platformforge/live/collectors/k8s_transport.py +85 -0
- platformforge/live/cursors.py +48 -0
- platformforge/live/drift.py +130 -0
- platformforge/live/envelope.py +85 -0
- platformforge/live/federation.py +113 -0
- platformforge/live/fixtures.py +168 -0
- platformforge/live/identity.py +258 -0
- platformforge/live/incident.py +200 -0
- platformforge/live/models.py +435 -0
- platformforge/live/reconcile.py +382 -0
- platformforge/live/remediate.py +169 -0
- platformforge/live/store.py +256 -0
- platformforge/live/topology.py +249 -0
- platformforge/mcp/__init__.py +7 -0
- platformforge/mcp/parity.py +69 -0
- platformforge/mcp/registry.py +427 -0
- platformforge/mcp/server.py +84 -0
- platformforge/mcp/tools.py +732 -0
- platformforge/models/__init__.py +21 -0
- platformforge/models/base.py +214 -0
- platformforge/observe/__init__.py +26 -0
- platformforge/observe/capacity.py +39 -0
- platformforge/observe/dr.py +67 -0
- platformforge/observe/grafana.py +76 -0
- platformforge/observe/incident.py +58 -0
- platformforge/observe/otel.py +135 -0
- platformforge/observe/postmortem.py +72 -0
- platformforge/observe/prometheus.py +88 -0
- platformforge/observe/slo.py +125 -0
- platformforge/ops/__init__.py +8 -0
- platformforge/ops/actions.py +215 -0
- platformforge/ops/analytics.py +83 -0
- platformforge/ops/approval.py +275 -0
- platformforge/ops/autorem.py +101 -0
- platformforge/ops/config.py +175 -0
- platformforge/ops/engine.py +758 -0
- platformforge/ops/envelope.py +135 -0
- platformforge/ops/executors/__init__.py +1 -0
- platformforge/ops/executors/base.py +211 -0
- platformforge/ops/executors/git.py +77 -0
- platformforge/ops/executors/gitops.py +134 -0
- platformforge/ops/executors/terraform.py +84 -0
- platformforge/ops/gates.py +135 -0
- platformforge/ops/goldenpath.py +180 -0
- platformforge/ops/material.py +181 -0
- platformforge/ops/models.py +421 -0
- platformforge/ops/operation.py +335 -0
- platformforge/ops/opgraph.py +233 -0
- platformforge/ops/policy.py +275 -0
- platformforge/ops/preconditions.py +109 -0
- platformforge/ops/registry.py +101 -0
- platformforge/ops/risk.py +216 -0
- platformforge/ops/rollback.py +287 -0
- platformforge/ops/rollback_builders.py +163 -0
- platformforge/ops/runbook.py +269 -0
- platformforge/ops/simulate.py +125 -0
- platformforge/ops/source_of_truth.py +198 -0
- platformforge/ops/store.py +157 -0
- platformforge/ops/verify.py +190 -0
- platformforge/optimize/__init__.py +4 -0
- platformforge/optimize/engine.py +109 -0
- platformforge/optimize/models.py +68 -0
- platformforge/optimize/scan.py +196 -0
- platformforge/plan/__init__.py +107 -0
- platformforge/portable_cli.py +199 -0
- platformforge/portal_adapter.py +740 -0
- platformforge/portal_assets/chunk-7_l-b4Tr.js +1 -0
- platformforge/portal_assets/chunk-B0Iad6pD.js +1 -0
- platformforge/portal_assets/chunk-B605XnCF.js +1 -0
- platformforge/portal_assets/chunk-B7N3TXcn.js +1 -0
- platformforge/portal_assets/chunk-BAj6yGem.js +1 -0
- platformforge/portal_assets/chunk-BGUitn2r.js +1 -0
- platformforge/portal_assets/chunk-BVKnCv85.js +1 -0
- platformforge/portal_assets/chunk-BYyM9V_w.js +1 -0
- platformforge/portal_assets/chunk-BaQMhq-G.js +1 -0
- platformforge/portal_assets/chunk-BavOrxVm.js +1 -0
- platformforge/portal_assets/chunk-Bb0C24YV.js +1 -0
- platformforge/portal_assets/chunk-BfCFGPzs.js +1 -0
- platformforge/portal_assets/chunk-C2t4OUVu.js +1 -0
- platformforge/portal_assets/chunk-C3pI4QjU.js +1 -0
- platformforge/portal_assets/chunk-C4gpbtbP.js +1 -0
- platformforge/portal_assets/chunk-CGWeHhKY.js +1 -0
- platformforge/portal_assets/chunk-CJCT86Dr.js +1 -0
- platformforge/portal_assets/chunk-CXcsLDPJ.js +1 -0
- platformforge/portal_assets/chunk-CcjxoLxw.js +1 -0
- platformforge/portal_assets/chunk-D-gBPXHa.js +1 -0
- platformforge/portal_assets/chunk-D3eZogIe.js +1 -0
- platformforge/portal_assets/chunk-DQU0VyoQ.js +1 -0
- platformforge/portal_assets/chunk-DqWNkF1O.js +1 -0
- platformforge/portal_assets/chunk-DuPPkSIE.js +3 -0
- platformforge/portal_assets/chunk-DyIHOW_a.js +1 -0
- platformforge/portal_assets/chunk-Fx4zSGL5.js +1 -0
- platformforge/portal_assets/chunk-UBKt05Rg.js +1 -0
- platformforge/portal_assets/chunk-htnJDQEY.js +1 -0
- platformforge/portal_assets/chunk-kXRFMGqe.js +1 -0
- platformforge/portal_assets/chunk-qe3u763a.js +1 -0
- platformforge/portal_assets/favicon.ico +0 -0
- platformforge/portal_assets/index.html +13 -0
- platformforge/portal_assets/main-QDFVZK3T.js +5 -0
- platformforge/portal_assets/styles-MQEBDFDX.css +1 -0
- platformforge/product/__init__.py +10 -0
- platformforge/product/backstage.py +49 -0
- platformforge/product/catalog.py +74 -0
- platformforge/product/crossplane.py +341 -0
- platformforge/product/golden_paths/__init__.py +5 -0
- platformforge/product/golden_paths/engine.py +97 -0
- platformforge/product/golden_paths/library.yaml +198 -0
- platformforge/product/golden_paths/model.py +84 -0
- platformforge/product/maturity.py +107 -0
- platformforge/product/scorecards.py +84 -0
- platformforge/resources.py +36 -0
- platformforge/risk/__init__.py +5 -0
- platformforge/risk/engine.py +95 -0
- platformforge/risk/signals.py +49 -0
- platformforge/routing/__init__.py +13 -0
- platformforge/routing/decision.py +191 -0
- platformforge/routing/router.py +328 -0
- platformforge/rtk/__init__.py +9 -0
- platformforge/rtk/compact.py +127 -0
- platformforge/rtk/parsers.py +199 -0
- platformforge/rules/__init__.py +5 -0
- platformforge/rules/engine.py +345 -0
- platformforge/sandbox/__init__.py +7 -0
- platformforge/sandbox/env.py +151 -0
- platformforge/sandbox/review.py +92 -0
- platformforge/sdd/__init__.py +8 -0
- platformforge/sdd/lifecycle.py +239 -0
- platformforge/security/__init__.py +19 -0
- platformforge/security/cosign.py +67 -0
- platformforge/security/iam.py +112 -0
- platformforge/security/identity.py +84 -0
- platformforge/security/kyverno.py +106 -0
- platformforge/security/sbom.py +59 -0
- platformforge/security/scan.py +63 -0
- platformforge/security/slsa.py +86 -0
- platformforge/security/supply.py +62 -0
- platformforge/tokensave/__init__.py +20 -0
- platformforge/tokensave/budget.py +62 -0
- platformforge/tokensave/estimate.py +21 -0
- platformforge/tokensave/index.py +172 -0
- platformforge/tokensave/ledger.py +121 -0
- platformforge/tokensave/packs.py +285 -0
- platformforge/ui/__init__.py +28 -0
- platformforge/ui/app.py +465 -0
- platformforge/ui/home.py +113 -0
- platformforge/ui/i18n.py +143 -0
- platformforge/ui/kit.py +689 -0
- platformforge/ui/screen.py +628 -0
- platformforge/ui/tui.py +94 -0
- platformforge/ui/wizard.py +309 -0
- platformforge/workspace/__init__.py +13 -0
- platformforge/workspace/models.py +35 -0
- platformforge/workspace/service.py +69 -0
- platformforge-0.1.0.dist-info/METADATA +212 -0
- platformforge-0.1.0.dist-info/RECORD +659 -0
- platformforge-0.1.0.dist-info/WHEEL +4 -0
- platformforge-0.1.0.dist-info/entry_points.txt +3 -0
- platformforge-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
"""Tool dispatch — same core as the CLI, output bounded by capability.
|
|
2
|
+
|
|
3
|
+
Oversized results are written to the artifact store and returned as a
|
|
4
|
+
reference + summary instead of a wall of text (MCP output stays bounded)."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from platformforge.core.store import ArtifactStore
|
|
13
|
+
from platformforge.mcp.registry import CAPABILITIES
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _dispatch(handler: str, inp: dict[str, Any], repo: str) -> Any:
|
|
17
|
+
"""Resolve handler to the same functions cmd_* use."""
|
|
18
|
+
if handler == "cli:inspect":
|
|
19
|
+
import argparse
|
|
20
|
+
|
|
21
|
+
from platformforge.cli.main import cmd_inspect
|
|
22
|
+
ns = argparse.Namespace(repo=inp.get("repo", repo), format="json",
|
|
23
|
+
strict=False)
|
|
24
|
+
import contextlib
|
|
25
|
+
import io
|
|
26
|
+
buf = io.StringIO()
|
|
27
|
+
with contextlib.redirect_stdout(buf):
|
|
28
|
+
cmd_inspect(ns)
|
|
29
|
+
return json.loads(buf.getvalue())
|
|
30
|
+
if handler == "cli:analyze":
|
|
31
|
+
return _analyze(inp.get("domain"), inp.get("path"))
|
|
32
|
+
if handler == "cli:judge":
|
|
33
|
+
from platformforge.models import Fact
|
|
34
|
+
from platformforge.rules import RuleEngine, load_catalog
|
|
35
|
+
doc = json.loads(Path(inp["facts_path"]).read_text())
|
|
36
|
+
facts = [Fact.from_dict(f) for f in doc.get("facts", doc)]
|
|
37
|
+
rules = load_catalog(Path(repo).parent / "rules" / "catalog"
|
|
38
|
+
if (Path(repo).parent / "rules").exists()
|
|
39
|
+
else _default_catalog())
|
|
40
|
+
findings, skipped = RuleEngine(rules).evaluate(facts)
|
|
41
|
+
return {"findings": [f.to_dict() for f in findings],
|
|
42
|
+
"skipped": skipped}
|
|
43
|
+
if handler == "cli:graph":
|
|
44
|
+
return _graph(inp, repo)
|
|
45
|
+
if handler == "cli:observe_slo":
|
|
46
|
+
from platformforge.observe import SloContract, error_budget
|
|
47
|
+
c = SloContract.load(inp["contract"])
|
|
48
|
+
return error_budget(c, **(inp.get("sli") or {}))
|
|
49
|
+
if handler == "cli:economy":
|
|
50
|
+
from platformforge.economy import EconomyEngine
|
|
51
|
+
return EconomyEngine(repo).report()
|
|
52
|
+
if handler == "cli:economy_explain":
|
|
53
|
+
from platformforge.economy.checkpoint import CheckpointStore
|
|
54
|
+
from platformforge.economy.ledger import unified_view
|
|
55
|
+
st = CheckpointStore(
|
|
56
|
+
Path(repo) / ".platformforge" / "checkpoints")
|
|
57
|
+
cp = st.load(inp["run_id"])
|
|
58
|
+
if cp is None:
|
|
59
|
+
return {"refusal": "PF-CHECKPOINT-MISSING",
|
|
60
|
+
"run_id": inp["run_id"]}
|
|
61
|
+
return {"run_id": inp["run_id"], "checkpoint": cp.to_dict(),
|
|
62
|
+
"ledger": unified_view(repo)}
|
|
63
|
+
if handler == "cli:context_inspect":
|
|
64
|
+
from platformforge.context.gateway import ContextGateway
|
|
65
|
+
return ContextGateway(repo).inspect(inp["ref"])
|
|
66
|
+
if handler == "cli:context_expand":
|
|
67
|
+
from platformforge.context.gateway import ContextGateway
|
|
68
|
+
return ContextGateway(repo).expand(inp["ref"],
|
|
69
|
+
inp.get("section", ""))
|
|
70
|
+
if handler == "cli:routing_explain":
|
|
71
|
+
from platformforge.routing.decision import RoutingRequest, decide, receipt
|
|
72
|
+
req = RoutingRequest(task=inp.get("task", ""),
|
|
73
|
+
profile=inp.get("profile", "balanced"),
|
|
74
|
+
risk=inp.get("risk", "low"),
|
|
75
|
+
signal=inp.get("signal") or {})
|
|
76
|
+
dec = decide(req)
|
|
77
|
+
return {"request": {"task": req.task,
|
|
78
|
+
"effective_profile": req.effective_profile(),
|
|
79
|
+
"risk": req.risk},
|
|
80
|
+
"receipt": receipt(req, dec, {})}
|
|
81
|
+
if handler == "cli:product_maturity":
|
|
82
|
+
from platformforge.product import maturity
|
|
83
|
+
return maturity(inp["signals"])
|
|
84
|
+
if handler == "cli:agents_list":
|
|
85
|
+
from platformforge.agents import AGENTS
|
|
86
|
+
return {"agents": [a.to_dict() for a in AGENTS.values()]}
|
|
87
|
+
if handler == "cli:collect":
|
|
88
|
+
from platformforge.collect import collect
|
|
89
|
+
return collect(inp["path"])
|
|
90
|
+
if handler == "cli:diagnose":
|
|
91
|
+
from platformforge import graph as G
|
|
92
|
+
from platformforge.diagnose import diagnose
|
|
93
|
+
g = G.load(repo)
|
|
94
|
+
findings = json.loads(Path(inp["findings"]).read_text()) \
|
|
95
|
+
if inp.get("findings") else []
|
|
96
|
+
facts = json.loads(Path(inp["facts"]).read_text()) \
|
|
97
|
+
if inp.get("facts") else []
|
|
98
|
+
return diagnose(g, inp["node"],
|
|
99
|
+
findings.get("findings", findings),
|
|
100
|
+
facts.get("facts", facts))
|
|
101
|
+
if handler == "cli:plan":
|
|
102
|
+
from platformforge.plan import remediation_plan
|
|
103
|
+
doc = json.loads(Path(inp["findings_path"]).read_text())
|
|
104
|
+
findings = doc.get("findings", doc if isinstance(doc, list) else [])
|
|
105
|
+
facts = doc.get("facts", [])
|
|
106
|
+
try:
|
|
107
|
+
from platformforge import graph as G
|
|
108
|
+
g = G.load(repo)
|
|
109
|
+
except FileNotFoundError:
|
|
110
|
+
g = None
|
|
111
|
+
return remediation_plan(findings, g, facts)
|
|
112
|
+
if handler == "cli:risk":
|
|
113
|
+
from platformforge.risk.engine import assess_change
|
|
114
|
+
signals = dict(inp.get("signals") or {})
|
|
115
|
+
if inp.get("node"):
|
|
116
|
+
from platformforge import graph as G
|
|
117
|
+
from platformforge.graph.query import blast_radius
|
|
118
|
+
from platformforge.risk.signals import signals_from_graph
|
|
119
|
+
g = G.load(repo)
|
|
120
|
+
blast = blast_radius(g, inp["node"])
|
|
121
|
+
signals = {**signals_from_graph(g, [inp["node"]], blast),
|
|
122
|
+
**signals}
|
|
123
|
+
return assess_change(signals)
|
|
124
|
+
if handler == "cli:explain":
|
|
125
|
+
doc = json.loads(Path(inp["findings_path"]).read_text())
|
|
126
|
+
findings = doc.get("findings", doc if isinstance(doc, list) else [])
|
|
127
|
+
f = next((x for x in findings
|
|
128
|
+
if x.get("finding_id") == inp["name"]
|
|
129
|
+
or x.get("rule_id") == inp["name"]), None)
|
|
130
|
+
if not f:
|
|
131
|
+
return {"refusal": "platform.finding.unresolved",
|
|
132
|
+
"name": inp["name"]}
|
|
133
|
+
facts = json.loads(Path(inp["facts_path"]).read_text()) \
|
|
134
|
+
if inp.get("facts_path") else doc.get("facts", [])
|
|
135
|
+
by_id = {x.get("fact_id"): x
|
|
136
|
+
for x in (facts.get("facts", facts)
|
|
137
|
+
if isinstance(facts, dict) else facts)}
|
|
138
|
+
return {"finding": f,
|
|
139
|
+
"evidence_chain": [{"fact_id": e,
|
|
140
|
+
"fact": by_id.get(e, "unresolved")}
|
|
141
|
+
for e in f.get("evidence", [])]}
|
|
142
|
+
if handler == "cli:recommend":
|
|
143
|
+
import argparse
|
|
144
|
+
import contextlib
|
|
145
|
+
import io
|
|
146
|
+
|
|
147
|
+
from platformforge.cli.main import cmd_recommend
|
|
148
|
+
ns = argparse.Namespace(path=inp["findings_path"], format="json",
|
|
149
|
+
strict=False, repo=repo, output=None,
|
|
150
|
+
detail_level="normal", offline=False)
|
|
151
|
+
buf = io.StringIO()
|
|
152
|
+
with contextlib.redirect_stdout(buf):
|
|
153
|
+
cmd_recommend(ns)
|
|
154
|
+
return json.loads(buf.getvalue())
|
|
155
|
+
if handler == "cli:security":
|
|
156
|
+
import argparse
|
|
157
|
+
import contextlib
|
|
158
|
+
import io
|
|
159
|
+
|
|
160
|
+
from platformforge.cli.main import cmd_security
|
|
161
|
+
ns = argparse.Namespace(path=inp["path"], repo=repo, format="json",
|
|
162
|
+
strict=False, output=None,
|
|
163
|
+
detail_level="normal", offline=False)
|
|
164
|
+
buf = io.StringIO()
|
|
165
|
+
with contextlib.redirect_stdout(buf):
|
|
166
|
+
cmd_security(ns)
|
|
167
|
+
return json.loads(buf.getvalue())
|
|
168
|
+
if handler == "cli:reliability":
|
|
169
|
+
import argparse
|
|
170
|
+
import contextlib
|
|
171
|
+
import io
|
|
172
|
+
|
|
173
|
+
from platformforge.cli.main import cmd_reliability
|
|
174
|
+
ns = argparse.Namespace(path=inp["facts_path"], repo=repo,
|
|
175
|
+
format="json", strict=False, output=None,
|
|
176
|
+
detail_level="normal", offline=False)
|
|
177
|
+
buf = io.StringIO()
|
|
178
|
+
with contextlib.redirect_stdout(buf):
|
|
179
|
+
cmd_reliability(ns)
|
|
180
|
+
return json.loads(buf.getvalue())
|
|
181
|
+
if handler == "cli:drift":
|
|
182
|
+
from platformforge.iac import analyze_hcl, analyze_state, drift
|
|
183
|
+
return drift(analyze_hcl(inp["config"])["facts"],
|
|
184
|
+
analyze_state(inp["state"])["facts"])
|
|
185
|
+
if handler == "cli:change_verify":
|
|
186
|
+
from platformforge.sandbox import sandbox_analyze
|
|
187
|
+
return sandbox_analyze(inp["root"], patch=inp.get("patch"),
|
|
188
|
+
files=inp.get("files"))
|
|
189
|
+
if handler == "cli:change_review":
|
|
190
|
+
from platformforge.sandbox import review_change
|
|
191
|
+
return review_change(inp["root"], patch=inp.get("patch"),
|
|
192
|
+
files=inp.get("files"),
|
|
193
|
+
signals=inp.get("signals"))
|
|
194
|
+
if handler == "cli:finops":
|
|
195
|
+
from platformforge import finops as F
|
|
196
|
+
sub = inp.get("op", "costs")
|
|
197
|
+
if sub == "costs":
|
|
198
|
+
doc = F.cost_facts(inp["path"])
|
|
199
|
+
return {**doc, "summary": F.cost_summary(doc["facts"])}
|
|
200
|
+
if sub == "allocate":
|
|
201
|
+
return F.allocate(F.cost_facts(inp["path"])["facts"],
|
|
202
|
+
by=inp.get("by", "cost_center"))
|
|
203
|
+
if sub == "focus":
|
|
204
|
+
rows = json.loads(Path(inp["path"]).read_text())
|
|
205
|
+
return F.to_focus(rows if isinstance(rows, list)
|
|
206
|
+
else rows.get("rows", []))
|
|
207
|
+
if sub == "focus-validate":
|
|
208
|
+
rows = json.loads(Path(inp["path"]).read_text())
|
|
209
|
+
return F.validate_focus(rows if isinstance(rows, list)
|
|
210
|
+
else rows.get("rows", []))
|
|
211
|
+
if sub == "unit":
|
|
212
|
+
doc = F.cost_facts(inp["path"])
|
|
213
|
+
return F.unit_economics(doc["facts"],
|
|
214
|
+
inp.get("denominators") or {})
|
|
215
|
+
if sub == "ingest":
|
|
216
|
+
return F.ingest_billing(inp["path"])
|
|
217
|
+
if sub == "report":
|
|
218
|
+
doc = F.cost_facts(inp["path"])
|
|
219
|
+
extra = inp.get("denominators") or {}
|
|
220
|
+
return F.finops_report(
|
|
221
|
+
doc["facts"], utilization=extra.get("utilization"),
|
|
222
|
+
requested_vs_used=extra.get("requested_vs_used"),
|
|
223
|
+
committed=extra.get("committed"),
|
|
224
|
+
split=extra.get("split"))
|
|
225
|
+
raise ValueError(f"unknown finops op {sub}")
|
|
226
|
+
if handler == "cli:observe":
|
|
227
|
+
return _observe(inp)
|
|
228
|
+
if handler == "cli:evals":
|
|
229
|
+
from platformforge.evals import run_all
|
|
230
|
+
return run_all(inp["cases"] if inp.get("cases") else
|
|
231
|
+
__import__("platformforge.evals.runner",
|
|
232
|
+
fromlist=["CASES_DIR"]).CASES_DIR,
|
|
233
|
+
type_filter=inp.get("type") or None)
|
|
234
|
+
if handler == "cli:lab_chaos":
|
|
235
|
+
from platformforge.lab.chaos import run_scenario
|
|
236
|
+
return run_scenario(inp["scenario"],
|
|
237
|
+
allow_prod=bool(inp.get("allow_prod")))
|
|
238
|
+
if handler == "cli:correlate":
|
|
239
|
+
from platformforge import observe as O
|
|
240
|
+
return O.correlate_spans(inp["path"])
|
|
241
|
+
if handler == "cli:policy":
|
|
242
|
+
from platformforge.rules import load_catalog
|
|
243
|
+
rules = load_catalog(_default_catalog())
|
|
244
|
+
if inp["op"] == "list":
|
|
245
|
+
return {"rules": [{"rule_id": r.rule_id, "domain": r.domain,
|
|
246
|
+
"severity": r.severity} for r in rules]}
|
|
247
|
+
from platformforge.models import Fact
|
|
248
|
+
from platformforge.rules import RuleEngine
|
|
249
|
+
doc = json.loads(Path(inp["facts_path"]).read_text())
|
|
250
|
+
facts = [Fact.from_dict(f)
|
|
251
|
+
for f in doc.get("facts", doc if isinstance(doc, list)
|
|
252
|
+
else [])]
|
|
253
|
+
findings, skipped = RuleEngine(rules).evaluate(facts)
|
|
254
|
+
return {"findings": [f.to_dict() for f in findings],
|
|
255
|
+
"skipped": skipped,
|
|
256
|
+
"violated": [f.rule_id for f in findings
|
|
257
|
+
if f.status == "violated"]}
|
|
258
|
+
if handler == "cli:integrate":
|
|
259
|
+
from platformforge.mcp.parity import detach, integrate
|
|
260
|
+
return detach(inp["host"], repo) if inp.get("detach") \
|
|
261
|
+
else integrate(inp["host"], repo)
|
|
262
|
+
if handler == "cli:hosts":
|
|
263
|
+
from platformforge.distribution import hostflow
|
|
264
|
+
return hostflow.hosts_flow(
|
|
265
|
+
inp.get("host") or "all", repo=Path(repo), launch=False)
|
|
266
|
+
if handler == "cli:capability":
|
|
267
|
+
op = inp.get("op", "manifest")
|
|
268
|
+
if op == "list":
|
|
269
|
+
return {"capabilities": [c.contract()
|
|
270
|
+
for c in CAPABILITIES.values()]}
|
|
271
|
+
if op == "check":
|
|
272
|
+
dom = inp.get("domain", "")
|
|
273
|
+
matches = [c for c in CAPABILITIES.values()
|
|
274
|
+
if c.domain == dom or dom in c.description.lower()
|
|
275
|
+
or dom in c.name]
|
|
276
|
+
if not matches:
|
|
277
|
+
return {"supported": False,
|
|
278
|
+
"refusal": "platform.capability.unresolved",
|
|
279
|
+
"domain": dom}
|
|
280
|
+
return {"supported": True, "domain": dom,
|
|
281
|
+
"version": inp.get("version"),
|
|
282
|
+
"capabilities": [c.contract() for c in matches]}
|
|
283
|
+
if op == "describe":
|
|
284
|
+
c = CAPABILITIES.get(inp.get("name", ""))
|
|
285
|
+
return c.contract() if c else {
|
|
286
|
+
"refusal": "platform.capability.unresolved",
|
|
287
|
+
"name": inp.get("name")}
|
|
288
|
+
from platformforge.forge import capability_manifest
|
|
289
|
+
return capability_manifest()
|
|
290
|
+
if handler == "cli:live":
|
|
291
|
+
return _live(inp, repo)
|
|
292
|
+
if handler == "cli:ops":
|
|
293
|
+
return _ops_tool(inp, repo)
|
|
294
|
+
if handler in ("cli:fleet", "cli:analytics", "cli:optimize",
|
|
295
|
+
"cli:ai", "cli:federation"):
|
|
296
|
+
return _cycle5(handler, inp)
|
|
297
|
+
raise ValueError(f"no handler {handler}")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _ops_tool(inp: dict[str, Any], repo: str) -> Any:
|
|
301
|
+
"""Cycle 4 ops surface for MCP — *prepare* side of the governed
|
|
302
|
+
pipeline plus read views over the operation store.
|
|
303
|
+
|
|
304
|
+
The delegation contract applies: this tool can plan, simulate,
|
|
305
|
+
assess risk, evaluate gates/policies and mint envelopes — it can
|
|
306
|
+
NEVER execute (`--execute` is not reachable from this surface)."""
|
|
307
|
+
import argparse # noqa: F401
|
|
308
|
+
import contextlib
|
|
309
|
+
import io
|
|
310
|
+
|
|
311
|
+
from platformforge.cli.main import build_parser, cmd_ops
|
|
312
|
+
op = inp.get("op", "capabilities")
|
|
313
|
+
argv: list[str] = ["ops", op, "--repo", repo]
|
|
314
|
+
if op in ("run", "prepare"):
|
|
315
|
+
if not inp.get("spec"):
|
|
316
|
+
return {"refusal": "PF-OPS-PREPARE-NEED-SPEC",
|
|
317
|
+
"unlock": "pass spec <ops.yaml>"}
|
|
318
|
+
argv = ["ops", "run", "--spec", inp["spec"], "--repo", repo]
|
|
319
|
+
# never --execute: MCP delegates preparation, not mutation
|
|
320
|
+
elif op in ("status", "history", "rollback", "store-verify",
|
|
321
|
+
"graph"):
|
|
322
|
+
if inp.get("operation_id"):
|
|
323
|
+
argv += ["--operation-id", inp["operation_id"]]
|
|
324
|
+
if op == "rollback":
|
|
325
|
+
return {"refusal": "PF-OPS-MCP-NO-EXECUTE",
|
|
326
|
+
"unlock": "rollback executes via CLI with "
|
|
327
|
+
"--execute + hash-bound approval"}
|
|
328
|
+
elif op == "autorem-eval":
|
|
329
|
+
if inp.get("plan"):
|
|
330
|
+
argv += ["--plan", inp["plan"]]
|
|
331
|
+
if inp.get("environment"):
|
|
332
|
+
argv += ["--environment", inp["environment"]]
|
|
333
|
+
if inp.get("evidence_tier"):
|
|
334
|
+
argv += ["--evidence-tier", inp["evidence_tier"]]
|
|
335
|
+
elif op in ("intent", "plan", "simulate", "risk", "policy-eval",
|
|
336
|
+
"runbook", "approve", "delegate", "config",
|
|
337
|
+
"capabilities", "analytics", "store-list"):
|
|
338
|
+
for flag, key in (("--intent", "intent"), ("--plan", "plan"),
|
|
339
|
+
("--spec", "spec"), ("--policies", "policies"),
|
|
340
|
+
("--environment", "environment"),
|
|
341
|
+
("--level", "level"), ("--request", "request"),
|
|
342
|
+
("--bind", "bind")):
|
|
343
|
+
if inp.get(key):
|
|
344
|
+
argv += [flag, str(inp[key])]
|
|
345
|
+
if op == "approve":
|
|
346
|
+
return {"refusal": "PF-OPS-MCP-NO-APPROVAL",
|
|
347
|
+
"unlock": "approvals are minted host-side by a "
|
|
348
|
+
"human actor; MCP cannot sign"}
|
|
349
|
+
else:
|
|
350
|
+
return {"refusal": "PF-OPS-MCP-UNKNOWN-OP",
|
|
351
|
+
"unlock": "ops: capabilities|prepare|status|history|"
|
|
352
|
+
"graph|analytics|autorem-eval|store-list|"
|
|
353
|
+
"store-verify"}
|
|
354
|
+
ns = build_parser().parse_args(argv)
|
|
355
|
+
buf = io.StringIO()
|
|
356
|
+
with contextlib.redirect_stdout(buf):
|
|
357
|
+
rc = cmd_ops(ns)
|
|
358
|
+
try:
|
|
359
|
+
out = json.loads(buf.getvalue())
|
|
360
|
+
except json.JSONDecodeError:
|
|
361
|
+
out = {"raw": buf.getvalue()[-2000:]}
|
|
362
|
+
out.setdefault("exit_code", rc)
|
|
363
|
+
return out
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _live(inp: dict[str, Any], repo: str) -> Any:
|
|
367
|
+
"""Live surface ops — same functions cmd_live calls. All ops are
|
|
368
|
+
read-only; snapshot uses host transports (kubectl/aws CLI)."""
|
|
369
|
+
op = inp.get("op", "status")
|
|
370
|
+
from platformforge.live.store import ObservationStore
|
|
371
|
+
if op == "status":
|
|
372
|
+
return ObservationStore(repo).status()
|
|
373
|
+
if op == "capability":
|
|
374
|
+
from platformforge.live.capability import availability
|
|
375
|
+
return availability(repo)
|
|
376
|
+
if op == "drift":
|
|
377
|
+
from platformforge.live.drift import diff_observations
|
|
378
|
+
store = ObservationStore(repo)
|
|
379
|
+
def _env(ref: str):
|
|
380
|
+
p = Path(ref)
|
|
381
|
+
if p.exists():
|
|
382
|
+
from platformforge.live.envelope import loads
|
|
383
|
+
return loads(p.read_text())
|
|
384
|
+
return store.get(ref)
|
|
385
|
+
b, a = inp.get("before"), inp.get("after")
|
|
386
|
+
if not (b and a):
|
|
387
|
+
rows = store.list(limit=2)
|
|
388
|
+
if len(rows) < 2:
|
|
389
|
+
return {"refusal": "PF-LIVE-NEED-2-OBS",
|
|
390
|
+
"unlock": "collect ≥2 observations or pass "
|
|
391
|
+
"before/after"}
|
|
392
|
+
a, b = rows[0]["observation_id"], rows[1]["observation_id"]
|
|
393
|
+
return diff_observations(_env(b), _env(a))
|
|
394
|
+
if op == "plan":
|
|
395
|
+
from platformforge.live.remediate import remediate
|
|
396
|
+
if inp.get("drift_events"):
|
|
397
|
+
events = json.loads(Path(inp["drift_events"]).read_text())
|
|
398
|
+
else:
|
|
399
|
+
from platformforge.live.drift import diff_observations
|
|
400
|
+
store = ObservationStore(repo)
|
|
401
|
+
rows = store.list(limit=2)
|
|
402
|
+
if len(rows) < 2:
|
|
403
|
+
return {"refusal": "PF-LIVE-NO-DRIFT-INPUT",
|
|
404
|
+
"unlock": "pass drift_events or collect "
|
|
405
|
+
"≥2 observations"}
|
|
406
|
+
events = diff_observations(
|
|
407
|
+
store.get(rows[1]["observation_id"]),
|
|
408
|
+
store.get(rows[0]["observation_id"]))["drift"]
|
|
409
|
+
return remediate(events)
|
|
410
|
+
if op == "incident":
|
|
411
|
+
if not inp.get("incident"):
|
|
412
|
+
return {"refusal": "PF-LIVE-NO-INCIDENT",
|
|
413
|
+
"unlock": "pass incident <incident.json>"}
|
|
414
|
+
from platformforge.live.incident import build_timeline, postmortem_v3, score_candidates
|
|
415
|
+
inc = json.loads(Path(inp["incident"]).read_text())
|
|
416
|
+
cands = json.loads(Path(inp["changes"]).read_text()) \
|
|
417
|
+
if inp.get("changes") else []
|
|
418
|
+
events = json.loads(Path(inp["events"]).read_text()) \
|
|
419
|
+
if inp.get("events") else []
|
|
420
|
+
try:
|
|
421
|
+
from platformforge import graph as G
|
|
422
|
+
g = G.load(repo)
|
|
423
|
+
except FileNotFoundError:
|
|
424
|
+
g = None
|
|
425
|
+
tl = build_timeline(events + [inc])
|
|
426
|
+
ranked = score_candidates(inc, cands,
|
|
427
|
+
window_s=float(inp.get("window", 3600)),
|
|
428
|
+
graph=g)
|
|
429
|
+
return {**ranked,
|
|
430
|
+
"postmortem": postmortem_v3(inc, tl, ranked)["postmortem"]}
|
|
431
|
+
if op == "clusters":
|
|
432
|
+
from platformforge.live.federation import ClusterRegistry
|
|
433
|
+
return {"clusters": ClusterRegistry(repo).list()}
|
|
434
|
+
if op == "topology":
|
|
435
|
+
from platformforge.live.topology import (
|
|
436
|
+
classify_behavior,
|
|
437
|
+
edges_from_endpointslices,
|
|
438
|
+
edges_from_hubble,
|
|
439
|
+
edges_from_otel,
|
|
440
|
+
)
|
|
441
|
+
edges: list[dict] = []
|
|
442
|
+
for key, fn in (("otel", edges_from_otel),
|
|
443
|
+
("hubble", edges_from_hubble),
|
|
444
|
+
("slices", edges_from_endpointslices)):
|
|
445
|
+
if inp.get(key):
|
|
446
|
+
doc = json.loads(Path(inp[key]).read_text())
|
|
447
|
+
edges += fn(doc if isinstance(doc, list) else [doc])
|
|
448
|
+
declared = set()
|
|
449
|
+
try:
|
|
450
|
+
from platformforge import graph as G
|
|
451
|
+
declared = set(G.load(repo).edges)
|
|
452
|
+
except FileNotFoundError:
|
|
453
|
+
pass
|
|
454
|
+
return {"edges": classify_behavior(edges, declared)}
|
|
455
|
+
if op == "snapshot":
|
|
456
|
+
return _live_snapshot(inp, repo)
|
|
457
|
+
raise ValueError(f"unknown live op {op}")
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _live_snapshot(inp: dict[str, Any], repo: str) -> Any:
|
|
461
|
+
provider = inp.get("provider", "kubernetes")
|
|
462
|
+
from platformforge.live.budget import ObservationBudget
|
|
463
|
+
from platformforge.live.models import ObservationScope
|
|
464
|
+
from platformforge.live.store import ObservationStore
|
|
465
|
+
budget = ObservationBudget()
|
|
466
|
+
scope = ObservationScope(
|
|
467
|
+
namespaces=[inp["namespace"]] if inp.get("namespace") else [])
|
|
468
|
+
if provider == "kubernetes":
|
|
469
|
+
from platformforge.live.collectors import k8s_transport
|
|
470
|
+
from platformforge.live.collectors.k8s import K8sCollector
|
|
471
|
+
if not k8s_transport.kubectl_available():
|
|
472
|
+
return {"refusal": "PF-LIVE-NO-KUBECTL",
|
|
473
|
+
"unlock": "install kubectl + kubeconfig"}
|
|
474
|
+
env = K8sCollector(
|
|
475
|
+
transport=k8s_transport.make_transport(
|
|
476
|
+
inp.get("context", "")),
|
|
477
|
+
context=inp.get("context", "")).collect(scope, budget)
|
|
478
|
+
elif provider == "aws":
|
|
479
|
+
from platformforge.live.collectors import aws_transport
|
|
480
|
+
from platformforge.live.collectors.aws import AwsCollector
|
|
481
|
+
if not aws_transport.aws_available():
|
|
482
|
+
return {"refusal": "PF-LIVE-NO-AWSCLI",
|
|
483
|
+
"unlock": "install aws CLI + credentials"}
|
|
484
|
+
env = AwsCollector(transport=aws_transport.make_transport()
|
|
485
|
+
).collect(scope, budget)
|
|
486
|
+
else:
|
|
487
|
+
return {"refusal": "PF-LIVE-UNKNOWN-PROVIDER",
|
|
488
|
+
"unlock": "provider kubernetes|aws"}
|
|
489
|
+
res = ObservationStore(repo).put(env)
|
|
490
|
+
return {**res, "provider": env.provider,
|
|
491
|
+
"coverage": env.coverage.get("status")}
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _observe(inp: dict[str, Any]) -> Any:
|
|
495
|
+
"""Observe sub-verbs — same functions cmd_observe calls."""
|
|
496
|
+
from platformforge import observe as O
|
|
497
|
+
sub = inp.get("op", "slo-burn")
|
|
498
|
+
if sub == "slo":
|
|
499
|
+
c = O.SloContract.load(inp["contract"])
|
|
500
|
+
return O.error_budget(c, **(inp.get("sli") or {}))
|
|
501
|
+
if sub == "slo-burn":
|
|
502
|
+
c = O.SloContract.load(inp["contract"])
|
|
503
|
+
return O.multi_window_burn(c, inp.get("windows") or {})
|
|
504
|
+
if sub == "otel":
|
|
505
|
+
return O.correlate_spans(inp["path"])
|
|
506
|
+
if sub == "semconv":
|
|
507
|
+
return O.analyze_semconv(inp["path"])
|
|
508
|
+
if sub == "timeline":
|
|
509
|
+
return O.timeline(json.loads(Path(inp["path"]).read_text()))
|
|
510
|
+
if sub == "postmortem":
|
|
511
|
+
inc = json.loads(Path(inp["incident"]).read_text())
|
|
512
|
+
return O.postmortem(inc, inc.get("hypotheses"))
|
|
513
|
+
if sub == "dr":
|
|
514
|
+
doc = json.loads(Path(inp["facts"]).read_text())
|
|
515
|
+
return O.dr_model(doc.get("facts", doc))
|
|
516
|
+
if sub == "prometheus":
|
|
517
|
+
return O.analyze_prometheus(inp["path"])
|
|
518
|
+
if sub == "grafana":
|
|
519
|
+
return O.analyze_grafana(inp["path"])
|
|
520
|
+
if sub == "incident":
|
|
521
|
+
alerts = json.loads(Path(inp["alerts"]).read_text())
|
|
522
|
+
changes = json.loads(Path(inp["changes"]).read_text())
|
|
523
|
+
return O.correlate(alerts, changes,
|
|
524
|
+
window_s=int(inp.get("window", 3600)))
|
|
525
|
+
if sub == "capacity":
|
|
526
|
+
items = json.loads(Path(inp["path"]).read_text())
|
|
527
|
+
return O.capacity(items)
|
|
528
|
+
raise ValueError(f"unknown observe op {sub}")
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _default_catalog() -> Path:
|
|
532
|
+
from platformforge.resources import data_path
|
|
533
|
+
return data_path("rules", "catalog")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _analyze(domain: str, path: str) -> Any:
|
|
537
|
+
if domain in ("iac",):
|
|
538
|
+
from platformforge.iac import analyze_hcl
|
|
539
|
+
return analyze_hcl(path)
|
|
540
|
+
if domain == "plan":
|
|
541
|
+
from platformforge.iac import analyze_plan
|
|
542
|
+
return analyze_plan(path)
|
|
543
|
+
if domain == "state":
|
|
544
|
+
from platformforge.iac import analyze_state
|
|
545
|
+
return analyze_state(path)
|
|
546
|
+
if domain == "k8s":
|
|
547
|
+
from platformforge.k8s import analyze_k8s
|
|
548
|
+
return analyze_k8s(path)
|
|
549
|
+
if domain == "gitops":
|
|
550
|
+
from platformforge.cicd import analyze_gitops
|
|
551
|
+
return analyze_gitops(path)
|
|
552
|
+
if domain == "gha":
|
|
553
|
+
from platformforge.cicd import analyze_gha
|
|
554
|
+
return analyze_gha(path)
|
|
555
|
+
if domain == "iam":
|
|
556
|
+
from platformforge.security import analyze_iam_policy
|
|
557
|
+
return analyze_iam_policy(path)
|
|
558
|
+
if domain == "sbom":
|
|
559
|
+
from platformforge.security import analyze_sbom
|
|
560
|
+
return analyze_sbom(path)
|
|
561
|
+
if domain == "secrets":
|
|
562
|
+
from platformforge.security import scan_secrets
|
|
563
|
+
return scan_secrets(path)
|
|
564
|
+
if domain == "supply":
|
|
565
|
+
from platformforge.security import analyze_supply
|
|
566
|
+
return analyze_supply(path)
|
|
567
|
+
if domain == "catalog":
|
|
568
|
+
from platformforge.product import analyze_catalog
|
|
569
|
+
return analyze_catalog(path)
|
|
570
|
+
if domain == "crossplane":
|
|
571
|
+
from platformforge.product import analyze_crossplane
|
|
572
|
+
return analyze_crossplane(path)
|
|
573
|
+
if domain == "helm":
|
|
574
|
+
from platformforge.k8s.helm import analyze_helm
|
|
575
|
+
return analyze_helm(path)
|
|
576
|
+
if domain == "kustomize":
|
|
577
|
+
from platformforge.k8s.helm import analyze_kustomize
|
|
578
|
+
return analyze_kustomize(path)
|
|
579
|
+
if domain == "hubble":
|
|
580
|
+
from platformforge.k8s.hubble import analyze_hubble
|
|
581
|
+
return analyze_hubble(path)
|
|
582
|
+
if domain == "kyverno":
|
|
583
|
+
from platformforge.security.kyverno import analyze_kyverno
|
|
584
|
+
return analyze_kyverno(path)
|
|
585
|
+
if domain == "cosign":
|
|
586
|
+
from platformforge.security.cosign import analyze_cosign
|
|
587
|
+
return analyze_cosign(path)
|
|
588
|
+
if domain == "slsa":
|
|
589
|
+
from platformforge.security.slsa import slsa_assess
|
|
590
|
+
doc = json.loads(Path(path).read_text())
|
|
591
|
+
return slsa_assess(doc.get("provenance", doc),
|
|
592
|
+
evidence=doc.get("evidence")
|
|
593
|
+
if isinstance(doc, dict) else None)
|
|
594
|
+
if domain == "cloud-aws":
|
|
595
|
+
from platformforge.cloud import analyze_aws_dump
|
|
596
|
+
return analyze_aws_dump(path)
|
|
597
|
+
if domain == "cloud-azure":
|
|
598
|
+
from platformforge.cloud import analyze_azure_dump
|
|
599
|
+
return analyze_azure_dump(path)
|
|
600
|
+
if domain == "cloud-gcp":
|
|
601
|
+
from platformforge.cloud import analyze_gcp_dump
|
|
602
|
+
return analyze_gcp_dump(path)
|
|
603
|
+
if domain == "ownership":
|
|
604
|
+
from platformforge.core.ownership import analyze_ownership
|
|
605
|
+
return analyze_ownership(path)
|
|
606
|
+
if domain == "contradictions":
|
|
607
|
+
from platformforge.core.ownership import detect_contradictions
|
|
608
|
+
doc = json.loads(Path(path).read_text())
|
|
609
|
+
facts = doc.get("facts", doc if isinstance(doc, list) else [])
|
|
610
|
+
out = detect_contradictions(facts)
|
|
611
|
+
return {"facts": out, "contradictions": len(out)}
|
|
612
|
+
raise ValueError(f"unknown domain {domain}")
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _graph(inp: dict[str, Any], repo: str) -> Any:
|
|
616
|
+
from platformforge import graph as G
|
|
617
|
+
g = G.load(repo)
|
|
618
|
+
q = inp["query"]
|
|
619
|
+
if q == "deps":
|
|
620
|
+
return G.dependencies(g, inp["node"])
|
|
621
|
+
if q == "dependents":
|
|
622
|
+
return G.dependents(g, inp["node"])
|
|
623
|
+
if q == "blast":
|
|
624
|
+
return G.blast_radius(g, inp["node"])
|
|
625
|
+
if q == "paths":
|
|
626
|
+
return G.paths(g, inp["src"], inp["dst"])
|
|
627
|
+
if q == "gaps":
|
|
628
|
+
return G.gaps(g)
|
|
629
|
+
if q == "cycles":
|
|
630
|
+
return {"cycles": G.cycles(g)}
|
|
631
|
+
if q == "stats":
|
|
632
|
+
return g.stats()
|
|
633
|
+
if q.startswith("identity"):
|
|
634
|
+
from platformforge.security.identity import (
|
|
635
|
+
compromise_blast,
|
|
636
|
+
who_can_access,
|
|
637
|
+
who_can_become,
|
|
638
|
+
workloads_using_identity,
|
|
639
|
+
)
|
|
640
|
+
target = inp.get("node") or inp.get("dst") or inp.get("src")
|
|
641
|
+
fn = {"identity-become": who_can_become,
|
|
642
|
+
"identity-access": who_can_access,
|
|
643
|
+
"identity-workloads": workloads_using_identity,
|
|
644
|
+
"identity-blast": compromise_blast}.get(q)
|
|
645
|
+
if not fn:
|
|
646
|
+
raise ValueError(f"unknown graph query {q}")
|
|
647
|
+
return fn(g, target)
|
|
648
|
+
raise ValueError(f"unknown graph query {q}")
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _cycle5(handler: str, inp: dict[str, Any]) -> Any:
|
|
652
|
+
"""Cycle 5 read-only surfaces — same cmd_* functions the CLI calls,
|
|
653
|
+
driven by a Namespace built from the tool input. `optimize plan` is
|
|
654
|
+
allowed but `out` is forced off — MCP never writes files."""
|
|
655
|
+
import argparse
|
|
656
|
+
|
|
657
|
+
from platformforge.cli import fleetcmd
|
|
658
|
+
op = inp.get("op", "")
|
|
659
|
+
common = {"path": inp.get("path", ""), "id": inp.get("id", ""),
|
|
660
|
+
"question": inp.get("question", ""),
|
|
661
|
+
"window": inp.get("window", ""),
|
|
662
|
+
"limit": inp.get("limit") or 0, "out": "",
|
|
663
|
+
"format": "json"}
|
|
664
|
+
if handler == "cli:fleet":
|
|
665
|
+
ns = argparse.Namespace(fleet_cmd=op, **common)
|
|
666
|
+
return fleetcmd.cmd_fleet(ns)
|
|
667
|
+
if handler == "cli:analytics":
|
|
668
|
+
ns = argparse.Namespace(analytics_cmd=op, **common)
|
|
669
|
+
return fleetcmd.cmd_analytics(ns)
|
|
670
|
+
if handler == "cli:optimize":
|
|
671
|
+
ns = argparse.Namespace(optimize_cmd=op, **common)
|
|
672
|
+
return fleetcmd.cmd_optimize(ns)
|
|
673
|
+
if handler == "cli:ai":
|
|
674
|
+
ns = argparse.Namespace(
|
|
675
|
+
ai_cmd=op, cost=inp.get("cost"),
|
|
676
|
+
denominators=inp.get("denominators", ""), **common)
|
|
677
|
+
return fleetcmd.cmd_ai(ns)
|
|
678
|
+
ns = argparse.Namespace(
|
|
679
|
+
federation_cmd=op, nodes=list(inp.get("nodes") or []),
|
|
680
|
+
classification=inp.get("classification", ""),
|
|
681
|
+
node_id=inp.get("node_id", ""),
|
|
682
|
+
capabilities=inp.get("capabilities", ""),
|
|
683
|
+
freshness=inp.get("freshness", ""),
|
|
684
|
+
manifest=inp.get("manifest", ""), **common)
|
|
685
|
+
return fleetcmd.cmd_federation(ns)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def _bound(result: Any, cap, repo: str) -> dict[str, Any]:
|
|
689
|
+
"""Enforce output bounds; oversize → artifact ref."""
|
|
690
|
+
text = json.dumps(result, default=str)
|
|
691
|
+
if len(text.encode()) <= cap.max_bytes:
|
|
692
|
+
return {"result": result, "bounded": False}
|
|
693
|
+
# Secrets are redacted BEFORE persistence — the artifact store is a
|
|
694
|
+
# file on disk, not a trusted vault, and rtk expand reads it back.
|
|
695
|
+
from platformforge.core.redaction import redact_obj, redact_text
|
|
696
|
+
store = ArtifactStore(repo)
|
|
697
|
+
sha = store.put(redact_text(text).encode(), meta={"kind": "mcp-result"})
|
|
698
|
+
ref = f"artifact://sha256/{sha}"
|
|
699
|
+
summary = result
|
|
700
|
+
if isinstance(result, dict):
|
|
701
|
+
summary = {k: v for k, v in result.items()
|
|
702
|
+
if not isinstance(v, (list, dict)) or len(
|
|
703
|
+
json.dumps(v, default=str)) < 2000}
|
|
704
|
+
summary = redact_obj(summary)
|
|
705
|
+
return {"bounded": True,
|
|
706
|
+
"artifact_ref": ref,
|
|
707
|
+
"summary": summary,
|
|
708
|
+
"note": f"result exceeded {cap.max_bytes}B — full output "
|
|
709
|
+
f"in artifact store"}
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def call_tool(name: str, arguments: dict[str, Any] | None = None,
|
|
713
|
+
repo: str = ".") -> dict[str, Any]:
|
|
714
|
+
cap = CAPABILITIES.get(name)
|
|
715
|
+
if not cap:
|
|
716
|
+
return {"error": "platform.tool.unresolved",
|
|
717
|
+
"unlock": "tools/list — unknown tool", "tool": name}
|
|
718
|
+
inp = dict(arguments or {})
|
|
719
|
+
detail = inp.pop("detail_level", "normal")
|
|
720
|
+
if detail not in cap.detail_levels:
|
|
721
|
+
detail = "normal"
|
|
722
|
+
try:
|
|
723
|
+
result = _dispatch(cap.handler, inp, repo)
|
|
724
|
+
except FileNotFoundError as e:
|
|
725
|
+
return {"refusal": "platform.evidence.unresolved",
|
|
726
|
+
"detail": str(e), "tool": name}
|
|
727
|
+
except Exception as e: # noqa: BLE001 — bounded error, never traceback over MCP
|
|
728
|
+
return {"error": type(e).__name__, "detail": str(e)[:500],
|
|
729
|
+
"tool": name}
|
|
730
|
+
out = _bound(result, cap, repo)
|
|
731
|
+
out["detail_level"] = detail
|
|
732
|
+
return out
|