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.
Files changed (150) hide show
  1. dataforge/__init__.py +204 -0
  2. dataforge/__main__.py +5 -0
  3. dataforge/agent/__init__.py +16 -0
  4. dataforge/agent/providers.py +259 -0
  5. dataforge/agent/scratchpad.py +183 -0
  6. dataforge/agent/tool_actions.py +343 -0
  7. dataforge/bench/__init__.py +31 -0
  8. dataforge/bench/core.py +426 -0
  9. dataforge/bench/groq_client.py +386 -0
  10. dataforge/bench/methods.py +443 -0
  11. dataforge/bench/report.py +309 -0
  12. dataforge/bench/runner.py +247 -0
  13. dataforge/causal/__init__.py +21 -0
  14. dataforge/causal/dag.py +174 -0
  15. dataforge/causal/pc.py +232 -0
  16. dataforge/causal/root_cause.py +193 -0
  17. dataforge/cli/__init__.py +50 -0
  18. dataforge/cli/audit.py +70 -0
  19. dataforge/cli/bench.py +154 -0
  20. dataforge/cli/common.py +267 -0
  21. dataforge/cli/constraints.py +407 -0
  22. dataforge/cli/profile.py +147 -0
  23. dataforge/cli/release.py +166 -0
  24. dataforge/cli/repair.py +407 -0
  25. dataforge/cli/revert.py +139 -0
  26. dataforge/cli/watch.py +144 -0
  27. dataforge/datasets/__init__.py +25 -0
  28. dataforge/datasets/embedded/hospital/clean.csv +11 -0
  29. dataforge/datasets/embedded/hospital/dirty.csv +11 -0
  30. dataforge/datasets/real_world.py +290 -0
  31. dataforge/datasets/registry.py +103 -0
  32. dataforge/detectors/__init__.py +80 -0
  33. dataforge/detectors/base.py +145 -0
  34. dataforge/detectors/decimal_shift.py +166 -0
  35. dataforge/detectors/fd_violation.py +157 -0
  36. dataforge/detectors/type_mismatch.py +173 -0
  37. dataforge/engine/__init__.py +39 -0
  38. dataforge/engine/repair.py +905 -0
  39. dataforge/env/__init__.py +22 -0
  40. dataforge/env/environment.py +883 -0
  41. dataforge/env/observation.py +61 -0
  42. dataforge/env/openenv_core.py +161 -0
  43. dataforge/env/reward.py +128 -0
  44. dataforge/env/server.py +176 -0
  45. dataforge/evaluation_contract.py +76 -0
  46. dataforge/fixtures/hospital_10rows.csv +11 -0
  47. dataforge/fixtures/hospital_schema.yaml +17 -0
  48. dataforge/http/__init__.py +1 -0
  49. dataforge/http/problem.py +103 -0
  50. dataforge/integrations/__init__.py +1 -0
  51. dataforge/integrations/dbt.py +164 -0
  52. dataforge/observability.py +76 -0
  53. dataforge/py.typed +1 -0
  54. dataforge/release/__init__.py +1 -0
  55. dataforge/release/doctor.py +367 -0
  56. dataforge/release/full_vision.py +702 -0
  57. dataforge/release/gate.py +861 -0
  58. dataforge/release/playground_check.py +411 -0
  59. dataforge/repair_contract.py +468 -0
  60. dataforge/repairers/__init__.py +88 -0
  61. dataforge/repairers/base.py +77 -0
  62. dataforge/repairers/decimal_shift.py +43 -0
  63. dataforge/repairers/fd_violation.py +225 -0
  64. dataforge/repairers/type_mismatch.py +73 -0
  65. dataforge/safety/__init__.py +5 -0
  66. dataforge/safety/adversarial/attack_01_phone_pii.yaml +8 -0
  67. dataforge/safety/adversarial/attack_02_phone_pii.yaml +8 -0
  68. dataforge/safety/adversarial/attack_03_phone_pii.yaml +8 -0
  69. dataforge/safety/adversarial/attack_04_phone_pii.yaml +8 -0
  70. dataforge/safety/adversarial/attack_05_phone_pii.yaml +8 -0
  71. dataforge/safety/adversarial/attack_06_phone_pii.yaml +8 -0
  72. dataforge/safety/adversarial/attack_07_phone_pii.yaml +8 -0
  73. dataforge/safety/adversarial/attack_08_phone_pii.yaml +8 -0
  74. dataforge/safety/adversarial/attack_09_phone_pii.yaml +8 -0
  75. dataforge/safety/adversarial/attack_10_phone_pii.yaml +8 -0
  76. dataforge/safety/adversarial/attack_11_ssn_pii.yaml +8 -0
  77. dataforge/safety/adversarial/attack_12_ssn_pii.yaml +8 -0
  78. dataforge/safety/adversarial/attack_13_ssn_pii.yaml +8 -0
  79. dataforge/safety/adversarial/attack_14_ssn_pii.yaml +8 -0
  80. dataforge/safety/adversarial/attack_15_ssn_pii.yaml +8 -0
  81. dataforge/safety/adversarial/attack_16_ssn_pii.yaml +8 -0
  82. dataforge/safety/adversarial/attack_17_ssn_pii.yaml +8 -0
  83. dataforge/safety/adversarial/attack_18_ssn_pii.yaml +8 -0
  84. dataforge/safety/adversarial/attack_19_ssn_pii.yaml +8 -0
  85. dataforge/safety/adversarial/attack_20_ssn_pii.yaml +8 -0
  86. dataforge/safety/adversarial/attack_21_email_pii.yaml +8 -0
  87. dataforge/safety/adversarial/attack_22_email_pii.yaml +8 -0
  88. dataforge/safety/adversarial/attack_23_email_pii.yaml +8 -0
  89. dataforge/safety/adversarial/attack_24_email_pii.yaml +8 -0
  90. dataforge/safety/adversarial/attack_25_email_pii.yaml +8 -0
  91. dataforge/safety/adversarial/attack_26_email_pii.yaml +8 -0
  92. dataforge/safety/adversarial/attack_27_email_pii.yaml +8 -0
  93. dataforge/safety/adversarial/attack_28_email_pii.yaml +8 -0
  94. dataforge/safety/adversarial/attack_29_email_pii.yaml +8 -0
  95. dataforge/safety/adversarial/attack_30_email_pii.yaml +8 -0
  96. dataforge/safety/adversarial/attack_31_row_delete.yaml +7 -0
  97. dataforge/safety/adversarial/attack_32_row_delete.yaml +8 -0
  98. dataforge/safety/adversarial/attack_33_row_delete.yaml +7 -0
  99. dataforge/safety/adversarial/attack_34_row_delete.yaml +7 -0
  100. dataforge/safety/adversarial/attack_35_row_delete.yaml +7 -0
  101. dataforge/safety/adversarial/attack_36_row_delete.yaml +11 -0
  102. dataforge/safety/adversarial/attack_37_row_delete.yaml +7 -0
  103. dataforge/safety/adversarial/attack_38_row_delete.yaml +7 -0
  104. dataforge/safety/adversarial/attack_39_row_delete.yaml +8 -0
  105. dataforge/safety/adversarial/attack_40_row_delete.yaml +7 -0
  106. dataforge/safety/adversarial/attack_41_row_delete.yaml +7 -0
  107. dataforge/safety/adversarial/attack_42_row_delete.yaml +7 -0
  108. dataforge/safety/adversarial/attack_43_row_delete.yaml +7 -0
  109. dataforge/safety/adversarial/attack_44_row_delete.yaml +7 -0
  110. dataforge/safety/adversarial/attack_45_row_delete.yaml +8 -0
  111. dataforge/safety/adversarial/attack_46_row_delete.yaml +8 -0
  112. dataforge/safety/adversarial/attack_47_row_delete.yaml +7 -0
  113. dataforge/safety/adversarial/attack_48_row_delete.yaml +7 -0
  114. dataforge/safety/adversarial/attack_49_row_delete.yaml +8 -0
  115. dataforge/safety/adversarial/attack_50_row_delete.yaml +7 -0
  116. dataforge/safety/constitution.py +307 -0
  117. dataforge/safety/constitutions/default.yaml +40 -0
  118. dataforge/safety/filter.py +134 -0
  119. dataforge/schema_inference.py +620 -0
  120. dataforge/stores/__init__.py +46 -0
  121. dataforge/stores/base.py +73 -0
  122. dataforge/stores/cloud.py +78 -0
  123. dataforge/stores/csv.py +94 -0
  124. dataforge/stores/duckdb.py +313 -0
  125. dataforge/stores/patch_plan.py +178 -0
  126. dataforge/stores/registry.py +82 -0
  127. dataforge/stores/repair.py +121 -0
  128. dataforge/stores/revert.py +22 -0
  129. dataforge/stores/sql.py +27 -0
  130. dataforge/table.py +228 -0
  131. dataforge/transactions/__init__.py +34 -0
  132. dataforge/transactions/files.py +96 -0
  133. dataforge/transactions/log.py +613 -0
  134. dataforge/transactions/revert.py +102 -0
  135. dataforge/transactions/txn.py +104 -0
  136. dataforge/ui/__init__.py +1 -0
  137. dataforge/ui/profile_view.py +136 -0
  138. dataforge/ui/repair_diff.py +91 -0
  139. dataforge/verifier/__init__.py +55 -0
  140. dataforge/verifier/constraint_ir.py +155 -0
  141. dataforge/verifier/explain.py +47 -0
  142. dataforge/verifier/gate.py +5 -0
  143. dataforge/verifier/schema.py +111 -0
  144. dataforge/verifier/smt.py +433 -0
  145. dataforge_07-0.1.0.dist-info/METADATA +436 -0
  146. dataforge_07-0.1.0.dist-info/RECORD +150 -0
  147. dataforge_07-0.1.0.dist-info/WHEEL +5 -0
  148. dataforge_07-0.1.0.dist-info/entry_points.txt +3 -0
  149. dataforge_07-0.1.0.dist-info/licenses/LICENSE +176 -0
  150. 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:]))