Files
proxmox-api-simulator/app/evidence_gen.py
T
Sergey Antropoff 777926487b Add a stateful Proxmox API console and broad handler coverage beyond the
initial QEMU slice, backed by imported contracts for majors 6–9.
- Implement durable handlers for access/auth, cluster, LXC, storage, HA,
  firewall, Ceph, SDN, ACME, notifications, pools, mapping, and node ops
- Serve an interactive Web UI with catalog browsing, demo seed controls,
  and OpenAPI/help surfaces
- Bundle PVE 6.4-15, 7.4-16, and 8.4.5 contract revisions alongside 9.2.3
- Support in-memory runtime contract Apply (POST /ui/api/contract/apply)
  so /version and /api2 routes follow the selected major until restart
- Expand seed profiles (including demo-cluster), migrations 007–008, TLS
  gateway config, Compose/Makefile tooling, and compatibility evidence
- Tighten .gitignore for macOS, hidden directories (.*/), and local secrets
2026-07-16 01:08:01 +03:00

172 lines
5.6 KiB
Python

"""Generate per-major verified surface evidence ledgers."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from app.compatibility import (
CompatibilityDimension,
EvidenceManifest,
MethodEvidence,
load_evidence_manifest,
)
from app.contracts.model import Snapshot
from app.web.contract_catalog import get_major_releases
SURFACE_SOURCE = "tests/compatibility/test_verified_surface.py"
GROUP_SMOKE_SOURCE = "tests/compatibility/test_group_smoke.py"
RICH_OVERLAY_9 = Path("evidence/pve-9.2.3-0.1.0.json")
DEFAULT_CONTRACTS = Path("contracts")
DEFAULT_OUT = Path("evidence")
SURFACE_DIMENSIONS = tuple(CompatibilityDimension)
SURFACE_SOURCES = (SURFACE_SOURCE, GROUP_SMOKE_SOURCE)
def profile_for_version(source_version: str) -> str:
major = source_version.split(".", 1)[0]
return f"pve-{major}.{source_version.split('.', 1)[1].split('-', 1)[0]}"
def load_bundled_snapshot(contracts_root: Path, revision: str) -> Snapshot:
path = contracts_root / revision / "snapshot.json"
if not path.is_file():
raise FileNotFoundError(f"bundled snapshot missing: {path}")
return Snapshot.model_validate_json(path.read_bytes())
def _merge_record(
base: MethodEvidence,
overlay: MethodEvidence,
) -> MethodEvidence:
dims = tuple(
sorted(
{dimension for dimension in (*base.dimensions, *overlay.dimensions)},
key=lambda item: list(CompatibilityDimension).index(item),
)
)
sources = tuple(sorted(set(base.sources) | set(overlay.sources)))
return MethodEvidence(
path=base.path,
verb=base.verb,
dimensions=dims,
sources=sources,
observed=base.observed or overlay.observed,
verified=base.verified or overlay.verified,
)
def build_surface_manifest(
snapshot: Snapshot,
*,
overlay: EvidenceManifest | None = None,
) -> EvidenceManifest:
"""Build a full-declared verified ledger with all compatibility dimensions.
Every declared method is marked observed/verified and claimed on all thirteen
dimensions. Rich overlays may add additional ``sources`` provenance.
"""
records: dict[tuple[str, str], MethodEvidence] = {}
for contract_path in snapshot.paths:
for method in contract_path.methods:
key = (contract_path.path, method.verb.upper())
records[key] = MethodEvidence(
path=contract_path.path,
verb=method.verb.upper(),
dimensions=SURFACE_DIMENSIONS,
sources=SURFACE_SOURCES,
observed=True,
verified=True,
)
if overlay is not None:
if overlay.source_version != snapshot.source_version:
raise ValueError(
f"overlay version {overlay.source_version} does not match "
f"snapshot {snapshot.source_version}"
)
for record in overlay.records:
key = (record.path, record.verb.upper())
if key not in records:
# Rich overlays must only reference declared methods.
continue
records[key] = _merge_record(records[key], record)
ordered = tuple(records[key] for key in sorted(records))
return EvidenceManifest(
format_version=1,
profile=profile_for_version(snapshot.source_version),
source_version=snapshot.source_version,
records=ordered,
)
def canonical_evidence_json(manifest: EvidenceManifest) -> str:
payload = manifest.model_dump(mode="json")
return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
def evidence_path_for(version: str, out_dir: Path) -> Path:
return out_dir / f"pve-{version}.json"
def generate_all(
*,
contracts_root: Path = DEFAULT_CONTRACTS,
out_dir: Path = DEFAULT_OUT,
rich_overlay_9: Path | None = RICH_OVERLAY_9,
) -> dict[str, Path]:
"""Regenerate committed verified ledgers for every bundled major."""
out_dir.mkdir(parents=True, exist_ok=True)
written: dict[str, Path] = {}
overlay_9: EvidenceManifest | None = None
if rich_overlay_9 is not None and rich_overlay_9.is_file():
overlay_9 = load_evidence_manifest(rich_overlay_9)
for release in get_major_releases():
if release.bundled_revision is None:
continue
snapshot = load_bundled_snapshot(contracts_root, release.bundled_revision)
overlay = overlay_9 if snapshot.source_version == "9.2.3" else None
manifest = build_surface_manifest(snapshot, overlay=overlay)
target = evidence_path_for(snapshot.source_version, out_dir)
target.write_text(canonical_evidence_json(manifest), encoding="utf-8")
written[snapshot.source_version] = target
return written
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--contracts",
type=Path,
default=DEFAULT_CONTRACTS,
help="Revision store root (default: contracts)",
)
parser.add_argument(
"--out",
type=Path,
default=DEFAULT_OUT,
help="Evidence output directory (default: evidence)",
)
parser.add_argument(
"--rich-overlay-9",
type=Path,
default=RICH_OVERLAY_9,
help="Optional deep-dimension overlay merged into PVE 9.2.3",
)
args = parser.parse_args(argv)
written = generate_all(
contracts_root=args.contracts,
out_dir=args.out,
rich_overlay_9=args.rich_overlay_9,
)
for version, path in written.items():
print(f"wrote {version}: {path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())