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,85 @@
|
|
|
1
|
+
"""Agent run ledger — per-run economy accounting (§117).
|
|
2
|
+
|
|
3
|
+
Every agent invocation appends one row: model calls, context bytes,
|
|
4
|
+
output bytes, tool calls, agents used, fanout, duration, cache reuse.
|
|
5
|
+
Separate from TokenLedger (which tracks context engineering); this
|
|
6
|
+
tracks *agentic* spend so quality-per-token can be compared across
|
|
7
|
+
single-specialist vs coordinated strategies (§118–121).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import asdict, dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class AgentRunRow:
|
|
21
|
+
run_id: str
|
|
22
|
+
agent: str
|
|
23
|
+
mode: str = "single-specialist" # router mode for comparability
|
|
24
|
+
model_calls: int = 0
|
|
25
|
+
context_bytes: int = 0
|
|
26
|
+
output_bytes: int = 0
|
|
27
|
+
tool_calls: int = 0
|
|
28
|
+
agents: int = 1
|
|
29
|
+
fanout: int = 0
|
|
30
|
+
duration_ms: float = 0.0
|
|
31
|
+
cache_reuse_bytes: int = 0 # §115 shared-pack reuse
|
|
32
|
+
delta_bytes: int = 0 # §116 delta-context savings
|
|
33
|
+
token_basis: str = "unknown" # observed|estimated|unknown
|
|
34
|
+
transcript_ref: str | None = None
|
|
35
|
+
ts: float = field(default_factory=time.time)
|
|
36
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
def __post_init__(self) -> None:
|
|
39
|
+
if self.token_basis not in ("observed", "estimated", "unknown"):
|
|
40
|
+
raise ValueError(f"bad token_basis {self.token_basis!r}")
|
|
41
|
+
if self.token_basis == "observed" and not self.transcript_ref:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"observed token counts require transcript_ref")
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict[str, Any]:
|
|
46
|
+
return asdict(self)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AgentRunLedger:
|
|
50
|
+
def __init__(self, root: str | Path):
|
|
51
|
+
self.dir = Path(root) / ".platformforge" / "ledger"
|
|
52
|
+
self.dir.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
self.path = self.dir / "agent-runs.jsonl"
|
|
54
|
+
|
|
55
|
+
def record(self, row: AgentRunRow) -> None:
|
|
56
|
+
with open(self.path, "a") as fh:
|
|
57
|
+
fh.write(json.dumps(row.to_dict(), default=str) + "\n")
|
|
58
|
+
|
|
59
|
+
def rows(self) -> list[dict[str, Any]]:
|
|
60
|
+
if not self.path.exists():
|
|
61
|
+
return []
|
|
62
|
+
return [json.loads(l) for l in self.path.read_text().splitlines()
|
|
63
|
+
if l.strip()]
|
|
64
|
+
|
|
65
|
+
def report(self) -> dict[str, Any]:
|
|
66
|
+
rows = self.rows()
|
|
67
|
+
def tot(k): return sum(r.get(k) or 0 for r in rows)
|
|
68
|
+
by_mode: dict[str, dict[str, Any]] = {}
|
|
69
|
+
for r in rows:
|
|
70
|
+
m = by_mode.setdefault(r.get("mode", "?"), {
|
|
71
|
+
"runs": 0, "model_calls": 0, "context_bytes": 0,
|
|
72
|
+
"tool_calls": 0, "duration_ms": 0.0,
|
|
73
|
+
"cache_reuse_bytes": 0})
|
|
74
|
+
m["runs"] += 1
|
|
75
|
+
for k in ("model_calls", "context_bytes", "tool_calls",
|
|
76
|
+
"duration_ms", "cache_reuse_bytes"):
|
|
77
|
+
m[k] += r.get(k) or 0
|
|
78
|
+
return {"runs": len(rows), "agents_invoked": tot("agents"),
|
|
79
|
+
"model_calls": tot("model_calls"),
|
|
80
|
+
"context_bytes": tot("context_bytes"),
|
|
81
|
+
"output_bytes": tot("output_bytes"),
|
|
82
|
+
"tool_calls": tot("tool_calls"),
|
|
83
|
+
"cache_reuse_bytes": tot("cache_reuse_bytes"),
|
|
84
|
+
"delta_savings_bytes": tot("delta_bytes"),
|
|
85
|
+
"by_mode": by_mode}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Agent run store — .platformforge/runs/ (§135–142).
|
|
2
|
+
|
|
3
|
+
Append-only run records: task spec hash, router decision, agents,
|
|
4
|
+
handoffs, budget, evidence refs, debates, gates, verifier, verdict.
|
|
5
|
+
This is run *memory* — receipts and refs, not long-term agent memory
|
|
6
|
+
(§134) and never user-specific context (§137).
|
|
7
|
+
|
|
8
|
+
Resume preserves spent budget and prior evidence/decisions; it
|
|
9
|
+
revalidates artifact hashes and observation freshness before
|
|
10
|
+
continuing (§141–142).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from platformforge.agents.contracts import AgentRunRecord
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RunStore:
|
|
23
|
+
def __init__(self, root: str | Path):
|
|
24
|
+
self.dir = Path(root) / ".platformforge" / "runs"
|
|
25
|
+
self.dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
|
|
27
|
+
def _path(self, run_id: str) -> Path:
|
|
28
|
+
if "/" in run_id or ".." in run_id:
|
|
29
|
+
raise ValueError(f"bad run_id {run_id!r}")
|
|
30
|
+
return self.dir / f"{run_id}.json"
|
|
31
|
+
|
|
32
|
+
def save(self, record: AgentRunRecord) -> Path:
|
|
33
|
+
p = self._path(record.run_id)
|
|
34
|
+
p.write_text(json.dumps(record.to_dict(), indent=2,
|
|
35
|
+
sort_keys=True, default=str))
|
|
36
|
+
return p
|
|
37
|
+
|
|
38
|
+
def load(self, run_id: str) -> AgentRunRecord | None:
|
|
39
|
+
p = self._path(run_id)
|
|
40
|
+
if not p.is_file():
|
|
41
|
+
return None
|
|
42
|
+
return AgentRunRecord.from_dict(json.loads(p.read_text()))
|
|
43
|
+
|
|
44
|
+
def runs(self) -> list[str]:
|
|
45
|
+
return sorted(p.stem for p in self.dir.glob("*.json"))
|
|
46
|
+
|
|
47
|
+
def resume(self, run_id: str, *, artifact_hashes: dict[str, str] |
|
|
48
|
+
None = None,
|
|
49
|
+
observation_freshness: str | None = None,
|
|
50
|
+
) -> dict[str, Any]:
|
|
51
|
+
"""§141–142 — resume preserves spent budget/decisions and
|
|
52
|
+
revalidates hashes + freshness before allowing continuation."""
|
|
53
|
+
rec = self.load(run_id)
|
|
54
|
+
if rec is None:
|
|
55
|
+
return {"resumed": False,
|
|
56
|
+
"reason": f"no run record {run_id} — nothing to "
|
|
57
|
+
"resume; start a new run"}
|
|
58
|
+
problems = []
|
|
59
|
+
if artifact_hashes:
|
|
60
|
+
for ref, h in artifact_hashes.items():
|
|
61
|
+
if not h:
|
|
62
|
+
problems.append(f"{ref}: hash unreadable — "
|
|
63
|
+
"re-collect before resume")
|
|
64
|
+
if observation_freshness in ("stale", "superseded", "conflicted"):
|
|
65
|
+
problems.append(
|
|
66
|
+
f"observation freshness is {observation_freshness} — "
|
|
67
|
+
"re-collect runtime evidence before resume")
|
|
68
|
+
return {"resumed": not problems, "run_id": run_id,
|
|
69
|
+
"problems": problems,
|
|
70
|
+
"preserved": {"budget_spent": rec.budget.get("spent"),
|
|
71
|
+
"evidence": list(rec.evidence),
|
|
72
|
+
"decisions": list(rec.debates),
|
|
73
|
+
"verdict": rec.verdict},
|
|
74
|
+
"note": "budget is never reset on resume (§141)"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Specialist machinery — the §98/§99 finding contract.
|
|
2
|
+
|
|
3
|
+
Every specialist finding is a structured record — claim, status,
|
|
4
|
+
evidence, coverage, freshness, confidence, limitations, next_action —
|
|
5
|
+
never bare prose. `finding()` validates the shape; `grade()` demotes
|
|
6
|
+
any status the evidence cannot carry (no evidence → never confirmed).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from platformforge.agents.contracts import OUTPUT_STATUSES, AgentRefusal, refusal
|
|
14
|
+
from platformforge.models.base import stable_id
|
|
15
|
+
|
|
16
|
+
# statuses that REQUIRE at least one evidence ref (§98 + §55)
|
|
17
|
+
_EVIDENCE_STATUSES = ("confirmed", "supported", "refuted")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def finding(agent: str, claim: str, *, status: str,
|
|
21
|
+
evidence: tuple[str, ...] = (),
|
|
22
|
+
coverage: float | None = None,
|
|
23
|
+
freshness: str = "unresolved",
|
|
24
|
+
confidence: str = "low",
|
|
25
|
+
limitations: tuple[str, ...] = (),
|
|
26
|
+
next_action: str = "") -> dict[str, Any]:
|
|
27
|
+
"""One specialist finding. `status` ∈ OUTPUT_STATUSES; evidence
|
|
28
|
+
statuses with no evidence are demoted to `unsupported` with the
|
|
29
|
+
demotion recorded — never silently upgraded (§98)."""
|
|
30
|
+
if status not in OUTPUT_STATUSES:
|
|
31
|
+
return refusal(
|
|
32
|
+
AgentRefusal.HOST_CAPABILITY,
|
|
33
|
+
f"status {status!r} outside OUTPUT_STATUSES",
|
|
34
|
+
"use one of: " + ", ".join(OUTPUT_STATUSES))
|
|
35
|
+
demoted = False
|
|
36
|
+
asked = status
|
|
37
|
+
if status in _EVIDENCE_STATUSES and not evidence:
|
|
38
|
+
demoted = True
|
|
39
|
+
status = "unsupported"
|
|
40
|
+
limitations = tuple(limitations) + (
|
|
41
|
+
"demoted: status required evidence but none was cited",)
|
|
42
|
+
if coverage is not None:
|
|
43
|
+
try:
|
|
44
|
+
coverage = min(max(float(coverage), 0.0), 1.0)
|
|
45
|
+
except (TypeError, ValueError):
|
|
46
|
+
coverage = None
|
|
47
|
+
doc = {"finding_id": stable_id("PF-FND", agent, claim),
|
|
48
|
+
"agent": agent, "claim": claim, "status": status,
|
|
49
|
+
"evidence": list(evidence),
|
|
50
|
+
"coverage": coverage, "freshness": freshness,
|
|
51
|
+
"confidence": confidence,
|
|
52
|
+
"limitations": list(limitations),
|
|
53
|
+
"next_action": next_action}
|
|
54
|
+
if demoted:
|
|
55
|
+
doc["demoted_from"] = asked
|
|
56
|
+
doc["demoted"] = True
|
|
57
|
+
return doc
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def grade(findings: list[dict[str, Any]]) -> dict[str, Any]:
|
|
61
|
+
"""Aggregate findings — counts per status; unsupported/absence is
|
|
62
|
+
surfaced, not hidden behind totals (§98)."""
|
|
63
|
+
by_status: dict[str, int] = {}
|
|
64
|
+
demoted = 0
|
|
65
|
+
for f in findings:
|
|
66
|
+
by_status[f.get("status", "unresolved")] = \
|
|
67
|
+
by_status.get(f.get("status", "unresolved"), 0) + 1
|
|
68
|
+
demoted += 1 if f.get("demoted") else 0
|
|
69
|
+
return {"total": len(findings), "by_status": by_status,
|
|
70
|
+
"demoted": demoted,
|
|
71
|
+
"ok": by_status.get("confirmed", 0)
|
|
72
|
+
+ by_status.get("supported", 0)
|
|
73
|
+
+ by_status.get("refuted", 0) > 0}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""TaskSpec lifecycle — the deterministic `platform-task-spec-reviewer`.
|
|
2
|
+
|
|
3
|
+
Review (§16) validates scope, evidence requirements, risk, rollback,
|
|
4
|
+
acceptance criteria, unknowns and operation boundaries BEFORE any
|
|
5
|
+
orchestration. A spec that fails review is rejected with notes; a spec
|
|
6
|
+
that passes is `reviewed`, then `seal()`ed — sealed specs are
|
|
7
|
+
content-hashed so the orchestrator executes exactly what was reviewed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from platformforge.agents.contracts import AgentRefusal, PlatformTaskSpec, refusal
|
|
15
|
+
|
|
16
|
+
MUTATING_CAPS = ("ops.apply", "ops.approve", "federation.delegate",
|
|
17
|
+
"change.apply", "cloud.mutate")
|
|
18
|
+
|
|
19
|
+
# risk levels that force stronger governance on the spec itself
|
|
20
|
+
_GOVERNED_RISK = ("high", "critical")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def review(spec: PlatformTaskSpec,
|
|
24
|
+
reviewer: str = "platform-task-spec-reviewer") -> PlatformTaskSpec:
|
|
25
|
+
"""draft → reviewed | rejected. Deterministic, explainable notes."""
|
|
26
|
+
if spec.state != "draft":
|
|
27
|
+
return spec # only drafts are reviewed; caller sees current state
|
|
28
|
+
notes: list[str] = []
|
|
29
|
+
if not spec.intent.strip():
|
|
30
|
+
notes.append("intent is empty — nothing to plan against")
|
|
31
|
+
if not spec.acceptance_criteria:
|
|
32
|
+
notes.append("no acceptance_criteria — verifier would have "
|
|
33
|
+
"nothing to check")
|
|
34
|
+
if not spec.evidence_requirements:
|
|
35
|
+
notes.append("no evidence_requirements — conclusions would be "
|
|
36
|
+
"unverifiable")
|
|
37
|
+
if spec.risk in _GOVERNED_RISK:
|
|
38
|
+
if spec.rollback_requirement == "none":
|
|
39
|
+
notes.append(f"risk={spec.risk} requires a declared or "
|
|
40
|
+
"material-bound rollback requirement")
|
|
41
|
+
missing = [c for c in MUTATING_CAPS
|
|
42
|
+
if c not in spec.forbidden_capabilities]
|
|
43
|
+
if missing:
|
|
44
|
+
notes.append("risky spec must explicitly forbid: "
|
|
45
|
+
+ ", ".join(missing))
|
|
46
|
+
if spec.autonomy not in ("read-only", "propose", "simulate"):
|
|
47
|
+
notes.append(f"autonomy {spec.autonomy!r} outside read-only/"
|
|
48
|
+
"propose/simulate — agents never execute")
|
|
49
|
+
if spec.budget not in ("tiny", "small", "standard", "deep",
|
|
50
|
+
"critical"):
|
|
51
|
+
notes.append(f"unknown budget class {spec.budget!r}")
|
|
52
|
+
if not spec.domains and spec.complexity != "low":
|
|
53
|
+
notes.append("non-trivial spec with no domains — scope is "
|
|
54
|
+
"unbounded")
|
|
55
|
+
if notes:
|
|
56
|
+
return spec.transition("rejected", reviewer=reviewer,
|
|
57
|
+
notes=tuple(notes))
|
|
58
|
+
return spec.transition("reviewed", reviewer=reviewer,
|
|
59
|
+
notes=(("scope/evidence/risk/rollback/"
|
|
60
|
+
"criteria validated"),))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def seal(spec: PlatformTaskSpec) -> PlatformTaskSpec | dict[str, Any]:
|
|
64
|
+
"""reviewed → sealed. Only sealed specs may orchestrate (§18)."""
|
|
65
|
+
if spec.state == "rejected":
|
|
66
|
+
return refusal(
|
|
67
|
+
AgentRefusal.SPEC_REJECTED,
|
|
68
|
+
"task spec was rejected: " + "; ".join(spec.review_notes),
|
|
69
|
+
"revise the spec against the review notes and re-review")
|
|
70
|
+
if spec.state != "reviewed":
|
|
71
|
+
return refusal(
|
|
72
|
+
AgentRefusal.SPEC_UNSEALED,
|
|
73
|
+
f"spec is {spec.state!r}, expected 'reviewed'",
|
|
74
|
+
"run the task-spec reviewer first")
|
|
75
|
+
return spec.transition("sealed")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def expire(spec: PlatformTaskSpec) -> PlatformTaskSpec:
|
|
79
|
+
return spec.transition("expired")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def require_sealed(spec: PlatformTaskSpec,
|
|
83
|
+
complex_: bool = True) -> dict[str, Any] | None:
|
|
84
|
+
"""§18 — orchestrators refuse unsealed specs for complex work.
|
|
85
|
+
Returns None when OK, else the refusal doc."""
|
|
86
|
+
if spec.state == "sealed":
|
|
87
|
+
return None
|
|
88
|
+
if not complex_ and spec.state == "reviewed":
|
|
89
|
+
return None
|
|
90
|
+
return refusal(
|
|
91
|
+
AgentRefusal.SPEC_UNSEALED,
|
|
92
|
+
f"orchestration requires a sealed TaskSpec (state={spec.state})",
|
|
93
|
+
"platformforge agents plan → review-spec → seal, then dispatch",
|
|
94
|
+
spec_state=spec.state)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Agent fan-out economy (§117–129): uniqueness audit + selective
|
|
2
|
+
agentics helpers.
|
|
3
|
+
|
|
4
|
+
Per-run audit measures who actually contributed — unique vs duplicated
|
|
5
|
+
vs refuted vs discarded contribution (§118). An agent that adds nothing
|
|
6
|
+
is a candidate for routing reduction (§119) — a recommendation, never
|
|
7
|
+
auto-applied (§115). No leaderboard: agents are graded as contribution
|
|
8
|
+
records, not ranked as people (§120).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import asdict, dataclass, field
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class AgentContribution:
|
|
19
|
+
agent: str
|
|
20
|
+
contribution_ids: list[str] = field(default_factory=list)
|
|
21
|
+
# finding/artifact ids the agent uniquely produced
|
|
22
|
+
refuted: list[str] = field(default_factory=list)
|
|
23
|
+
discarded: list[str] = field(default_factory=list)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class AgentUniqueness:
|
|
28
|
+
"""§117 — per-run fanout audit."""
|
|
29
|
+
run_id: str = ""
|
|
30
|
+
agents_invoked: list[str] = field(default_factory=list)
|
|
31
|
+
unique_contribution: dict[str, list[str]] = field(default_factory=dict)
|
|
32
|
+
duplicated_contribution: dict[str, list[str]] = field(
|
|
33
|
+
default_factory=dict)
|
|
34
|
+
refuted_contribution: dict[str, list[str]] = field(default_factory=dict)
|
|
35
|
+
discarded_contribution: dict[str, list[str]] = field(
|
|
36
|
+
default_factory=dict)
|
|
37
|
+
findings: list[dict[str, Any]] = field(default_factory=list)
|
|
38
|
+
|
|
39
|
+
def audit(self, contributions: list[AgentContribution]
|
|
40
|
+
) -> AgentUniqueness:
|
|
41
|
+
"""Classify each agent's output ids: an id claimed by ≥2 agents is
|
|
42
|
+
duplicated for the later claimants; refuted/discarded buckets come
|
|
43
|
+
from the contribution rows themselves."""
|
|
44
|
+
seen: dict[str, str] = {}
|
|
45
|
+
for c in contributions:
|
|
46
|
+
self.agents_invoked.append(c.agent)
|
|
47
|
+
unique, dup = [], []
|
|
48
|
+
for cid in c.contribution_ids:
|
|
49
|
+
if cid in seen:
|
|
50
|
+
dup.append(cid)
|
|
51
|
+
else:
|
|
52
|
+
seen[cid] = c.agent
|
|
53
|
+
unique.append(cid)
|
|
54
|
+
if unique:
|
|
55
|
+
self.unique_contribution[c.agent] = unique
|
|
56
|
+
if dup:
|
|
57
|
+
self.duplicated_contribution[c.agent] = dup
|
|
58
|
+
if c.refuted:
|
|
59
|
+
self.refuted_contribution[c.agent] = list(c.refuted)
|
|
60
|
+
if c.discarded:
|
|
61
|
+
self.discarded_contribution[c.agent] = list(c.discarded)
|
|
62
|
+
if not unique and not c.refuted and not c.discarded \
|
|
63
|
+
and not dup:
|
|
64
|
+
self.findings.append({
|
|
65
|
+
"waste_type": "unused_agent", "agent": c.agent,
|
|
66
|
+
"confidence": "high",
|
|
67
|
+
"evidence": "invoked with zero contribution ids",
|
|
68
|
+
"recommended_fix": "review routing rule — candidate "
|
|
69
|
+
"for fanout reduction"})
|
|
70
|
+
elif not unique and (dup or c.refuted or c.discarded):
|
|
71
|
+
self.findings.append({
|
|
72
|
+
"waste_type": "unused_agent", "agent": c.agent,
|
|
73
|
+
"confidence": "medium",
|
|
74
|
+
"evidence": "all contribution ids duplicated, "
|
|
75
|
+
"refuted or discarded",
|
|
76
|
+
"recommended_fix": "routing review — contribution "
|
|
77
|
+
"overlapped entirely"})
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def to_dict(self) -> dict[str, Any]:
|
|
81
|
+
d = asdict(self)
|
|
82
|
+
d["agents_invoked_count"] = len(self.agents_invoked)
|
|
83
|
+
d["unique_ratio"] = (
|
|
84
|
+
sum(len(v) for v in self.unique_contribution.values()) /
|
|
85
|
+
max(1, sum(len(v) for v in self.unique_contribution.values())
|
|
86
|
+
+ sum(len(v) for v in self.duplicated_contribution.values())))
|
|
87
|
+
return d
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# §123 — selective agentics policy (data, not judgment).
|
|
91
|
+
def suggested_fanout(task_type: str, domains: list[str], risk: str,
|
|
92
|
+
conflict: bool = False) -> dict[str, Any]:
|
|
93
|
+
"""simple → ≤1 specialist; cross-domain → multi; conflict/high-risk
|
|
94
|
+
→ debate/review. Mirrors the Router V2 policy as an economy check."""
|
|
95
|
+
if conflict or risk in ("high", "critical"):
|
|
96
|
+
mode, agents = "debate-or-review", 3
|
|
97
|
+
elif len(domains) > 1:
|
|
98
|
+
mode, agents = "multi-specialist", min(len(domains), 4)
|
|
99
|
+
elif task_type in ("lint", "inventory", "policy", "diff"):
|
|
100
|
+
mode, agents = "deterministic", 0
|
|
101
|
+
else:
|
|
102
|
+
mode, agents = "single-specialist", 1
|
|
103
|
+
return {"mode": mode, "suggested_max_agents": agents,
|
|
104
|
+
"reason": "§123 selective agentics"}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def fanout_verdict(envelope_check: dict[str, Any], requested_agents: int,
|
|
108
|
+
max_agents: int | None) -> dict[str, Any]:
|
|
109
|
+
"""§122/§246 — fanout may never exceed the envelope's hard limit."""
|
|
110
|
+
if max_agents is not None and requested_agents > max_agents:
|
|
111
|
+
return {"refusal": "PF-ECONOMY-BUDGET-EXHAUSTED",
|
|
112
|
+
"reason": f"fanout {requested_agents} > max_agents "
|
|
113
|
+
f"{max_agents}",
|
|
114
|
+
"dimension": "fanout"}
|
|
115
|
+
return {"decision": "ok", "fanout": requested_agents}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""platform-verifier machinery — independent closure check (§19–21).
|
|
2
|
+
|
|
3
|
+
The verifier is deterministic and INDEPENDENT: the component that
|
|
4
|
+
produced a conclusion cannot be its sole verifier. It maps acceptance
|
|
5
|
+
criteria → evidence, checks coverage/freshness claims, recomputes
|
|
6
|
+
receipts, and returns a verdict with a receipt — it never upgrades a
|
|
7
|
+
claim the evidence doesn't carry.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from platformforge.agents.contracts import AgentRefusal, AgentRunRecord, refusal
|
|
15
|
+
from platformforge.models.base import stable_id
|
|
16
|
+
|
|
17
|
+
PRODUCER_ROLES = ("orchestrator", "planner", "coordinator",
|
|
18
|
+
"specialist", "executor", "critic")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def verify_run(record: AgentRunRecord,
|
|
22
|
+
*, spec: Any | None = None,
|
|
23
|
+
producers: tuple[str, ...] | None = None,
|
|
24
|
+
evidence_index: dict[str, Any] | None = None,
|
|
25
|
+
verifier: str = "platform-verifier") -> dict[str, Any]:
|
|
26
|
+
"""Check a run record before closure.
|
|
27
|
+
|
|
28
|
+
record the run under review
|
|
29
|
+
spec the sealed PlatformTaskSpec (acceptance criteria)
|
|
30
|
+
producers agents that produced the run's outputs
|
|
31
|
+
evidence_index fact_id → fact doc (for existence/freshness checks)
|
|
32
|
+
"""
|
|
33
|
+
# default producers = run agents minus the verifier itself — the
|
|
34
|
+
# verifier appears in record.agents as a DAG participant, but it is
|
|
35
|
+
# only independent if it produced none of the conclusions
|
|
36
|
+
producers = tuple(producers) if producers is not None else tuple(
|
|
37
|
+
a for a in record.agents if a != verifier)
|
|
38
|
+
problems: list[str] = []
|
|
39
|
+
|
|
40
|
+
# §21/§183 — independence: a producer cannot be the sole verifier
|
|
41
|
+
if verifier in producers:
|
|
42
|
+
return refusal(
|
|
43
|
+
AgentRefusal.SELF_VERIFICATION,
|
|
44
|
+
f"{verifier} produced this run and cannot verify it",
|
|
45
|
+
"assign a verifier that is not in the producer set",
|
|
46
|
+
producers=list(producers))
|
|
47
|
+
if record.verifier and record.verifier in producers:
|
|
48
|
+
return refusal(
|
|
49
|
+
AgentRefusal.INDEPENDENCE_FAILED,
|
|
50
|
+
f"declared verifier {record.verifier} is in the producer "
|
|
51
|
+
"set", "verify with an agent that produced no output here")
|
|
52
|
+
|
|
53
|
+
# §20 — criteria → evidence map
|
|
54
|
+
criteria = list(getattr(spec, "acceptance_criteria", ()) or ())
|
|
55
|
+
ev_index = evidence_index or {}
|
|
56
|
+
evidence = [e for e in record.evidence
|
|
57
|
+
if not ev_index or e in ev_index]
|
|
58
|
+
missing_ev = [e for e in record.evidence if e not in ev_index] \
|
|
59
|
+
if ev_index else []
|
|
60
|
+
unmapped = []
|
|
61
|
+
if criteria:
|
|
62
|
+
# a criterion is satisfied if any evidence or finding addresses
|
|
63
|
+
# it — deterministic check: evidence must exist at all
|
|
64
|
+
for c in criteria:
|
|
65
|
+
if not evidence:
|
|
66
|
+
unmapped.append(c)
|
|
67
|
+
if unmapped:
|
|
68
|
+
problems.append("criteria with no evidence: "
|
|
69
|
+
+ "; ".join(unmapped))
|
|
70
|
+
for f in getattr(spec, "evidence_requirements", ()) or ():
|
|
71
|
+
if not evidence and "coverage" not in f:
|
|
72
|
+
problems.append(f"evidence requirement unmet: {f}")
|
|
73
|
+
|
|
74
|
+
# freshness check when the index carries it
|
|
75
|
+
stale = [eid for eid, f in ev_index.items()
|
|
76
|
+
if isinstance(f, dict)
|
|
77
|
+
and f.get("freshness", {}).get("status")
|
|
78
|
+
in ("stale", "deprecated", "superseded")]
|
|
79
|
+
if stale:
|
|
80
|
+
problems.append("stale evidence cited: " + ", ".join(sorted(stale)))
|
|
81
|
+
|
|
82
|
+
verdict = "confirmed"
|
|
83
|
+
if problems:
|
|
84
|
+
verdict = "unresolved"
|
|
85
|
+
elif record.verdict in ("refuted", "unsupported"):
|
|
86
|
+
verdict = record.verdict # never upgrade a refuted producer output
|
|
87
|
+
elif not evidence and record.evidence:
|
|
88
|
+
verdict = "unsupported"
|
|
89
|
+
elif not evidence:
|
|
90
|
+
verdict = "unresolved"
|
|
91
|
+
receipt_id = stable_id("PF-VRF", record.run_id, verdict)
|
|
92
|
+
out = {"verdict": verdict, "receipt": {
|
|
93
|
+
"receipt_id": receipt_id, "run_id": record.run_id,
|
|
94
|
+
"task_spec_hash": record.task_spec_hash,
|
|
95
|
+
"verifier": verifier,
|
|
96
|
+
"producers": list(producers),
|
|
97
|
+
"evidence_count": len(evidence),
|
|
98
|
+
"missing_evidence_refs": missing_ev,
|
|
99
|
+
"problems": problems}}
|
|
100
|
+
if verdict == "unresolved" and not evidence:
|
|
101
|
+
out.update(refusal(AgentRefusal.NO_EVIDENCE,
|
|
102
|
+
"run closed with no verifiable evidence",
|
|
103
|
+
"collect evidence for the declared criteria "
|
|
104
|
+
"and re-verify"))
|
|
105
|
+
out["receipt"]["problems"] = problems
|
|
106
|
+
return out
|