backblaze-proofframe / scripts /post_credential_live_proof.py
ADJCJH's picture
Sync post-live local Genblaze proof evidence
74ee21a verified
Raw
History Blame Contribute Delete
18.2 kB
#!/usr/bin/env python3
"""Run the post-credential live proof sequence without storing secrets."""
from __future__ import annotations
import argparse
from dataclasses import dataclass
from datetime import datetime, timezone
import json
from pathlib import Path
import re
import shlex
import subprocess
import sys
from typing import Any, Callable, Sequence
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_ENV_FILE = ROOT / ".env.final.local"
DEFAULT_JSON = ROOT / "docs" / "assets" / "post-credential-live-proof-plan.json"
DEFAULT_MD = ROOT / "docs" / "assets" / "post-credential-live-proof-plan.md"
SCHEMA = "proofframe.post_credential_live_proof.v1"
B2_EVIDENCE = ROOT / "docs" / "assets" / "b2-live-proof-evidence.json"
FINAL_EVIDENCE = ROOT / "docs" / "assets" / "final-live-proof-evidence.json"
FORBIDDEN_EVIDENCE_KEYS = re.compile(
r"(?i)(api[_-]?key|application[_-]?key|authorization|cookie|password|secret|token)"
)
FORBIDDEN_EVIDENCE_VALUES = [
re.compile(r"(?i)authorization:\s*bearer\s+[A-Za-z0-9._\-]{20,}"),
re.compile(r"(?i)(api[_-]?key|application[_-]?key|secret|token|cookie)=[^&\s]{8,}"),
re.compile(r"(?i)x-amz-(credential|security-token|signature)=[^&\s]{8,}"),
re.compile(r"(?i)gmi-[A-Za-z0-9_\-]{16,}"),
]
REQUIRED_EVIDENCE_VALUES = ("asset_sha256", "manifest_sha256", "asset_storage_key", "manifest_key")
FINAL_ALLOWED_ASSET_PROVIDERS = {
"genblaze/gmicloud-image",
"genblaze/openai-image",
"genblaze/local-image",
}
EXPECTED_EVIDENCE_FIELDS = {
"b2": {
"ok": True,
"storage_backend": "b2",
"generation_backend": "mock",
"asset_storage_backend": "b2",
"asset_provider": "mock",
"manifest_storage_backend": "b2",
},
"final": {
"ok": True,
"storage_backend": "b2",
"generation_backend": "genblaze",
"asset_storage_backend": "b2",
"manifest_storage_backend": "b2",
},
}
@dataclass(frozen=True)
class CommandSpec:
command_id: str
label: str
command: list[str]
def utc_now() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def rel(path: Path) -> str:
try:
return str(path.resolve().relative_to(ROOT.resolve()))
except ValueError:
return str(path)
def display_arg(arg: str) -> str:
path = Path(arg)
if path.is_absolute():
try:
return str(path.resolve().relative_to(ROOT.resolve()))
except ValueError:
if path.name.startswith("python"):
return "python"
return path.name
return arg
def display_command(command: Sequence[str]) -> str:
return shlex.join(display_arg(arg) for arg in command)
def script_command(script: str, *args: str, python: str = sys.executable) -> list[str]:
return [python, str(ROOT / "scripts" / script), *args]
def task_done_command(task_id: str, note: str, python: str = sys.executable) -> list[str]:
return script_command("task.py", "done", task_id, "--note", note, python=python)
def validate_evidence_command(kind: str, evidence_path: Path, python: str = sys.executable) -> list[str]:
return [
python,
str(ROOT / "scripts" / "post_credential_live_proof.py"),
"--validate-evidence",
kind,
"--evidence-path",
rel(evidence_path),
]
def build_commands(
*,
env_file: Path = DEFAULT_ENV_FILE,
update_tasks: bool = False,
python: str = sys.executable,
) -> list[CommandSpec]:
env_arg = rel(env_file)
commands = [
CommandSpec(
"credential_handoff",
"Verify local live credential presence without printing values",
script_command("live_env_handoff.py", "--env-file", env_arg, "--strict", python=python),
),
CommandSpec(
"b2_live_proof",
"Capture Backblaze B2 storage proof with mock generation",
script_command(
"run_b2_live_proof.py",
"--env-file",
env_arg,
"--evidence-out",
"docs/assets/b2-live-proof-evidence.json",
python=python,
),
),
CommandSpec(
"validate_b2_evidence",
"Validate sanitized B2 evidence before any T020 task update",
validate_evidence_command("b2", B2_EVIDENCE, python=python),
),
]
if update_tasks:
commands.append(
CommandSpec(
"mark_t020_done",
"Mark T020 done after sanitized B2 evidence exists",
task_done_command(
"T020",
"B2 live proof evidence captured in docs/assets/b2-live-proof-evidence.json.",
python=python,
),
)
)
commands.append(
CommandSpec(
"final_live_proof",
"Capture final B2 plus Genblaze proof",
script_command(
"run_final_live_proof.py",
"--env-file",
env_arg,
"--evidence-out",
"docs/assets/final-live-proof-evidence.json",
python=python,
),
)
)
commands.append(
CommandSpec(
"validate_final_evidence",
"Validate sanitized final B2 plus Genblaze evidence before any T021 task update",
validate_evidence_command("final", FINAL_EVIDENCE, python=python),
)
)
if update_tasks:
commands.append(
CommandSpec(
"mark_t021_done",
"Mark T021 done after sanitized final evidence exists",
task_done_command(
"T021",
"Final B2 plus Genblaze live proof evidence captured in docs/assets/final-live-proof-evidence.json.",
python=python,
),
)
)
commands.extend(
[
CommandSpec(
"live_env_handoff_report",
"Regenerate live credential handoff report",
script_command("live_env_handoff.py", "--env-file", env_arg, python=python),
),
CommandSpec("devpost_form_kit", "Regenerate Devpost form kit", script_command("devpost_form_kit.py", python=python)),
CommandSpec(
"devpost_submission_checklist",
"Regenerate Devpost submission checklist",
script_command("devpost_submission_checklist.py", python=python),
),
CommandSpec("judge_brief", "Regenerate judge brief", script_command("judge_brief.py", python=python)),
CommandSpec("judge_crosswalk", "Regenerate judge crosswalk", script_command("judge_crosswalk.py", python=python)),
CommandSpec("demo_storyboard", "Regenerate demo storyboard", script_command("demo_storyboard.py", python=python)),
CommandSpec("demo_readiness", "Regenerate demo readiness report", script_command("demo_readiness.py", python=python)),
CommandSpec(
"recording_assets",
"Regenerate recording assets report and verify public mock assets",
script_command("recording_assets.py", "--verify-public", python=python),
),
CommandSpec(
"award_readiness",
"Regenerate award readiness scorecard",
script_command("award_readiness.py", "--min-score", "75", python=python),
),
CommandSpec(
"final_operator_brief",
"Regenerate final operator brief",
script_command("final_operator_brief.py", python=python),
),
CommandSpec(
"final_launch_plan",
"Regenerate final launch plan",
script_command("final_launch_plan.py", python=python),
),
CommandSpec(
"final_rehearsal",
"Regenerate final rehearsal checklist",
script_command("final_rehearsal.py", python=python),
),
CommandSpec(
"final_submission_control",
"Regenerate final submission control",
script_command("final_submission_control.py", python=python),
),
CommandSpec(
"submission_audit",
"Regenerate pre-submit audit report",
script_command("submission_audit.py", python=python),
),
CommandSpec(
"devpost_submission_preview",
"Regenerate one-page Devpost submission preview",
script_command("devpost_submission_preview.py", python=python),
),
CommandSpec("secret_scan", "Run no-value secret scan", script_command("secret_scan.py", python=python)),
CommandSpec("submission_bundle", "Regenerate submission bundle manifest", script_command("submission_bundle.py", python=python)),
]
)
return commands
def load_evidence(path: Path) -> tuple[dict[str, Any] | None, str | None]:
try:
evidence = json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
return None, "Evidence file is missing."
except json.JSONDecodeError as exc:
return None, f"Evidence file is invalid JSON: {exc}"
if not isinstance(evidence, dict):
return None, "Evidence JSON must be an object."
return evidence, None
def evidence_safety_findings(value: Any, path: str = "$") -> list[str]:
findings: list[str] = []
if isinstance(value, dict):
for key, item in value.items():
key_path = f"{path}.{key}"
if FORBIDDEN_EVIDENCE_KEYS.search(str(key)):
findings.append(f"{key_path}: forbidden evidence key")
findings.extend(evidence_safety_findings(item, key_path))
elif isinstance(value, list):
for index, item in enumerate(value):
findings.extend(evidence_safety_findings(item, f"{path}[{index}]"))
elif isinstance(value, str):
for pattern in FORBIDDEN_EVIDENCE_VALUES:
if pattern.search(value):
findings.append(f"{path}: forbidden evidence value")
break
return findings
def validate_evidence(kind: str, path: Path) -> dict[str, Any]:
evidence, load_error = load_evidence(path)
findings: list[dict[str, str]] = []
if load_error:
findings.append({"field": "file", "detail": load_error})
evidence = {}
expected = EXPECTED_EVIDENCE_FIELDS[kind]
for key, expected_value in expected.items():
actual = evidence.get(key)
if actual != expected_value:
findings.append({"field": key, "detail": f"Expected {expected_value!r}, got {actual!r}."})
if kind == "final":
asset_provider = evidence.get("asset_provider")
if asset_provider not in FINAL_ALLOWED_ASSET_PROVIDERS:
findings.append(
{
"field": "asset_provider",
"detail": (
"Expected one of "
f"{sorted(FINAL_ALLOWED_ASSET_PROVIDERS)!r}, got {asset_provider!r}."
),
}
)
for key in REQUIRED_EVIDENCE_VALUES:
if not evidence.get(key):
findings.append({"field": key, "detail": "Required evidence value is missing."})
for finding in evidence_safety_findings(evidence):
findings.append({"field": "secret_safety", "detail": finding})
return {
"ok": not findings,
"kind": kind,
"path": rel(path),
"checks": {
"expected_fields": expected,
"allowed_final_asset_providers": sorted(FINAL_ALLOWED_ASSET_PROVIDERS),
"required_values": list(REQUIRED_EVIDENCE_VALUES),
"secret_safety": "forbidden keys, bearer tokens, signed URL parameters, and GMI-style keys",
},
"findings": findings,
}
def command_record(spec: CommandSpec, status: str, returncode: int | None = None) -> dict[str, Any]:
return {
"id": spec.command_id,
"label": spec.label,
"status": status,
"returncode": returncode,
"command": display_command(spec.command),
"argv": [display_arg(arg) for arg in spec.command],
}
def run_sequence(
commands: Sequence[CommandSpec],
*,
execute: bool,
runner: Callable[..., subprocess.CompletedProcess[Any]] = subprocess.run,
) -> dict[str, Any]:
records: list[dict[str, Any]] = []
if not execute:
return {
"ok": True,
"mode": "plan_only",
"commands": [command_record(spec, "planned") for spec in commands],
"failed_command": None,
}
failed_command: str | None = None
for index, spec in enumerate(commands):
completed = runner(spec.command, cwd=ROOT, check=False)
records.append(command_record(spec, "passed" if completed.returncode == 0 else "failed", completed.returncode))
if completed.returncode != 0:
failed_command = spec.command_id
for skipped in commands[index + 1 :]:
records.append(command_record(skipped, "skipped"))
break
return {
"ok": failed_command is None,
"mode": "executed" if failed_command is None else "failed",
"commands": records,
"failed_command": failed_command,
}
def build_report(args: argparse.Namespace, sequence: dict[str, Any]) -> dict[str, Any]:
next_actions = [
"If mode is plan_only, rerun with --execute after credentials are entered locally.",
"If live proof succeeds, record and upload the final public demo video.",
"After the public video is verified, run the final secret scan, strict audit, Devpost submit, and receipt capture.",
]
if sequence.get("failed_command") == "credential_handoff":
next_actions.insert(
0,
"Enter missing local credentials with python scripts/final_env_wizard.py --output .env.final.local --missing-only --force.",
)
return {
"schema": SCHEMA,
"created_at": utc_now(),
"ok": sequence["ok"],
"mode": sequence["mode"],
"env_file": rel(args.env_file),
"update_tasks": bool(args.update_tasks),
"execute": bool(args.execute),
"commands": sequence["commands"],
"failed_command": sequence["failed_command"],
"next_actions": next_actions,
"secret_policy": (
"This report stores command strings, statuses, and artifact paths only. It never stores "
"Backblaze keys, Genblaze provider keys, Devpost cookies, provider responses, or signed URLs."
),
}
def render_markdown(report: dict[str, Any]) -> str:
lines = [
"# ProofFrame Post-Credential Live Proof Plan",
"",
f"Mode: `{report['mode']}`",
f"OK: `{str(report['ok']).lower()}`",
f"Created: `{report['created_at']}`",
f"Env file: `{report['env_file']}`",
f"Update tasks: `{str(report['update_tasks']).lower()}`",
"",
report["secret_policy"],
"",
"## Commands",
"",
"| Status | ID | Command |",
"| --- | --- | --- |",
]
for command in report["commands"]:
lines.append(f"| {command['status'].upper()} | `{command['id']}` | `{command['command']}` |")
lines.extend(["", "## Next Actions", ""])
lines.extend(f"- {action}" for action in report["next_actions"])
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="Plan or run the post-credential ProofFrame live proof sequence."
)
parser.add_argument("--env-file", type=Path, default=DEFAULT_ENV_FILE)
parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON)
parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MD)
parser.add_argument("--execute", action="store_true", help="Run the live proof sequence.")
parser.add_argument(
"--update-tasks",
action="store_true",
help="Mark T020/T021 done after their proof commands pass.",
)
parser.add_argument(
"--validate-evidence",
choices=sorted(EXPECTED_EVIDENCE_FIELDS),
help="Internal fail-closed evidence validator used before task updates.",
)
parser.add_argument("--evidence-path", type=Path, help="Evidence JSON path for --validate-evidence.")
return parser
def main() -> None:
args = build_parser().parse_args()
if args.validate_evidence:
evidence_path = args.evidence_path
if evidence_path is None:
evidence_path = B2_EVIDENCE if args.validate_evidence == "b2" else FINAL_EVIDENCE
report = validate_evidence(args.validate_evidence, evidence_path)
print(json.dumps(report, indent=2))
raise SystemExit(0 if report["ok"] else 2)
commands = build_commands(env_file=args.env_file, update_tasks=args.update_tasks)
sequence = run_sequence(commands, execute=args.execute)
report = build_report(args, sequence)
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),
"failed_command": report["failed_command"],
"commands": len(report["commands"]),
},
indent=2,
)
)
if args.execute and not report["ok"]:
raise SystemExit(2)
if __name__ == "__main__":
main()