From b7e6fe9fbf40884f7fc4db1e8855e4605cf31a1e Mon Sep 17 00:00:00 2001 From: BucketComps Automation Date: Thu, 23 Jul 2026 05:54:16 -0500 Subject: [PATCH] repair and stage the static public rebuild dossier --- .editorconfig | 12 + .github/CODEOWNERS | 1 + .github/dependabot.yml | 8 + .github/workflows/pages.yml | 57 ++++ CONTRIBUTING.md | 14 + README.md | 22 +- docs/ARCHITECTURE.md | 29 ++ docs/PUBLISHING.md | 17 + docs/SANITIZED-SNAPSHOT.md | 22 ++ docs/SECURITY-BOUNDARY.md | 21 ++ scripts/check_site.py | 422 ++++++++++++++++++++++++ site/404.html | 26 ++ site/assets/site.css | 629 ++++++++++++++++++++++++++++++++++++ site/data/status.json | 34 ++ site/devlog/index.html | 86 +++++ site/index.html | 166 ++++++++++ site/method/index.html | 87 +++++ site/roadmap/index.html | 91 ++++++ tests/test_check_site.py | 171 ++++++++++ 19 files changed, 1913 insertions(+), 2 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/pages.yml create mode 100644 CONTRIBUTING.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/PUBLISHING.md create mode 100644 docs/SANITIZED-SNAPSHOT.md create mode 100644 docs/SECURITY-BOUNDARY.md create mode 100644 scripts/check_site.py create mode 100644 site/404.html create mode 100644 site/assets/site.css create mode 100644 site/data/status.json create mode 100644 site/devlog/index.html create mode 100644 site/index.html create mode 100644 site/method/index.html create mode 100644 site/roadmap/index.html create mode 100644 tests/test_check_site.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..de91110 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..f842c37 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @deucebucket diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e56c416 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + labels: + - dependencies diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..d7cbd0b --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,57 @@ +name: Validate and deploy public dossier + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Check out the exact revision + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Run publication-boundary regressions + env: + PYTHONDONTWRITEBYTECODE: "1" + run: python3 -m unittest discover -s tests -p "test_*.py" -v + - name: Validate links, inventory, history, and publication boundary + env: + PYTHONDONTWRITEBYTECODE: "1" + run: python3 scripts/check_site.py + + deploy: + if: github.event_name != 'pull_request' + needs: validate + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Check out the exact revision + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Configure GitHub Pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + - name: Upload the static dossier + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: site + - name: Deploy + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..93d1ba0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +# Contributing + +This is a curated public dossier, not a mirror of the development repository. + +1. Start from an already sanitized, public-safe milestone. +2. Rewrite it for readers; do not copy private notes or evidence packets. +3. Preserve the proof vocabulary: matched, verified, and snapshot-verified are + different claims. +4. Keep game-derived media and live payloads out of Git. +5. Run `python3 scripts/check_site.py`. +6. Open a pull request for review. + +Numbers on the dossier are dated snapshots. Do not silently turn them into a +live feed. diff --git a/README.md b/README.md index d973d92..c0e0ed9 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,23 @@ outside this public projection. This repository must remain safe to clone and publish in full: no game assets, ROM data, keys, raw evidence, private wiki copies, credentials, or live telemetry. -The themed dossier is being prepared on a review branch before any Pages -deployment. +## Public surfaces + +- Default dossier URL after merge: +- Eventual dossier URL: `https://decomp.deucebucket.com/infamous/` +- Proposed local-live URL: `https://live.decomp.deucebucket.com/infamous` + +The custom domain and proposed live hostname are deliberately **not cut over**. +The live link is labeled as a hold everywhere it appears. + +## Work locally + +```sh +python3 scripts/check_site.py +python3 -m http.server 8080 --directory site +``` + +Then open . + +Pull requests validate but do not deploy. A merge to `main` deploys only the +`site/` artifact through GitHub Pages. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..8474132 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,29 @@ +# Dossier architecture + +`site/` is a hand-curated static artifact. It has no build-time connection to +private source, evidence stores, game data, or live telemetry. + +## Content map + +- `/infamous/` — current public snapshot and two-track overview +- `/infamous/devlog/` — dated, public-safe milestone notes +- `/infamous/method/` — proof vocabulary and clean-room boundaries +- `/infamous/roadmap/` — gate-by-gate route to playable gameplay +- `/infamous/data/status.json` — the same small dated snapshot in machine-readable form + +The JSON file is static and reviewed. It is not a runtime payload. + +## Hosting + +This repository is a GitHub Pages **project site**, so its default URL is +`https://bucketcomps.github.io/infamous/`. GitHub documents that a custom domain +assigned to the organization site is inherited by public project sites by +default; after a separately approved cutover, the intended URL is +`https://decomp.deucebucket.com/infamous/`. + +The dynamic stream/dashboard remains a separate locally hosted system. This +site only contains a clearly held link to the proposed +`https://live.decomp.deucebucket.com/infamous` endpoint. + +Official reference: +[About custom domains and GitHub Pages](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages). diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md new file mode 100644 index 0000000..ba1b8a9 --- /dev/null +++ b/docs/PUBLISHING.md @@ -0,0 +1,17 @@ +# Publishing + +The custom GitHub Pages workflow: + +1. validates every relevant pull request and push; +2. uploads only `site/`; +3. grants deployment permissions only to the deploy job; +4. skips deployment on pull requests; and +5. pins every action to a full commit SHA. + +There is intentionally no `CNAME` file. Custom-domain work belongs to the +organization Pages cutover and is not part of this dossier PR. + +Official references: + +- [Configuring a Pages publishing source](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site) +- [Using custom Pages workflows](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages) diff --git a/docs/SANITIZED-SNAPSHOT.md b/docs/SANITIZED-SNAPSHOT.md new file mode 100644 index 0000000..d427679 --- /dev/null +++ b/docs/SANITIZED-SNAPSHOT.md @@ -0,0 +1,22 @@ +# Sanitized source snapshot + +The initial dossier copy was written from the already published, fail-closed +public release—not from private project documentation. + +Audit record: + +- Public release: `20260723-ee63eaa984f6aee96265` +- Public manifest SHA-256: + `ccbbdacae803bea9be244d096994602078d62c7a78040d2ad2977c6e7629f22f` +- Manifest-enumerated payloads: 55 +- Complete release tree: 56 files, including the release manifest +- Snapshot date: 2026-07-23 + +Only small text facts needed by the static dossier were selected. No image, +video, audio, manifest media entry, live support payload, or raw telemetry file +was copied into this repository. + +The dossier intentionally retains honest zeros: 4 of 13 renderer checklist +steps, 0 draws, 0 flips, 0 captures, and 0 verified frames. Save/load is shown +as 4 of 34 subsystem checks represented; it is not presented as an end-to-end +gameplay save/load proof. diff --git a/docs/SECURITY-BOUNDARY.md b/docs/SECURITY-BOUNDARY.md new file mode 100644 index 0000000..297d902 --- /dev/null +++ b/docs/SECURITY-BOUNDARY.md @@ -0,0 +1,21 @@ +# Public dossier boundary 🔒 + +## Allowed + +- Public-safe prose derived from an already sanitized release +- Dated headline numbers with explicit denominators and proof labels +- Hand-authored HTML and CSS +- Public repository and documentation links +- Generic interface marks made from text and CSS + +## Forbidden + +- Game-derived screenshots, models, textures, video, audio, executables, or data +- ROMs, keys, firmware, SDK material, or decrypted content +- Private wiki/source copies, raw notes, evidence packets, or internal paths +- Credentials, infrastructure coordinates, process metadata, or personal data +- Live telemetry, runtime payloads, automatically copied dashboards, or symlinks + +The workflow uploads only `site/`. A negative-exposure test rejects media-like +files, secret-like strings, internal markers, unpinned actions, broken links, +symlinks, and accidental custom-domain files. diff --git a/scripts/check_site.py b/scripts/check_site.py new file mode 100644 index 0000000..24fbf5e --- /dev/null +++ b/scripts/check_site.py @@ -0,0 +1,422 @@ +#!/usr/bin/env python3 +"""Fail closed unless the complete public repository matches its reviewed projection.""" + +from __future__ import annotations + +import argparse +from html.parser import HTMLParser +import json +from pathlib import Path +import re +import subprocess +import sys +from urllib.parse import urlsplit + + +DEFAULT_ROOT = Path(__file__).resolve().parent.parent +BASE_PATH = "/infamous/" +AUTOMATION_NAME = "BucketComps Automation" +AUTOMATION_EMAIL = "bucketcomps-automation@users.noreply.github.com" + +EXPECTED_REPOSITORY_FILES = { + ".editorconfig", + ".gitattributes", + ".github/CODEOWNERS", + ".github/dependabot.yml", + ".github/workflows/pages.yml", + ".gitignore", + "CONTRIBUTING.md", + "LICENSE", + "README.md", + "SECURITY.md", + "docs/ARCHITECTURE.md", + "docs/PUBLISHING.md", + "docs/SANITIZED-SNAPSHOT.md", + "docs/SECURITY-BOUNDARY.md", + "scripts/check_site.py", + "site/404.html", + "site/assets/site.css", + "site/data/status.json", + "site/devlog/index.html", + "site/index.html", + "site/method/index.html", + "site/roadmap/index.html", + "tests/test_check_site.py", +} +EXPECTED_SITE_FILES = { + "404.html", + "assets/site.css", + "data/status.json", + "devlog/index.html", + "index.html", + "method/index.html", + "roadmap/index.html", +} +EXPECTED_ACTIONS = ( + "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d", + "actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9", + "actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128", +) +EXPECTED_STATUS = { + "schema_version": 1, + "as_of": "2026-07-23", + "renderer": { + "steps_done": 4, + "steps_total": 13, + "draws": 0, + "flips": 0, + "captures": 0, + "verified_frames": 0, + }, + "lifted_fallback": { + "functions": 20298, + "inventory_total": 20298, + "clean_source_proof": False, + }, + "save_system": { + "checks_represented": 4, + "checks_total": 34, + "end_to_end_gameplay_cycle": False, + }, + "texture_decode": { + "percent": 98.1, + "records": 15437, + }, + "model": { + "name": "ArcEndian Fast", + "verified_pass": 18, + "verified_total": 75, + "accuracy_percent": 24.0, + "accepted_into_source": 0, + }, + "finish_line": "verified playable gameplay with working save and load", +} +FORBIDDEN_SUFFIXES = { + ".3ds", + ".avi", + ".bin", + ".dds", + ".elf", + ".iso", + ".mkv", + ".mov", + ".mp3", + ".mp4", + ".ogg", + ".png", + ".psarc", + ".self", + ".sprx", + ".wav", +} +FORBIDDEN_FRAGMENTS = ( + "/" + "var" + "/" + "home" + "/", + "/" + "home" + "/", + "infamous" + "-decomp", + "proof" + "_scope", + "BEGIN" + " PRIVATE KEY", + "CF_" + "API_TOKEN", + "GITHUB_" + "TOKEN", + "gmail" + ".com", + "jerry" + "mares", + "Jerry" + " Mares", +) +SECRET_PATTERNS = ( + re.compile(r"gh[opsu]_[A-Za-z0-9]{20,}"), + re.compile(r"sk-[A-Za-z0-9_-]{20,}"), +) +EMAIL_PATTERN = re.compile(r"(?i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b") + + +class Document(HTMLParser): + def __init__(self) -> None: + super().__init__() + self.links: list[tuple[str, str]] = [] + self.ids: set[str] = set() + self.h1 = 0 + self.lang = False + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + values = dict(attrs) + if tag == "html" and values.get("lang"): + self.lang = True + if values.get("id"): + self.ids.add(values["id"] or "") + if tag == "h1": + self.h1 += 1 + if tag in {"a", "link", "script"}: + attr = "href" if tag in {"a", "link"} else "src" + if values.get(attr): + self.links.append((tag, values[attr] or "")) + + +def fail(message: str) -> None: + raise AssertionError(message) + + +def public_files(root: Path) -> list[Path]: + result: list[Path] = [] + for path in root.rglob("*"): + relative = path.relative_to(root) + if ".git" in relative.parts: + continue + if path.is_symlink(): + fail(f"symlink forbidden in public repository: {relative.as_posix()}") + if path.is_file(): + result.append(path) + return sorted(result) + + +def check_inventory(root: Path, files: list[Path]) -> None: + actual = {path.relative_to(root).as_posix() for path in files} + missing = sorted(EXPECTED_REPOSITORY_FILES - actual) + extra = sorted(actual - EXPECTED_REPOSITORY_FILES) + if missing or extra: + fail(f"repository inventory mismatch; missing={missing}, extra={extra}") + + site = root / "site" + actual_site = { + path.relative_to(site).as_posix() + for path in files + if path.is_relative_to(site) + } + missing_site = sorted(EXPECTED_SITE_FILES - actual_site) + extra_site = sorted(actual_site - EXPECTED_SITE_FILES) + if missing_site or extra_site: + fail(f"published inventory mismatch; missing={missing_site}, extra={extra_site}") + + if any(path.suffix.lower() in FORBIDDEN_SUFFIXES for path in files): + fail("game/media-like file forbidden in public repository") + + +def check_disclosures(root: Path, files: list[Path]) -> None: + for path in files: + relative = path.relative_to(root).as_posix() + try: + text = path.read_text(encoding="utf-8") + except UnicodeError as error: + raise AssertionError( + f"non-UTF-8 file forbidden in public repository: {relative}" + ) from error + for fragment in FORBIDDEN_FRAGMENTS: + if fragment.lower() in text.lower(): + fail(f"forbidden disclosure marker in {relative}") + for pattern in SECRET_PATTERNS: + if pattern.search(text): + fail(f"secret-like token in {relative}") + for email in EMAIL_PATTERN.findall(text): + if email.lower() != AUTOMATION_EMAIL: + fail(f"non-automation email address in {relative}") + + +def local_target(site: Path, source: Path, link: str) -> Path: + parsed = urlsplit(link) + path = parsed.path + if path.startswith(BASE_PATH): + return site / path.removeprefix(BASE_PATH) + if path.startswith("/"): + fail(f"absolute local link escapes {BASE_PATH}: {link}") + return source if not path else source.parent / path + + +def check_documents(root: Path) -> dict[Path, Document]: + site = root / "site" + documents: dict[Path, Document] = {} + for path in sorted(site.rglob("*.html")): + document = Document() + document.feed(path.read_text(encoding="utf-8")) + documents[path.resolve()] = document + if document.h1 != 1 or not document.lang: + fail(f"{path.relative_to(site)} needs one h1 and an html language") + if "#main" not in [value for tag, value in document.links if tag == "a"]: + fail(f"{path.relative_to(site)} is missing a skip link") + + for source, document in documents.items(): + for _tag, link in document.links: + parsed = urlsplit(link) + if parsed.scheme: + if parsed.scheme != "https": + fail(f"non-HTTPS link in {source.relative_to(site)}: {link}") + continue + if link.startswith("//"): + fail(f"scheme-relative link forbidden: {link}") + target = local_target(site, source, link) + if target.is_dir(): + target /= "index.html" + if not target.exists(): + fail(f"broken local link in {source.relative_to(site)}: {link}") + if parsed.fragment and target.suffix == ".html": + target_document = documents.get(target.resolve()) + if target_document is None: + target_document = Document() + target_document.feed(target.read_text(encoding="utf-8")) + if parsed.fragment not in target_document.ids: + fail(f"missing fragment in {source.relative_to(site)}: {link}") + + not_found = (site / "404.html").read_text(encoding="utf-8") + required_404_links = ( + 'href="/infamous/assets/site.css"', + 'href="/infamous/"', + ) + if any(marker not in not_found for marker in required_404_links): + fail(f"404 document must preserve the {BASE_PATH} project base") + + architecture = (root / "docs/ARCHITECTURE.md").read_text(encoding="utf-8") + architecture_paths = ( + "`/infamous/`", + "`/infamous/devlog/`", + "`/infamous/method/`", + "`/infamous/roadmap/`", + "`/infamous/data/status.json`", + ) + if any(path not in architecture for path in architecture_paths): + fail(f"architecture content map must use the {BASE_PATH} project base") + return documents + + +def channel(value: int) -> float: + component = value / 255 + return component / 12.92 if component <= 0.04045 else ((component + 0.055) / 1.055) ** 2.4 + + +def contrast_ratio(foreground: str, background: str) -> float: + colors = [] + for value in (foreground, background): + red, green, blue = (int(value[index:index + 2], 16) for index in (1, 3, 5)) + colors.append(0.2126 * channel(red) + 0.7152 * channel(green) + 0.0722 * channel(blue)) + lighter, darker = sorted(colors, reverse=True) + return (lighter + 0.05) / (darker + 0.05) + + +def check_accessibility_contract(root: Path) -> None: + css = (root / "site/assets/site.css").read_text(encoding="utf-8") + required_css = ".callout > .kicker {\n color: #284b50;\n}" + if required_css not in css: + fail("callout kicker color must remain explicitly scoped to the paper surface") + ratio = contrast_ratio("#284b50", "#ece5d2") + if ratio < 4.5: + fail(f"callout kicker contrast is below WCAG AA: {ratio:.2f}:1") + + index = (root / "site/index.html").read_text(encoding="utf-8") + if '
    ' not in index: + fail("metrics must be a named semantic list") + for relative in ("site/index.html", "site/method/index.html", "site/roadmap/index.html"): + text = (root / relative).read_text(encoding="utf-8") + if '
    None: + workflow = (root / ".github/workflows/pages.yml").read_text(encoding="utf-8") + actions = tuple(re.findall(r"uses:\s*([^\s#]+)", workflow)) + if actions != EXPECTED_ACTIONS: + fail("workflow action inventory or immutable pins changed") + required = ( + "github.event_name != 'pull_request'", + "fetch-depth: 0", + "github.event.pull_request.head.sha || github.sha", + "python3 -m unittest discover", + "python3 scripts/check_site.py", + ) + if any(marker not in workflow for marker in required): + fail("workflow no longer enforces full-history PR validation without deployment") + + +def check_history(root: Path) -> None: + if not (root / ".git").is_dir(): + fail("full Git metadata is required for commit identity validation") + result = subprocess.run( + [ + "git", + "-C", + str(root), + "log", + "--all", + "--format=%H%x1f%an%x1f%ae%x1f%cn%x1f%ce%x1e", + ], + check=True, + capture_output=True, + text=True, + ) + records = [record.strip() for record in result.stdout.split("\x1e") if record.strip()] + if not records: + fail("public repository history is empty") + for record in records: + commit, author_name, author_email, committer_name, committer_email = record.split("\x1f") + if ( + author_name != AUTOMATION_NAME + or author_email != AUTOMATION_EMAIL + or committer_name != AUTOMATION_NAME + or committer_email != AUTOMATION_EMAIL + ): + fail(f"non-automation commit identity remains reachable: {commit}") + + +def validate(root: Path, *, include_history: bool = True) -> tuple[int, int]: + root = root.resolve() + site = root / "site" + if not site.is_dir() or site.is_symlink(): + fail("site must be a real directory") + if (site / "CNAME").exists(): + fail("custom-domain cutover is intentionally held") + + files = public_files(root) + check_inventory(root, files) + check_disclosures(root, files) + documents = check_documents(root) + check_accessibility_contract(root) + check_status(root) + check_workflow(root) + if include_history: + check_history(root) + return len(files), len(documents) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=DEFAULT_ROOT) + parser.add_argument( + "--skip-history", + action="store_true", + help="test-fixture mode only; production validation must inspect full Git history", + ) + args = parser.parse_args(argv) + files, documents = validate(args.root, include_history=not args.skip_history) + print( + f"dossier checks passed: {files} repository files, " + f"{documents} HTML documents, base {BASE_PATH}" + ) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except ( + AssertionError, + json.JSONDecodeError, + OSError, + subprocess.CalledProcessError, + UnicodeError, + ValueError, + ) as error: + print(f"dossier check failed: {error}", file=sys.stderr) + raise SystemExit(1) diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..c69f744 --- /dev/null +++ b/site/404.html @@ -0,0 +1,26 @@ + + + + + + + + 404 // circuit open · inFAMOUS rebuild + + + + +
    404 // circuit open
    + +
    +
    +

    route not in public dossier

    +

    Dead wire.

    +

    That path is not part of the published surface.

    + Return to status → +
    +
    + + diff --git a/site/assets/site.css b/site/assets/site.css new file mode 100644 index 0000000..74c0508 --- /dev/null +++ b/site/assets/site.css @@ -0,0 +1,629 @@ +:root { + color-scheme: dark; + --night: #090d0e; + --storm: #101719; + --storm-2: #182124; + --paper: #ece5d2; + --paper-dim: #c4bdac; + --ink: #111615; + --arc: #62f4ff; + --charge: #efff66; + --hot: #ff596d; + --line: #39484b; + --max: 1160px; + font-family: "Arial Narrow", "Roboto Condensed", "Helvetica Neue", Arial, sans-serif; +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + min-width: 300px; + background: + radial-gradient(ellipse at 15% -5%, rgb(98 244 255 / 16%), transparent 35rem), + repeating-linear-gradient(87deg, transparent 0 73px, rgb(255 255 255 / 2%) 74px 75px), + repeating-linear-gradient(3deg, transparent 0 29px, rgb(255 255 255 / 2%) 30px), + var(--night); + color: var(--paper); +} + +a { + color: inherit; +} + +a:focus-visible { + outline: 3px solid var(--arc); + outline-offset: 4px; +} + +.skip { + position: fixed; + top: 1rem; + left: 1rem; + z-index: 20; + padding: 0.75rem 1rem; + background: var(--paper); + color: var(--ink); + font-weight: 900; + transform: translateY(-180%); +} + +.skip:focus { + transform: translateY(0); +} + +.shell { + width: min(calc(100% - 2rem), var(--max)); + margin-inline: auto; +} + +.alert { + padding: 0.55rem 1rem; + border-bottom: 1px solid #39484b; + background: var(--charge); + color: var(--ink); + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.74rem; + font-weight: 900; + letter-spacing: 0.05em; + text-align: center; + text-transform: uppercase; +} + +.site-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; + min-height: 5rem; + border-bottom: 1px solid var(--line); +} + +.brand { + display: inline-flex; + align-items: center; + gap: 0.75rem; + font-weight: 1000; + letter-spacing: -0.03em; + text-decoration: none; + text-transform: uppercase; +} + +.bolt { + display: grid; + place-items: center; + width: 2.2rem; + aspect-ratio: 1; + background: var(--arc); + color: var(--ink); + clip-path: polygon(47% 0, 100% 0, 62% 38%, 88% 38%, 26% 100%, 40% 54%, 10% 54%); + font-size: 0; + filter: drop-shadow(0 0 0.6rem rgb(98 244 255 / 70%)); +} + +nav ul { + display: flex; + gap: clamp(0.65rem, 2vw, 1.4rem); + margin: 0; + padding: 0; + list-style: none; +} + +nav a { + color: var(--paper-dim); + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.76rem; + font-weight: 800; + text-decoration-thickness: 2px; + text-underline-offset: 0.35rem; + text-transform: uppercase; +} + +nav a[aria-current="page"], +nav a:hover { + color: var(--arc); +} + +.hero { + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(18rem, 0.62fr); + gap: clamp(2rem, 7vw, 7rem); + align-items: center; + min-height: 44rem; + padding-block: 5rem; +} + +.hero > * { + min-width: 0; +} + +.kicker, +.stamp { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.72rem; + font-weight: 900; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.kicker { + color: var(--arc); +} + +h1, +h2, +h3, +p { + margin-top: 0; +} + +h1 { + max-width: 9ch; + margin-bottom: 1.2rem; + font-size: clamp(4.2rem, 12vw, 10rem); + font-weight: 1000; + letter-spacing: -0.09em; + line-height: 0.74; + text-transform: uppercase; + overflow-wrap: anywhere; +} + +h1 .ghost { + color: transparent; + -webkit-text-stroke: 1px var(--paper); + text-shadow: 7px 7px 0 rgb(98 244 255 / 16%); +} + +.lede { + max-width: 58ch; + color: var(--paper-dim); + font-size: clamp(1.05rem, 2vw, 1.3rem); + line-height: 1.55; +} + +.hero-card { + position: relative; + padding: 1.6rem; + border: 1px solid var(--line); + background: var(--paper); + color: var(--ink); + transform: rotate(1deg); + box-shadow: + 12px 12px 0 rgb(98 244 255 / 18%), + -8px -8px 0 rgb(255 89 109 / 9%); +} + +.hero-card::after { + content: ""; + position: absolute; + right: 1.1rem; + bottom: -0.7rem; + width: 4.5rem; + height: 1.2rem; + background: rgb(239 255 102 / 78%); + transform: rotate(-4deg); +} + +.hero-card h2 { + margin: 0.65rem 0 1rem; + font-size: 2rem; + letter-spacing: -0.05em; + text-transform: uppercase; +} + +.hero-card p { + line-height: 1.5; +} + +.hero-card strong { + background: linear-gradient(transparent 55%, rgb(239 255 102 / 85%) 55%); +} + +.buttons { + display: flex; + flex-wrap: wrap; + gap: 0.7rem; + margin-top: 1.6rem; +} + +.button { + display: inline-flex; + align-items: center; + min-height: 2.9rem; + padding: 0.72rem 1rem; + border: 1px solid var(--paper); + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.76rem; + font-weight: 900; + text-decoration: none; + text-transform: uppercase; + max-width: 100%; + overflow-wrap: anywhere; + text-align: center; +} + +.button:hover { + background: var(--paper); + color: var(--ink); +} + +.button.live { + border-color: var(--hot); + color: #ff8a98; +} + +.button.live::before { + content: "●"; + margin-right: 0.5rem; +} + +.divider { + height: 0.6rem; + border-block: 1px solid var(--line); + background: + repeating-linear-gradient(135deg, var(--arc) 0 8px, transparent 8px 16px); + opacity: 0.72; +} + +.section { + padding-block: clamp(4rem, 8vw, 7rem); +} + +.section-head { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + align-items: end; + margin-bottom: 2rem; +} + +h2 { + margin-bottom: 0; + font-size: clamp(2.6rem, 7vw, 5.5rem); + font-weight: 1000; + letter-spacing: -0.065em; + line-height: 0.88; + text-transform: uppercase; +} + +.section-intro { + margin-bottom: 0; + color: var(--paper-dim); + line-height: 1.55; +} + +.metrics { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1px; + margin: 0; + padding: 0; + background: var(--line); + border: 1px solid var(--line); + list-style: none; +} + +.metric { + min-width: 0; + min-height: 10rem; + padding: 1.3rem; + background: var(--storm); +} + +.metric .value { + display: block; + margin: 1.8rem 0 0.5rem; + color: var(--charge); + font-size: clamp(2rem, 5vw, 4.3rem); + font-weight: 1000; + letter-spacing: -0.07em; + line-height: 0.85; +} + +.metric small { + color: var(--paper-dim); + line-height: 1.35; +} + +.zero .value { + color: #ff8290; +} + +.honesty { + margin-top: 1rem; + padding: 1rem 1.2rem; + border-left: 0.35rem solid var(--hot); + background: rgb(255 89 109 / 9%); + color: #f2c4c8; + line-height: 1.5; +} + +.tracks { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.2rem; +} + +.track { + position: relative; + overflow: hidden; + min-height: 24rem; + padding: 2rem; + border: 1px solid var(--line); + background: var(--storm); + min-width: 0; +} + +.track::after { + content: attr(data-mark); + position: absolute; + right: -0.1em; + bottom: -0.28em; + color: rgb(255 255 255 / 3%); + font-size: 18rem; + font-weight: 1000; + line-height: 1; +} + +.track h3 { + position: relative; + z-index: 1; + margin: 2rem 0 1rem; + font-size: clamp(2.2rem, 5vw, 4.2rem); + line-height: 0.9; + text-transform: uppercase; +} + +.track p, +.track ul { + position: relative; + z-index: 1; + max-width: 50ch; + color: var(--paper-dim); + line-height: 1.55; +} + +.track li { + margin-block: 0.55rem; +} + +.circuit { + position: relative; + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 1px; + margin: 2rem 0 0; + padding: 0; + background: var(--line); + list-style: none; +} + +.gate { + position: relative; + min-height: 13rem; + padding: 1.1rem; + background: var(--storm); +} + +.gate::before { + content: ""; + display: block; + width: 0.85rem; + height: 0.85rem; + margin-bottom: 2rem; + border: 2px solid var(--paper-dim); + border-radius: 50%; +} + +.gate.done::before { + border-color: var(--charge); + background: var(--charge); + box-shadow: 0 0 1rem var(--charge); +} + +.gate.current::before { + border-color: var(--arc); + background: var(--arc); + box-shadow: 0 0 1rem var(--arc); +} + +.gate b { + display: block; + margin-bottom: 0.5rem; + text-transform: uppercase; +} + +.gate small { + color: var(--paper-dim); + line-height: 1.4; +} + +.log-list { + display: grid; + gap: 0.8rem; +} + +.log-entry { + display: grid; + grid-template-columns: 9rem minmax(0, 1fr); + gap: 1.5rem; + padding: 1.3rem; + border: 1px solid var(--line); + background: var(--storm); +} + +.log-entry time, +.log-entry .tag { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.72rem; + font-weight: 900; +} + +.log-entry time { + color: var(--arc); +} + +.log-entry .tag { + display: inline-block; + margin-top: 0.55rem; + padding: 0.25rem 0.4rem; + border: 1px solid var(--line); + color: var(--paper-dim); +} + +.log-entry h3 { + margin-bottom: 0.4rem; + font-size: 1.2rem; + text-transform: uppercase; +} + +.log-entry p { + margin-bottom: 0; + color: var(--paper-dim); + line-height: 1.5; +} + +.proof-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.proof { + padding: 1.4rem; + border: 1px solid var(--line); + background: var(--storm); +} + +.proof h3 { + color: var(--arc); + text-transform: uppercase; +} + +.proof p { + margin-bottom: 0; + color: var(--paper-dim); + line-height: 1.55; +} + +.callout { + padding: clamp(1.5rem, 5vw, 3.5rem); + border: 1px solid var(--line); + background: var(--paper); + color: var(--ink); + box-shadow: 12px 12px 0 rgb(98 244 255 / 14%); +} + +.callout h2 { + margin-bottom: 1rem; +} + +.callout p { + max-width: 65ch; + line-height: 1.55; +} + +.callout > .kicker { + color: #284b50; +} + +.callout .button { + border-color: var(--ink); +} + +.callout .button:hover { + background: var(--ink); + color: var(--paper); +} + +.footer { + display: flex; + justify-content: space-between; + gap: 2rem; + padding-block: 2rem 3rem; + border-top: 1px solid var(--line); + color: var(--paper-dim); + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.72rem; +} + +.footer a { + color: var(--paper); +} + +@media (max-width: 900px) { + .hero, + .section-head, + .tracks { + grid-template-columns: 1fr; + } + + .hero { + min-height: auto; + } + + .metrics, + .proof-grid { + grid-template-columns: 1fr 1fr; + } + + .circuit { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 620px) { + .shell { + width: min(calc(100% - 1.2rem), var(--max)); + } + + .site-header { + align-items: flex-start; + flex-direction: column; + padding-block: 1rem; + } + + nav ul { + flex-wrap: wrap; + } + + h1 { + font-size: clamp(4rem, 24vw, 7rem); + } + + .hero { + padding-block: 3.5rem; + } + + .hero-card { + transform: none; + } + + .metrics, + .proof-grid, + .circuit { + grid-template-columns: 1fr; + } + + .log-entry { + grid-template-columns: 1fr; + gap: 0.8rem; + } + + .footer { + align-items: flex-start; + flex-direction: column; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} diff --git a/site/data/status.json b/site/data/status.json new file mode 100644 index 0000000..b904eec --- /dev/null +++ b/site/data/status.json @@ -0,0 +1,34 @@ +{ + "schema_version": 1, + "as_of": "2026-07-23", + "renderer": { + "steps_done": 4, + "steps_total": 13, + "draws": 0, + "flips": 0, + "captures": 0, + "verified_frames": 0 + }, + "lifted_fallback": { + "functions": 20298, + "inventory_total": 20298, + "clean_source_proof": false + }, + "save_system": { + "checks_represented": 4, + "checks_total": 34, + "end_to_end_gameplay_cycle": false + }, + "texture_decode": { + "percent": 98.1, + "records": 15437 + }, + "model": { + "name": "ArcEndian Fast", + "verified_pass": 18, + "verified_total": 75, + "accuracy_percent": 24.0, + "accepted_into_source": 0 + }, + "finish_line": "verified playable gameplay with working save and load" +} diff --git a/site/devlog/index.html b/site/devlog/index.html new file mode 100644 index 0000000..2e90452 --- /dev/null +++ b/site/devlog/index.html @@ -0,0 +1,86 @@ + + + + + + + + Devlog 📓 inFAMOUS native rebuild + + + + +
    📓 public dispatches · holds remain visible · snapshot 2026-07-23
    + + +
    +
    +
    +

    public preservation log

    +

    Field notes.

    +

    Evidence-backed milestones rewritten for public readers. Raw traces, private packets, local infrastructure, and game data stay outside this repository.

    +
    + +
    + + + +
    +
    +

    latest five

    2026-07-23 dispatches

    +

    These entries came from an already sanitized public release. They are a dated record, not a live telemetry feed.

    +
    +
    +
    +
    MODEL
    +

    Fast's retained C clears syntax and ABI shape

    The comment-tolerant posthoc gate accepted the exact retained function and passed two AST plus two strict-C11 / reviewed-ABI checks. Its first oracle call then stopped on a stale ABI-authority schema, so the candidate earns no behavioral result yet.

    +
    +
    +
    RENDERER
    +

    The graphics probe reached Python and exposed a recipe-context bug

    Namespace rehearsal passed and the exact checkpoint was selected. The data-only probe stopped before restore because the host instance was created outside the required handler context. Guest and renderer execution remained forbidden.

    +
    +
    +
    RAG
    +

    Citation-only prompt integration shipped provider-dark

    Fast and the external lane can now receive append-only, hash-bound reference context without changing the raw task prompt. The integration stays separate from training labels and awaits one controlled A/B executor.

    +
    +
    +
    ORACLE
    +

    Reviewed external snapshot specifications are supported

    The verifier gained exact-hash external snapshot specifications with 38 passing tests. Its first execution packet remains held before launch until one-use and evidence-separation controls are complete.

    +
    +
    +
    NIM
    +

    The repaired eight-target conveyor shipped provider-dark

    Construction and runtime callbacks are separated, failures become durable error traces, and redaction evidence binds original to sanitized hashes and sizes. Live callbacks and a short authority are still required; nothing is auto-banked.

    +
    +
    +
    + +
    +
    +

    archive contract

    +

    Short prose, hard edges.

    +

    Future entries should state the proof class, measurable change, honest hold, and next gate. The private development record remains the operational authority; this is its deliberately small public projection.

    + Proof vocabulary → +
    +
    +
    + + + + diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..0c1f1c4 --- /dev/null +++ b/site/index.html @@ -0,0 +1,166 @@ + + + + + + + + inFAMOUS native rebuild ⚡ public dossier + + + + +
    ⚠ public dossier · static snapshot 2026-07-23 · no game data lives here
    + + + +
    +
    +
    +

    BCUS-98119 // preservation circuit

    +

    Rebuild the current.

    +

    Turning the 2009 PlayStation 3 game back into readable, verifiable source and a native PC build—without shipping the game, its assets, or its keys.

    + +
    + +
    + + + +
    +
    +
    +

    measured snapshot

    +

    No percentage theater.

    +
    +

    Every number keeps its own denominator and meaning. Fallback coverage, source proof, graphics work, save checks, and model accuracy are not mixed into one fake completion bar.

    +
    +
      +
    • 🖥️ renderer4 / 13first-frame checklist gates complete
    • +
    • 🎞️ proof0verified renderer frames
    • +
    • 🧬 fallback20,298inventoried functions with runnable lifted coverage; not clean-source proof
    • +
    • 💾 save / load4 / 34subsystem checks represented; no end-to-end gameplay cycle yet
    • +
    • 🎨 art readiness98.1%texture decode across 15,437 records
    • +
    • 🤖 ArcEndian Fast18 / 75canonical verified benchmark; zero accepted into source from the current conduit
    • +
    +

    Honest hold: current graphics work remains before guest rendering. Save structures have meaningful native coverage, but the final gameplay save/load claim is not earned.

    +
    + +
    +
    +
    +

    parallel workfronts

    +

    Two tracks. One game.

    +
    +

    The renderer makes it playable. Source recovery makes it moddable. Neither gets to pretend the other is finished.

    +
    +
    +
    + ⚡ playable track +

    Clean-room graphics

    +

    A native consumer for the original RSX command stream is advancing through a 13-gate checklist.

    +
      +
    • 4 gates complete
    • +
    • 7 infrastructure gates built
    • +
    • 0 draws, flips, captures, or frames
    • +
    • Next proof: reach the first real draw without widening claims
    • +
    +
    +
    + 🧬 moddable track +

    Readable source

    +

    Lifted fallback keeps the full inventory runnable while functions are replaced one by one with reviewed C.

    +
      +
    • Binary behavior remains the ground truth
    • +
    • Matched, verified, and snapshot-verified stay distinct
    • +
    • Compiler and behavioral oracles gate promotion
    • +
    • Model output proposes; it never self-approves
    • +
    +
    +
    +
    + +
    +
    +
    +

    the circuit spine

    +

    Route to playable

    +
    +

    Six visible gates keep a first frame, a boot, and a playable build from collapsing into the same claim.

    +
    +
      +
    1. 01 // LiftedRunnable fallback foundation is complete.
    2. +
    3. 02 // SourceReadable verified C is being banked function by function.
    4. +
    5. 03 // GraphicsNative renderer is at 4 of 13 first-frame gates.
    6. +
    7. 04 // ModelCandidate proposer remains behind compiler and oracle gates.
    8. +
    9. 05 // SavesSubsystem work exists; gameplay cycle remains queued.
    10. +
    11. 06 // PlayableReal gameplay plus working save and load.
    12. +
    + +
    + +
    +
    +
    +

    latest dispatches

    +

    Field log

    +
    +

    Short public-safe updates. Holds are part of the record, not embarrassing footnotes.

    +
    +
    +
    +
    MODEL
    +

    Retained C clears syntax and ABI shape

    The exact candidate passed two AST and two strict C11 / reviewed-ABI checks. Its first oracle call stopped on a stale authority schema, so it earns no behavioral result.

    +
    +
    +
    RENDERER
    +

    Graphics probe exposes a recipe-context bug

    Namespace rehearsal passed and the exact checkpoint was selected. The data-only probe stopped before restore because instance creation happened outside its required handler context.

    +
    +
    +
    ORACLE
    +

    External snapshot specifications supported

    The verifier gained exact-hash external snapshot specifications with 38 passing tests. The first execution packet remains held until its one-use and evidence-separation controls are complete.

    +
    +
    + +
    + +
    +
    +

    🔒 bring-your-own-dump boundary

    +

    No game in this repo.

    +

    This dossier contains only original site code and public-safe prose. It ships no ROM, executable, key, decrypted data, screenshot, texture, model, video, audio, or private evidence. Preservation work requires a legally obtained copy supplied outside Git.

    + +
    +
    +
    + +
    + inFAMOUS native rebuild // public snapshot 2026-07-23 + ← BucketComps · CC0 site source +
    + + diff --git a/site/method/index.html b/site/method/index.html new file mode 100644 index 0000000..105f503 --- /dev/null +++ b/site/method/index.html @@ -0,0 +1,87 @@ + + + + + + + + Method 🧪 inFAMOUS native rebuild + + + + +
    🧪 binary evidence first · recovered C is a hypothesis
    + + +
    +
    +
    +

    verification handbook

    +

    Trust the receipts.

    +

    The retail program defines behavior. Recovered C, lifted code, renderer infrastructure, and model suggestions carry only the claims their evidence earns.

    +
    + +
    + + + +
    +
    +

    proof classes

    Words mean things.

    +

    A weaker result is still useful when it is labeled honestly. The public count never blurs these categories.

    +
    +
    +

    🧷 Matched

    The recovered function compiles to the exact reference bytes under the reviewed toolchain and settings.

    +

    ✅ Verified

    Tier-A behavioral proof covers all inputs admitted by the function's reviewed contract.

    +

    📸 Snapshot-verified

    Behavior matches one exact captured heap state. Valuable evidence, but strictly weaker than all-input verification.

    +

    🧪 Attempted

    A bounded experiment ran and produced evidence but did not earn a stronger status.

    +

    🛑 Blocked

    A named gate prevents further progress until its prerequisites or authority are repaired.

    +

    🏗️ Built

    Infrastructure exists and passes its own checks. That alone does not prove a game frame or gameplay behavior.

    +
    +
    + +
    +
    +

    promotion circuit

    Proposal is not proof.

    +

    Human-written or model-proposed C follows the same gates. No candidate can promote itself.

    +
    +
      +
    1. 01 // EvidenceDefine the function contract from the binary and reviewed observations.
    2. +
    3. 02 // CandidateWrite or propose readable C without treating prior C as truth.
    4. +
    5. 03 // ABICheck signature, calling convention, memory shape, and allowed behavior.
    6. +
    7. 04 // CompilerRequire the reviewed native compiler path to accept the exact candidate.
    8. +
    9. 05 // OracleCompare behavior under the appropriate verification tier.
    10. +
    11. 06 // ReviewOnly a reviewed grant banks the source and updates the public count.
    12. +
    +
    + +
    +
    +

    public repository promise

    +

    Safe to clone in full.

    +

    The public dossier is hand-curated static prose and interface code. It contains no live ingestion, private mirror, or game-derived media. The automated checker is a baseline; every change still needs human review.

    + Read the repository boundary ↗ +
    +
    +
    + + + + diff --git a/site/roadmap/index.html b/site/roadmap/index.html new file mode 100644 index 0000000..c9e3d2e --- /dev/null +++ b/site/roadmap/index.html @@ -0,0 +1,91 @@ + + + + + + + + Roadmap 🧭 inFAMOUS native rebuild + + + + +
    🧭 finish line: verified playable gameplay + working save and load
    + + +
    +
    +
    +

    from fallback to city

    +

    Follow the current.

    +

    The route is deliberately longer than “show a frame.” Each gate has its own evidence and refuses to borrow confidence from the others.

    +
    + +
    + + + +
    +
    +

    six project gates

    The full circuit

    +

    Complete, current, and queued are state labels—not vibes. The dated snapshot below is intentionally static.

    +
    +
      +
    1. 01 // Lifted20,298 of 20,298 inventoried functions have runnable fallback coverage. Complete, but not clean-source proof.
    2. +
    3. 02 // SourceReadable C is recovered and banked only under matched, verified, or snapshot-verified grants.
    4. +
    5. 03 // GraphicsThe native RSX consumer has completed 4 of 13 first-frame gates.
    6. +
    7. 04 // ModelArcEndian Fast proposes candidates. Compiler, ABI, oracle, and review decide whether they count.
    8. +
    9. 05 // Saves4 of 34 subsystem checks are represented. Real native gameplay save/load remains queued.
    10. +
    11. 06 // PlayableBoot, input, graphics, audio, gameplay, and working save/load are proven together.
    12. +
    +
    + +
    +
    +

    playable track

    First-frame ladder

    +

    The public scoreboard reports 4 of 13 strict gates complete and 7 infrastructure gates built. “Built” does not substitute for renderer output.

    +
    +
    +
    + proven +

    Four strict exits

    +

    Foundational command-stream work is in place. The exact implementation detail stays in the private development record; the public claim is the measured gate count.

    +
    +
    + not yet proven +

    No frame claim

    +

    Draws: 0. Flips: 0. Captures: 0. Verified frames: 0. The next milestone must change those numbers with accepted evidence.

    +
    +
    +
    + +
    +
    +

    end-state test

    +

    Can you play, save, quit, load, and continue?

    +

    That is the finish line. A native executable, boot sequence, first frame, or isolated save parser can be a real milestone without being the finished game.

    + See current work → +
    +
    +
    + + + + diff --git a/tests/test_check_site.py b/tests/test_check_site.py new file mode 100644 index 0000000..e29c6a8 --- /dev/null +++ b/tests/test_check_site.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Regression probes for the fail-closed public repository checker.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parent.parent +CHECKER = Path("scripts/check_site.py") +STATUS_DRIFTS = ( + (("schema_version",), 2), + (("as_of",), "2099-01-01"), + (("renderer", "steps_done"), 5), + (("renderer", "steps_total"), 14), + (("renderer", "draws"), 1), + (("renderer", "flips"), 1), + (("renderer", "captures"), 1), + (("renderer", "verified_frames"), 1), + (("lifted_fallback", "functions"), 20299), + (("lifted_fallback", "inventory_total"), 20299), + (("lifted_fallback", "clean_source_proof"), True), + (("save_system", "checks_represented"), 5), + (("save_system", "checks_total"), 35), + (("save_system", "end_to_end_gameplay_cycle"), True), + (("texture_decode", "percent"), 99.0), + (("texture_decode", "records"), 15438), + (("model", "name"), "unreviewed"), + (("model", "verified_pass"), 19), + (("model", "verified_total"), 76), + (("model", "accuracy_percent"), 25.0), + (("model", "accepted_into_source"), 1), + (("finish_line",), "first frame"), +) + + +class CheckerRegressions(unittest.TestCase): + def setUp(self) -> None: + self.temporary = tempfile.TemporaryDirectory() + self.fixture = Path(self.temporary.name) / "repo" + shutil.copytree( + ROOT, + self.fixture, + ignore=shutil.ignore_patterns(".git", "__pycache__", "*.pyc"), + ) + + def tearDown(self) -> None: + self.temporary.cleanup() + + def run_checker(self, *, expect_success: bool = False) -> subprocess.CompletedProcess[str]: + environment = dict(os.environ) + environment["PYTHONDONTWRITEBYTECODE"] = "1" + result = subprocess.run( + [ + sys.executable, + str(CHECKER), + "--root", + str(self.fixture), + "--skip-history", + ], + cwd=self.fixture, + env=environment, + capture_output=True, + text=True, + ) + if expect_success: + self.assertEqual(result.returncode, 0, result.stderr) + else: + self.assertNotEqual(result.returncode, 0, result.stdout) + return result + + def test_reviewed_repository_passes_without_history_fixture(self) -> None: + result = self.run_checker(expect_success=True) + self.assertIn("23 repository files", result.stdout) + self.assertIn("base /infamous/", result.stdout) + + def test_rejects_extra_file_outside_published_site(self) -> None: + (self.fixture / "notes.txt").write_text("extra\n", encoding="utf-8") + result = self.run_checker() + self.assertIn("repository inventory mismatch", result.stderr) + + def test_rejects_extra_file_inside_published_site(self) -> None: + (self.fixture / "site/extra.html").write_text("

    extra

    \n", encoding="utf-8") + result = self.run_checker() + self.assertIn("repository inventory mismatch", result.stderr) + + def test_rejects_missing_allowlisted_file(self) -> None: + (self.fixture / "CONTRIBUTING.md").unlink() + result = self.run_checker() + self.assertIn("repository inventory mismatch", result.stderr) + + def test_rejects_forbidden_text_outside_site(self) -> None: + forbidden = "/" + "var" + "/" + "home" + "/private" + readme = self.fixture / "README.md" + readme.write_text(readme.read_text(encoding="utf-8") + forbidden, encoding="utf-8") + result = self.run_checker() + self.assertIn("forbidden disclosure marker", result.stderr) + + def test_rejects_every_declared_status_leaf_drift(self) -> None: + original = json.loads( + (self.fixture / "site/data/status.json").read_text(encoding="utf-8") + ) + for path, replacement in STATUS_DRIFTS: + with self.subTest(path=".".join(path)): + status = json.loads(json.dumps(original)) + target = status + for component in path[:-1]: + target = target[component] + target[path[-1]] = replacement + (self.fixture / "site/data/status.json").write_text( + json.dumps(status, indent=2) + "\n", + encoding="utf-8", + ) + result = self.run_checker() + self.assertIn("status snapshot drifted", result.stderr) + (self.fixture / "site/data/status.json").write_text( + json.dumps(original, indent=2) + "\n", + encoding="utf-8", + ) + + def test_rejects_project_base_path_drift(self) -> None: + page = self.fixture / "site/404.html" + page.write_text( + page.read_text(encoding="utf-8").replace("/infamous/", "/wrong/"), + encoding="utf-8", + ) + result = self.run_checker() + self.assertIn("escapes /infamous/", result.stderr) + + def test_rejects_architecture_content_map_base_drift(self) -> None: + architecture = self.fixture / "docs/ARCHITECTURE.md" + architecture.write_text( + architecture.read_text(encoding="utf-8").replace( + "`/infamous/devlog/`", + "`/devlog/`", + ), + encoding="utf-8", + ) + result = self.run_checker() + self.assertIn("architecture content map", result.stderr) + + def test_rejects_low_contrast_callout_kicker(self) -> None: + css = self.fixture / "site/assets/site.css" + css.write_text( + css.read_text(encoding="utf-8").replace("#284b50", "#62f4ff"), + encoding="utf-8", + ) + result = self.run_checker() + self.assertIn("callout kicker color", result.stderr) + + def test_rejects_generic_circuit_container(self) -> None: + page = self.fixture / "site/method/index.html" + text = page.read_text(encoding="utf-8") + text = text.replace( + '
      ', + '
      ', + ).replace("
    ", "
    ", 1) + page.write_text(text, encoding="utf-8") + result = self.run_checker() + self.assertIn("generic metric/circuit container", result.stderr) + + +if __name__ == "__main__": + unittest.main()