From 24935f69399bf55d79af019a6e07f1f9078da461 Mon Sep 17 00:00:00 2001 From: Alwan Juliawan Date: Thu, 30 Jul 2026 14:57:07 +0700 Subject: [PATCH] docs: prepare v2.0.0rc3 release --- .github/workflows/release.yml | 16 +++++++------- CHANGELOG.md | 15 +++++++++++++ README.md | 4 ++-- comic_sol_product/__init__.py | 2 +- comic_sol_product/distribution.py | 2 +- compose.yaml | 2 +- docs/install.md | 22 +++++++++---------- docs/releases/v2.0.0rc3.md | 36 +++++++++++++++++++++++++++++++ pyproject.toml | 2 +- scripts/assemble_release.py | 2 +- tests/test_distribution.py | 24 ++++++++++----------- tests/test_product_cli.py | 2 +- tests/test_release_docs.py | 6 +++--- 13 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 docs/releases/v2.0.0rc3.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a944f6..f274e6a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Native Release on: workflow_dispatch: push: - tags: [ 'v2.0.0rc2' ] + tags: [ 'v2.0.0rc3' ] permissions: contents: read @@ -54,9 +54,9 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f - name: Build OCI image - run: docker build -t comic-sol:2.0.0rc2 . + run: docker build -t comic-sol:2.0.0rc3 . - name: Smoke OCI image - run: docker run --rm --entrypoint comic-sol comic-sol:2.0.0rc2 doctor --output-root /data/doctor + run: docker run --rm --entrypoint comic-sol comic-sol:2.0.0rc3 doctor --output-root /data/doctor source: runs-on: ubuntu-latest @@ -82,7 +82,7 @@ jobs: publish: needs: [native, container, source] runs-on: ubuntu-latest - if: github.ref == 'refs/tags/v2.0.0rc2' + if: github.ref == 'refs/tags/v2.0.0rc3' permissions: contents: write env: @@ -108,7 +108,7 @@ jobs: records = [] for platform in ('linux', 'macos', 'windows'): bundle = root / f'comic-sol-{platform}-x86_64' - verify_release_directory(bundle, ReleaseIdentity('2.0.0rc2', platform, 'x86_64')) + verify_release_directory(bundle, ReleaseIdentity('2.0.0rc3', platform, 'x86_64')) for path in sorted(bundle.iterdir()): if path.name == 'SHA256SUMS': continue @@ -121,6 +121,6 @@ jobs: - name: Publish immutable prerelease assets shell: bash run: | - gh release create v2.0.0rc2 --repo "${GITHUB_REPOSITORY}" --verify-tag --prerelease --title 'Comic Sol v2.0.0rc2' --notes-file docs/releases/v2.0.0rc2.md - find bundles -type f ! -name SHA256SUMS -print0 | xargs -0 gh release upload v2.0.0rc2 --repo "${GITHUB_REPOSITORY}" --clobber - gh release upload v2.0.0rc2 bundles/SHA256SUMS --repo "${GITHUB_REPOSITORY}" --clobber + gh release create v2.0.0rc3 --repo "${GITHUB_REPOSITORY}" --verify-tag --prerelease --title 'Comic Sol v2.0.0rc3' --notes-file docs/releases/v2.0.0rc3.md + find bundles -type f ! -name SHA256SUMS -print0 | xargs -0 gh release upload v2.0.0rc3 --repo "${GITHUB_REPOSITORY}" --clobber + gh release upload v2.0.0rc3 bundles/SHA256SUMS --repo "${GITHUB_REPOSITORY}" --clobber diff --git a/CHANGELOG.md b/CHANGELOG.md index c910457..540d7a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 2.0.0rc3 — 2026-07-30 + +Focused PDF fidelity prerelease. + +### Fixed + +- Encode composed page rasters at JPEG quality 95 before deterministic PDF publication. +- Preserve detailed, high-frequency comic artwork under the unchanged full-content verification thresholds. +- Add a deterministic regression reproducing the live Codex App dogfood failure. + +### Distribution notes + +- Linux, macOS, and Windows x86_64 archives remain available with wheel, sdist, installers, checksums, and CycloneDX SBOMs. +- Native artifacts remain **unsigned**; verify downloads against `SHA256SUMS`. + ## 2.0.0rc2 — 2026-07-30 Comic Quality release candidate. diff --git a/README.md b/README.md index 6bc079f..ae97150 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ The CLI currently exposes `doctor`, `init`, `status`, `validate`, `resume`, `finalize`, and the optional `mcp` launcher. Machine-readable responses use one stable envelope containing `ok`, `command`, `data`, and `error`. -## Native Distribution (`v2.0.0rc2`) +## Native Distribution (`v2.0.0rc3`) -The `v2.0.0rc2` prerelease provides bundled portable archives for Linux, macOS, +The `v2.0.0rc3` prerelease provides bundled portable archives for Linux, macOS, and Windows x86_64, plus transactional user-local installers and a non-root OCI image. Native archives include Python, Pillow, MCP, fonts, templates, the Skill, and references, so installed execution does not require a system Python. diff --git a/comic_sol_product/__init__.py b/comic_sol_product/__init__.py index cd6a6e8..6766f87 100644 --- a/comic_sol_product/__init__.py +++ b/comic_sol_product/__init__.py @@ -1,3 +1,3 @@ """Installed product surface for Comic Sol.""" -__version__ = "2.0.0rc2" +__version__ = "2.0.0rc3" diff --git a/comic_sol_product/distribution.py b/comic_sol_product/distribution.py index 0109f46..3d2515e 100644 --- a/comic_sol_product/distribution.py +++ b/comic_sol_product/distribution.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import Iterable -_VERSION = "2.0.0rc2" +_VERSION = "2.0.0rc3" _PLATFORMS = {"linux", "macos", "windows"} _ARCHITECTURES = {"x86_64", "arm64"} _SAFE_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") diff --git a/compose.yaml b/compose.yaml index f02e651..d41c338 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,7 +1,7 @@ services: comic-sol: build: . - image: comic-sol:2.0.0rc2 + image: comic-sol:2.0.0rc3 read_only: true command: ["mcp", "--root", "/data"] volumes: diff --git a/docs/install.md b/docs/install.md index dc8a72f..fef773a 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,6 +1,6 @@ -# Install Comic Sol v2.0.0rc2 +# Install Comic Sol v2.0.0rc3 -Comic Sol `v2.0.0rc2` is a prerelease distributed as native portable archives for Linux, macOS, and Windows, plus a Python wheel/source archive and an OCI image definition. Native archives bundle Python 3.11, Pillow, MCP, fonts, templates, the Skill, and references; no system Python is required after extraction. +Comic Sol `v2.0.0rc3` is a prerelease distributed as native portable archives for Linux, macOS, and Windows, plus a Python wheel/source archive and an OCI image definition. Native archives bundle Python 3.11, Pillow, MCP, fonts, templates, the Skill, and references; no system Python is required after extraction. ## Security status @@ -11,29 +11,29 @@ This release is **unsigned**. It is not Authenticode-signed, notarized, or GPG-s Download the matching ZIP and copy `installers/install.sh` from the same release or repository checkout. Read it before execution, then verify and install: ```bash -sha256sum comic-sol-2.0.0rc2-linux-x86_64.zip +sha256sum comic-sol-2.0.0rc3-linux-x86_64.zip # Compare the digest with SHA256SUMS. sh installers/install.sh \ - --archive ./comic-sol-2.0.0rc2-linux-x86_64.zip \ + --archive ./comic-sol-2.0.0rc3-linux-x86_64.zip \ --sha256 $HOME/.local/share/comic-sol/bin/comic-sol --version $HOME/.local/share/comic-sol/bin/comic-sol doctor ``` -For macOS, use `comic-sol-2.0.0rc2-macos-x86_64.zip`. The default installation root is `$HOME/.local/share/comic-sol`. Override it with `--install-root PATH` or `COMIC_SOL_INSTALL_ROOT`. +For macOS, use `comic-sol-2.0.0rc3-macos-x86_64.zip`. The default installation root is `$HOME/.local/share/comic-sol`. Override it with `--install-root PATH` or `COMIC_SOL_INSTALL_ROOT`. The POSIX installer requires `sha256sum`, `unzip`, and standard POSIX utilities. Native binaries are unsigned, so macOS Gatekeeper may require an explicit local approval for this prerelease. ## Windows PowerShell -Download `comic-sol-2.0.0rc2-windows-x86_64.zip` and copy `installers/install.ps1` from the same release or repository checkout, then run: +Download `comic-sol-2.0.0rc3-windows-x86_64.zip` and copy `installers/install.ps1` from the same release or repository checkout, then run: ```powershell -(Get-FileHash .\comic-sol-2.0.0rc2-windows-x86_64.zip -Algorithm SHA256).Hash +(Get-FileHash .\comic-sol-2.0.0rc3-windows-x86_64.zip -Algorithm SHA256).Hash # Compare the digest with SHA256SUMS. .\installers\install.ps1 ` - -Archive .\comic-sol-2.0.0rc2-windows-x86_64.zip ` + -Archive .\comic-sol-2.0.0rc3-windows-x86_64.zip ` -SHA256 & "$HOME\AppData\Local\ComicSol\bin\comic-sol.exe" --version @@ -80,8 +80,8 @@ Uninstall removes only the user-local runtime installation. Comic projects are p Build and run the non-root image from a checkout: ```bash -docker build -t comic-sol:2.0.0rc2 . -docker run --rm --entrypoint comic-sol comic-sol:2.0.0rc2 doctor --output-root /tmp/comic-sol-doctor +docker build -t comic-sol:2.0.0rc3 . +docker run --rm --entrypoint comic-sol comic-sol:2.0.0rc3 doctor --output-root /tmp/comic-sol-doctor docker compose up ``` @@ -91,7 +91,7 @@ The image runs as `comic-sol`, uses `/data` for persistent projects, and exposes Each platform bundle contains: -- `comic-sol-2.0.0rc2--x86_64.zip` +- `comic-sol-2.0.0rc3--x86_64.zip` - platform metadata declaring the unsigned state - a CycloneDX SBOM - `SHA256SUMS` diff --git a/docs/releases/v2.0.0rc3.md b/docs/releases/v2.0.0rc3.md new file mode 100644 index 0000000..3fb5804 --- /dev/null +++ b/docs/releases/v2.0.0rc3.md @@ -0,0 +1,36 @@ +# Comic Sol v2.0.0rc3 + +`v2.0.0rc3` is a focused Comic Quality **prerelease** that preserves detailed composed pages during PDF export. + +## Fixed + +- PDF page rasters are encoded at JPEG quality 95 before deterministic publication. +- Detailed, high-frequency comic artwork now remains within the existing full-content fidelity ceiling without relaxing any verification threshold. +- A deterministic high-frequency regression fixture protects the behavior. + +The issue was found during a live Codex App dogfood run. The same page measured a mean absolute channel error of 3.87 with the previous encoder default and 2.24 with the RC3 setting, below the unchanged 3.1 ceiling. The completed two-page output passed final validation with no warnings. + +## Quality and compatibility + +RC3 retains every RC2 Comic Quality guarantee: deterministic panel normalization, +typography preflight, the immutable `four-grid` layout, artifact-bound page QA, +full-content PDF verification, and the deterministic quality matrix. Matrix evidence +proves mechanics only and is not a substitute for visual review. + +- The exact 17-tool MCP surface is unchanged. +- Quality schema and project formats are unchanged. +- Full-page verification thresholds remain `3.1 / 0.021 / 16.0`. +- RC2 projects remain readable; no migration is required. + +## Distribution + +The prerelease includes: + +- Linux, macOS, and Windows x86_64 portable archives +- Python wheel and source archive +- transactional POSIX and PowerShell installers +- SHA-256 checksums +- CycloneDX SBOMs +- non-root OCI build definition + +Native archives and installers remain **unsigned**. Verify every downloaded artifact against `SHA256SUMS` before use. diff --git a/pyproject.toml b/pyproject.toml index 9d95c48..f95fa1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "comic-sol" -version = "2.0.0rc2" +version = "2.0.0rc3" description = "Local-first deterministic comic production pipeline" readme = "README.md" requires-python = ">=3.11" diff --git a/scripts/assemble_release.py b/scripts/assemble_release.py index c22b47d..5eb1e0f 100644 --- a/scripts/assemble_release.py +++ b/scripts/assemble_release.py @@ -29,7 +29,7 @@ def main() -> int: parser.add_argument("--architecture", default="x86_64", choices=("x86_64", "arm64")) args = parser.parse_args() - identity = ReleaseIdentity("2.0.0rc2", args.platform, args.architecture) + identity = ReleaseIdentity("2.0.0rc3", args.platform, args.architecture) output = args.output.resolve() output.mkdir(parents=True, exist_ok=True) archive = output / artifact_name(identity, "zip") diff --git a/tests/test_distribution.py b/tests/test_distribution.py index f7f2ef8..f981996 100644 --- a/tests/test_distribution.py +++ b/tests/test_distribution.py @@ -24,25 +24,25 @@ class NativeDistributionContractTests(unittest.TestCase): def setUp(self): self.identity = ReleaseIdentity( - version="2.0.0rc2", platform="linux", architecture="x86_64" + version="2.0.0rc3", platform="linux", architecture="x86_64" ) def test_identity_and_artifact_names_are_canonical(self): - self.assertEqual("v2.0.0rc2", self.identity.tag) + self.assertEqual("v2.0.0rc3", self.identity.tag) self.assertEqual( - "comic-sol-2.0.0rc2-linux-x86_64.tar.gz", + "comic-sol-2.0.0rc3-linux-x86_64.tar.gz", artifact_name(self.identity, "tar.gz"), ) with self.assertRaises(ValueError): ReleaseIdentity("2.0.0-rc2", "linux", "x86_64") with self.assertRaises(ValueError): - ReleaseIdentity("2.0.0rc2", "Linux", "amd64") + ReleaseIdentity("2.0.0rc3", "Linux", "amd64") def test_metadata_checksum_and_sbom_are_deterministic(self): with tempfile.TemporaryDirectory() as temporary_directory: release = Path(temporary_directory) first = release / artifact_name(self.identity, "tar.gz") - second = release / "comic-sol-2.0.0rc2-linux-x86_64.sbom.json" + second = release / "comic-sol-2.0.0rc3-linux-x86_64.sbom.json" first.write_bytes(b"portable-runtime") second.write_text("{}\n", encoding="utf-8") @@ -64,7 +64,7 @@ def test_metadata_checksum_and_sbom_are_deterministic(self): self.assertEqual("CycloneDX", sbom_record["bomFormat"]) self.assertEqual("1.6", sbom_record["specVersion"]) self.assertEqual("comic-sol", sbom_record["metadata"]["component"]["name"]) - self.assertEqual("2.0.0rc2", sbom_record["metadata"]["component"]["version"]) + self.assertEqual("2.0.0rc3", sbom_record["metadata"]["component"]["version"]) def test_verifier_rejects_missing_or_tampered_artifact(self): with tempfile.TemporaryDirectory() as temporary_directory: @@ -75,7 +75,7 @@ def test_verifier_rejects_missing_or_tampered_artifact(self): write_sbom(release, self.identity) write_checksums( release, - [artifact, release / "comic-sol-2.0.0rc2-linux-x86_64.sbom.json"], + [artifact, release / "comic-sol-2.0.0rc3-linux-x86_64.sbom.json"], ) verify_release_directory(release, self.identity) @@ -155,7 +155,7 @@ def test_container_and_release_workflow_are_hardened(self): self.assertIn("/data", compose) self.assertIn("workflow_dispatch:", workflow) - self.assertIn("tags: [ 'v2.0.0rc2' ]", workflow) + self.assertIn("tags: [ 'v2.0.0rc3' ]", workflow) for runner in ("ubuntu-latest", "macos-latest", "windows-latest"): self.assertIn(runner, workflow) self.assertIn("scripts/build_portable.py", workflow) @@ -169,9 +169,9 @@ def test_container_and_release_workflow_are_hardened(self): self.assertIn("installers/install.sh", workflow) self.assertIn("installers/install.ps1", workflow) self.assertIn("prerelease: true", workflow) - self.assertIn("if: github.ref == 'refs/tags/v2.0.0rc2'", workflow) - self.assertIn("comic-sol:2.0.0rc2", workflow) - self.assertIn("gh release create v2.0.0rc2", workflow) + self.assertIn("if: github.ref == 'refs/tags/v2.0.0rc3'", workflow) + self.assertIn("comic-sol:2.0.0rc3", workflow) + self.assertIn("gh release create v2.0.0rc3", workflow) self.assertNotIn("refs/tags/v2.0.0rc1", workflow) for line in workflow.splitlines(): if "uses:" in line: @@ -187,7 +187,7 @@ def test_rc2_version_sources_and_quality_runtime_are_consistent(self): compose = (root / "compose.yaml").read_text(encoding="utf-8") release_contract = (root / "comic_sol_product/release.py").read_text(encoding="utf-8") for source in (pyproject, package, distribution, assembler, compose): - self.assertIn("2.0.0rc2", source) + self.assertIn("2.0.0rc3", source) for module in ( "normalize_panels.py", "typography.py", "layouts.py", "page_quality.py", "pdf_quality.py", "quality_sample.py", diff --git a/tests/test_product_cli.py b/tests/test_product_cli.py index de99e14..4505eee 100644 --- a/tests/test_product_cli.py +++ b/tests/test_product_cli.py @@ -25,7 +25,7 @@ def test_version_flag_reports_release_identity(self): with self.assertRaises(SystemExit) as exit_context, redirect_stdout(stdout): cli.build_parser().parse_args(["--version"]) self.assertEqual(0, exit_context.exception.code) - self.assertEqual("comic-sol 2.0.0rc2\n", stdout.getvalue()) + self.assertEqual("comic-sol 2.0.0rc3\n", stdout.getvalue()) def test_default_output_roots_are_platform_native(self): home = Path("/users/example") diff --git a/tests/test_release_docs.py b/tests/test_release_docs.py index 7b7c6b6..6706340 100644 --- a/tests/test_release_docs.py +++ b/tests/test_release_docs.py @@ -9,11 +9,11 @@ def setUpClass(cls): cls.readme = (cls.root / "README.md").read_text(encoding="utf-8") cls.install = (cls.root / "docs/install.md").read_text(encoding="utf-8") cls.changelog = (cls.root / "CHANGELOG.md").read_text(encoding="utf-8") - cls.notes = (cls.root / "docs/releases/v2.0.0rc2.md").read_text(encoding="utf-8") + cls.notes = (cls.root / "docs/releases/v2.0.0rc3.md").read_text(encoding="utf-8") def test_readme_links_native_install_and_release_security(self): self.assertIn("docs/install.md", self.readme) - self.assertIn("v2.0.0rc2", self.readme) + self.assertIn("v2.0.0rc3", self.readme) self.assertIn("SHA256SUMS", self.readme) self.assertIn("unsigned", self.readme.lower()) self.assertIn("docker compose", self.install) @@ -42,7 +42,7 @@ def test_install_guide_covers_every_supported_lifecycle(self): def test_release_notes_and_changelog_identify_rc_limitations(self): for document in (self.changelog, self.notes): - self.assertIn("2.0.0rc2", document) + self.assertIn("2.0.0rc3", document) self.assertIn("unsigned", document.lower()) self.assertIn("Linux", document) self.assertIn("macOS", document)