backblaze-proofframe / scripts /live_env_handoff.py
ADJCJH's picture
Sync post-live local Genblaze proof evidence
74ee21a verified
Raw
History Blame Contribute Delete
11.3 kB
#!/usr/bin/env python3
"""Build a redacted live credential handoff report."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
from typing import Any
from proofframe.config import normalize_genblaze_provider
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_JSON = ROOT / "docs" / "assets" / "live-credential-handoff.json"
DEFAULT_MD = ROOT / "docs" / "assets" / "live-credential-handoff.md"
PLACEHOLDERS = {"", "placeholder", "change-me", "changeme", "todo", "tbd", "none", "null", "..."}
REQUIRED_GROUPS = [
{
"id": "storage_backend_mode",
"label": "ProofFrame storage backend mode",
"accepted_names": ["PROOFFRAME_STORAGE_BACKEND"],
"expected": "b2",
"remediation": "Set PROOFFRAME_STORAGE_BACKEND=b2.",
},
{
"id": "generation_backend_mode",
"label": "ProofFrame generation backend mode",
"accepted_names": ["PROOFFRAME_GENERATION_BACKEND"],
"expected": "genblaze",
"remediation": "Set PROOFFRAME_GENERATION_BACKEND=genblaze.",
},
{
"id": "b2_endpoint",
"label": "Backblaze B2 S3 endpoint",
"accepted_names": ["B2_ENDPOINT_URL", "B2_S3_ENDPOINT_URL"],
"remediation": "Set B2_ENDPOINT_URL from the Backblaze bucket S3 endpoint.",
},
{
"id": "b2_bucket",
"label": "Backblaze B2 bucket",
"accepted_names": ["B2_BUCKET"],
"remediation": "Set B2_BUCKET to the dedicated demo bucket name.",
},
{
"id": "b2_key_id",
"label": "Backblaze B2 key id",
"accepted_names": ["B2_KEY_ID"],
"remediation": "Set B2_KEY_ID for a least-privilege application key.",
},
{
"id": "b2_application_key",
"label": "Backblaze B2 application key",
"accepted_names": ["B2_APPLICATION_KEY", "B2_APP_KEY"],
"remediation": "Set B2_APPLICATION_KEY or B2_APP_KEY.",
},
{
"id": "genblaze_provider_key",
"label": "Genblaze provider API key",
"accepted_names_by_provider": {
"gmicloud": ["GENBLAZE_API_KEY", "GMI_API_KEY"],
"openai": ["OPENAI_API_KEY"],
"local": [],
},
"remediation_by_provider": {
"gmicloud": "Set GENBLAZE_API_KEY or GMI_API_KEY.",
"openai": "Set OPENAI_API_KEY.",
"local": "GENBLAZE_PROVIDER=local does not require a provider API key.",
},
},
{
"id": "genblaze_image_model",
"label": "Genblaze image model",
"accepted_names": ["GENBLAZE_IMAGE_MODEL"],
"remediation": "Set GENBLAZE_IMAGE_MODEL to the verified image model.",
},
]
OPTIONAL_GROUPS = [
{
"id": "b2_public_base_url",
"label": "B2 public base URL",
"accepted_names": ["B2_PUBLIC_BASE_URL"],
},
{
"id": "b2_region",
"label": "B2 region for Genblaze sink",
"accepted_names": ["B2_REGION"],
},
{
"id": "genblaze_provider",
"label": "Genblaze provider",
"accepted_names": ["GENBLAZE_PROVIDER"],
},
{
"id": "genblaze_aspect_ratio",
"label": "Genblaze aspect ratio",
"accepted_names": ["GENBLAZE_ASPECT_RATIO"],
},
{
"id": "genblaze_timeout",
"label": "Genblaze timeout seconds",
"accepted_names": ["GENBLAZE_TIMEOUT_SECONDS"],
},
]
NEXT_COMMANDS = [
"python scripts/final_env_wizard.py --prefill-non-secret --output .env.final.local",
"python scripts/final_env_wizard.py --output .env.final.local --missing-only --force",
"python scripts/live_env_handoff.py --env-file .env.final.local",
"python scripts/run_final_live_proof.py --env-file .env.final.local --preflight-only",
"python scripts/run_final_live_proof.py --env-file .env.final.local --genblaze-provider local --genblaze-image-model local-svg-v1 --preflight-only",
"python scripts/run_final_live_proof.py --env-file .env.final.local --genblaze-provider openai --genblaze-image-model gpt-image-1 --preflight-only",
"python scripts/run_final_live_proof.py --env-file .env.final.local --evidence-out docs/assets/final-live-proof-evidence.json",
'python scripts/devpost_packet.py --post-live --video-url "$PROOFFRAME_PUBLIC_VIDEO_URL"',
"python scripts/secret_scan.py",
"python scripts/claim_lint.py",
"python scripts/submission_audit.py --strict-final",
]
def normalize_value(value: str | None) -> str:
if value is None:
return ""
return value.strip().strip('"').strip("'")
def has_real_value(value: str | None) -> bool:
normalized = normalize_value(value)
if normalized.lower() in PLACEHOLDERS:
return False
if normalized.startswith("<") and normalized.endswith(">"):
return False
return bool(normalized)
def parse_env_file(path: Path) -> dict[str, str]:
values: dict[str, str] = {}
for raw_line in path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
if key.startswith("export "):
key = key.removeprefix("export ").strip()
if key:
values[key] = normalize_value(value)
return values
def env_source(env_file: Path | None) -> tuple[dict[str, str], str]:
values = {key: value for key, value in os.environ.items()}
source = "process environment"
if env_file:
file_values = parse_env_file(env_file)
values.update(file_values)
source = str(env_file)
return values, source
def evaluate_group(group: dict[str, Any], values: dict[str, str]) -> dict[str, Any]:
provider = normalize_genblaze_provider(values.get("GENBLAZE_PROVIDER", "gmicloud"))
accepted_names = group.get("accepted_names")
remediation = group.get("remediation", "")
if "accepted_names_by_provider" in group:
accepted_by_provider = group["accepted_names_by_provider"]
accepted_names = accepted_by_provider.get(
provider,
sorted({name for names in accepted_by_provider.values() for name in names}),
)
remediation = group.get("remediation_by_provider", {}).get(
provider,
"Set GENBLAZE_PROVIDER to gmicloud, openai, or local and provide its required setup.",
)
if provider == "local" and not accepted_names:
return {
"id": group["id"],
"label": group["label"],
"ok": True,
"accepted_names": accepted_names,
"present_names": [],
"expected": group.get("expected"),
"remediation": "",
}
present_names = [name for name in accepted_names if has_real_value(values.get(name))]
expected = group.get("expected")
expected_ok = True
if expected is not None:
expected_ok = any(normalize_value(values.get(name)).lower() == expected for name in present_names)
ok = bool(present_names) and expected_ok
return {
"id": group["id"],
"label": group["label"],
"ok": ok,
"accepted_names": accepted_names,
"present_names": present_names,
"expected": expected,
"remediation": "" if ok else remediation,
}
def build_report(env_file: Path | None = None) -> dict[str, Any]:
values, source = env_source(env_file)
required = [evaluate_group(group, values) for group in REQUIRED_GROUPS]
optional = [evaluate_group(group, values) for group in OPTIONAL_GROUPS]
missing = [item for item in required if not item["ok"]]
return {
"schema": "proofframe.live_credential_handoff.v1",
"ok": not missing,
"mode": "live_env_ready" if not missing else "missing_live_env",
"source": source,
"required": required,
"optional": optional,
"missing_ids": [item["id"] for item in missing],
"next_commands": NEXT_COMMANDS,
"secret_policy": (
"This report records only variable names and presence checks. It never prints, hashes, "
"stores, or commits credential values."
),
}
def render_markdown(report: dict[str, Any]) -> str:
lines = [
"# ProofFrame Live Credential Handoff",
"",
f"Mode: `{report['mode']}`",
f"Ready for live proof: `{str(report['ok']).lower()}`",
f"Source: `{report['source']}`",
"",
report["secret_policy"],
"",
"## Required Values",
"",
]
for item in report["required"]:
marker = "OK" if item["ok"] else "MISSING"
names = ", ".join(f"`{name}`" for name in item["accepted_names"])
present = ", ".join(f"`{name}`" for name in item["present_names"]) or "none"
expected = f"; expected `{item['expected']}`" if item["expected"] else ""
lines.append(f"- {marker} {item['label']}: {names}; present {present}{expected}.")
if item["remediation"]:
lines.append(f" Remediation: {item['remediation']}")
lines.extend(["", "## Optional Values", ""])
for item in report["optional"]:
marker = "SET" if item["present_names"] else "UNSET"
names = ", ".join(f"`{name}`" for name in item["accepted_names"])
present = ", ".join(f"`{name}`" for name in item["present_names"]) or "none"
lines.append(f"- {marker} {item['label']}: {names}; present {present}.")
lines.extend(["", "## Next Commands", ""])
lines.extend(f"```bash\n{command}\n```" for command in report["next_commands"])
return "\n".join(lines) + "\n"
def write_outputs(report: dict[str, Any], json_path: Path, markdown_path: Path) -> None:
json_path.parent.mkdir(parents=True, exist_ok=True)
markdown_path.parent.mkdir(parents=True, exist_ok=True)
json_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
markdown_path.write_text(render_markdown(report), encoding="utf-8")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Build a redacted ProofFrame live credential handoff report."
)
parser.add_argument("--env-file", type=Path, help="Optional local env file; values are not printed.")
parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON)
parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MD)
parser.add_argument(
"--strict",
action="store_true",
help="Exit nonzero when required live variables are missing.",
)
return parser
def main() -> None:
args = build_parser().parse_args()
report = build_report(args.env_file)
write_outputs(report, args.json_out, args.markdown_out)
print(
json.dumps(
{
"ok": report["ok"],
"mode": report["mode"],
"json": str(args.json_out),
"markdown": str(args.markdown_out),
"missing_ids": report["missing_ids"],
},
indent=2,
)
)
if args.strict and not report["ok"]:
raise SystemExit(2)
if __name__ == "__main__":
main()