Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/public-proof-release-2026-09-07.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ The finished-example showcase at https://mashup.highsignal.app now serves real a
- Provider confirmed production/main deployment `837580c0-b324-4e6a-a53d-0fa3e974b748`, source `9673d78`: https://837580c0.mashup-a6h.pages.dev.
- Never replace this release with plain `web/dist`: that build omits generated media. Re-run the complete-bundle guard before every deployment.

### Guarded publisher

Run `pnpm deploy /absolute/path/to/complete-public-proof-bundle` from `web/`
through Fleet Workspace after the exact main commit passes CI. The publisher
refuses dirty or unpublished source, builds the current site, compares every
public static file to the supplied bundle, rejects operator routes and
unexpected files, and verifies both approved media/caption receipts with the
existing MP4/WebVTT guard before invoking the existing Mashup Pages target.
Generated media remains outside Git. A build alone cannot produce this bundle;
retain the approved media when staging new static files. After publication,
verify canonical asset hashes and both players; provider success is not playback
proof. The publisher uses Python's standard library and the already-used pinned
Wrangler CLI, without adding production dependencies.

## Hosted acceptance

Ordinary canonical public URLs for both MP4s and both VTTs were downloaded read-only and matched the approved receipts byte-for-byte and SHA-256-for-SHA-256. Exact hashes remain in the [recovery receipt](shareability-qualification-2026-09-07.md). Both public JSON receipts decode successfully, report approved status and retain licensed source provenance.
Expand Down
94 changes: 94 additions & 0 deletions scripts/deploy_public_proof.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Publish only a complete approved proof bundle matching a fresh site build."""

from __future__ import annotations

import argparse
import json
import subprocess
from pathlib import Path
from urllib.parse import urlparse

from check_public_proof import PROOFS, validate_bundle

REPO = Path(__file__).resolve().parents[1]
EXCLUDED = {"editor", "visual-lab"}


def validate_static_bundle(bundle: Path, build: Path) -> None:
bundle, build = bundle.resolve(), build.resolve()
if not (build / "index.html").is_file():
raise ValueError("missing fresh site build")
allowed = set()
for asset in build.rglob("*"):
relative = asset.relative_to(build)
if relative.parts[0] in EXCLUDED or not asset.is_file():
continue
if asset.is_symlink() or (bundle / relative).is_symlink():
raise ValueError(f"symlink in static bundle: {relative}")
if asset.read_bytes() != (bundle / relative).read_bytes():
raise ValueError(f"stale or altered build asset: {relative}")
allowed.add(relative.as_posix())
for name in PROOFS:
receipt_path = f"receipts/{name}.receipt.json"
receipt = json.loads((bundle / receipt_path).read_text())
allowed.add(receipt_path)
for kind in ("video", "captions"):
allowed.add(urlparse(receipt["output"][kind]["path"]).path.lstrip("/"))
allowed.add("receipts/survive-technology.score.json")
for asset in bundle.rglob("*"):
relative = asset.relative_to(bundle)
if asset.is_symlink():
raise ValueError(f"symlink in public bundle: {relative}")
if relative.parts[0] in EXCLUDED:
raise ValueError(f"operator route in public bundle: {relative}")
if asset.is_file() and relative.as_posix() not in allowed:
raise ValueError(f"unexpected file in public bundle: {relative}")


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"bundle", type=Path, help="explicit complete staging directory, not web/dist"
)
args = parser.parse_args()
bundle = args.bundle.resolve()
commit = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=REPO, text=True).strip()
subprocess.run(["git", "diff", "--quiet", "HEAD"], cwd=REPO, check=True)
untracked = subprocess.check_output(
["git", "ls-files", "--others", "--exclude-standard"], cwd=REPO, text=True
).strip()
if untracked:
parser.exit(1, "Refusing public deployment: checkout has unpublished source files\n")
main_ref = subprocess.check_output(
["git", "ls-remote", "origin", "refs/heads/main"], cwd=REPO, text=True
).split()
if not main_ref or main_ref[0] != commit:
parser.exit(1, "Refusing public deployment: checkout is not the published main commit\n")
# Build and deployment use the same checkout; old staging cannot acquire a new commit tag.
subprocess.run(["pnpm", "build"], cwd=REPO / "web", check=True)
try:
validate_static_bundle(bundle, REPO / "web/dist")
assets = validate_bundle(bundle)
except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
parser.exit(1, f"Refusing public deployment: {error}\n")
print(json.dumps({"commit": commit, "bundle": str(bundle), "assets": assets}), flush=True)
subprocess.run(
[
"pnpm",
"dlx",
"--package=wrangler@4.120.0",
"wrangler",
"pages",
"deploy",
str(bundle),
"--project-name=mashup",
"--branch=main",
f"--commit-hash={commit}",
],
cwd=REPO / "web",
check=True,
)


if __name__ == "__main__":
main()
68 changes: 68 additions & 0 deletions tests/test_public_proof_deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""A fresh static footer build must never replace the complete proof release."""

import importlib.util
import json
import sys
from pathlib import Path

import pytest

SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(SCRIPTS))
spec = importlib.util.spec_from_file_location(
"deploy_public_proof", SCRIPTS / "deploy_public_proof.py"
)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)


def fixture_bundle(tmp_path):
build, bundle = tmp_path / "build", tmp_path / "bundle"
build.mkdir()
bundle.mkdir()
(build / "index.html").write_text("current footer")
(bundle / "index.html").write_text("current footer")
(bundle / "receipts").mkdir()
for name in ("survive-technology", "operators"):
(bundle / f"receipts/{name}.receipt.json").write_text(
json.dumps(
{
"output": {
"video": {"path": f"/media/{name}.mp4"},
"captions": {"path": f"/media/{name}.vtt"},
}
}
)
)
return build, bundle


def test_plain_build_cannot_be_published(tmp_path):
build = tmp_path / "build"
build.mkdir()
(build / "index.html").write_text("current footer")
with pytest.raises(FileNotFoundError):
module.validate_static_bundle(build, build)


def test_old_staging_cannot_receive_current_source_tag(tmp_path):
build, bundle = fixture_bundle(tmp_path)
(bundle / "index.html").write_text("old footer")
with pytest.raises(ValueError, match="stale"):
module.validate_static_bundle(bundle, build)


@pytest.mark.parametrize("unexpected", ["editor/index.html", ".fleet-local/private.json"])
def test_operator_or_private_files_cannot_enter_release(tmp_path, unexpected):
build, bundle = fixture_bundle(tmp_path)
asset = bundle / unexpected
asset.parent.mkdir(parents=True, exist_ok=True)
asset.write_text("private")
with pytest.raises(ValueError, match="operator route|unexpected file"):
module.validate_static_bundle(bundle, build)


def test_current_public_static_bundle_is_accepted(tmp_path):
build, bundle = fixture_bundle(tmp_path)
module.validate_static_bundle(bundle, build)
3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"check": "astro check"
"check": "astro check",
"deploy": "python3 ../scripts/deploy_public_proof.py"
},
"dependencies": {
"@astrojs/react": "^4.4.0",
Expand Down
Loading