File size: 9,818 Bytes
f74bce1
 
 
413c5fe
 
f74bce1
 
413c5fe
f74bce1
 
 
 
 
 
 
 
 
 
 
 
 
cbb6866
 
 
 
 
 
 
413c5fe
 
 
 
 
 
 
 
 
 
 
 
 
 
f74bce1
 
 
 
 
 
 
 
 
cbb6866
413c5fe
2608b33
50a1c46
5b93402
413c5fe
40cb975
 
bd6cbae
 
6609140
 
72e4ba4
 
f74bce1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cbb6866
 
 
 
 
d05817f
cbb6866
413c5fe
 
 
 
 
 
 
 
2608b33
 
 
 
 
 
 
 
 
50a1c46
 
 
 
 
 
d05817f
50a1c46
5b93402
 
 
 
 
a7a715a
5b93402
 
 
40cb975
 
 
 
 
 
 
a7a715a
 
40cb975
bd6cbae
 
 
 
 
 
 
 
 
6609140
 
 
 
 
 
 
 
 
72e4ba4
 
 
 
 
 
 
 
f74bce1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from io import BytesIO
from zipfile import ZipFile

import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient

from proofframe.app import create_app, frontend_index_path, load_public_artifact, public_artifact_path


def test_frontend_index_path_can_use_explicit_web_root(tmp_path, monkeypatch):
    web_root = tmp_path / "web"
    web_root.mkdir()
    index_path = web_root / "index.html"
    index_path.write_text("<html>ProofFrame</html>", encoding="utf-8")

    monkeypatch.setenv("PROOFFRAME_WEB_ROOT", str(web_root))

    assert frontend_index_path() == index_path


def test_public_artifact_path_finds_judge_brief():
    path = public_artifact_path("judge-brief.json")

    assert path is not None
    assert path.name == "judge-brief.json"


def test_public_artifact_loader_rejects_schema_mismatch(tmp_path, monkeypatch):
    artifact = tmp_path / "docs" / "assets"
    artifact.mkdir(parents=True)
    path = artifact / "bad.json"
    path.write_text('{"schema": "wrong"}', encoding="utf-8")
    monkeypatch.chdir(tmp_path)

    with pytest.raises(HTTPException) as exc_info:
        load_public_artifact("bad.json", "proofframe.expected.v1", "Bad")

    assert exc_info.value.status_code == 503
    assert exc_info.value.detail == "Bad schema mismatch"


def test_health_and_campaign_flow(tmp_path):
    client = TestClient(create_app(storage_root=tmp_path))

    index_response = client.get("/")
    assert index_response.status_code == 200
    assert "Generated Media Ledger" in index_response.text
    assert "Search Evidence" in index_response.text
    assert "Judge recording slate" in index_response.text
    assert "Sponsor Evidence Model" in index_response.text
    assert "30-Second Judge Brief" in index_response.text
    assert "Criteria Crosswalk" in index_response.text
    assert "Decision brief" in index_response.text
    assert "Evidence index" in index_response.text
    assert "Video publish kit" in index_response.text
    assert "crosswalkRows" in index_response.text
    assert "Recording Runbook" in index_response.text
    assert "recordingRows" in index_response.text
    assert "Devpost Kit" in index_response.text
    assert "devpostRows" in index_response.text
    assert "Submit Checklist" in index_response.text
    assert "submitRows" in index_response.text
    assert "Final Closeout" in index_response.text
    assert "closeoutRows" in index_response.text
    assert "B2/Genblaze final proof gated" in index_response.text
    assert "Claim Boundary" in index_response.text
    assert "Submission readiness gate" in index_response.text
    assert "shouldAutoLoadJudgeDemo" in index_response.text

    health = client.get("/api/health")
    assert health.status_code == 200
    assert health.json()["ready"] is True

    gate_response = client.get("/api/submission/gate")
    assert gate_response.status_code == 200
    gate = gate_response.json()
    assert gate["mode"] in {"pre_live_safe", "final_ready"}
    assert "task_gates" in gate
    assert "evidence_gate" in gate
    assert "report_gate" in gate

    brief_response = client.get("/api/judge/brief")
    assert brief_response.status_code == 200
    brief = brief_response.json()
    assert brief["schema"] == "proofframe.judge_brief.v1"
    assert brief["status"]["safe_to_submit"] is False
    assert "Genblaze" in " ".join(brief["not_yet_claimed"] + brief["safe_claims"])

    crosswalk_response = client.get("/api/judge/crosswalk")
    assert crosswalk_response.status_code == 200
    crosswalk = crosswalk_response.json()
    assert crosswalk["schema"] == "proofframe.judge_crosswalk.v1"
    assert crosswalk["safe_to_submit"] is False
    row_ids = {row["id"] for row in crosswalk["rows"]}
    assert {"real_world_utility", "production_readiness"} <= row_ids

    decision_brief_response = client.get("/api/judge/decision-brief")
    assert decision_brief_response.status_code == 200
    decision_brief = decision_brief_response.json()
    assert decision_brief["schema"] == "proofframe.judge_decision_brief.v1"
    assert decision_brief["ok"] is True
    assert decision_brief["safe_to_share"] is True
    assert decision_brief["safe_to_submit"] is False
    assert len(decision_brief["decision_checks"]) >= 6

    evidence_index_response = client.get("/api/judge/evidence-index")
    assert evidence_index_response.status_code == 200
    evidence_index = evidence_index_response.json()
    assert evidence_index["schema"] == "proofframe.judge_evidence_index.v1"
    assert evidence_index["safe_to_submit"] is False
    assert len(evidence_index["links"]) >= 10
    assert {"genblaze_live_proof", "public_video"} & set(evidence_index["status"]["final_blockers"])

    video_kit_response = client.get("/api/judge/video-publish-kit")
    assert video_kit_response.status_code == 200
    video_kit = video_kit_response.json()
    assert video_kit["schema"] == "proofframe.final_video_publish_kit.v1"
    assert video_kit["safe_to_submit"] is False
    assert video_kit["final_video_ready"] is False
    assert len(video_kit["upload_checklist"]) >= 4
    assert "YouTube" in video_kit["allowed_hosts"]

    recording_response = client.get("/api/judge/recording")
    assert recording_response.status_code == 200
    recording = recording_response.json()
    assert recording["schema"] == "proofframe.recording_assets.v1"
    assert recording["final_video_ready"] is False
    assert len(recording["shot_plan"]) >= 3
    assert recording["source_reports"]["storyboard"]["schema_ok"] is True
    assert recording["source_reports"]["final_video_publish_kit"]["schema_ok"] is True
    assert recording["source_reports"]["final_video_publish_kit"]["safe_to_submit"] is False

    devpost_response = client.get("/api/judge/devpost")
    assert devpost_response.status_code == 200
    devpost = devpost_response.json()
    assert devpost["schema"] == "proofframe.devpost_form_kit.v1"
    assert devpost["mode"] == "pre_live_form_ready"
    assert devpost["final_form_ready"] is False
    field_ids = {field["id"] for field in devpost["fields"]}
    assert {"project_name", "tagline", "video_url"} <= field_ids

    checklist_response = client.get("/api/judge/submission-checklist")
    assert checklist_response.status_code == 200
    checklist = checklist_response.json()
    assert checklist["schema"] == "proofframe.devpost_submission_checklist.v1"
    assert checklist["mode"] == "pre_submit_blocked"
    assert checklist["safe_to_submit"] is False
    assert len(checklist["preflight"]) >= 3
    assert checklist["field_gate"]["final_pending_fields"] == ["video_url"]

    closeout_response = client.get("/api/judge/final-closeout")
    assert closeout_response.status_code == 200
    closeout = closeout_response.json()
    assert closeout["schema"] == "proofframe.final_closeout_status.v1"
    assert closeout["closeout_health_ok"] is True
    assert closeout["safe_to_submit"] is False
    assert any(gate["id"] == "b2_live_proof" for gate in closeout["gates"])

    campaign_response = client.post(
        "/api/campaigns",
        json={
            "title": "Library Night",
            "brief": "Invite teens to a late-night zine and music event.",
            "audience": "teen creators",
            "tone": "bold but trustworthy",
        },
    )
    assert campaign_response.status_code == 200
    campaign = campaign_response.json()

    assets_response = client.post(f"/api/campaigns/{campaign['id']}/generate?count=2")
    assert assets_response.status_code == 200
    assets = assets_response.json()
    assert len(assets) == 2
    assert assets[0]["sha256"]

    status_response = client.post(
        f"/api/assets/{assets[0]['id']}/status",
        json={"status": "approved", "risk_note": "Approved for demo use."},
    )
    assert status_response.status_code == 200
    assert status_response.json()["status"] == "approved"

    manifest_response = client.get(f"/api/campaigns/{campaign['id']}/manifest")
    assert manifest_response.status_code == 200
    manifest = manifest_response.json()
    assert manifest["campaign"]["id"] == campaign["id"]
    assert len(manifest["assets"]) == 2

    export_response = client.post(f"/api/campaigns/{campaign['id']}/export")
    assert export_response.status_code == 200
    export_payload = export_response.json()
    assert export_payload["stored_manifest"]["storage_key"].endswith("-manifest.json")
    assert (tmp_path / export_payload["stored_manifest"]["storage_key"]).exists()

    packet_response = client.get(f"/api/campaigns/{campaign['id']}/packet.zip")
    assert packet_response.status_code == 200
    assert packet_response.headers["content-type"] == "application/zip"
    with ZipFile(BytesIO(packet_response.content)) as packet:
        names = set(packet.namelist())
        assert "manifest.json" in names
        assert "README.txt" in names
        assert any(name.startswith("media/") and name.endswith(".svg") for name in names)
        assert campaign["id"] in packet.read("manifest.json").decode("utf-8")


def test_judge_demo_packet_flow(tmp_path):
    client = TestClient(create_app(storage_root=tmp_path))

    demo_response = client.post("/api/demo/judge-packet")
    assert demo_response.status_code == 200
    demo = demo_response.json()
    campaign = demo["campaign"]
    assets = demo["assets"]

    assert campaign["title"] == "Judge Ready Provenance Packet"
    assert len(assets) == 3
    assert assets[0]["status"] == "approved"
    assert demo["manifest"]["campaign"]["id"] == campaign["id"]

    packet_response = client.get(f"/api/campaigns/{campaign['id']}/packet.zip")
    assert packet_response.status_code == 200
    with ZipFile(BytesIO(packet_response.content)) as packet:
        assert "manifest.json" in packet.namelist()
        assert "README.txt" in packet.namelist()