File size: 11,868 Bytes
08a4bf0
540e67a
08a4bf0
04c0bde
540e67a
08a4bf0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf07180
6a1869c
08a4bf0
 
04c0bde
45c1706
08a4bf0
29331c9
 
c325020
29331c9
 
6a1869c
b7a466b
29331c9
08a4bf0
 
 
 
 
 
b7a466b
08a4bf0
149cadc
 
 
 
 
 
 
 
4173e02
 
 
 
 
 
d9be7c0
4173e02
cca436c
 
 
 
 
cf07180
b7a466b
cca436c
5331178
 
 
 
 
 
 
 
9d58132
 
 
 
 
 
b7a466b
9d58132
2c5b88c
 
 
 
 
756e790
b7a466b
2c5b88c
08a4bf0
 
540e67a
08a4bf0
 
540e67a
b7a466b
08a4bf0
 
 
 
 
 
cf07180
b7a466b
08a4bf0
 
cf07180
6a1869c
08a4bf0
 
04c0bde
b7a466b
08a4bf0
c4212da
 
540e67a
c4212da
 
04c0bde
b7a466b
c4212da
08a4bf0
 
 
 
 
 
b7a466b
08a4bf0
 
 
 
11fb2f4
 
 
 
 
 
08a4bf0
 
 
 
 
 
 
cfd29be
 
08a4bf0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540e67a
08a4bf0
 
540e67a
08a4bf0
 
540e67a
08a4bf0
 
 
 
 
540e67a
08a4bf0
540e67a
08a4bf0
 
 
 
 
cf07180
08a4bf0
540e67a
08a4bf0
540e67a
08a4bf0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29331c9
cca436c
5331178
2c5b88c
9d58132
08a4bf0
149cadc
4173e02
08a4bf0
 
 
c4212da
08a4bf0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
258
259
260
261
262
#!/usr/bin/env python3
"""Build the public release-check summary.

This is a public release map over the existing validators. It does not replace
the validators; it makes the release state readable in one file and one
machine-readable JSON bundle.
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
OUTPUT_JSON = ROOT / "docs/data/quality_gates.json"
OUTPUT_MD = ROOT / "QUALITY_GATES.md"


GATES = [
    {
        "id": "scale_up_status",
        "title": "Multi-episode pilot status",
        "command": "python scripts/validate_scope_claims.py",
        "report": "docs/data/scope_claims_audit.json",
        "blocks_if": "Setup identifiers are presented as completed held-out metrics.",
        "shows": "Qwen3-Omni setup artifacts stay distinct from the planned held-out pilot.",
    },
    {
        "id": "source_alignment",
        "title": "Source alignment",
        "command": "python scripts/validate_source_alignment.py",
        "report": "docs/data/source_alignment_audit.json",
        "blocks_if": "Official full-dataset facts, sample-card facts, API-listing notes, or project coverage are missing or inconsistent.",
        "shows": "The repo, website, and Hugging Face cards preserve the Xperience-10M source facts and current project scope.",
    },
    {
        "id": "website_integrity",
        "title": "Website integrity",
        "command": "python scripts/validate_website_integrity.py",
        "report": "docs/data/website_integrity.json",
        "blocks_if": "Local links, anchors, JSON bundles, or referenced image assets are missing or invalid.",
        "shows": "The GitHub Pages / HF static surface is self-consistent before upload.",
    },
    {
        "id": "rendered_site_check",
        "title": "Rendered website check",
        "command": "python scripts/build_rendered_site_check.py --input /tmp/xperience_rendered_site_observations.json",
        "report": "docs/data/rendered_site_check.json",
        "blocks_if": "The local rendered site cannot load, switch tabs, deep-link to the walkthrough, update player controls, or stay console-clean.",
        "shows": "The published static page has a recent browser-level interaction record in addition to static link checks.",
    },
    {
        "id": "task_surface_integrity",
        "title": "Task surface integrity",
        "command": "python scripts/validate_task_surface.py",
        "report": "docs/data/task_surface_integrity.json",
        "blocks_if": "Task cards expose raw artifact ids, human-readable task names drift, modality thumbnails are missing, or the interactive task player is not wired to the generated JSON.",
        "shows": "The public task cards and walkthrough/player stay aligned with generated task-suite metadata.",
    },
    {
        "id": "evaluation_protocol",
        "title": "Evaluation protocol",
        "command": "python scripts/build_evaluation_protocol.py",
        "report": "docs/data/evaluation_protocol.json",
        "blocks_if": "Windowing, split policy, leakage controls, task metrics, or current limitations are not explicit.",
        "shows": "The task evaluation protocol is generated from committed metric artifacts.",
    },
    {
        "id": "task_method_source_audit",
        "title": "Task-method source audit",
        "command": "python scripts/validate_task_method_matrix_sources.py",
        "report": "docs/data/task_method_20_source_audit.json",
        "blocks_if": "A scored 20-task matrix cell points to a JSON metric source that does not contain the same metric value.",
        "shows": "Public 20-task scores remain traceable to their task-specific metric artifacts.",
    },
    {
        "id": "figure_index",
        "title": "Figure index",
        "command": "python scripts/build_figure_index.py",
        "report": "docs/data/figure_index.json",
        "blocks_if": "Public figures, charts, or modality thumbnails are missing, unreadable, or lack source-script provenance.",
        "shows": "Public visual assets have dimensions, SHA-256 hashes, source scripts, and presentation roles.",
    },
    {
        "id": "brand_assets",
        "title": "Brand assets",
        "command": "python scripts/build_brand_assets.py",
        "report": "docs/data/brand_assets.json",
        "blocks_if": "The generated logo system, favicon, social card, or app icons are missing or not reproducibly packaged.",
        "shows": "The same project logo is available for website header, favicon, README, Hugging Face cards, and social previews.",
    },
    {
        "id": "quality_gate_manifest",
        "title": "Release-check manifest",
        "command": "python scripts/build_quality_gates.py",
        "report": "docs/data/quality_gates.json",
        "blocks_if": "A public reader cannot see the current release state in one place.",
        "shows": "The release checklist is explicit, versioned, and mirrored with the repo.",
    },
    {
        "id": "artifact_index",
        "title": "Artifact index",
        "command": "python scripts/build_artifact_index.py",
        "report": "docs/data/artifact_index.json",
        "blocks_if": "Project-critical evidence files are missing from the indexed artifact layer.",
        "shows": "Core project artifacts exist and stable files have SHA-256 hashes.",
    },
    {
        "id": "publication_package",
        "title": "Public bundle contents",
        "command": "python scripts/validate_publication_package.py",
        "report": "docs/data/publication_audit.json",
        "blocks_if": "Raw data, caches, heavy archives, credential text, missing required assets, or outdated public-card assets enter public bundles.",
        "shows": "The repo and prepared HF bundles contain the intended public files for release.",
    },
    {
        "id": "public_surface_qa",
        "title": "Public project surface",
        "command": "python scripts/build_public_surface_qa.py",
        "report": "docs/data/public_surface_qa.json",
        "blocks_if": "Repo, website, or Hugging Face presentation loses SEO/social metadata, accessible tab semantics, source links, project-check links, or clear project language.",
        "shows": "The public repo, website, and Hugging Face cards read as one cohesive research project surface.",
    },
    {
        "id": "mirror_parity",
        "title": "Prepared mirror parity",
        "command": "python scripts/validate_mirror_parity.py",
        "report": "docs/data/mirror_parity.json",
        "blocks_if": "Prepared HF Space, artifact dataset, or model bundle diverges from the repo for critical files.",
        "shows": "The files staged for GitHub and Hugging Face are synchronized before upload.",
    },
]

POST_PUBLISH_CHECKS = [
    {
        "id": "live_publication_verifier",
        "title": "Live publication verifier",
        "evidence": "python scripts/verify_live_publication.py",
        "required_result": "live GitHub Pages, GitHub raw, HF Space, artifact dataset, and model mirrors match the current release assets",
    },
    {
        "id": "github_pages_deploy",
        "title": "GitHub Pages deployment",
        "evidence": "gh run list --repo ChaoYue0307/ropedia-xperience-10m-task-suite --limit 5",
        "required_result": "latest pages-build-deployment run succeeds",
    },
    {
        "id": "rendered_browser_check",
        "title": "Rendered browser check",
        "evidence": "Browser/Playwright page identity, nonblank render, console health, and one local interaction",
        "required_result": "no relevant console warnings/errors and target links work",
    },
]


def read_status(path: Path) -> dict:
    if not path.exists():
        return {"exists": False, "status": "missing"}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        return {"exists": True, "status": "invalid_json", "error": str(exc)}
    return {
        "exists": True,
        "status": str(payload.get("status", "unknown")),
    }


def build_payload() -> dict:
    gate_records = []
    generated_at = datetime.now(timezone.utc).isoformat(timespec="seconds")
    for gate in GATES:
        if gate["id"] == "quality_gate_manifest":
            status = {"exists": True, "status": "pass"}
        else:
            status = read_status(ROOT / gate["report"])
        gate_records.append({**gate, "current_report": status})
    overall_status = "pass" if all(item["current_report"]["status"] == "pass" for item in gate_records) else "fail"
    return {
        "title": "Ropedia Xperience-10M Release Checks",
        "status": overall_status,
        "generated_at_utc": generated_at,
        "rule": "A release is current when the automated reports pass and the live GitHub/Hugging Face mirrors are verified after publishing.",
        "automated_gates": gate_records,
        "post_publish_checks": POST_PUBLISH_CHECKS,
        "scope_note": "These checks cover public packaging, project status wording, mirror parity, and website integrity. Cross-episode model quality is measured by later held-out evaluation reports.",
    }


def markdown(payload: dict) -> str:
    lines = [
        "# Release Checks",
        "",
        "This file is the release map for the Ropedia Xperience-10M Task Suite.",
        "",
        f"Current gate status: **{payload['status']}**",
        "",
        payload["rule"],
        "",
        payload["scope_note"],
        "",
        "## Automated Checks",
        "",
        "| Check | Command | Report | Current status | Needs attention when |",
        "| --- | --- | --- | --- | --- |",
    ]
    for gate in payload["automated_gates"]:
        report_status = gate["current_report"]["status"]
        lines.append(
            f"| {gate['title']} | `{gate['command']}` | `{gate['report']}` | `{report_status}` | {gate['blocks_if']} |"
        )
    lines.extend([
        "",
        "## Post-Publish Checks",
        "",
        "| Check | Evidence | Required result |",
        "| --- | --- | --- |",
    ])
    for check in payload["post_publish_checks"]:
        lines.append(f"| {check['title']} | `{check['evidence']}` | {check['required_result']} |")
    lines.extend([
        "",
        "## Rerun Order",
        "",
        "```bash",
        "python scripts/validate_scope_claims.py",
        "python scripts/validate_source_alignment.py",
        "python scripts/build_evaluation_protocol.py",
        "python scripts/validate_task_method_matrix_sources.py",
        "python scripts/build_brand_assets.py",
        "python scripts/build_figure_index.py",
        "python scripts/validate_website_integrity.py",
        "python scripts/build_rendered_site_check.py --input /tmp/xperience_rendered_site_observations.json",
        "python scripts/validate_task_surface.py",
        "python scripts/build_quality_gates.py",
        "python scripts/build_artifact_index.py",
        "python scripts/validate_publication_package.py",
        "python scripts/build_public_surface_qa.py",
        "python scripts/validate_mirror_parity.py",
        "```",
        "",
        "After Hugging Face bundle sync, rerun `validate_publication_package.py` and `validate_mirror_parity.py` once more before upload.",
        "",
    ])
    return "\n".join(lines)


def main() -> int:
    payload = build_payload()
    OUTPUT_JSON.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_JSON.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
    OUTPUT_MD.write_text(markdown(payload), encoding="utf-8")
    print(f"{payload['status'].upper()}: wrote {OUTPUT_JSON}")
    print(f"{payload['status'].upper()}: wrote {OUTPUT_MD}")
    return 0 if payload["status"] == "pass" else 1


if __name__ == "__main__":
    raise SystemExit(main())