diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..4c8ffbc --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,20 @@ +name: validate + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + repository-contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Run repository contract tests + run: python3 -m unittest discover -s tests -v + - name: Compile tests + run: python3 -m compileall -q tests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43ae0e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.py[cod] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c7bc161 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,49 @@ +# Plotter Image Animations agent guide + +This repository is a Published Media Snapshot containing three checked-in MP4 +exports. Read `PROJECT.md` and `CONTEXT.md` before making changes. + +## Boundaries + +- Preserve every checked-in MP4 byte-for-byte unless a reviewed change + explicitly replaces the published snapshot. +- Do not recompress, rename, regenerate, or remove media without identified + generating source, provenance, and publication-rights review. +- Do not claim that the exports are reproducible while the source project is + unresolved. +- Keep credentials, private machine paths, and raw secret-scan findings out of + committed files. +- Tests must use the Python standard library and repository fixtures. They must + not publish media, register services, or mutate external systems. + +## Commands + +```bash +python3 -m unittest discover -s tests -v +python3 -m compileall -q tests +gitleaks git --no-banner --redact +``` + +## Validation + +Run the complete unit suite and compile the Python tests before claiming the +repository contract works. Verify media size and SHA-256 values rather than +trusting filenames or visual similarity. + +## Agent skills + +### Issue tracker + +Issues and follow-up custody work live in GitHub Issues for +`ReidSurmeier/plotter-image-animations`. See +`docs/agents/issue-tracker.md`. + +### Triage labels + +Use the five standard Matt Pocock triage roles. See +`docs/agents/triage-labels.md`. + +### Domain docs + +This is a single-context repository with `CONTEXT.md` at the root and decisions +in `docs/adr/`. See `docs/agents/domain.md`. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..df5170f --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,59 @@ +# Plotter Image Animations context + +## Purpose + +Preserve the known public animation exports and make their custody, +reproducibility, publication, and deployment boundaries explicit. + +## Domain language + +### Published Media Snapshot + +A fixed set of rendered files preserved as public evidence. A snapshot can be +validated byte-for-byte but is not automatically reproducible. + +### Animation Export + +One final MP4 in `animations/`. It is an immutable member of the current +Published Media Snapshot. + +### Mark-build + +The progressive appearance of plotter-like marks in the left panel of each +Animation Export. + +### Plate/Contact Sheet + +The static right-hand panel showing the component marks or printing plates that +correspond to the animation. + +### Generating Source + +The project files, source imagery, transforms, scripts, dependencies, and +export settings needed to reproduce an Animation Export. The Generating Source +for this snapshot has not been located. + +### Publication Rights + +The documented authority to distribute the source imagery and derived +Animation Exports. Public repository visibility is not proof of Publication +Rights. + +### Deployment Owner + +The repository responsible for an active service, DNS route, Pages site, or +other runtime. This snapshot has no Deployment Owner role because it has no +runtime or deployment. + +## Invariants + +- The three Animation Exports retain their recorded byte sizes and SHA-256 + hashes. +- Missing Generating Source is stated as an unresolved custody fact. +- Publication Rights are reviewed by a human before expanding distribution. +- No runtime, Pages, Droplet, or Pugnet ownership is inferred from the presence + of public media. + +## Decision records + +- `docs/adr/0001-treat-mp4s-as-published-media-evidence.md` diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..b85c546 --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,72 @@ +# Project packet + +## Classification + +`plotter-image-animations` is a Published Media Snapshot. It preserves public +rendered evidence; it is not a runnable application or a reproducible media +pipeline. + +Verified on 2026-07-31 against the original `main` commit +`348af448e71d53ab94a4aa6b4b6d4ad8b2204057`. + +## Verified contents + +The repository contains three silent 25-second MP4 exports. All are 1920x1080 +H.264 video with a white background, a progressive mark-build animation on the +left, and a plate/contact sheet on the right. + +| Export | Frame rate | Bytes | SHA-256 | +| --- | ---: | ---: | --- | +| Portrait | 30 fps | 8,286,219 | `d0bb5232b30e8e1ecc57ff5229b1e5fd7a5746ce99d8d61da2fee447c4df040f` | +| Bathroom | 25 fps | 3,097,091 | `a7bd0d91aface1ccf123401b1f10a253e24e26fa2b0d34811036c78268ba0676` | +| Fashion | 30 fps | 9,713,253 | `fe4c716b0d7dd629b10a35f9ab874ee78413bcdce91557263f13b97f27c19a24` | + +The files are ordinary Git blobs, not Git LFS pointers. Their hashes and sizes +are enforced by the repository contract test. + +## Custody and reproducibility boundary + +The generating source remains unresolved. A local inventory found no duplicate +originals or project files that could regenerate these exact exports. The +repository also lacks a source-image provenance record and an explicit +publication-rights review. + +Until that evidence is recovered: + +- preserve the three MP4s byte-for-byte; +- do not claim the exports are reproducible; +- do not infer licenses or publication rights from public Git visibility; and +- record newly recovered source as additive evidence before proposing changes + to the snapshot. + +## Runtime and deployment ownership + +There is no runtime. GitHub Pages is absent, the GitHub deployments list is +empty, and the repository owns no Droplet or Pugnet process, container, systemd +unit, DNS name, or public endpoint. + +The public GitHub repository itself is the current delivery surface for these +files. A Release or archival publication surface may be considered only after +source provenance and publication rights are resolved. + +## Validation + +```bash +python3 -m unittest discover -s tests -v +python3 -m compileall -q tests +``` + +The test suite checks the documentation contract and the exact published media +bytes. Secret scanning and GitHub Actions provide additional repository gates. + +## Next decisions + +1. [Issue 1](https://github.com/ReidSurmeier/plotter-image-animations/issues/1): + locate and identify the generating project, source imagery, and export + process. +2. [Issue 2](https://github.com/ReidSurmeier/plotter-image-animations/issues/2): + complete a human publication-rights review for the portrait, bathroom, and + fashion imagery. +3. [Issue 3](https://github.com/ReidSurmeier/plotter-image-animations/issues/3): + decide whether GitHub Releases or another durable media surface should + supplement raw repository delivery after provenance is established. diff --git a/README.md b/README.md index 33fa7ff..4e73695 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,42 @@ # Plotter Image Animations -Public MP4 exports for three plotter mark-build image tests. +Status: **Published Media Snapshot** -## Files +This repository preserves three public MP4 exports from plotter mark-build +image tests. It does not contain the generating source, source images, or a +reproducible export pipeline. Treat the checked-in media as published evidence, +not as build output that can currently be regenerated. -- `animations/01-portrait-markbuild-full-contact-25s.mp4` -- `animations/02-bathroom-svg-reveal-25s.mp4` -- `animations/03-fashion-markbuild-full-contact-25s.mp4` +Deployment ownership: none. The repository is not an application, has no +GitHub Pages site, and owns no Droplet or Pugnet service. -Each file is a 1920x1080 white-background layout with the animation on the left and contact/plate sheet on the right. +## Published media + +| File | Bytes | Video | SHA-256 | +| --- | ---: | --- | --- | +| `animations/01-portrait-markbuild-full-contact-25s.mp4` | 8,286,219 | 25 s, 1920x1080, H.264, 30 fps, silent | `d0bb5232b30e8e1ecc57ff5229b1e5fd7a5746ce99d8d61da2fee447c4df040f` | +| `animations/02-bathroom-svg-reveal-25s.mp4` | 3,097,091 | 25 s, 1920x1080, H.264, 25 fps, silent | `a7bd0d91aface1ccf123401b1f10a253e24e26fa2b0d34811036c78268ba0676` | +| `animations/03-fashion-markbuild-full-contact-25s.mp4` | 9,713,253 | 25 s, 1920x1080, H.264, 30 fps, silent | `fe4c716b0d7dd629b10a35f9ab874ee78413bcdce91557263f13b97f27c19a24` | + +Each export uses a white 1920x1080 layout: a progressive mark build appears on +the left and a plate/contact sheet appears on the right. The subjects are a +portrait, a bathroom scene, and a fashion image. + +Do not recompress, rename, or replace these files until the generating source +and publication rights have been identified. Byte-level tests intentionally +fail if the published snapshot changes. + +## Repository guide + +- [PROJECT.md](PROJECT.md) records the verified state, ownership boundary, and + unresolved custody work. +- [CONTEXT.md](CONTEXT.md) defines the repository's domain language. +- [AGENTS.md](AGENTS.md) gives contributor and validation instructions. +- [`docs/adr/`](docs/adr/) records durable decisions. + +## Validate + +```bash +python3 -m unittest discover -s tests -v +python3 -m compileall -q tests +``` diff --git a/context.toml b/context.toml new file mode 100644 index 0000000..acb07fa --- /dev/null +++ b/context.toml @@ -0,0 +1,24 @@ +schema_version = 1 +project = "plotter-image-animations" +classification = "published-media-snapshot" +context_document = "CONTEXT.md" +project_document = "PROJECT.md" +decision_directory = "docs/adr" +issue_tracker = "github" +deployment_ownership = "none" +reproducible = false + +[[artifacts]] +path = "animations/01-portrait-markbuild-full-contact-25s.mp4" +bytes = 8286219 +sha256 = "d0bb5232b30e8e1ecc57ff5229b1e5fd7a5746ce99d8d61da2fee447c4df040f" + +[[artifacts]] +path = "animations/02-bathroom-svg-reveal-25s.mp4" +bytes = 3097091 +sha256 = "a7bd0d91aface1ccf123401b1f10a253e24e26fa2b0d34811036c78268ba0676" + +[[artifacts]] +path = "animations/03-fashion-markbuild-full-contact-25s.mp4" +bytes = 9713253 +sha256 = "fe4c716b0d7dd629b10a35f9ab874ee78413bcdce91557263f13b97f27c19a24" diff --git a/docs/adr/0001-treat-mp4s-as-published-media-evidence.md b/docs/adr/0001-treat-mp4s-as-published-media-evidence.md new file mode 100644 index 0000000..e1b2127 --- /dev/null +++ b/docs/adr/0001-treat-mp4s-as-published-media-evidence.md @@ -0,0 +1,34 @@ +# ADR 0001: Treat MP4s as published media evidence + +- Status: Accepted +- Date: 2026-07-31 + +## Context + +The repository contains three public MP4 exports but no generating project, +source-image provenance, dependency record, or export instructions. The files +can be inspected and verified byte-for-byte, but they cannot currently be +regenerated from repository contents. + +Replacing or recompressing them would destroy the only local evidence of the +published outputs. Conversely, describing them as reproducible would overstate +what the repository proves. + +## Decision + +Treat the three MP4s as an immutable Published Media Snapshot: + +- record and test exact byte sizes and SHA-256 hashes; +- preserve them without transformation; +- state that Generating Source and Publication Rights remain unresolved; and +- keep runtime and deployment ownership explicitly set to none. + +Recovered source or rights evidence must be added and reviewed before changing +the snapshot or expanding its delivery surface. + +## Consequences + +The repository has a meaningful validation gate despite having no application +runtime. Accidental binary replacement fails tests. Future contributors can +distinguish published evidence from reproducible source, while follow-up work +remains visible rather than being guessed into the documentation. diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 0000000..e945c8b --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,8 @@ +# Domain documentation + +The canonical domain model is `CONTEXT.md`. Use its terms in issues, commits, +and decision records. + +Changes to the meaning of Published Media Snapshot, Animation Export, +Generating Source, Publication Rights, or Deployment Owner require an ADR in +`docs/adr/`. diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 0000000..885437c --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,8 @@ +# Issue tracker + +This repository uses GitHub Issues in +`ReidSurmeier/plotter-image-animations`. + +Issues should identify the verified evidence, the unresolved custody or +publication question, and the acceptance criteria. Never paste credentials, +private paths, or raw secret-scan findings into an issue. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 0000000..fd25421 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,13 @@ +# Triage labels + +Use one of these role labels when work is ready to enter the corresponding +state: + +- `needs-triage`: the issue needs classification or reproduction. +- `needs-info`: additional evidence or a decision is required. +- `ready-for-agent`: bounded implementation or research can proceed. +- `ready-for-human`: judgment, authorship, rights, or visual review is needed. +- `wontfix`: the issue is intentionally declined with a recorded reason. + +Move an issue between roles as its evidence and ownership change. Avoid using +multiple role labels at once. diff --git a/tests/test_repository_contract.py b/tests/test_repository_contract.py new file mode 100644 index 0000000..a5396a4 --- /dev/null +++ b/tests/test_repository_contract.py @@ -0,0 +1,98 @@ +import hashlib +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + +MEDIA = { + "animations/01-portrait-markbuild-full-contact-25s.mp4": ( + 8_286_219, + "d0bb5232b30e8e1ecc57ff5229b1e5fd7a5746ce99d8d61da2fee447c4df040f", + ), + "animations/02-bathroom-svg-reveal-25s.mp4": ( + 3_097_091, + "a7bd0d91aface1ccf123401b1f10a253e24e26fa2b0d34811036c78268ba0676", + ), + "animations/03-fashion-markbuild-full-contact-25s.mp4": ( + 9_713_253, + "fe4c716b0d7dd629b10a35f9ab874ee78413bcdce91557263f13b97f27c19a24", + ), +} + + +def read(relative_path: str) -> str: + return (ROOT / relative_path).read_text(encoding="utf-8") + + +class RepositoryContractTests(unittest.TestCase): + def test_project_documentation_contract_exists(self) -> None: + required = ( + "README.md", + "PROJECT.md", + "AGENTS.md", + "CONTEXT.md", + "context.toml", + "docs/adr/0001-treat-mp4s-as-published-media-evidence.md", + "docs/agents/domain.md", + "docs/agents/issue-tracker.md", + "docs/agents/triage-labels.md", + ".github/workflows/validate.yml", + ) + + self.assertEqual( + [path for path in required if not (ROOT / path).is_file()], + [], + ) + + def test_readme_states_the_media_snapshot_boundary(self) -> None: + text = read("README.md") + + self.assertIn("Published Media Snapshot", text) + self.assertIn("does not contain the generating source", text) + self.assertIn("Deployment ownership: none", text) + self.assertIn("[PROJECT.md](PROJECT.md)", text) + self.assertIn("[CONTEXT.md](CONTEXT.md)", text) + + def test_media_bytes_match_the_published_snapshot(self) -> None: + for relative_path, (expected_size, expected_sha256) in MEDIA.items(): + path = ROOT / relative_path + digest = hashlib.sha256() + with path.open("rb") as handle: + while chunk := handle.read(1024 * 1024): + digest.update(chunk) + + self.assertEqual(path.stat().st_size, expected_size, relative_path) + self.assertEqual(digest.hexdigest(), expected_sha256, relative_path) + self.assertEqual(path.read_bytes()[4:8], b"ftyp", relative_path) + + def test_project_packet_records_verified_media_and_unknown_provenance(self) -> None: + text = read("PROJECT.md") + + self.assertIn("348af448e71d53ab94a4aa6b4b6d4ad8b2204057", text) + self.assertIn("three silent 25-second", text) + self.assertIn("1920x1080", text) + self.assertIn("H.264", text) + self.assertIn("generating source remains unresolved", text) + self.assertIn("GitHub Pages is absent", text) + self.assertIn("issues/1", text) + self.assertIn("issues/2", text) + self.assertIn("issues/3", text) + + def test_active_contracts_do_not_embed_private_machine_paths(self) -> None: + private_home = "/" + "home/reidsurmeier" + paths = ( + "README.md", + "PROJECT.md", + "AGENTS.md", + "CONTEXT.md", + "context.toml", + ".github/workflows/validate.yml", + ) + + for relative_path in paths: + self.assertNotIn(private_home, read(relative_path), relative_path) + + +if __name__ == "__main__": + unittest.main()