Skip to content

Commit 1f1043b

Browse files
Pigbibicodex
andcommitted
feat: reslice daily preview artifact evidence gate
Co-Authored-By: Codex <noreply@openai.com>
1 parent c5ba801 commit 1f1043b

5 files changed

Lines changed: 424 additions & 0 deletions
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: QAR vNext D3 Daily Preview Artifact Reslice
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/qar_vnext_d3_daily_preview_artifact_reslice.yml"
7+
- "scripts/d3_reslice_build_preview.py"
8+
- "scripts/d3_reslice_verify_preview.py"
9+
- "src/quant_advisor_research/**"
10+
- "examples/political_events.example.csv"
11+
- "examples/political_watchlist.example.csv"
12+
- "pyproject.toml"
13+
- "uv.lock"
14+
- "tests/test_d3_reslice_artifact.py"
15+
- "tests/test_preview_bundle.py"
16+
- "docs/qar_vnext_d3_reslice_design_v1_2026-07-15.md"
17+
workflow_dispatch:
18+
inputs:
19+
as_of:
20+
description: "Representative daily report date"
21+
required: true
22+
default: "2026-06-20"
23+
type: string
24+
25+
permissions:
26+
contents: read
27+
28+
jobs:
29+
build-and-verify:
30+
runs-on: ubuntu-latest
31+
timeout-minutes: 15
32+
steps:
33+
- name: Checkout repository
34+
uses: actions/checkout@v6
35+
with:
36+
fetch-depth: 0
37+
- name: Setup Python
38+
uses: actions/setup-python@v6
39+
with:
40+
python-version: "3.11"
41+
- name: Install advisor package
42+
run: python -m pip install -e .
43+
- name: Build representative daily preview twice
44+
env:
45+
AS_OF: ${{ inputs.as_of || '2026-06-20' }}
46+
BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }}
47+
run: |
48+
set -euo pipefail
49+
python scripts/d3_reslice_build_preview.py \
50+
--as-of "${AS_OF}" \
51+
--political-events examples/political_events.example.csv \
52+
--political-watchlist examples/political_watchlist.example.csv \
53+
--artifact-dir "${RUNNER_TEMP}/qar-daily-preview" \
54+
--evidence-path "${RUNNER_TEMP}/qar-daily-preview-build-evidence.json" \
55+
--base-sha "${BASE_SHA}" \
56+
--frozen-generated-at "2026-07-15T00:00:00Z" \
57+
> "${RUNNER_TEMP}/qar-daily-preview-build-evidence.log"
58+
cat "${RUNNER_TEMP}/qar-daily-preview-build-evidence.log"
59+
- name: Upload daily preview artifact
60+
uses: actions/upload-artifact@v7
61+
with:
62+
name: qar-daily-preview-reslice
63+
path: ${{ runner.temp }}/qar-daily-preview
64+
if-no-files-found: error
65+
- name: Download daily preview artifact
66+
uses: actions/download-artifact@v7
67+
with:
68+
name: qar-daily-preview-reslice
69+
path: ${{ runner.temp }}/qar-daily-preview-downloaded
70+
- name: Verify downloaded artifact against build evidence
71+
env:
72+
BASE_SHA: ${{ github.event.pull_request.base.sha || github.sha }}
73+
run: |
74+
set -euo pipefail
75+
python scripts/d3_reslice_verify_preview.py \
76+
--artifact-dir "${RUNNER_TEMP}/qar-daily-preview-downloaded" \
77+
--build-evidence-path "${RUNNER_TEMP}/qar-daily-preview-build-evidence.json" \
78+
--evidence-path "${RUNNER_TEMP}/qar-daily-preview-download-evidence.json" \
79+
--base-sha "${BASE_SHA}"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# QAR D3 fresh reslice
2+
3+
This is representative-fixture evidence only. It builds two independent daily reports with a harness-frozen `generated_at`, emits exactly `report.json`, `report.html`, and `manifest.json`, uploads only those three files, downloads them, and verifies canonical manifest bytes plus build evidence binding.
4+
5+
It does not change weekly/monthly, Pages, publisher/archive/feed, legacy/compatibility/migration, identity/store, or production trust. Issue #50 remains a production gate.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python3
2+
"""Build a fresh D3 representative daily preview and external build evidence."""
3+
from __future__ import annotations
4+
5+
import argparse
6+
import hashlib
7+
import json
8+
import re
9+
import shutil
10+
import sys
11+
import tempfile
12+
from pathlib import Path
13+
from unittest.mock import patch
14+
15+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
16+
17+
from quant_advisor_research import advisory_report
18+
from quant_advisor_research.preview_bundle import PreviewBundleError, build_preview_bundle, read_preview_bundle
19+
20+
21+
BASE_SHA_RE = re.compile(r"[0-9a-f]{40}")
22+
BUNDLE_CONTRACT = "qar.preview_bundle.v1"
23+
SCHEMA_VERSION = "5"
24+
REPORT_CONTRACT = "model_recommendations.v5"
25+
FIXED_FILES = ["manifest.json", "report.html", "report.json"]
26+
27+
28+
def fail(code: str) -> PreviewBundleError:
29+
return PreviewBundleError(code)
30+
31+
32+
def sha256(path: Path) -> str:
33+
return hashlib.sha256(path.read_bytes()).hexdigest()
34+
35+
36+
def require_contract(report: dict[str, object], manifest: dict[str, object]) -> dict[str, object]:
37+
source = manifest.get("source")
38+
expected = {
39+
"schema_version": SCHEMA_VERSION,
40+
"contract_version": REPORT_CONTRACT,
41+
"cadence": "daily",
42+
"as_of": report.get("as_of"),
43+
"generated_at": report.get("generated_at"),
44+
}
45+
if (
46+
report.get("schema_version") != SCHEMA_VERSION
47+
or report.get("cadence") != "daily"
48+
or manifest.get("bundle_contract") != BUNDLE_CONTRACT
49+
or not isinstance(source, dict)
50+
or source != expected
51+
):
52+
raise fail("contract_drift")
53+
return source
54+
55+
56+
def build(args: argparse.Namespace) -> None:
57+
if BASE_SHA_RE.fullmatch(args.base_sha) is None:
58+
raise fail("base_sha_invalid")
59+
if not args.frozen_generated_at:
60+
raise fail("frozen_generated_at_required")
61+
events = Path(args.political_events)
62+
watchlist = Path(args.political_watchlist)
63+
output = Path(args.artifact_dir)
64+
if not events.is_file() or not watchlist.is_file():
65+
raise fail("fixture_missing")
66+
67+
repeat_parent = Path(tempfile.mkdtemp(prefix=f".{output.name}.repeat-", dir=output.parent))
68+
try:
69+
repeat_output = repeat_parent / "preview"
70+
with patch.object(advisory_report, "utc_now_iso", return_value=args.frozen_generated_at):
71+
first = advisory_report.build_advisory_report(
72+
as_of=args.as_of,
73+
cadence="daily",
74+
political_events_path=events,
75+
political_watchlist_path=watchlist,
76+
)
77+
build_preview_bundle(first, output)
78+
first_readback = read_preview_bundle(output)
79+
second = advisory_report.build_advisory_report(
80+
as_of=args.as_of,
81+
cadence="daily",
82+
political_events_path=events,
83+
political_watchlist_path=watchlist,
84+
)
85+
build_preview_bundle(second, repeat_output)
86+
first_report = dict(first_readback.report)
87+
first_manifest = dict(first_readback.manifest)
88+
manifest_source = require_contract(first_report, first_manifest)
89+
repeat_equal = all((output / name).read_bytes() == (repeat_output / name).read_bytes() for name in FIXED_FILES)
90+
finally:
91+
shutil.rmtree(repeat_parent, ignore_errors=True)
92+
if not repeat_equal:
93+
raise fail("repeat_build_non_deterministic")
94+
payload = {
95+
"source_kind": "repository_representative_fixture",
96+
"base_sha": args.base_sha,
97+
"bundle_contract": BUNDLE_CONTRACT,
98+
"source": {key: first_report[key] for key in ("cadence", "as_of", "generated_at", "schema_version")},
99+
"manifest_source": manifest_source,
100+
"deterministic_clock": {"mode": "frozen_harness", "generated_at": args.frozen_generated_at},
101+
"files": sorted(path.name for path in output.iterdir()),
102+
"sha256": {name: sha256(output / name) for name in FIXED_FILES},
103+
"repeat_build_bytes": repeat_equal,
104+
}
105+
if payload["files"] != FIXED_FILES:
106+
raise fail("file_set_invalid")
107+
evidence_path = Path(args.evidence_path)
108+
if evidence_path.exists():
109+
raise fail("evidence_exists")
110+
evidence_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
111+
print(json.dumps(payload, ensure_ascii=False, sort_keys=True))
112+
113+
114+
def main() -> None:
115+
parser = argparse.ArgumentParser()
116+
parser.add_argument("--as-of", required=True)
117+
parser.add_argument("--political-events", required=True)
118+
parser.add_argument("--political-watchlist", required=True)
119+
parser.add_argument("--artifact-dir", required=True)
120+
parser.add_argument("--evidence-path", required=True)
121+
parser.add_argument("--base-sha", required=True)
122+
parser.add_argument("--frozen-generated-at", required=True)
123+
try:
124+
build(parser.parse_args())
125+
except PreviewBundleError as exc:
126+
print(f"d3_reslice_build_failed:{exc.code}", file=sys.stderr)
127+
raise SystemExit(1) from None
128+
except (OSError, TypeError, ValueError, UnicodeError):
129+
print("d3_reslice_build_failed", file=sys.stderr)
130+
raise SystemExit(1) from None
131+
132+
133+
if __name__ == "__main__":
134+
main()
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
"""Verify downloaded D3 bytes against canonical manifest and build evidence."""
3+
from __future__ import annotations
4+
5+
import argparse
6+
import hashlib
7+
import json
8+
import re
9+
import sys
10+
from pathlib import Path
11+
12+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
13+
14+
from quant_advisor_research.preview_bundle import PreviewBundleError, read_preview_bundle
15+
16+
17+
BASE_SHA_RE = re.compile(r"[0-9a-f]{40}")
18+
BUNDLE_CONTRACT = "qar.preview_bundle.v1"
19+
SCHEMA_VERSION = "5"
20+
REPORT_CONTRACT = "model_recommendations.v5"
21+
FIXED_FILES = ["manifest.json", "report.html", "report.json"]
22+
23+
24+
def fail(code: str) -> PreviewBundleError:
25+
return PreviewBundleError(code)
26+
27+
28+
def canonical_json(value: object) -> bytes:
29+
try:
30+
return json.dumps(
31+
value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
32+
).encode("utf-8")
33+
except (TypeError, ValueError, UnicodeError, RecursionError):
34+
raise fail("manifest_canonical_invalid") from None
35+
36+
37+
def verify(args: argparse.Namespace) -> None:
38+
if BASE_SHA_RE.fullmatch(args.base_sha) is None:
39+
raise fail("base_sha_invalid")
40+
output = Path(args.artifact_dir)
41+
if sorted(path.name for path in output.iterdir()) != FIXED_FILES:
42+
raise fail("file_set_invalid")
43+
raw_manifest = (output / "manifest.json").read_bytes()
44+
try:
45+
parsed_manifest = json.loads(raw_manifest.decode("utf-8"))
46+
except (UnicodeError, TypeError, ValueError, RecursionError):
47+
raise fail("manifest_canonical_invalid") from None
48+
if raw_manifest != canonical_json(parsed_manifest):
49+
raise fail("manifest_noncanonical")
50+
readback = read_preview_bundle(output)
51+
report = dict(readback.report)
52+
manifest = dict(readback.manifest)
53+
source = manifest.get("source")
54+
expected_source = {
55+
"schema_version": SCHEMA_VERSION,
56+
"contract_version": REPORT_CONTRACT,
57+
"cadence": "daily",
58+
"as_of": report.get("as_of"),
59+
"generated_at": report.get("generated_at"),
60+
}
61+
if (
62+
report.get("schema_version") != SCHEMA_VERSION
63+
or report.get("cadence") != "daily"
64+
or manifest.get("bundle_contract") != BUNDLE_CONTRACT
65+
or not isinstance(source, dict)
66+
or source != expected_source
67+
):
68+
raise fail("contract_drift")
69+
try:
70+
build_evidence = json.loads(Path(args.build_evidence_path).read_text(encoding="utf-8"))
71+
except (OSError, UnicodeError, TypeError, ValueError, RecursionError):
72+
raise fail("build_evidence_invalid") from None
73+
actual_hashes = {name: hashlib.sha256((output / name).read_bytes()).hexdigest() for name in FIXED_FILES}
74+
if (
75+
not isinstance(build_evidence, dict)
76+
or build_evidence.get("source_kind") != "repository_representative_fixture"
77+
or build_evidence.get("base_sha") != args.base_sha
78+
or build_evidence.get("bundle_contract") != BUNDLE_CONTRACT
79+
or build_evidence.get("files") != FIXED_FILES
80+
or build_evidence.get("manifest_source") != source
81+
or build_evidence.get("sha256") != actual_hashes
82+
or build_evidence.get("repeat_build_bytes") is not True
83+
):
84+
raise fail("build_evidence_mismatch")
85+
payload = {
86+
"source_kind": "downloaded_repository_representative_fixture",
87+
"base_sha": args.base_sha,
88+
"bundle_contract": BUNDLE_CONTRACT,
89+
"source": source,
90+
"files": FIXED_FILES,
91+
"sha256": actual_hashes,
92+
"manifest_canonical_bytes": True,
93+
"build_evidence_bound": True,
94+
}
95+
evidence_path = Path(args.evidence_path)
96+
if evidence_path.exists():
97+
raise fail("evidence_exists")
98+
evidence_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
99+
print(json.dumps(payload, ensure_ascii=False, sort_keys=True))
100+
101+
102+
def main() -> None:
103+
parser = argparse.ArgumentParser()
104+
parser.add_argument("--artifact-dir", required=True)
105+
parser.add_argument("--build-evidence-path", required=True)
106+
parser.add_argument("--evidence-path", required=True)
107+
parser.add_argument("--base-sha", required=True)
108+
try:
109+
verify(parser.parse_args())
110+
except (PreviewBundleError, OSError, TypeError, ValueError, UnicodeError):
111+
print("d3_reslice_readback_failed", file=sys.stderr)
112+
raise SystemExit(1) from None
113+
114+
115+
if __name__ == "__main__":
116+
main()

0 commit comments

Comments
 (0)