FrameBridge 0.3.1 community source release

This commit is contained in:
FrameBridge Development committed 2026-10-26 22:09:18 -04:00
commit b23819fff1
1354 files changed
+343716

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/python3
"""Archive the reviewed community source layout without runtime profiles or QA logs."""
import calendar
import gzip
import hashlib
import io
import json
from pathlib import Path
import tarfile
BASE = Path(__file__).resolve().parents[1]
ROOT = BASE.parent
EPOCH = calendar.timegm((2026, 10, 26, 0, 0, 0))
ARCHIVE_ROOT = "FrameBridge-0.3.1"
ARCHIVE_NAME = "FrameBridge-0.3.1-source.tar.gz"
SOURCE_FOLDERS = ("sources", "expanded", "portable", "packaging", "docs", "tests")
ROOT_FILES = ("README.md", "LICENSE", "LICENSING.md", "COPYRIGHT.md",
"LICENSE-STATUS.md", "OFFICIAL-RELEASES.md", "CONTRIBUTING.md", "SECURITY.md", "RELEASE-NOTES.md")
ROOT_FOLDERS = ("docs", "provenance", "licensing")
BLOG_POLICY_FILES = ("README.md", "LICENSING.md", "POST-TEMPLATE.md", "LICENSE-CC-BY-SA-4.0.txt")
ARCHIVE_README_NAME = "README-SOURCE-ARCHIVE.md"
DEPENDENCY_SOURCE_ASSETS = [{'filename': 'FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz', 'bytes': 82606998, 'sha256': '64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a'}, {'filename': 'FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz', 'bytes': 26941745, 'sha256': '6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9'}]
ARCHIVE_README = "# FrameBridge 0.3.1 source archive\n\nThis archive preserves the repository layout needed by the offline builder.\nFrom this directory, run python3 source/packaging/build_expanded.py to build\nFrameBridge-0.3.1.deb. The builder writes source/build and source/release;\nit does not install or start FrameBridge. Debian's dpkg-deb and the documented\nbuild prerequisites must already be available.\n\nOriginal FrameBridge material is GNU GPL version 3 only. Read LICENSE,\nLICENSING.md, COPYRIGHT.md, LICENSE-STATUS.md and OFFICIAL-RELEASES.md for\nscope, attribution, third-party terms and the official-release trust model.\n\nThe project source asset is accompanied by these separate dependency assets:\n\n- FrameBridge-0.3.1-Community-Preview1-dependency-sources.tar.gz\n 82,606,998 bytes\n SHA-256 64540a85bd65eac3f394d8c0a967cacbc00e112676d57d644dcd1bb691c1926a\n- FrameBridge-0.3.1-tunnel-client-dependency-sources.tar.gz\n 26,941,745 bytes\n SHA-256 6445d4f0e773aba833fe5c610fbae54102b92de2ce636438f769f21e890c2bd9\n\nThe Preview1 filename deliberately preserves the identity of an immutable\nshared asset. Retain licensing/third-party/R2-SUPPLEMENTS.json and its referenced\nsource notices with the dependency sources. The source indices and\nSOURCE-AVAILABILITY.md there record scope and provenance. Do not substitute\nunrelated archives. The retained tunnel executable is not claimed to have been\nindependently rebuilt from an identified own-source revision.\n\nThe blog directory contains licensing policies and the post template.\nEditorial article drafts are omitted because they are not source build inputs.\n\nOnly source/evidence/BASELINE.json is included from technical evidence. Runtime\nprofiles, private QA logs, generated build/release files and Git history are\noutside the source archive allowlist.\n\nA completed archive or builder run is not a declaration of public readiness.\nRelease checksums, signatures and publication status are recorded separately.\n".encode("utf-8")
GPL_SHA256 = "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986"
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def source_files():
selected = {}
def add(path):
relative = path.relative_to(ROOT)
if path.is_symlink():
raise RuntimeError("Source archives must not include symbolic links: " + str(relative))
if "__pycache__" in relative.parts or path.suffix == ".pyc":
return
if path.is_file():
selected[relative.as_posix()] = path
for folder in SOURCE_FOLDERS:
directory = BASE / folder
if not directory.is_dir() or directory.is_symlink():
raise RuntimeError("Required source directory missing: " + folder)
for path in directory.rglob("*"):
add(path)
# The builder needs this input; no other evidence, QA log, or diff is selected.
baseline = BASE / "evidence/BASELINE.json"
if not baseline.is_file():
raise RuntimeError("Required build baseline missing.")
add(baseline)
for name in ROOT_FILES:
path = ROOT / name
if not path.is_file():
raise RuntimeError("Required repository input missing: " + name)
add(path)
for name in ROOT_FOLDERS:
directory = ROOT / name
if not directory.is_dir() or directory.is_symlink():
raise RuntimeError("Required repository directory missing: " + name)
for path in directory.rglob("*"):
add(path)
# Preserve policy references, but do not publish draft blog articles implicitly.
for name in BLOG_POLICY_FILES:
path = ROOT / "blog" / name
if not path.is_file():
raise RuntimeError("Required blog licensing policy input missing: " + name)
add(path)
if sha(ROOT / "LICENSE") != GPL_SHA256:
raise RuntimeError("The GPLv3 text differs from the recorded official license.")
required_notices = ROOT / "licensing/third-party"
for name in ("THIRD-PARTY-NOTICES.md", "DEPENDENCY-SOURCES.json", "SOURCE-AVAILABILITY.md", "R2-SUPPLEMENTS.json", "NATIVE-DEPENDENCY-TERMS.json", "NATIVE-DEPENDENCY-TERMS.md", "TUNNEL-DEPENDENCY-SOURCES.json"):
path = required_notices / name
if not path.is_file() or not path.stat().st_size:
raise RuntimeError("Required third-party source/notice input missing: " + name)
return selected
def main():
names = source_files()
release = BASE / "release"
release.mkdir(exist_ok=True)
output = release / ARCHIVE_NAME
with output.open("wb") as raw:
with gzip.GzipFile(filename="", mode="wb", fileobj=raw,
mtime=EPOCH, compresslevel=6) as zipped:
with tarfile.open(fileobj=zipped, mode="w", format=tarfile.GNU_FORMAT) as archive:
info = tarfile.TarInfo(ARCHIVE_ROOT + "/" + ARCHIVE_README_NAME)
info.uid = info.gid = 0
info.uname = info.gname = ""
info.mtime = EPOCH
info.mode = 0o644
info.size = len(ARCHIVE_README)
archive.addfile(info, io.BytesIO(ARCHIVE_README))
for relative, path in sorted(names.items()):
info = archive.gettarinfo(str(path), arcname=ARCHIVE_ROOT + "/" + relative)
info.uid = info.gid = 0
info.uname = info.gname = ""
info.mtime = EPOCH
info.mode = 0o755 if path.stat().st_mode & 0o111 else 0o644
with path.open("rb") as stream:
archive.addfile(info, stream)
manifest = {"status": "COMMUNITY_RELEASE_BUILD", "archive": output.name,
"bytes": output.stat().st_size, "sha256": sha(output),
"files": [{"path": ARCHIVE_README_NAME,
"sha256": hashlib.sha256(ARCHIVE_README).hexdigest(), "generated": True}]
+ [{"path": relative, "sha256": sha(path)}
for relative, path in sorted(names.items())],
"draft_blog_articles_included": False,
"evidence_allowlist": ["source/evidence/BASELINE.json"],
"dependency_sources": DEPENDENCY_SOURCE_ASSETS[0]["filename"],
"dependency_source_archives": DEPENDENCY_SOURCE_ASSETS}
(release / "SOURCE-ARCHIVE-MANIFEST.json").write_text(json.dumps(manifest, indent=2) + "\n")
print(json.dumps({"archive": str(output), "bytes": output.stat().st_size,
"sha256": manifest["sha256"], "files": len(names) + 1,
"status": manifest["status"]}))
if __name__ == "__main__":
main()