File size: 9,859 Bytes
088795a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
from __future__ import annotations

import argparse
import json
import sys
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any

ROOT_DIR = Path(__file__).resolve().parent.parent
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

from scripts.next_deployment_step import (
    LIVE_PROOF,
    LOCAL_REPORT,
    SITE_REPORT,
    WORKER_REPORT,
    choose_next_step,
    deployment_urls_look_real,
    load_json,
    local_ready,
)


@dataclass
class ReportStatus:
    path: str
    exists: bool
    valid_json: bool
    summary: str


@dataclass
class DeploymentStatus:
    ready_for_live_urls: bool
    live_urls_look_real: bool
    live_complete: bool
    reports_are_real_live_evidence: bool
    local_report: ReportStatus
    site_report: ReportStatus
    worker_report: ReportStatus
    live_proof: ReportStatus
    missing_site_checks: list[str]
    missing_worker_checks: list[str]
    next_status: str
    next_title: str
    next_detail: str
    next_command: str


def _report_status(path: Path) -> ReportStatus:
    payload = load_json(path)
    if payload is None:
        return ReportStatus(str(path), path.exists(), False, "missing or invalid JSON")
    return ReportStatus(str(path), True, True, summarize_payload(path, payload))


def _list_checks_ok(payload: Any, names: set[str]) -> bool:
    if not isinstance(payload, list):
        return False
    checks = {str(item.get("name", "")): bool(item.get("ok")) for item in payload if isinstance(item, dict)}
    return all(checks.get(name) is True for name in names)


def _missing_checks(payload: Any, names: set[str]) -> list[str]:
    if not isinstance(payload, list):
        return sorted(names)
    checks = {str(item.get("name", "")): bool(item.get("ok")) for item in payload if isinstance(item, dict)}
    return sorted(name for name in names if checks.get(name) is not True)


def _looks_like_test_artifact(payload: Any) -> bool:
    if not isinstance(payload, dict):
        return False
    text = json.dumps(payload, ensure_ascii=False).lower()
    return "pytest-" in text or "your-space.hf.space" in text or "your-vercel-app.vercel.app" in text


def _url_evidence_matches(live_payload: Any, worker_url: str | None, vercel_origin: str | None) -> bool:
    if not isinstance(live_payload, dict):
        return False
    proof_worker = str(live_payload.get("workerUrl") or "").rstrip("/")
    proof_origin = str(live_payload.get("origin") or "").rstrip("/")
    expected_worker = (worker_url or proof_worker).rstrip("/")
    expected_origin = (vercel_origin or proof_origin).rstrip("/")
    if not deployment_urls_look_real(expected_worker, expected_origin):
        return False
    return proof_worker == expected_worker and proof_origin == expected_origin


def summarize_payload(path: Path, payload: Any) -> str:
    if isinstance(payload, dict):
        if "ready" in payload or "complete" in payload:
            return f"ready={payload.get('ready')} complete={payload.get('complete')}"
        if "counts" in payload:
            return f"counts={payload.get('counts')}"
        return f"object keys={','.join(sorted(str(key) for key in payload.keys())[:6])}"
    if isinstance(payload, list):
        passed = sum(1 for item in payload if isinstance(item, dict) and item.get("ok") is True)
        failed = sum(1 for item in payload if isinstance(item, dict) and item.get("ok") is False)
        return f"checks={len(payload)} pass={passed} fail={failed}"
    return type(payload).__name__


def build_status(

    worker_url: str | None = None,

    vercel_origin: str | None = None,

    code: str = "1234",

    local_report: Path = LOCAL_REPORT,

    site_report: Path = SITE_REPORT,

    worker_report: Path = WORKER_REPORT,

    live_proof: Path = LIVE_PROOF,

) -> DeploymentStatus:
    site_required = {
        "site login",
        "site platform vercel",
        "site worker configured",
        "site large PDF ready",
        "site production worker ready",
        "site hosted limits documented",
        "site recommended stack documented",
        "site direct cloud fallback disabled",
        "site worker diagnostics endpoint",
        "site worker reachable from vercel",
        "site worker CORS ready",
    }
    worker_required = {
        "recommended stack documented",
        "smoke upload accepted",
        "smoke job complete",
        "smoke usable text",
        "smoke audio url",
        "smoke download url",
        "smoke audio bytes",
        "smoke audio file signature",
        "smoke download bytes",
        "smoke download file signature",
        "scanned smoke upload accepted",
        "scanned smoke job complete",
        "scanned smoke usable text",
        "scanned smoke OCR extraction",
        "scanned smoke audio url",
        "scanned smoke download url",
        "scanned smoke audio bytes",
        "scanned smoke audio file signature",
        "scanned smoke download bytes",
        "scanned smoke download file signature",
    }
    live_payload = load_json(live_proof)
    site_payload = load_json(site_report)
    worker_payload = load_json(worker_report)
    missing_site_checks = _missing_checks(site_payload, site_required)
    missing_worker_checks = _missing_checks(worker_payload, worker_required)
    live_complete = isinstance(live_payload, dict) and bool(live_payload.get("complete"))
    live_urls_look_real = deployment_urls_look_real(worker_url, vercel_origin)
    reports_are_real_live_evidence = bool(
        live_complete
        and not missing_site_checks
        and not missing_worker_checks
        and not _looks_like_test_artifact(live_payload)
        and _url_evidence_matches(live_payload, worker_url, vercel_origin)
    )
    next_step = choose_next_step(
        worker_url=worker_url,
        vercel_origin=vercel_origin,
        code=code,
        local_report=local_report,
        site_report=site_report,
        worker_report=worker_report,
        live_proof=live_proof,
    )
    if live_complete and not reports_are_real_live_evidence:
        next_status = "live-proof"
        next_title = "Re-run live proof against real deployed URLs"
        next_detail = (
            "A complete live proof file exists, but it does not look like trustworthy live evidence. "
            "Re-run the proof after replacing placeholder/test URLs with the real Hugging Face worker and Vercel site."
        )
        next_command = (
            f"python scripts\\prove_live_deployment.py {worker_url or 'https://your-space.hf.space'} "
            f"--origin {vercel_origin or 'https://your-vercel-app.vercel.app'} --code {code} "
            "--smoke-ocr-engine arabic "
            "--check-hf-metadata --hf-metadata-report outputs\\hf-model-metadata.md "
            "--proof-out outputs\\live-deployment-proof.json"
        )
    else:
        next_status = next_step.status
        next_title = next_step.title
        next_detail = next_step.detail
        next_command = next_step.command
    return DeploymentStatus(
        ready_for_live_urls=local_ready(local_report) and live_urls_look_real,
        live_urls_look_real=live_urls_look_real,
        live_complete=live_complete,
        reports_are_real_live_evidence=reports_are_real_live_evidence,
        local_report=_report_status(local_report),
        site_report=_report_status(site_report),
        worker_report=_report_status(worker_report),
        live_proof=_report_status(live_proof),
        missing_site_checks=missing_site_checks,
        missing_worker_checks=missing_worker_checks,
        next_status=next_status,
        next_title=next_title,
        next_detail=next_detail,
        next_command=next_command,
    )


def print_status(status: DeploymentStatus) -> None:
    print(f"Ready for live URLs: {status.ready_for_live_urls}")
    print(f"Live URLs look real: {status.live_urls_look_real}")
    print(f"Live proof complete: {status.live_complete}")
    print(f"Reports are real live evidence: {status.reports_are_real_live_evidence}")
    print()
    for label, report in [
        ("Local", status.local_report),
        ("Site", status.site_report),
        ("Worker", status.worker_report),
        ("Live proof", status.live_proof),
    ]:
        print(f"{label}: {report.summary} ({report.path})")
    if status.missing_site_checks:
        print()
        print("Missing site proof checks:")
        for name in status.missing_site_checks:
            print(f"- {name}")
    if status.missing_worker_checks:
        print()
        print("Missing worker proof checks:")
        for name in status.missing_worker_checks:
            print(f"- {name}")
    print()
    print(status.next_title)
    print(status.next_detail)
    print()
    print(status.next_command)


def main() -> None:
    parser = argparse.ArgumentParser(description="Show the current deployment proof status and next command.")
    parser.add_argument("--worker-url", help="Live worker URL, for example https://your-space.hf.space")
    parser.add_argument("--origin", help="Live Vercel origin, for example https://your-app.vercel.app")
    parser.add_argument("--code", default="1234", help="Access code for live verification.")
    parser.add_argument("--json", action="store_true", help="Print JSON.")
    args = parser.parse_args()

    status = build_status(worker_url=args.worker_url, vercel_origin=args.origin, code=args.code)
    if args.json:
        print(json.dumps(asdict(status), ensure_ascii=False, indent=2))
    else:
        print_status(status)


if __name__ == "__main__":
    main()