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