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,157 @@
|
|
|
1
|
+
"""Knowledge packs — operational knowledge, not a provenance registry
|
|
2
|
+
(cycle 2.1 §60–68).
|
|
3
|
+
|
|
4
|
+
A pack is a YAML document under ``knowledge/<domain>/<id>.yaml``:
|
|
5
|
+
|
|
6
|
+
```yaml
|
|
7
|
+
schema: platformforge/knowledge/v1
|
|
8
|
+
id: kubernetes-api-deprecations
|
|
9
|
+
domain: kubernetes
|
|
10
|
+
applies_to: {versions: [kubernetes]}
|
|
11
|
+
sources: [kubernetes-docs]
|
|
12
|
+
claims:
|
|
13
|
+
- id: k8s-dep-v122
|
|
14
|
+
statement: "..."
|
|
15
|
+
versions: {kubernetes: ">=1.22"}
|
|
16
|
+
used_by:
|
|
17
|
+
rules: [PF-K8S-030]
|
|
18
|
+
analyzers: [k8s]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Consumption is checked, not asserted: ``contract_check`` resolves every
|
|
22
|
+
``sources`` id against the source registry, every ``used_by.rules`` against
|
|
23
|
+
the rule catalog, and every ``used_by.analyzers`` against the analyzer
|
|
24
|
+
registry — a pack nothing consumes is dead weight (§63/§136). Each pack
|
|
25
|
+
carries a ``content_hash`` (sha256 of the canonical claim set) so consumers
|
|
26
|
+
detect drift when content changes (§68).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import hashlib
|
|
32
|
+
import json
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
import yaml
|
|
38
|
+
|
|
39
|
+
SCHEMA = "platformforge/knowledge/v1"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class Claim:
|
|
44
|
+
claim_id: str
|
|
45
|
+
statement: str
|
|
46
|
+
versions: dict[str, str] = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class Pack:
|
|
51
|
+
pack_id: str
|
|
52
|
+
domain: str
|
|
53
|
+
applies_to: dict[str, Any]
|
|
54
|
+
sources: tuple[str, ...]
|
|
55
|
+
claims: tuple[Claim, ...]
|
|
56
|
+
used_by: dict[str, list[str]]
|
|
57
|
+
path: str = ""
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def content_hash(self) -> str:
|
|
61
|
+
"""§68 — sha256 over the canonical claim set; drift detectable."""
|
|
62
|
+
blob = json.dumps({
|
|
63
|
+
"id": self.pack_id,
|
|
64
|
+
"claims": [{"id": c.claim_id, "statement": c.statement,
|
|
65
|
+
"versions": c.versions} for c in self.claims],
|
|
66
|
+
}, sort_keys=True)
|
|
67
|
+
return hashlib.sha256(blob.encode()).hexdigest()[:16]
|
|
68
|
+
|
|
69
|
+
def to_dict(self) -> dict[str, Any]:
|
|
70
|
+
return {"id": self.pack_id, "domain": self.domain,
|
|
71
|
+
"path": self.path, "content_hash": self.content_hash,
|
|
72
|
+
"sources": list(self.sources),
|
|
73
|
+
"claims": [{"id": c.claim_id, "statement": c.statement,
|
|
74
|
+
"versions": c.versions} for c in self.claims],
|
|
75
|
+
"used_by": self.used_by}
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_dict(cls, d: dict[str, Any], path: str = "") -> Pack:
|
|
79
|
+
if d.get("schema") != SCHEMA:
|
|
80
|
+
raise ValueError(f"{path or d.get('id')}: schema must be "
|
|
81
|
+
f"{SCHEMA}")
|
|
82
|
+
missing = [k for k in ("id", "domain", "sources", "claims")
|
|
83
|
+
if k not in d]
|
|
84
|
+
if missing:
|
|
85
|
+
raise ValueError(f"{d.get('id', path)}: missing {missing}")
|
|
86
|
+
claims = tuple(Claim(str(c["id"]), str(c["statement"]),
|
|
87
|
+
dict(c.get("versions") or {}))
|
|
88
|
+
for c in d["claims"])
|
|
89
|
+
return cls(pack_id=str(d["id"]), domain=str(d["domain"]),
|
|
90
|
+
applies_to=dict(d.get("applies_to") or {}),
|
|
91
|
+
sources=tuple(str(s) for s in d["sources"]),
|
|
92
|
+
claims=claims,
|
|
93
|
+
used_by=dict(d.get("used_by") or {}), path=path)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class PackRegistry:
|
|
97
|
+
def __init__(self, root: str | Path | None = None):
|
|
98
|
+
if root is None:
|
|
99
|
+
from platformforge.resources import data_path
|
|
100
|
+
root = data_path("knowledge")
|
|
101
|
+
self.root = Path(root)
|
|
102
|
+
self.packs: dict[str, Pack] = {}
|
|
103
|
+
if self.root.is_dir():
|
|
104
|
+
for f in sorted(self.root.rglob("*.yaml")):
|
|
105
|
+
if f.name == "sources.yaml":
|
|
106
|
+
continue
|
|
107
|
+
d = yaml.safe_load(f.read_text())
|
|
108
|
+
if not isinstance(d, dict) or d.get("schema") != SCHEMA:
|
|
109
|
+
continue
|
|
110
|
+
p = Pack.from_dict(d, path=str(f.relative_to(self.root)))
|
|
111
|
+
self.packs[p.pack_id] = p
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def default(cls) -> PackRegistry:
|
|
115
|
+
return cls()
|
|
116
|
+
|
|
117
|
+
def for_rule(self, rule_id: str) -> list[Pack]:
|
|
118
|
+
return [p for p in self.packs.values()
|
|
119
|
+
if rule_id in (p.used_by.get("rules") or [])]
|
|
120
|
+
|
|
121
|
+
def for_analyzer(self, name: str) -> list[Pack]:
|
|
122
|
+
return [p for p in self.packs.values()
|
|
123
|
+
if name in (p.used_by.get("analyzers") or [])]
|
|
124
|
+
|
|
125
|
+
def contract_check(self) -> dict[str, Any]:
|
|
126
|
+
"""Every pack consumed by something real: sources resolve against
|
|
127
|
+
the registry, rules against the catalog, analyzers against the lab
|
|
128
|
+
analyzer table."""
|
|
129
|
+
from platformforge.knowledge.registry import SourceRegistry
|
|
130
|
+
from platformforge.lab.runner import _ANALYZERS
|
|
131
|
+
from platformforge.resources import data_path
|
|
132
|
+
from platformforge.rules import load_catalog
|
|
133
|
+
sources = SourceRegistry.default().entries
|
|
134
|
+
rules = {r.rule_id
|
|
135
|
+
for r in load_catalog(data_path("rules", "catalog"))}
|
|
136
|
+
problems: list[dict[str, Any]] = []
|
|
137
|
+
consumed = 0
|
|
138
|
+
for p in sorted(self.packs.values(), key=lambda x: x.pack_id):
|
|
139
|
+
bad_src = [s for s in p.sources if s not in sources]
|
|
140
|
+
bad_rules = [r for r in p.used_by.get("rules", [])
|
|
141
|
+
if r not in rules]
|
|
142
|
+
bad_an = [a for a in p.used_by.get("analyzers", [])
|
|
143
|
+
if a not in _ANALYZERS]
|
|
144
|
+
if not p.used_by.get("rules") and not p.used_by.get("analyzers"):
|
|
145
|
+
problems.append({"pack": p.pack_id, "issue": "unconsumed"})
|
|
146
|
+
else:
|
|
147
|
+
consumed += 1
|
|
148
|
+
for kind, ids in (("unknown_source", bad_src),
|
|
149
|
+
("unknown_rule", bad_rules),
|
|
150
|
+
("unknown_analyzer", bad_an)):
|
|
151
|
+
for i in ids:
|
|
152
|
+
problems.append({"pack": p.pack_id, "issue": kind,
|
|
153
|
+
"ref": i})
|
|
154
|
+
if not p.claims:
|
|
155
|
+
problems.append({"pack": p.pack_id, "issue": "no_claims"})
|
|
156
|
+
return {"packs": len(self.packs), "consumed": consumed,
|
|
157
|
+
"problems": problems, "ok": not problems}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Source registry + knowledge freshness.
|
|
2
|
+
|
|
3
|
+
Old documentation is never eternal truth. Every version-dependent claim cites
|
|
4
|
+
a source entry; freshness is computed, not assumed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import date, datetime, timezone
|
|
12
|
+
|
|
13
|
+
UTC = timezone.utc # datetime.UTC alias is 3.11+; keep 3.10 compatible
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from platformforge.models import FreshnessStatus
|
|
20
|
+
|
|
21
|
+
FRESH_DAYS = 120 # verified within ~4 months
|
|
22
|
+
STALE_DAYS = 365 # older than a year → stale by default
|
|
23
|
+
|
|
24
|
+
# Closed vocabulary for `source_authority` — asserted by the CI gate.
|
|
25
|
+
SOURCE_AUTHORITIES = ("official", "vendor-research", "first-party-sibling")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SourceEntry:
|
|
30
|
+
id: str
|
|
31
|
+
source: str
|
|
32
|
+
source_authority: str = "unknown"
|
|
33
|
+
retrieved_at: str = ""
|
|
34
|
+
product: str = ""
|
|
35
|
+
version: str = ""
|
|
36
|
+
releases_tracked: list[str] = field(default_factory=list)
|
|
37
|
+
scope: str = ""
|
|
38
|
+
cloud: str = ""
|
|
39
|
+
deprecated: bool = False
|
|
40
|
+
superseded_by: str = ""
|
|
41
|
+
valid_from: str = ""
|
|
42
|
+
valid_until: str = ""
|
|
43
|
+
confidence: str = "medium"
|
|
44
|
+
notes: str = ""
|
|
45
|
+
aliases: list[str] = field(default_factory=list)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, d: dict[str, Any]) -> SourceEntry:
|
|
49
|
+
known = {f for f in cls.__dataclass_fields__}
|
|
50
|
+
return cls(**{k: v for k, v in d.items() if k in known})
|
|
51
|
+
|
|
52
|
+
def freshness(self, today: date | None = None) -> str:
|
|
53
|
+
"""current|fresh|stale|deprecated|superseded|conflicted|unresolved."""
|
|
54
|
+
today = today or datetime.now(UTC).date()
|
|
55
|
+
if self.deprecated:
|
|
56
|
+
return FreshnessStatus.DEPRECATED
|
|
57
|
+
if self.superseded_by:
|
|
58
|
+
return FreshnessStatus.SUPERSEDED
|
|
59
|
+
if self.valid_until and str(self.valid_until) < today.isoformat():
|
|
60
|
+
return FreshnessStatus.STALE
|
|
61
|
+
if self.valid_from and str(self.valid_from) > today.isoformat():
|
|
62
|
+
return FreshnessStatus.UNRESOLVED
|
|
63
|
+
if not self.retrieved_at:
|
|
64
|
+
return FreshnessStatus.UNRESOLVED
|
|
65
|
+
try:
|
|
66
|
+
days = (today - date.fromisoformat(str(self.retrieved_at)[:10])).days
|
|
67
|
+
except ValueError:
|
|
68
|
+
return FreshnessStatus.UNRESOLVED
|
|
69
|
+
if days <= FRESH_DAYS:
|
|
70
|
+
return FreshnessStatus.CURRENT if days <= 30 else FreshnessStatus.FRESH
|
|
71
|
+
return FreshnessStatus.STALE if days > STALE_DAYS else FreshnessStatus.FRESH
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SourceRegistry:
|
|
75
|
+
def __init__(self, path: str | Path):
|
|
76
|
+
self.path = Path(path)
|
|
77
|
+
doc = yaml.safe_load(self.path.read_text())
|
|
78
|
+
self.schema = doc.get("schema", "")
|
|
79
|
+
self.entries: dict[str, SourceEntry] = {}
|
|
80
|
+
self.duplicate_ids: list[str] = []
|
|
81
|
+
for e in doc.get("sources", []):
|
|
82
|
+
if e["id"] in self.entries:
|
|
83
|
+
self.duplicate_ids.append(e["id"])
|
|
84
|
+
continue
|
|
85
|
+
self.entries[e["id"]] = SourceEntry.from_dict(e)
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def default(cls) -> SourceRegistry:
|
|
89
|
+
from platformforge.resources import data_path
|
|
90
|
+
return cls(data_path("knowledge", "sources.yaml"))
|
|
91
|
+
|
|
92
|
+
def get(self, source_id: str) -> SourceEntry | None:
|
|
93
|
+
return self.entries.get(source_id)
|
|
94
|
+
|
|
95
|
+
def resolve(self, ref: str) -> SourceEntry | None:
|
|
96
|
+
"""Canonical id, else explicit `aliases:` declared on an entry.
|
|
97
|
+
No inference — a ref that is neither is unresolvable."""
|
|
98
|
+
if ref in self.entries:
|
|
99
|
+
return self.entries[ref]
|
|
100
|
+
for e in self.entries.values():
|
|
101
|
+
if ref in e.aliases:
|
|
102
|
+
return e
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def check(self, today: date | None = None) -> list[dict[str, Any]]:
|
|
106
|
+
"""Freshness report for every registered source."""
|
|
107
|
+
return [
|
|
108
|
+
{"id": e.id, "product": e.product, "version": e.version,
|
|
109
|
+
"retrieved_at": e.retrieved_at, "status": e.freshness(today),
|
|
110
|
+
"superseded_by": e.superseded_by}
|
|
111
|
+
for e in sorted(self.entries.values(), key=lambda x: x.id)
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def unresolved(self, today: date | None = None) -> list[str]:
|
|
115
|
+
return [r["id"] for r in self.check(today) if r["status"] in
|
|
116
|
+
(FreshnessStatus.UNRESOLVED, FreshnessStatus.CONFLICTED)]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# §46 — knowledge entry contract: these fields are mandatory for an
|
|
120
|
+
# entry to count as knowledge-backed.
|
|
121
|
+
REQUIRED = ("id", "source", "source_authority", "retrieved_at",
|
|
122
|
+
"confidence")
|
|
123
|
+
|
|
124
|
+
def contract_check(self) -> dict[str, Any]:
|
|
125
|
+
"""§46 — report entries missing contract fields, duplicate ids,
|
|
126
|
+
and alias collisions (an alias owned by two entries resolves to
|
|
127
|
+
whichever loads first — silent wrong-linkage)."""
|
|
128
|
+
bad = {}
|
|
129
|
+
for e in self.entries.values():
|
|
130
|
+
missing = [f for f in self.REQUIRED
|
|
131
|
+
if not getattr(e, f, None)]
|
|
132
|
+
if missing:
|
|
133
|
+
bad[e.id or "?"] = missing
|
|
134
|
+
alias_owner: dict[str, str] = {}
|
|
135
|
+
alias_collisions: dict[str, list[str]] = {}
|
|
136
|
+
for e in self.entries.values():
|
|
137
|
+
for a in e.aliases:
|
|
138
|
+
if a in alias_owner and alias_owner[a] != e.id:
|
|
139
|
+
alias_collisions.setdefault(a, []).append(e.id)
|
|
140
|
+
else:
|
|
141
|
+
alias_owner[a] = e.id
|
|
142
|
+
for a, owners in alias_collisions.items():
|
|
143
|
+
owners.insert(0, alias_owner[a])
|
|
144
|
+
ok = not bad and not self.duplicate_ids and not alias_collisions
|
|
145
|
+
return {"entries": len(self.entries), "invalid": bad,
|
|
146
|
+
"duplicate_ids": self.duplicate_ids,
|
|
147
|
+
"alias_collisions": alias_collisions,
|
|
148
|
+
"ok": ok}
|
|
149
|
+
|
|
150
|
+
def link_rules(self, *catalog_dirs: str | Path) -> dict[str, Any]:
|
|
151
|
+
"""§44/§47 + cycle 2.1 — rule→source linkage by exact canonical id.
|
|
152
|
+
|
|
153
|
+
Every `sources:` entry on a rule must be a registered id (or an
|
|
154
|
+
entry's explicit `aliases:` member). No domain-suffix inference —
|
|
155
|
+
`k8s.io` must never silently match `gateway-api.sigs.k8s.io`.
|
|
156
|
+
Unresolvable refs are reported `unlinked`, not silently trusted.
|
|
157
|
+
"""
|
|
158
|
+
from platformforge.rules import load_catalog
|
|
159
|
+
linked, unlinked, bad_refs = {}, [], []
|
|
160
|
+
for r in load_catalog(*catalog_dirs):
|
|
161
|
+
hits = [{"source": s,
|
|
162
|
+
"entry": (self.resolve(s) or SourceEntry(
|
|
163
|
+
id="", source="")).id or None}
|
|
164
|
+
for s in r.sources]
|
|
165
|
+
if hits and all(h["entry"] for h in hits):
|
|
166
|
+
linked[r.rule_id] = [h["entry"] for h in hits]
|
|
167
|
+
else:
|
|
168
|
+
unlinked.append({"rule_id": r.rule_id, "sources": hits})
|
|
169
|
+
dangling = sorted(k for k in getattr(r, "source_refs", {})
|
|
170
|
+
if k not in r.sources)
|
|
171
|
+
if dangling:
|
|
172
|
+
bad_refs.append({"rule_id": r.rule_id,
|
|
173
|
+
"source_refs": dangling})
|
|
174
|
+
return {"linked": linked, "unlinked": unlinked, "bad_refs": bad_refs,
|
|
175
|
+
"coverage": len(linked) / max(len(linked) + len(unlinked), 1)}
|
|
176
|
+
|
|
177
|
+
def drift_report(self, *catalog_dirs: str | Path,
|
|
178
|
+
today: date | None = None) -> dict[str, Any]:
|
|
179
|
+
"""§47 — knowledge drift: new major versions tracked but not
|
|
180
|
+
reflected, deprecated/superseded sources still cited, unavailable
|
|
181
|
+
sources, and rules whose backing sources went stale.
|
|
182
|
+
|
|
183
|
+
Never a bare "ok": every signal carries the entry/rule it
|
|
184
|
+
concerns and the reason."""
|
|
185
|
+
today = today or datetime.now(UTC).date()
|
|
186
|
+
link = self.link_rules(*catalog_dirs)
|
|
187
|
+
statuses = {r["id"]: r for r in self.check(today)}
|
|
188
|
+
|
|
189
|
+
signals: list[dict[str, Any]] = []
|
|
190
|
+
for e in sorted(self.entries.values(), key=lambda x: x.id):
|
|
191
|
+
st = statuses.get(e.id, {}).get("status", "unresolved")
|
|
192
|
+
if st in (FreshnessStatus.DEPRECATED,):
|
|
193
|
+
signals.append({"kind": "deprecated-source",
|
|
194
|
+
"source": e.id,
|
|
195
|
+
"reason": "entry marked deprecated"})
|
|
196
|
+
if st in (FreshnessStatus.SUPERSEDED,):
|
|
197
|
+
signals.append({"kind": "superseded-source",
|
|
198
|
+
"source": e.id,
|
|
199
|
+
"reason": f"superseded_by={e.superseded_by}"})
|
|
200
|
+
if st in (FreshnessStatus.STALE, FreshnessStatus.UNRESOLVED):
|
|
201
|
+
signals.append({"kind": "stale-source", "source": e.id,
|
|
202
|
+
"reason": f"freshness={st}"})
|
|
203
|
+
if not e.source:
|
|
204
|
+
signals.append({"kind": "unavailable-source",
|
|
205
|
+
"source": e.id,
|
|
206
|
+
"reason": "no source locator"})
|
|
207
|
+
# new major version: releases_tracked majors newer than the
|
|
208
|
+
# entry's declared version → knowledge drift signal
|
|
209
|
+
def _maj(v: str) -> int | None:
|
|
210
|
+
import re
|
|
211
|
+
m = re.match(r"\s*v?(\d+)", str(v))
|
|
212
|
+
return int(m.group(1)) if m else None
|
|
213
|
+
cur = _maj(e.version)
|
|
214
|
+
newer = sorted({_maj(r) for r in e.releases_tracked
|
|
215
|
+
if _maj(r) is not None and cur is not None
|
|
216
|
+
and _maj(r) > cur})
|
|
217
|
+
for mj in newer:
|
|
218
|
+
signals.append({
|
|
219
|
+
"kind": "new-major-version", "source": e.id,
|
|
220
|
+
"reason": f"entry pinned to {e.version} but v{mj} "
|
|
221
|
+
"is tracked — re-derive version-gated claims"})
|
|
222
|
+
|
|
223
|
+
# rules citing stale/deprecated/superseded sources
|
|
224
|
+
stale_ids = {s["source"] for s in signals
|
|
225
|
+
if s["kind"] in ("deprecated-source",
|
|
226
|
+
"superseded-source", "stale-source")}
|
|
227
|
+
stale_rules = [
|
|
228
|
+
{"rule_id": rid, "sources": [s for s in srcs
|
|
229
|
+
if s in stale_ids]}
|
|
230
|
+
for rid, srcs in link["linked"].items()
|
|
231
|
+
if any(s in stale_ids for s in srcs)]
|
|
232
|
+
return {"linkage": link,
|
|
233
|
+
"signals": signals,
|
|
234
|
+
"stale_rule_refs": stale_rules,
|
|
235
|
+
"unresolved": [u["rule_id"] for u in link["unlinked"]],
|
|
236
|
+
"counts": {"signals": len(signals),
|
|
237
|
+
"stale_rules": len(stale_rules),
|
|
238
|
+
"unlinked": len(link["unlinked"])}}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def knowledge_age_days(retrieved_at: str, today: date | None = None) -> int | None:
|
|
242
|
+
try:
|
|
243
|
+
return ((today or datetime.now(UTC).date()) - date.fromisoformat(str(retrieved_at)[:10])).days
|
|
244
|
+
except ValueError:
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def now_iso() -> str:
|
|
249
|
+
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Knowledge economy (§208–215) — load only the relevant packs.
|
|
2
|
+
|
|
3
|
+
Selector inputs: task, domain, version, risk (§209). A pack declares
|
|
4
|
+
applicability (`applies_to`); PackApplicability is the verdict (§210).
|
|
5
|
+
Pack budget caps how many packs a task loads — never the whole registry
|
|
6
|
+
(§211). Stale packs are flagged `review_needed`, not silently trusted
|
|
7
|
+
(§212). Search tiers are lexical/graph — no embeddings required (§213–214).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import asdict, dataclass
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class PackApplicability:
|
|
19
|
+
"""§210 — why a pack does or doesn't apply to a task."""
|
|
20
|
+
pack_id: str
|
|
21
|
+
applicable: bool
|
|
22
|
+
reason: str
|
|
23
|
+
freshness: str = "unknown" # current|stale|unresolved
|
|
24
|
+
score: int = 0
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, Any]:
|
|
27
|
+
return asdict(self)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def select_packs(packs: list[Any], *, domain: str = "",
|
|
31
|
+
versions: dict[str, str] | None = None,
|
|
32
|
+
risk: str = "low", max_packs: int = 8,
|
|
33
|
+
freshness_of=None) -> dict[str, Any]:
|
|
34
|
+
"""§209–212 — select applicable packs within budget.
|
|
35
|
+
|
|
36
|
+
`packs` are knowledge.packs.Pack-like objects exposing
|
|
37
|
+
`pack_id/domain/applies_to/claims`. `freshness_of` maps a pack's
|
|
38
|
+
source ids to a freshness state (defaults: unknown → review_needed
|
|
39
|
+
for high risk).
|
|
40
|
+
"""
|
|
41
|
+
versions = versions or {}
|
|
42
|
+
verdicts: list[PackApplicability] = []
|
|
43
|
+
for p in packs:
|
|
44
|
+
score, reasons = 0, []
|
|
45
|
+
if domain and getattr(p, "domain", "") == domain:
|
|
46
|
+
score += 2
|
|
47
|
+
reasons.append("domain match")
|
|
48
|
+
applies = getattr(p, "applies_to", {}) or {}
|
|
49
|
+
ok_versions = True
|
|
50
|
+
for prod, want in (applies.get("versions") or {}).items():
|
|
51
|
+
# applies_to.versions lists products the pack covers; the
|
|
52
|
+
# task's detected version for that product decides fit.
|
|
53
|
+
want = str(want)
|
|
54
|
+
have = versions.get(prod, "")
|
|
55
|
+
if prod in versions and _version_ok(have, want):
|
|
56
|
+
score += 2
|
|
57
|
+
reasons.append(f"{prod} {have} matches {want}")
|
|
58
|
+
elif prod not in versions:
|
|
59
|
+
pass # pack covers more than needed
|
|
60
|
+
if domain and getattr(p, "domain", "") and \
|
|
61
|
+
p.domain != domain:
|
|
62
|
+
ok_versions = False
|
|
63
|
+
reasons.append("domain mismatch")
|
|
64
|
+
verdicts.append(PackApplicability(
|
|
65
|
+
pack_id=getattr(p, "pack_id", "?"),
|
|
66
|
+
applicable=ok_versions and score > 0,
|
|
67
|
+
reason="; ".join(reasons) or "no applicability signal",
|
|
68
|
+
score=score))
|
|
69
|
+
|
|
70
|
+
applicable = sorted((v for v in verdicts if v.applicable),
|
|
71
|
+
key=lambda v: -v.score)
|
|
72
|
+
selected: list[PackApplicability] = []
|
|
73
|
+
for v in applicable[:max_packs]:
|
|
74
|
+
fr = freshness_of(v.pack_id) if freshness_of else "unknown"
|
|
75
|
+
v.freshness = fr
|
|
76
|
+
if fr in ("stale", "unresolved") and risk in ("high", "critical"):
|
|
77
|
+
v.reason += "; review_needed (stale pack under high risk)"
|
|
78
|
+
selected.append(v)
|
|
79
|
+
return {"selected": [v.to_dict() for v in selected],
|
|
80
|
+
"skipped": [v.to_dict() for v in verdicts
|
|
81
|
+
if not v.applicable],
|
|
82
|
+
"budget": max_packs,
|
|
83
|
+
"overflow": max(0, len(applicable) - max_packs)}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _version_ok(have: str, want: str) -> bool:
|
|
87
|
+
"""Minimal semver-ish range check (>=X.Y, >X.Y, ==X.Y, bare prefix)."""
|
|
88
|
+
m = re.match(r"^\s*(>=|>|<=|<|==)?\s*([\d.]+)", want)
|
|
89
|
+
if not m:
|
|
90
|
+
return have == want
|
|
91
|
+
op, target = m.groups()
|
|
92
|
+
h = [int(x) for x in have.split(".") if x.isdigit()]
|
|
93
|
+
t = [int(x) for x in target.split(".") if x.isdigit()]
|
|
94
|
+
n = max(len(h), len(t))
|
|
95
|
+
h += [0] * (n - len(h)); t += [0] * (n - len(t))
|
|
96
|
+
if op in (None, "=="):
|
|
97
|
+
return h == t
|
|
98
|
+
if op == ">=":
|
|
99
|
+
return h >= t
|
|
100
|
+
if op == ">":
|
|
101
|
+
return h > t
|
|
102
|
+
if op == "<=":
|
|
103
|
+
return h <= t
|
|
104
|
+
return h < t
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def search_knowledge(packs: list[Any], query: str,
|
|
108
|
+
*, tier2_graph=None) -> dict[str, Any]:
|
|
109
|
+
"""§213–214 — tiered search, no embeddings.
|
|
110
|
+
|
|
111
|
+
Tier 0: exact id/ref match. Tier 1: lexical substring/FTS over pack
|
|
112
|
+
id+domain+claims. Tier 2: caller-supplied graph expansion hook."""
|
|
113
|
+
q = query.lower()
|
|
114
|
+
t0 = [p for p in packs if getattr(p, "pack_id", "") == query]
|
|
115
|
+
if t0:
|
|
116
|
+
return {"tier": 0, "packs": t0}
|
|
117
|
+
t1 = [p for p in packs if q in getattr(p, "pack_id", "").lower()
|
|
118
|
+
or q in getattr(p, "domain", "").lower()
|
|
119
|
+
or any(q in c.statement.lower()
|
|
120
|
+
for c in getattr(p, "claims", ()))]
|
|
121
|
+
if t1:
|
|
122
|
+
return {"tier": 1, "packs": t1}
|
|
123
|
+
if tier2_graph is not None:
|
|
124
|
+
expanded = tier2_graph(query)
|
|
125
|
+
return {"tier": 2, "packs": expanded}
|
|
126
|
+
return {"tier": 2, "packs": [], "note": "no lexical match; "
|
|
127
|
+
"tier-2 graph expansion unavailable"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Chaos lab (§93) — deterministic fault injection *on the graph*.
|
|
2
|
+
|
|
3
|
+
Offline by construction: a fault removes or degrades nodes in a built
|
|
4
|
+
graph, then blast_radius computes the impacted set. Results are declared
|
|
5
|
+
simulations (`environment: simulation`), never live fault injection.
|
|
6
|
+
|
|
7
|
+
Production targets are refused unless the caller passes allow_prod — the
|
|
8
|
+
boundary is explicit, not implicit.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
from platformforge.graph.model import Graph
|
|
19
|
+
from platformforge.graph.query import blast_radius
|
|
20
|
+
|
|
21
|
+
FAULTS = ("pod_kill", "latency_injection", "dependency_unavailable",
|
|
22
|
+
"dns_failure", "network_loss", "node_unavailable")
|
|
23
|
+
|
|
24
|
+
# faults that degrade edges (dependencies stay, SLO risk rises)
|
|
25
|
+
_DEGRADE = {"latency_injection", "network_loss"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def chaos(g: Graph, fault: str, target: str,
|
|
29
|
+
allow_prod: bool = False) -> dict[str, Any]:
|
|
30
|
+
if fault not in FAULTS:
|
|
31
|
+
return {"refusal": "platform.chaos.unknown_fault",
|
|
32
|
+
"known": list(FAULTS)}
|
|
33
|
+
node = g.nodes.get(target)
|
|
34
|
+
if node is None:
|
|
35
|
+
return {"refusal": "platform.chaos.unknown_node", "node": target}
|
|
36
|
+
if node.attrs.get("env") == "production" and not allow_prod:
|
|
37
|
+
return {"refusal": "platform.chaos.production_default",
|
|
38
|
+
"node": target,
|
|
39
|
+
"unlock": "re-run with allow_prod=True in a controlled env"}
|
|
40
|
+
|
|
41
|
+
blast = blast_radius(g, target)
|
|
42
|
+
degraded = sorted(blast["nodes"])
|
|
43
|
+
impact = {"fault": fault, "target": target,
|
|
44
|
+
"environment": "simulation",
|
|
45
|
+
"mode": ("degraded" if fault in _DEGRADE else "removed"),
|
|
46
|
+
"impacted_total": blast["impacted_total"],
|
|
47
|
+
"impacted_by_class": blast["by_class"],
|
|
48
|
+
"degraded_nodes": degraded,
|
|
49
|
+
"unresolved": [
|
|
50
|
+
("post-fault recovery behavior is not observable from "
|
|
51
|
+
"static artifacts")]}
|
|
52
|
+
return {"chaos": "platformforge.chaos/v1", **impact}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run_scenario(path: str | Path,
|
|
56
|
+
allow_prod: bool = False) -> dict[str, Any]:
|
|
57
|
+
"""chaos.yaml: {fault, target, facts} → simulation against fixture graph."""
|
|
58
|
+
d = Path(path)
|
|
59
|
+
spec = yaml.safe_load((d / "chaos.yaml").read_text())
|
|
60
|
+
facts_doc = yaml.safe_load((d / spec.get("facts", "facts.json"))
|
|
61
|
+
.read_text())
|
|
62
|
+
from platformforge.graph import GraphBuilder
|
|
63
|
+
g = GraphBuilder().from_facts(
|
|
64
|
+
facts_doc.get("facts", facts_doc if isinstance(facts_doc, list)
|
|
65
|
+
else [])).graph
|
|
66
|
+
out = chaos(g, spec["fault"], spec["target"], allow_prod=allow_prod)
|
|
67
|
+
expected = spec.get("expected_impacted")
|
|
68
|
+
if expected is not None and "degraded_nodes" in out:
|
|
69
|
+
out["expected_match"] = \
|
|
70
|
+
sorted(expected) == out["degraded_nodes"]
|
|
71
|
+
return out
|