dataforge-07 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataforge/__init__.py +204 -0
- dataforge/__main__.py +5 -0
- dataforge/agent/__init__.py +16 -0
- dataforge/agent/providers.py +259 -0
- dataforge/agent/scratchpad.py +183 -0
- dataforge/agent/tool_actions.py +343 -0
- dataforge/bench/__init__.py +31 -0
- dataforge/bench/core.py +426 -0
- dataforge/bench/groq_client.py +386 -0
- dataforge/bench/methods.py +443 -0
- dataforge/bench/report.py +309 -0
- dataforge/bench/runner.py +247 -0
- dataforge/causal/__init__.py +21 -0
- dataforge/causal/dag.py +174 -0
- dataforge/causal/pc.py +232 -0
- dataforge/causal/root_cause.py +193 -0
- dataforge/cli/__init__.py +50 -0
- dataforge/cli/audit.py +70 -0
- dataforge/cli/bench.py +154 -0
- dataforge/cli/common.py +267 -0
- dataforge/cli/constraints.py +407 -0
- dataforge/cli/profile.py +147 -0
- dataforge/cli/release.py +166 -0
- dataforge/cli/repair.py +407 -0
- dataforge/cli/revert.py +139 -0
- dataforge/cli/watch.py +144 -0
- dataforge/datasets/__init__.py +25 -0
- dataforge/datasets/embedded/hospital/clean.csv +11 -0
- dataforge/datasets/embedded/hospital/dirty.csv +11 -0
- dataforge/datasets/real_world.py +290 -0
- dataforge/datasets/registry.py +103 -0
- dataforge/detectors/__init__.py +80 -0
- dataforge/detectors/base.py +145 -0
- dataforge/detectors/decimal_shift.py +166 -0
- dataforge/detectors/fd_violation.py +157 -0
- dataforge/detectors/type_mismatch.py +173 -0
- dataforge/engine/__init__.py +39 -0
- dataforge/engine/repair.py +905 -0
- dataforge/env/__init__.py +22 -0
- dataforge/env/environment.py +883 -0
- dataforge/env/observation.py +61 -0
- dataforge/env/openenv_core.py +161 -0
- dataforge/env/reward.py +128 -0
- dataforge/env/server.py +176 -0
- dataforge/evaluation_contract.py +76 -0
- dataforge/fixtures/hospital_10rows.csv +11 -0
- dataforge/fixtures/hospital_schema.yaml +17 -0
- dataforge/http/__init__.py +1 -0
- dataforge/http/problem.py +103 -0
- dataforge/integrations/__init__.py +1 -0
- dataforge/integrations/dbt.py +164 -0
- dataforge/observability.py +76 -0
- dataforge/py.typed +1 -0
- dataforge/release/__init__.py +1 -0
- dataforge/release/doctor.py +367 -0
- dataforge/release/full_vision.py +702 -0
- dataforge/release/gate.py +861 -0
- dataforge/release/playground_check.py +411 -0
- dataforge/repair_contract.py +468 -0
- dataforge/repairers/__init__.py +88 -0
- dataforge/repairers/base.py +77 -0
- dataforge/repairers/decimal_shift.py +43 -0
- dataforge/repairers/fd_violation.py +225 -0
- dataforge/repairers/type_mismatch.py +73 -0
- dataforge/safety/__init__.py +5 -0
- dataforge/safety/adversarial/attack_01_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_02_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_03_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_04_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_05_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_06_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_07_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_08_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_09_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_10_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_11_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_12_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_13_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_14_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_15_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_16_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_17_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_18_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_19_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_20_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_21_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_22_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_23_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_24_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_25_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_26_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_27_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_28_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_29_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_30_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_31_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_32_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_33_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_34_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_35_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_36_row_delete.yaml +11 -0
- dataforge/safety/adversarial/attack_37_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_38_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_39_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_40_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_41_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_42_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_43_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_44_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_45_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_46_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_47_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_48_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_49_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_50_row_delete.yaml +7 -0
- dataforge/safety/constitution.py +307 -0
- dataforge/safety/constitutions/default.yaml +40 -0
- dataforge/safety/filter.py +134 -0
- dataforge/schema_inference.py +620 -0
- dataforge/stores/__init__.py +46 -0
- dataforge/stores/base.py +73 -0
- dataforge/stores/cloud.py +78 -0
- dataforge/stores/csv.py +94 -0
- dataforge/stores/duckdb.py +313 -0
- dataforge/stores/patch_plan.py +178 -0
- dataforge/stores/registry.py +82 -0
- dataforge/stores/repair.py +121 -0
- dataforge/stores/revert.py +22 -0
- dataforge/stores/sql.py +27 -0
- dataforge/table.py +228 -0
- dataforge/transactions/__init__.py +34 -0
- dataforge/transactions/files.py +96 -0
- dataforge/transactions/log.py +613 -0
- dataforge/transactions/revert.py +102 -0
- dataforge/transactions/txn.py +104 -0
- dataforge/ui/__init__.py +1 -0
- dataforge/ui/profile_view.py +136 -0
- dataforge/ui/repair_diff.py +91 -0
- dataforge/verifier/__init__.py +55 -0
- dataforge/verifier/constraint_ir.py +155 -0
- dataforge/verifier/explain.py +47 -0
- dataforge/verifier/gate.py +5 -0
- dataforge/verifier/schema.py +111 -0
- dataforge/verifier/smt.py +433 -0
- dataforge_07-0.1.0.dist-info/METADATA +436 -0
- dataforge_07-0.1.0.dist-info/RECORD +150 -0
- dataforge_07-0.1.0.dist-info/WHEEL +5 -0
- dataforge_07-0.1.0.dist-info/entry_points.txt +3 -0
- dataforge_07-0.1.0.dist-info/licenses/LICENSE +176 -0
- dataforge_07-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
"""External completion gate for the full original DataForge vision.
|
|
2
|
+
|
|
3
|
+
This gate is intentionally stricter than the local release gate. It checks
|
|
4
|
+
public package names, the production Workers playground, Hugging Face artifacts,
|
|
5
|
+
and evidence files that can only be produced by real external work.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.request
|
|
14
|
+
from dataclasses import asdict, dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
SCHEMA_VERSION = "dataforge_full_vision_gate_v1"
|
|
19
|
+
EXPECTED_VERSION = "0.1.0"
|
|
20
|
+
EXPECTED_PACKAGES = (
|
|
21
|
+
"dataforge_07",
|
|
22
|
+
"dataforge_07_mcp",
|
|
23
|
+
"dataforge_07_evals",
|
|
24
|
+
"dataforge_07_dbt",
|
|
25
|
+
"dataforge_07_agent_patterns",
|
|
26
|
+
)
|
|
27
|
+
EXPECTED_MODEL_SIZES = ("0.5B", "1.5B", "3B", "7B")
|
|
28
|
+
EXPECTED_MODEL_STAGES = ("SFT", "GRPO", "GiGPO")
|
|
29
|
+
EXPECTED_DESIGN_PERSONAS = ("marcus", "priya", "shreya", "agent")
|
|
30
|
+
DEFAULT_FRONTEND_URL = "https://dataforge.praneshrajan15.workers.dev/playground"
|
|
31
|
+
DEFAULT_BACKEND_URL = "https://Praneshrajan15-dataforge-playground.hf.space"
|
|
32
|
+
DEFAULT_HF_OWNER = "Praneshrajan15"
|
|
33
|
+
REQUIRED_PACKAGE_EVIDENCE_FIELDS = (
|
|
34
|
+
"version",
|
|
35
|
+
"workflow_run_url",
|
|
36
|
+
"testpypi_smoke_log_path",
|
|
37
|
+
"pypi_smoke_log_path",
|
|
38
|
+
)
|
|
39
|
+
EXPECTED_PUBLISHER_REPOSITORY = "Aegis15/dataforge"
|
|
40
|
+
EXPECTED_OIDC_ISSUER = "https://token.actions.githubusercontent.com"
|
|
41
|
+
PUBLISH_ATTESTATION_PREDICATE = "https://docs.pypi.org/attestations/publish/v1"
|
|
42
|
+
PYPI_WORKFLOWS = {
|
|
43
|
+
"dataforge_07": "publish-dataforge.yml",
|
|
44
|
+
"dataforge_07_mcp": "publish-dataforge-mcp.yml",
|
|
45
|
+
"dataforge_07_evals": "publish-dataforge-evals.yml",
|
|
46
|
+
"dataforge_07_dbt": "publish-dataforge-dbt.yml",
|
|
47
|
+
"dataforge_07_agent_patterns": "publish-dataforge-agent-patterns.yml",
|
|
48
|
+
}
|
|
49
|
+
TESTPYPI_WORKFLOWS = {
|
|
50
|
+
"dataforge_07": "publish-testpypi.yml",
|
|
51
|
+
"dataforge_07_mcp": "publish-dataforge-mcp-testpypi.yml",
|
|
52
|
+
"dataforge_07_evals": "publish-dataforge-evals-testpypi.yml",
|
|
53
|
+
"dataforge_07_dbt": "publish-dataforge-dbt-testpypi.yml",
|
|
54
|
+
"dataforge_07_agent_patterns": "publish-dataforge-agent-patterns-testpypi.yml",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class FullVisionCheck:
|
|
60
|
+
"""One full-vision completion check."""
|
|
61
|
+
|
|
62
|
+
name: str
|
|
63
|
+
ok: bool
|
|
64
|
+
detail: str
|
|
65
|
+
metadata: dict[str, Any]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class FullVisionReport:
|
|
70
|
+
"""Machine-readable full-vision completion report."""
|
|
71
|
+
|
|
72
|
+
ok: bool
|
|
73
|
+
checks: list[FullVisionCheck]
|
|
74
|
+
schema_version: str = SCHEMA_VERSION
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> dict[str, Any]:
|
|
77
|
+
"""Return a JSON-serializable report."""
|
|
78
|
+
return asdict(self)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _project_root() -> Path:
|
|
82
|
+
"""Return the repository root."""
|
|
83
|
+
return Path(__file__).resolve().parents[2]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _evidence_root(root: Path | None = None) -> Path:
|
|
87
|
+
"""Return the release evidence directory."""
|
|
88
|
+
return root if root is not None else _project_root() / "docs" / "evidence"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _fetch_json(url: str, *, timeout_s: float = 20.0) -> tuple[dict[str, Any] | None, str]:
|
|
92
|
+
"""Fetch JSON from a public endpoint."""
|
|
93
|
+
request = urllib.request.Request(url, headers={"User-Agent": "dataforge-full-vision-gate"})
|
|
94
|
+
try:
|
|
95
|
+
with urllib.request.urlopen(request, timeout=timeout_s) as response:
|
|
96
|
+
return json.loads(response.read().decode("utf-8")), ""
|
|
97
|
+
except urllib.error.HTTPError as exc:
|
|
98
|
+
return None, f"HTTP {exc.code}"
|
|
99
|
+
except Exception as exc:
|
|
100
|
+
return None, str(exc)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _fetch_text(
|
|
104
|
+
url: str,
|
|
105
|
+
*,
|
|
106
|
+
timeout_s: float = 20.0,
|
|
107
|
+
headers: dict[str, str] | None = None,
|
|
108
|
+
) -> tuple[int | None, str, dict[str, str], str]:
|
|
109
|
+
"""Fetch text and return status, body, response headers, and error."""
|
|
110
|
+
request = urllib.request.Request(
|
|
111
|
+
url,
|
|
112
|
+
headers={"User-Agent": "dataforge-full-vision-gate", **(headers or {})},
|
|
113
|
+
)
|
|
114
|
+
try:
|
|
115
|
+
with urllib.request.urlopen(request, timeout=timeout_s) as response:
|
|
116
|
+
response_headers = {key.lower(): value for key, value in response.headers.items()}
|
|
117
|
+
return (
|
|
118
|
+
int(response.status),
|
|
119
|
+
response.read().decode("utf-8", errors="replace"),
|
|
120
|
+
response_headers,
|
|
121
|
+
"",
|
|
122
|
+
)
|
|
123
|
+
except urllib.error.HTTPError as exc:
|
|
124
|
+
return int(exc.code), exc.read().decode("utf-8", errors="replace"), {}, f"HTTP {exc.code}"
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
return None, "", {}, str(exc)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _load_json_file(path: Path) -> tuple[dict[str, Any] | None, str]:
|
|
130
|
+
"""Load a JSON evidence file."""
|
|
131
|
+
if not path.exists():
|
|
132
|
+
return None, f"Missing evidence file: {path}"
|
|
133
|
+
try:
|
|
134
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
return None, f"Invalid JSON evidence file {path}: {exc}"
|
|
137
|
+
if not isinstance(payload, dict):
|
|
138
|
+
return None, f"Evidence file {path} must contain a JSON object."
|
|
139
|
+
return payload, ""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _resolve_evidence_file(evidence_root: Path, raw_path: Any) -> Path | None:
|
|
143
|
+
"""Resolve a manifest evidence path and require it to point to a file."""
|
|
144
|
+
text = str(raw_path or "").strip()
|
|
145
|
+
if not text:
|
|
146
|
+
return None
|
|
147
|
+
path = Path(text)
|
|
148
|
+
if not path.is_absolute():
|
|
149
|
+
path = evidence_root / path
|
|
150
|
+
if not path.is_file():
|
|
151
|
+
return None
|
|
152
|
+
return path
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _is_https_url(value: Any) -> bool:
|
|
156
|
+
"""Return whether a manifest field is a concrete HTTPS URL."""
|
|
157
|
+
return str(value or "").strip().startswith("https://")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _is_sha256(value: Any) -> bool:
|
|
161
|
+
"""Return whether a manifest field looks like a SHA-256 hex digest."""
|
|
162
|
+
text = str(value or "").strip().lower()
|
|
163
|
+
return len(text) == 64 and all(char in "0123456789abcdef" for char in text)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _check_distribution_evidence(
|
|
167
|
+
*,
|
|
168
|
+
package: str,
|
|
169
|
+
index: str,
|
|
170
|
+
kind: str,
|
|
171
|
+
payload: Any,
|
|
172
|
+
expected_workflow: str,
|
|
173
|
+
) -> list[str]:
|
|
174
|
+
"""Return errors for one wheel or sdist evidence object."""
|
|
175
|
+
errors: list[str] = []
|
|
176
|
+
label = f"{package}.{index}.{kind}"
|
|
177
|
+
if not isinstance(payload, dict):
|
|
178
|
+
return [f"{label}: evidence must be an object"]
|
|
179
|
+
for field in (
|
|
180
|
+
"filename",
|
|
181
|
+
"package_type",
|
|
182
|
+
"download_url",
|
|
183
|
+
"sha256",
|
|
184
|
+
"upload_time_iso_8601",
|
|
185
|
+
"provenance_url",
|
|
186
|
+
"integrity_predicate_type",
|
|
187
|
+
"integrity_subject_sha256",
|
|
188
|
+
):
|
|
189
|
+
if not str(payload.get(field, "")).strip():
|
|
190
|
+
errors.append(f"{label}: {field} is required")
|
|
191
|
+
for field in ("download_url", "provenance_url"):
|
|
192
|
+
if payload.get(field) and not _is_https_url(payload.get(field)):
|
|
193
|
+
errors.append(f"{label}: {field} must be an HTTPS URL")
|
|
194
|
+
sha256 = str(payload.get("sha256", "")).lower()
|
|
195
|
+
subject_sha256 = str(payload.get("integrity_subject_sha256", "")).lower()
|
|
196
|
+
if sha256 and not _is_sha256(sha256):
|
|
197
|
+
errors.append(f"{label}: sha256 must be a SHA-256 hex digest")
|
|
198
|
+
if subject_sha256 and not _is_sha256(subject_sha256):
|
|
199
|
+
errors.append(f"{label}: integrity_subject_sha256 must be a SHA-256 hex digest")
|
|
200
|
+
if sha256 and subject_sha256 and sha256 != subject_sha256:
|
|
201
|
+
errors.append(f"{label}: integrity_subject_sha256 must match sha256")
|
|
202
|
+
if payload.get("integrity_predicate_type") != PUBLISH_ATTESTATION_PREDICATE:
|
|
203
|
+
errors.append(f"{label}: integrity_predicate_type must be {PUBLISH_ATTESTATION_PREDICATE}")
|
|
204
|
+
publisher = payload.get("trusted_publisher")
|
|
205
|
+
if not isinstance(publisher, dict):
|
|
206
|
+
errors.append(f"{label}: trusted_publisher must be an object")
|
|
207
|
+
return errors
|
|
208
|
+
if publisher.get("repository") != EXPECTED_PUBLISHER_REPOSITORY:
|
|
209
|
+
errors.append(
|
|
210
|
+
f"{label}: trusted_publisher.repository must be {EXPECTED_PUBLISHER_REPOSITORY}"
|
|
211
|
+
)
|
|
212
|
+
if publisher.get("workflow") != expected_workflow:
|
|
213
|
+
errors.append(f"{label}: trusted_publisher.workflow must be {expected_workflow}")
|
|
214
|
+
if publisher.get("oidc_issuer") != EXPECTED_OIDC_ISSUER:
|
|
215
|
+
errors.append(f"{label}: trusted_publisher.oidc_issuer must be {EXPECTED_OIDC_ISSUER}")
|
|
216
|
+
identity = str(publisher.get("identity", ""))
|
|
217
|
+
if identity and expected_workflow not in identity:
|
|
218
|
+
errors.append(f"{label}: trusted_publisher.identity must reference {expected_workflow}")
|
|
219
|
+
return errors
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _check_index_evidence(
|
|
223
|
+
*,
|
|
224
|
+
package: str,
|
|
225
|
+
index: str,
|
|
226
|
+
payload: Any,
|
|
227
|
+
expected_workflow: str,
|
|
228
|
+
) -> list[str]:
|
|
229
|
+
"""Return errors for one package-index evidence object."""
|
|
230
|
+
errors: list[str] = []
|
|
231
|
+
label = f"{package}.{index}"
|
|
232
|
+
if not isinstance(payload, dict):
|
|
233
|
+
return [f"{label}: evidence must be an object"]
|
|
234
|
+
if payload.get("index") != index:
|
|
235
|
+
errors.append(f"{label}: index must be {index}")
|
|
236
|
+
if not _is_https_url(payload.get("project_url")):
|
|
237
|
+
errors.append(f"{label}: project_url must be an HTTPS URL")
|
|
238
|
+
for kind in ("wheel", "sdist"):
|
|
239
|
+
errors.extend(
|
|
240
|
+
_check_distribution_evidence(
|
|
241
|
+
package=package,
|
|
242
|
+
index=index,
|
|
243
|
+
kind=kind,
|
|
244
|
+
payload=payload.get(kind),
|
|
245
|
+
expected_workflow=expected_workflow,
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
return errors
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _version_tuple(value: str) -> tuple[int, ...]:
|
|
252
|
+
"""Parse a simple dotted numeric version for release-gate comparisons."""
|
|
253
|
+
parts: list[int] = []
|
|
254
|
+
for raw_part in value.split("."):
|
|
255
|
+
digits = "".join(ch for ch in raw_part if ch.isdigit())
|
|
256
|
+
parts.append(int(digits or "0"))
|
|
257
|
+
return tuple(parts)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _check_package_index(index_name: str, base_url: str) -> FullVisionCheck:
|
|
261
|
+
"""Verify all final package names exist on one package index."""
|
|
262
|
+
package_results: dict[str, Any] = {}
|
|
263
|
+
errors: list[str] = []
|
|
264
|
+
for package in EXPECTED_PACKAGES:
|
|
265
|
+
url = f"{base_url.rstrip('/')}/{package}/json"
|
|
266
|
+
payload, error = _fetch_json(url)
|
|
267
|
+
if payload is None:
|
|
268
|
+
package_results[package] = {"ok": False, "error": error}
|
|
269
|
+
errors.append(f"{package}: {error}")
|
|
270
|
+
continue
|
|
271
|
+
info = payload.get("info", {}) if isinstance(payload.get("info"), dict) else {}
|
|
272
|
+
version = str(info.get("version", ""))
|
|
273
|
+
summary = str(info.get("summary", ""))
|
|
274
|
+
home_page = str(info.get("home_page", ""))
|
|
275
|
+
project_urls = info.get("project_urls", {})
|
|
276
|
+
project_urls_text = (
|
|
277
|
+
json.dumps(project_urls, sort_keys=True) if isinstance(project_urls, dict) else ""
|
|
278
|
+
)
|
|
279
|
+
ok = _version_tuple(version) >= _version_tuple(EXPECTED_VERSION)
|
|
280
|
+
if package == "dataforge":
|
|
281
|
+
metadata_text = f"{summary} {home_page} {project_urls_text}".lower()
|
|
282
|
+
ok = (
|
|
283
|
+
ok
|
|
284
|
+
and ("dataforge" in metadata_text)
|
|
285
|
+
and ("aegis15" in metadata_text or "data-quality" in metadata_text)
|
|
286
|
+
)
|
|
287
|
+
package_results[package] = {
|
|
288
|
+
"ok": ok,
|
|
289
|
+
"version": version,
|
|
290
|
+
"summary": summary,
|
|
291
|
+
"home_page": home_page,
|
|
292
|
+
"project_urls": project_urls,
|
|
293
|
+
"url": url,
|
|
294
|
+
}
|
|
295
|
+
if not ok:
|
|
296
|
+
errors.append(
|
|
297
|
+
f"{package}: found version={version!r}, summary={summary!r}, home_page={home_page!r}"
|
|
298
|
+
)
|
|
299
|
+
return FullVisionCheck(
|
|
300
|
+
name=f"{index_name}_packages",
|
|
301
|
+
ok=not errors,
|
|
302
|
+
detail=f"All final DataForge packages are published on {index_name}."
|
|
303
|
+
if not errors
|
|
304
|
+
else f"{index_name} package publication is incomplete or not owned by this project.",
|
|
305
|
+
metadata={"packages": package_results, "errors": errors},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _check_publish_evidence(evidence_root: Path) -> FullVisionCheck:
|
|
310
|
+
"""Verify local evidence for trusted publishing and attestations."""
|
|
311
|
+
path = evidence_root / "pypi" / "publish_report.json"
|
|
312
|
+
payload, error = _load_json_file(path)
|
|
313
|
+
if payload is None:
|
|
314
|
+
return FullVisionCheck("pypi_publish_evidence", False, error, {"path": str(path)})
|
|
315
|
+
packages = payload.get("packages")
|
|
316
|
+
errors: list[str] = []
|
|
317
|
+
if payload.get("schema_version") != "dataforge_pypi_publish_report_v2":
|
|
318
|
+
errors.append("schema_version must be dataforge_pypi_publish_report_v2")
|
|
319
|
+
if not isinstance(packages, list):
|
|
320
|
+
errors.append("packages must be a list")
|
|
321
|
+
packages = []
|
|
322
|
+
seen = {str(item.get("name", "")) for item in packages if isinstance(item, dict)}
|
|
323
|
+
missing = sorted(set(EXPECTED_PACKAGES) - seen)
|
|
324
|
+
errors.extend(f"missing package evidence: {name}" for name in missing)
|
|
325
|
+
for item in packages:
|
|
326
|
+
if not isinstance(item, dict):
|
|
327
|
+
errors.append("package evidence entries must be objects")
|
|
328
|
+
continue
|
|
329
|
+
name = str(item.get("name", ""))
|
|
330
|
+
for field in REQUIRED_PACKAGE_EVIDENCE_FIELDS:
|
|
331
|
+
if not str(item.get(field, "")).strip():
|
|
332
|
+
errors.append(f"{name}: {field} is required")
|
|
333
|
+
if item.get("version") and str(item.get("version")) != EXPECTED_VERSION:
|
|
334
|
+
errors.append(f"{name}: version must be {EXPECTED_VERSION}")
|
|
335
|
+
if item.get("trusted_publishing") is not True:
|
|
336
|
+
errors.append(f"{name}: trusted_publishing must be true")
|
|
337
|
+
if item.get("attestations") is not True:
|
|
338
|
+
errors.append(f"{name}: attestations must be true")
|
|
339
|
+
if item.get("testpypi_fresh_install") is not True:
|
|
340
|
+
errors.append(f"{name}: TestPyPI fresh-install proof is missing")
|
|
341
|
+
if item.get("pypi_fresh_install") is not True:
|
|
342
|
+
errors.append(f"{name}: PyPI fresh-install proof is missing")
|
|
343
|
+
for field in ("workflow_run_url",):
|
|
344
|
+
if item.get(field) and not _is_https_url(item.get(field)):
|
|
345
|
+
errors.append(f"{name}: {field} must be an HTTPS URL")
|
|
346
|
+
for field in ("testpypi_smoke_log_path", "pypi_smoke_log_path"):
|
|
347
|
+
if item.get(field) and _resolve_evidence_file(evidence_root, item.get(field)) is None:
|
|
348
|
+
errors.append(f"{name}: {field} must point to an evidence file")
|
|
349
|
+
if name in PYPI_WORKFLOWS:
|
|
350
|
+
errors.extend(
|
|
351
|
+
_check_index_evidence(
|
|
352
|
+
package=name,
|
|
353
|
+
index="pypi",
|
|
354
|
+
payload=item.get("pypi"),
|
|
355
|
+
expected_workflow=PYPI_WORKFLOWS[name],
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
errors.extend(
|
|
359
|
+
_check_index_evidence(
|
|
360
|
+
package=name,
|
|
361
|
+
index="testpypi",
|
|
362
|
+
payload=item.get("testpypi"),
|
|
363
|
+
expected_workflow=TESTPYPI_WORKFLOWS[name],
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
return FullVisionCheck(
|
|
367
|
+
name="pypi_publish_evidence",
|
|
368
|
+
ok=not errors,
|
|
369
|
+
detail="Trusted publishing, attestations, provenance, and fresh-install evidence exist."
|
|
370
|
+
if not errors
|
|
371
|
+
else "Publishing evidence is incomplete.",
|
|
372
|
+
metadata={"path": str(path), "errors": errors},
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _check_workers_playground(frontend_url: str, backend_url: str) -> FullVisionCheck:
|
|
377
|
+
"""Verify the production Workers playground serves the frontend shell."""
|
|
378
|
+
errors: list[str] = []
|
|
379
|
+
metadata: dict[str, Any] = {"frontend_url": frontend_url, "backend_url": backend_url}
|
|
380
|
+
|
|
381
|
+
status, body, headers, error = _fetch_text(frontend_url)
|
|
382
|
+
metadata["status_code"] = status
|
|
383
|
+
metadata["content_type"] = headers.get("content-type", "")
|
|
384
|
+
if error:
|
|
385
|
+
errors.append(f"frontend fetch failed: {error}")
|
|
386
|
+
if status != 200:
|
|
387
|
+
errors.append(f"frontend returned {status}, expected 200")
|
|
388
|
+
lowered = body.lower()
|
|
389
|
+
for marker in ("<!doctype html>", 'id="root"', "config.js"):
|
|
390
|
+
if marker not in lowered:
|
|
391
|
+
errors.append(f"frontend HTML missing marker {marker!r}")
|
|
392
|
+
config_url = f"{frontend_url.rstrip('/')}/config.js"
|
|
393
|
+
config_status, config_body, config_headers, config_error = _fetch_text(config_url)
|
|
394
|
+
metadata["config_status_code"] = config_status
|
|
395
|
+
metadata["config_cache_control"] = config_headers.get("cache-control", "")
|
|
396
|
+
if config_error:
|
|
397
|
+
errors.append(f"config.js fetch failed: {config_error}")
|
|
398
|
+
if backend_url not in config_body:
|
|
399
|
+
errors.append("config.js does not point at the expected HF backend")
|
|
400
|
+
if "no-store" not in metadata["config_cache_control"].lower():
|
|
401
|
+
errors.append("config.js is not served with Cache-Control: no-store")
|
|
402
|
+
return FullVisionCheck(
|
|
403
|
+
name="workers_dev_playground",
|
|
404
|
+
ok=not errors,
|
|
405
|
+
detail="workers.dev playground serves the production frontend."
|
|
406
|
+
if not errors
|
|
407
|
+
else "workers.dev playground is not production-ready.",
|
|
408
|
+
metadata={**metadata, "errors": errors},
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _check_hf_backend(
|
|
413
|
+
frontend_url: str, backend_url: str, expected_git_sha: str | None
|
|
414
|
+
) -> FullVisionCheck:
|
|
415
|
+
"""Verify the deployed HF Space backend and CORS."""
|
|
416
|
+
errors: list[str] = []
|
|
417
|
+
health_url = f"{backend_url.rstrip('/')}/api/health"
|
|
418
|
+
status, body, _headers, error = _fetch_text(health_url)
|
|
419
|
+
metadata: dict[str, Any] = {
|
|
420
|
+
"backend_url": backend_url,
|
|
421
|
+
"health_url": health_url,
|
|
422
|
+
"status_code": status,
|
|
423
|
+
}
|
|
424
|
+
if error:
|
|
425
|
+
errors.append(f"health fetch failed: {error}")
|
|
426
|
+
payload: dict[str, Any] = {}
|
|
427
|
+
if status == 200:
|
|
428
|
+
try:
|
|
429
|
+
decoded = json.loads(body)
|
|
430
|
+
if isinstance(decoded, dict):
|
|
431
|
+
payload = decoded
|
|
432
|
+
except json.JSONDecodeError as exc:
|
|
433
|
+
errors.append(f"health JSON decode failed: {exc}")
|
|
434
|
+
else:
|
|
435
|
+
errors.append(f"health returned {status}, expected 200")
|
|
436
|
+
metadata["health"] = payload
|
|
437
|
+
if payload.get("status") != "ok":
|
|
438
|
+
errors.append("health status is not ok")
|
|
439
|
+
if payload.get("environment") != "production":
|
|
440
|
+
errors.append("health environment is not production")
|
|
441
|
+
if expected_git_sha:
|
|
442
|
+
build_sha = str(payload.get("build_sha", ""))
|
|
443
|
+
if not build_sha.startswith(expected_git_sha[:12]):
|
|
444
|
+
errors.append(
|
|
445
|
+
f"build_sha {build_sha!r} does not match release SHA {expected_git_sha!r}"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
origin = frontend_url.split("/playground", 1)[0]
|
|
449
|
+
cors_status, _cors_body, cors_headers, cors_error = _fetch_text(
|
|
450
|
+
health_url,
|
|
451
|
+
headers={"Origin": origin},
|
|
452
|
+
)
|
|
453
|
+
metadata["cors_status_code"] = cors_status
|
|
454
|
+
metadata["cors_allow_origin"] = cors_headers.get("access-control-allow-origin", "")
|
|
455
|
+
if cors_error:
|
|
456
|
+
errors.append(f"CORS probe failed: {cors_error}")
|
|
457
|
+
if metadata["cors_allow_origin"] != origin:
|
|
458
|
+
errors.append(f"CORS does not allow frontend origin {origin}")
|
|
459
|
+
return FullVisionCheck(
|
|
460
|
+
name="hf_space_backend",
|
|
461
|
+
ok=not errors,
|
|
462
|
+
detail="HF Space backend is live, production, and CORS-compatible."
|
|
463
|
+
if not errors
|
|
464
|
+
else "HF Space backend verification failed.",
|
|
465
|
+
metadata={**metadata, "errors": errors},
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _check_dbt_evidence(evidence_root: Path) -> FullVisionCheck:
|
|
470
|
+
"""Verify the dbt-duckdb fresh environment proof."""
|
|
471
|
+
path = evidence_root / "dbt_duckdb" / "fresh_env_report.json"
|
|
472
|
+
payload, error = _load_json_file(path)
|
|
473
|
+
if payload is None:
|
|
474
|
+
return FullVisionCheck("dbt_duckdb_fresh_env", False, error, {"path": str(path)})
|
|
475
|
+
errors: list[str] = []
|
|
476
|
+
if payload.get("schema_version") != "dataforge_dbt_fresh_env_proof_v1":
|
|
477
|
+
errors.append("schema_version must be dataforge_dbt_fresh_env_proof_v1")
|
|
478
|
+
if payload.get("install_source") != "pypi":
|
|
479
|
+
errors.append("install_source must be pypi")
|
|
480
|
+
if payload.get("package") != "dataforge_07_dbt":
|
|
481
|
+
errors.append("package must be dataforge_07_dbt")
|
|
482
|
+
if not str(payload.get("python_version", "")).startswith("3.12"):
|
|
483
|
+
errors.append("python_version must be 3.12.x")
|
|
484
|
+
if not str(payload.get("dbt_core_version", "")).strip():
|
|
485
|
+
errors.append("dbt_core_version is required")
|
|
486
|
+
if not str(payload.get("dbt_duckdb_version", "")).strip():
|
|
487
|
+
errors.append("dbt_duckdb_version is required")
|
|
488
|
+
for field in ("dbt_seed_passed", "dbt_run_passed", "dbt_test_passed"):
|
|
489
|
+
if payload.get(field) is not True:
|
|
490
|
+
errors.append(f"{field} must be true")
|
|
491
|
+
for field in (
|
|
492
|
+
"dataforge_dbt_dry_run_passed",
|
|
493
|
+
"dataforge_dbt_refuse_passed",
|
|
494
|
+
"dataforge_dbt_apply_passed",
|
|
495
|
+
"dataforge_table_store_audit_passed",
|
|
496
|
+
"dataforge_table_store_revert_passed",
|
|
497
|
+
):
|
|
498
|
+
if payload.get(field) is not True:
|
|
499
|
+
errors.append(f"{field} must be true")
|
|
500
|
+
if payload.get("dbt_duckdb_e2e_passed") is not True:
|
|
501
|
+
errors.append("dbt_duckdb_e2e_passed must be true")
|
|
502
|
+
if int(payload.get("skipped_tests", -1)) != 0:
|
|
503
|
+
errors.append("skipped_tests must be 0")
|
|
504
|
+
if payload.get("audit_artifact_written") is not True:
|
|
505
|
+
errors.append("audit_artifact_written must be true")
|
|
506
|
+
for field in ("artifact_path", "command_log_path"):
|
|
507
|
+
if _resolve_evidence_file(evidence_root, payload.get(field)) is None:
|
|
508
|
+
errors.append(f"{field} must point to an evidence file")
|
|
509
|
+
return FullVisionCheck(
|
|
510
|
+
name="dbt_duckdb_fresh_env",
|
|
511
|
+
ok=not errors,
|
|
512
|
+
detail="dbt-duckdb fresh-env proof is complete."
|
|
513
|
+
if not errors
|
|
514
|
+
else "dbt-duckdb fresh-env proof is incomplete.",
|
|
515
|
+
metadata={"path": str(path), "errors": errors},
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _check_design_partner_evidence(evidence_root: Path) -> FullVisionCheck:
|
|
520
|
+
"""Verify real design-partner evidence exists for every required path."""
|
|
521
|
+
path = evidence_root / "design_partners" / "manifest.json"
|
|
522
|
+
payload, error = _load_json_file(path)
|
|
523
|
+
if payload is None:
|
|
524
|
+
return FullVisionCheck("design_partner_evidence", False, error, {"path": str(path)})
|
|
525
|
+
errors: list[str] = []
|
|
526
|
+
if payload.get("schema_version") != "dataforge_design_partner_manifest_v1":
|
|
527
|
+
errors.append("schema_version must be dataforge_design_partner_manifest_v1")
|
|
528
|
+
entries = payload.get("validations")
|
|
529
|
+
if not isinstance(entries, list):
|
|
530
|
+
errors.append("validations must be a list")
|
|
531
|
+
entries = []
|
|
532
|
+
by_persona = {
|
|
533
|
+
str(entry.get("persona", "")).lower(): entry for entry in entries if isinstance(entry, dict)
|
|
534
|
+
}
|
|
535
|
+
for persona in EXPECTED_DESIGN_PERSONAS:
|
|
536
|
+
entry = by_persona.get(persona)
|
|
537
|
+
if entry is None:
|
|
538
|
+
errors.append(f"missing design-partner validation: {persona}")
|
|
539
|
+
continue
|
|
540
|
+
for field in ("role", "session_date", "production_surface", "task_completed"):
|
|
541
|
+
if not str(entry.get(field, "")).strip():
|
|
542
|
+
errors.append(f"{persona}: {field} is required")
|
|
543
|
+
if entry.get("validated") is not True:
|
|
544
|
+
errors.append(f"{persona}: validated must be true")
|
|
545
|
+
if entry.get("trust_confirmed") is not True:
|
|
546
|
+
errors.append(f"{persona}: trust_confirmed must be true")
|
|
547
|
+
if not entry.get("evidence_path"):
|
|
548
|
+
errors.append(f"{persona}: evidence_path is required")
|
|
549
|
+
elif _resolve_evidence_file(evidence_root, entry.get("evidence_path")) is None:
|
|
550
|
+
errors.append(f"{persona}: evidence_path must point to an evidence file")
|
|
551
|
+
if not entry.get("consent_record"):
|
|
552
|
+
errors.append(f"{persona}: consent_record is required")
|
|
553
|
+
elif _resolve_evidence_file(evidence_root, entry.get("consent_record")) is None:
|
|
554
|
+
errors.append(f"{persona}: consent_record must point to an evidence file")
|
|
555
|
+
if entry.get("blocking_findings_closed") is not True:
|
|
556
|
+
errors.append(f"{persona}: blocking_findings_closed must be true")
|
|
557
|
+
timing = entry.get("timing_seconds")
|
|
558
|
+
if not isinstance(timing, int | float) or timing <= 0:
|
|
559
|
+
errors.append(f"{persona}: timing_seconds must be positive")
|
|
560
|
+
return FullVisionCheck(
|
|
561
|
+
name="design_partner_evidence",
|
|
562
|
+
ok=not errors,
|
|
563
|
+
detail="Design-partner evidence covers Marcus, Priya, Shreya, and agent paths."
|
|
564
|
+
if not errors
|
|
565
|
+
else "Design-partner evidence is incomplete.",
|
|
566
|
+
metadata={"path": str(path), "errors": errors},
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _check_hf_model_family(evidence_root: Path, hf_owner: str) -> FullVisionCheck:
|
|
571
|
+
"""Verify public HF model repos and local quality evidence for the full family."""
|
|
572
|
+
errors: list[str] = []
|
|
573
|
+
repo_results: dict[str, Any] = {}
|
|
574
|
+
for size in EXPECTED_MODEL_SIZES:
|
|
575
|
+
for stage in EXPECTED_MODEL_STAGES:
|
|
576
|
+
repo_id = f"{hf_owner}/DataForge-{size}-{stage}"
|
|
577
|
+
payload, error = _fetch_json(f"https://huggingface.co/api/models/{repo_id}")
|
|
578
|
+
if payload is None:
|
|
579
|
+
repo_results[repo_id] = {"ok": False, "error": error}
|
|
580
|
+
errors.append(f"{repo_id}: {error}")
|
|
581
|
+
continue
|
|
582
|
+
card = payload.get("cardData", {})
|
|
583
|
+
card = card if isinstance(card, dict) else {}
|
|
584
|
+
missing_card_fields = [
|
|
585
|
+
field for field in ("license", "datasets", "base_model") if not card.get(field)
|
|
586
|
+
]
|
|
587
|
+
ok = not missing_card_fields
|
|
588
|
+
repo_results[repo_id] = {
|
|
589
|
+
"ok": ok,
|
|
590
|
+
"sha": payload.get("sha"),
|
|
591
|
+
"lastModified": payload.get("lastModified"),
|
|
592
|
+
"missing_card_fields": missing_card_fields,
|
|
593
|
+
}
|
|
594
|
+
if not ok:
|
|
595
|
+
errors.append(f"{repo_id}: missing model-card fields {missing_card_fields}")
|
|
596
|
+
|
|
597
|
+
path = evidence_root / "models" / "model_family_report.json"
|
|
598
|
+
payload, error = _load_json_file(path)
|
|
599
|
+
if payload is None:
|
|
600
|
+
errors.append(error)
|
|
601
|
+
evidence_metadata: dict[str, Any] = {"path": str(path), "loaded": False}
|
|
602
|
+
else:
|
|
603
|
+
evidence_metadata = {"path": str(path), "loaded": True}
|
|
604
|
+
if payload.get("schema_version") != "dataforge_model_family_report_v1":
|
|
605
|
+
errors.append(
|
|
606
|
+
"model_family_report schema_version must be dataforge_model_family_report_v1"
|
|
607
|
+
)
|
|
608
|
+
models = payload.get("models")
|
|
609
|
+
if not isinstance(models, list):
|
|
610
|
+
errors.append("model_family_report models must be a list")
|
|
611
|
+
models = []
|
|
612
|
+
entries_by_repo = {
|
|
613
|
+
str(item.get("repo_id", "")): item for item in models if isinstance(item, dict)
|
|
614
|
+
}
|
|
615
|
+
expected_repos = {
|
|
616
|
+
f"{hf_owner}/DataForge-{size}-{stage}"
|
|
617
|
+
for size in EXPECTED_MODEL_SIZES
|
|
618
|
+
for stage in EXPECTED_MODEL_STAGES
|
|
619
|
+
}
|
|
620
|
+
missing = sorted(expected_repos - set(entries_by_repo))
|
|
621
|
+
errors.extend(f"missing model evidence: {repo_id}" for repo_id in missing)
|
|
622
|
+
for repo_id in sorted(expected_repos & set(entries_by_repo)):
|
|
623
|
+
item = entries_by_repo[repo_id]
|
|
624
|
+
if item.get("verifier_passed") is not True:
|
|
625
|
+
errors.append(f"{repo_id}: verifier_passed must be true")
|
|
626
|
+
if item.get("limitations_documented") is not True:
|
|
627
|
+
errors.append(f"{repo_id}: limitations_documented must be true")
|
|
628
|
+
if not str(item.get("dataset_repo", "")).strip():
|
|
629
|
+
errors.append(f"{repo_id}: dataset_repo is required")
|
|
630
|
+
for field in ("training_run_url", "model_card_url"):
|
|
631
|
+
if not _is_https_url(item.get(field)):
|
|
632
|
+
errors.append(f"{repo_id}: {field} must be an HTTPS URL")
|
|
633
|
+
for field in ("eval_report_path", "verification_report_path"):
|
|
634
|
+
if _resolve_evidence_file(evidence_root, item.get(field)) is None:
|
|
635
|
+
errors.append(f"{repo_id}: {field} must point to an evidence file")
|
|
636
|
+
metrics = item.get("eval_metrics")
|
|
637
|
+
if not isinstance(metrics, dict) or not metrics:
|
|
638
|
+
errors.append(f"{repo_id}: eval_metrics must be a non-empty object")
|
|
639
|
+
return FullVisionCheck(
|
|
640
|
+
name="hf_model_family",
|
|
641
|
+
ok=not errors,
|
|
642
|
+
detail="Full HF model family exists with verifier-passed quality evidence."
|
|
643
|
+
if not errors
|
|
644
|
+
else "Full HF model family is incomplete.",
|
|
645
|
+
metadata={"repos": repo_results, "evidence": evidence_metadata, "errors": errors},
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def run_full_vision_gate(
|
|
650
|
+
*,
|
|
651
|
+
evidence_root: Path | None = None,
|
|
652
|
+
frontend_url: str = DEFAULT_FRONTEND_URL,
|
|
653
|
+
backend_url: str = DEFAULT_BACKEND_URL,
|
|
654
|
+
expected_git_sha: str | None = None,
|
|
655
|
+
hf_owner: str = DEFAULT_HF_OWNER,
|
|
656
|
+
) -> FullVisionReport:
|
|
657
|
+
"""Run the external full-original-vision completion gate."""
|
|
658
|
+
root = _evidence_root(evidence_root)
|
|
659
|
+
checks = [
|
|
660
|
+
_check_package_index("pypi", "https://pypi.org/pypi"),
|
|
661
|
+
_check_package_index("testpypi", "https://test.pypi.org/pypi"),
|
|
662
|
+
_check_publish_evidence(root),
|
|
663
|
+
_check_workers_playground(frontend_url, backend_url),
|
|
664
|
+
_check_hf_backend(frontend_url, backend_url, expected_git_sha),
|
|
665
|
+
_check_dbt_evidence(root),
|
|
666
|
+
_check_design_partner_evidence(root),
|
|
667
|
+
_check_hf_model_family(root, hf_owner),
|
|
668
|
+
]
|
|
669
|
+
return FullVisionReport(ok=all(check.ok for check in checks), checks=checks)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def main(argv: list[str] | None = None) -> int:
|
|
673
|
+
"""Run the full-vision gate as a standalone script."""
|
|
674
|
+
import argparse
|
|
675
|
+
|
|
676
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
677
|
+
parser.add_argument("--json", action="store_true", help="Print JSON instead of text.")
|
|
678
|
+
parser.add_argument("--evidence-root", type=Path, default=None)
|
|
679
|
+
parser.add_argument("--frontend-url", default=DEFAULT_FRONTEND_URL)
|
|
680
|
+
parser.add_argument("--backend-url", default=DEFAULT_BACKEND_URL)
|
|
681
|
+
parser.add_argument("--expected-git-sha", default=None)
|
|
682
|
+
parser.add_argument("--hf-owner", default=DEFAULT_HF_OWNER)
|
|
683
|
+
args = parser.parse_args(argv)
|
|
684
|
+
|
|
685
|
+
report = run_full_vision_gate(
|
|
686
|
+
evidence_root=args.evidence_root,
|
|
687
|
+
frontend_url=args.frontend_url,
|
|
688
|
+
backend_url=args.backend_url,
|
|
689
|
+
expected_git_sha=args.expected_git_sha,
|
|
690
|
+
hf_owner=args.hf_owner,
|
|
691
|
+
)
|
|
692
|
+
if args.json:
|
|
693
|
+
print(json.dumps(report.to_dict(), indent=2, sort_keys=True))
|
|
694
|
+
else:
|
|
695
|
+
for check in report.checks:
|
|
696
|
+
status = "ok" if check.ok else "fail"
|
|
697
|
+
print(f"{status:4} {check.name}: {check.detail}")
|
|
698
|
+
return 0 if report.ok else 1
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
if __name__ == "__main__":
|
|
702
|
+
raise SystemExit(main(sys.argv[1:]))
|