diff --git a/.github/workflows/capture-screenshots.yml b/.github/workflows/capture-screenshots.yml index 91ecbfc1..f5d01f87 100644 --- a/.github/workflows/capture-screenshots.yml +++ b/.github/workflows/capture-screenshots.yml @@ -15,7 +15,11 @@ on: - src/** - data/** - tools/screenshots/** + - tools/news/** + - news/** + - archive/development/** - .github/workflows/capture-screenshots.yml + - .github/workflows/prepare-release-news.yml permissions: contents: read diff --git a/.github/workflows/prepare-release-news.yml b/.github/workflows/prepare-release-news.yml index 06bf050c..ad2a1b0d 100644 --- a/.github/workflows/prepare-release-news.yml +++ b/.github/workflows/prepare-release-news.yml @@ -130,6 +130,8 @@ jobs: root / manifest['patchnote'], root / manifest['image'], manifest_path, + release_dir / 'README.md', + release_dir / 'SHA256SUMS.internal.txt', release_dir / 'historical', ] @@ -151,6 +153,26 @@ jobs: print(f'Created {archive_path.relative_to(root)} with {len(seen)} files') PY + - name: Create SHA-256 release asset record + if: steps.marker.outputs.exists == 'true' + shell: bash + run: | + python3 - <<'PY' + import hashlib + import json + import pathlib + + root = pathlib.Path('.').resolve() + release_dir = root / 'archive' / 'development' / '${{ env.RELEASE_VERSION }}' + manifest = json.loads((release_dir / 'release.json').read_text(encoding='utf-8')) + assets = [root / manifest['image'], root / manifest['archive']] + lines = [] + for asset in assets: + digest = hashlib.sha256(asset.read_bytes()).hexdigest() + lines.append(f"{digest} {asset.relative_to(root).as_posix()}") + (release_dir / 'SHA256SUMS.txt').write_text('\n'.join(lines) + '\n', encoding='utf-8') + PY + - name: Commit news and permanent development archive if: steps.marker.outputs.exists == 'true' shell: bash diff --git a/archive/development/0.7.0-alpha.5/README.md b/archive/development/0.7.0-alpha.5/README.md new file mode 100644 index 00000000..c165b4a9 --- /dev/null +++ b/archive/development/0.7.0-alpha.5/README.md @@ -0,0 +1,69 @@ +# uImposition 0.7.0-alpha.5 evidence + +Generated: 2026-07-27T04:18:53Z + +This permanent archive records the M7.5 release checkpoint and its focused browser evidence. + +## Exact release boundary + +- Functional M7.5 baseline: `009451cce94d5cde05ee72305f30447aa65a646c` +- Version checkpoint release commit: `195d6496a291095a69cc9089a64154561ffbb1fa` +- Focused screenshot source head: `a8db529c29e7b71a7809bb5f857d48cfde115597` +- Version PR: `#49` +- Version: `0.7.0-alpha.5` +- Recovery branch: `release/v0.7.0-alpha.5` +- Immutable tag: `v0.7.0-alpha.5` + +The recovery branch and immutable tag must point to the version checkpoint commit. The later publication merge contains only release tooling, patchnote, image, manifest, hashes, and evidence. + +## Confirmed M7.5 scope + +- verified user production-plan catalog inside one shared product format, uniform grids, fitting 0°/90°, paper-minimum and dedicated-pair strategies, separate front/back forms, one shared duplex color specification, and complete page pairs; +- all feasible variants inside that documented scope stay in the catalog; +- filters change only the view; +- recommendation and explicit operator selection remain separate; +- selected-plan details and scheme/report PDF export; +- objective priority reranking reuses validated plans without rebuilding geometry. + +The checkpoint does not claim a globally complete solver, a lower-bound proof outside explicitly proven cases, or compatibility with a particular press. + +## Production invariants + +- underproduction is forbidden; +- back layouts derive only from a validated front; +- missing cost never becomes zero; +- layout forms and color plates remain separate; +- recommendations never replace the operator's choice; +- fixtures and bounded searches are not described as automatic global solvers; +- geometric symmetry does not prove machine compatibility. + +## Exact checks + +- Quality run: `30236119913` — 173/173 tests, 0 failures; +- Quality artifact: `8641678651`, `uimposition-quality-49-1`; +- Quality digest: `sha256:7570c0a0c529d9994ea416d28cfefdbcfd688c8701fed09213b9c12642315f27`; +- Full Chromium/PDF run: `30236119914` — 16/16 scenarios; +- Full Chromium/PDF artifact: `8641691735`, `uimposition-screenshots-49-1`; +- Full Chromium/PDF digest: `sha256:1cf047532caf18e7d49a6464a8f4b339dcc2ea15c37eb6f6ca0581491b0d33df`; +- scheme PDF: 8 A4 pages, verified by pdfinfo and rendered with Poppler; +- production report PDF: 6 A4 pages, verified by pdfinfo and rendered with Poppler; +- focused evidence workflow run: `30236703966`; +- focused image captured at: `2026-07-27T04:18:46.632Z`. + +The two exact-head Action archives are preserved under `historical/`. + +## Focused screenshot + +- Scenario: `m7-objective-priority-editor` +- Original: `uimposition-m7-5-objective-priority-editor.png` +- Release image: `news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png` +- Viewport: 1440 × 1180 +- Selector: `#userObjectivePriorityEditor` + +Desktop and mobile focused PNGs were visually reviewed. Text is readable without clipping or overflow; estimated cost is first; the panel reports four reused plans and zero regenerated plans; `uniform-r90-dedicated-pairs` remains a recommendation rather than an operator selection. No secrets or private data are visible. + +## Integrity + +- `SHA256SUMS.internal.txt` records the PNG, patchnote, release manifest, and this README inside the ZIP; +- `SHA256SUMS.txt` records the release PNG and the final evidence ZIP; +- the ZIP includes the six focused M7.5 Chromium scenarios, capture log and NDJSON manifest, patchnote, release manifest, README, internal hashes, and both historical exact-head Action archives. diff --git a/archive/development/0.7.0-alpha.5/SHA256SUMS.internal.txt b/archive/development/0.7.0-alpha.5/SHA256SUMS.internal.txt new file mode 100644 index 00000000..45d8b4b0 --- /dev/null +++ b/archive/development/0.7.0-alpha.5/SHA256SUMS.internal.txt @@ -0,0 +1,4 @@ +bb5686efa1d0f75990885b6f5e8736c1c4c93233eae181c3ae68f81397cf5a6e news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png +6113f35f62ee5ab03b85094ba8582895a82d98e49cbf4f9374906e91d8f01126 news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.md +9dd91f438cbc8485b6fe5844ce44f354d2b5edf7cc3d2219052569bf9aa4f105 archive/development/0.7.0-alpha.5/release.json +4c1d9d85c4af7cae0a01d81ff72bfd929b5cfed2c4a5b1bbf49c5dac8c6ff5b5 archive/development/0.7.0-alpha.5/README.md diff --git a/archive/development/0.7.0-alpha.5/SHA256SUMS.txt b/archive/development/0.7.0-alpha.5/SHA256SUMS.txt new file mode 100644 index 00000000..7048d8aa --- /dev/null +++ b/archive/development/0.7.0-alpha.5/SHA256SUMS.txt @@ -0,0 +1,2 @@ +bb5686efa1d0f75990885b6f5e8736c1c4c93233eae181c3ae68f81397cf5a6e news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png +9ac0cfb4c01c34e530b4f734e499e6184cf2d6ab78f2157ea1216e6ee5742ec9 archive/development/0.7.0-alpha.5/uimposition-v0-7-0-alpha-5-evidence.zip diff --git a/archive/development/0.7.0-alpha.5/historical/uimposition-quality-49-1.zip b/archive/development/0.7.0-alpha.5/historical/uimposition-quality-49-1.zip new file mode 100644 index 00000000..62ac16f2 Binary files /dev/null and b/archive/development/0.7.0-alpha.5/historical/uimposition-quality-49-1.zip differ diff --git a/archive/development/0.7.0-alpha.5/historical/uimposition-screenshots-49-1.zip b/archive/development/0.7.0-alpha.5/historical/uimposition-screenshots-49-1.zip new file mode 100644 index 00000000..fec9f02d Binary files /dev/null and b/archive/development/0.7.0-alpha.5/historical/uimposition-screenshots-49-1.zip differ diff --git a/archive/development/0.7.0-alpha.5/release.json b/archive/development/0.7.0-alpha.5/release.json new file mode 100644 index 00000000..26ba1e03 --- /dev/null +++ b/archive/development/0.7.0-alpha.5/release.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "project": "uImposition", + "version": "0.7.0-alpha.5", + "tag": "v0.7.0-alpha.5", + "title": "uImposition v0.7.0-alpha.5", + "prerelease": true, + "sourceCommit": "a8db529c29e7b71a7809bb5f857d48cfde115597", + "releaseCommit": "195d6496a291095a69cc9089a64154561ffbb1fa", + "createdAt": "2026-07-27T04:18:53Z", + "patchnote": "news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.md", + "image": "news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png", + "archive": "archive/development/0.7.0-alpha.5/uimposition-v0-7-0-alpha-5-evidence.zip", + "screenshotScenario": "m7-objective-priority-editor", + "archiveScenarios": [ + "m7-objective-priority-editor", + "m7-objective-priority-editor-mobile", + "m7-user-order-plan-catalog", + "m7-user-order-plan-catalog-mobile", + "m7-user-selected-plan-details", + "m7-user-selected-plan-details-mobile" + ], + "historicalArtifacts": [ + { + "artifactId": 8641678651, + "fileName": "uimposition-quality-49-1.zip" + }, + { + "artifactId": 8641691735, + "fileName": "uimposition-screenshots-49-1.zip" + } + ], + "telegramText": "uImposition 0.7.0-alpha.5: M7.5 выпускает проверенные производственные планы для пользовательского заказа. Внутри текущей ограниченной области каталог сохраняет все допустимые варианты; фильтры меняют только представление. Оператор выбирает план явно и выгружает схемы/отчёт PDF. Приоритеты переоценивают 4 готовых плана без перестроения геометрии; рекомендация не заменяет выбор." +} diff --git a/archive/development/0.7.0-alpha.5/uimposition-v0-7-0-alpha-5-evidence.zip b/archive/development/0.7.0-alpha.5/uimposition-v0-7-0-alpha-5-evidence.zip new file mode 100644 index 00000000..ed7505ed Binary files /dev/null and b/archive/development/0.7.0-alpha.5/uimposition-v0-7-0-alpha-5-evidence.zip differ diff --git a/news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.md b/news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.md new file mode 100644 index 00000000..3effde9e --- /dev/null +++ b/news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.md @@ -0,0 +1,40 @@ +--- +type: release +project: uImposition +series: uimposition +title: uImposition 0.7.0-alpha.5: проверенные производственные планы +version: 0.7.0-alpha.5 +queued_at: 2026-07-27T04:18:53Z +repo_url: https://github.com/sunpole/uImposition +web_url: https://sunpole.github.io/uImposition/ +image: 2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png +image_text: Редактор приоритетов M7.5: расчётная стоимость поставлена первой; 4 готовых плана переоценены без перестроения геометрии; рекомендация показана отдельно от выбора оператора. +image_source: playwright +image_target: scenario/m7-objective-priority-editor +image_commit: a8db529c29e7b71a7809bb5f857d48cfde115597 +image_captured_at: 2026-07-27T04:18:46.632Z +--- + +# uImposition 0.7.0-alpha.5: проверенные производственные планы + +M7.5 выводит проверенные варианты производства для пользовательского заказа, сохраняет полный допустимый каталог внутри явно ограниченной области, позволяет оператору выбрать план и выгрузить схемы и отчёт PDF. Приоритеты меняют рекомендацию без перестроения геометрии. + +Что добавлено: + +- полный каталог допустимых планов внутри текущей ограниченной области, без удаления вариантов фильтрами; +- отдельные рекомендация и явный выбор оператора; +- детали выбранного плана, схемы и производственный отчёт PDF; +- редактор приоритетов целей с переоценкой уже проверенного каталога без регенерации геометрии. + +Проверенный контрольный результат: + +- в focused-сценарии доступно 4 проверенных плана; +- при приоритете стоимости повторно использовано 4 плана и построено заново 0; +- рекомендован uniform-r90-dedicated-pairs; операторский выбор остаётся независимым; +- недопечатка запрещена; отсутствие стоимости не превращается в ноль. + +Следующий самостоятельный этап — M7.6 / 0.7.0-alpha.6. Он не входит в этот релиз; текущий поиск нельзя считать глобально полным вне задокументированной области. + +Короткий текст для Telegram: + +uImposition 0.7.0-alpha.5: M7.5 выпускает проверенные производственные планы для пользовательского заказа. Внутри текущей ограниченной области каталог сохраняет все допустимые варианты; фильтры меняют только представление. Оператор выбирает план явно и выгружает схемы/отчёт PDF. Приоритеты переоценивают 4 готовых плана без перестроения геометрии; рекомендация не заменяет выбор. diff --git a/news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png b/news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png new file mode 100644 index 00000000..60ec52cd Binary files /dev/null and b/news/2026-07-27-uimposition-v0-7-0-alpha-5-user-production-plans.png differ diff --git a/tools/news/prepare-release.mjs b/tools/news/prepare-release.mjs index 7e060080..ec10d80b 100644 --- a/tools/news/prepare-release.mjs +++ b/tools/news/prepare-release.mjs @@ -1,3 +1,4 @@ +import { createHash } from "node:crypto"; import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -5,15 +6,20 @@ import { fileURLToPath } from "node:url"; const here = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(here, "../.."); const markerPath = path.join(root, "news/.prepare-release.json"); -const manifestPath = path.join(root, "artifacts/screenshots/manifest.json"); +const manifestPath = path.join(root, "artifacts/screenshots/manifest.ndjson"); const marker = JSON.parse(await readFile(markerPath, "utf8")); -const manifest = JSON.parse(await readFile(manifestPath, "utf8")); -const entry = manifest.entries.find((item) => item.scenario === marker.scenario); +const manifestEntries = (await readFile(manifestPath, "utf8")) + .split(/\r?\n/) + .filter(Boolean) + .map((line) => JSON.parse(line)); +const entry = manifestEntries.find((item) => item.scenario === marker.scenario); const expectedCommit = process.env.SCREENSHOT_COMMIT || process.env.GITHUB_SHA; +const releaseCommit = String(marker.releaseCommit ?? ""); if (!entry) throw new Error(`Screenshot scenario not found: ${marker.scenario}`); if (!expectedCommit) throw new Error("Expected screenshot commit is not available"); +if (!/^[0-9a-f]{40}$/i.test(releaseCommit)) throw new Error("releaseCommit must be a full 40-character commit SHA"); if (!entry.commit || entry.commit !== expectedCommit) { throw new Error(`Screenshot commit ${entry.commit} does not match expected commit ${expectedCommit}`); } @@ -30,6 +36,8 @@ const targetPatchnote = path.join(root, "news", patchnoteName); const releaseDirectory = path.join(root, "archive", "development", marker.version); const archiveName = `uimposition-v${safeVersion}-evidence.zip`; const releaseManifestPath = path.join(releaseDirectory, "release.json"); +const evidenceReadmePath = path.join(releaseDirectory, "README.md"); +const internalSumsPath = path.join(releaseDirectory, "SHA256SUMS.internal.txt"); function bulletList(items) { return items.map((item) => `- ${item};`).join("\n").replace(/;$/, "."); @@ -88,6 +96,7 @@ const releaseManifest = { title: `uImposition v${marker.version}`, prerelease, sourceCommit: entry.commit, + releaseCommit, createdAt: queuedAt, patchnote: path.relative(root, targetPatchnote).replaceAll("\\", "/"), image: path.relative(root, targetImage).replaceAll("\\", "/"), @@ -101,6 +110,26 @@ const releaseManifest = { }; await writeFile(releaseManifestPath, `${JSON.stringify(releaseManifest, null, 2)}\n`, "utf8"); + +const evidenceReadmeTemplate = String(marker.evidenceReadme ?? ""); +if (!evidenceReadmeTemplate.trim()) throw new Error("evidenceReadme is required"); +const evidenceReadme = evidenceReadmeTemplate + .replaceAll("{{SOURCE_COMMIT}}", entry.commit) + .replaceAll("{{RELEASE_COMMIT}}", releaseCommit) + .replaceAll("{{QUEUED_AT}}", queuedAt) + .replaceAll("{{CAPTURED_AT}}", entry.capturedAt) + .replaceAll("{{PREPARE_RUN_ID}}", String(process.env.GITHUB_RUN_ID ?? "local")); +await writeFile(evidenceReadmePath, evidenceReadme.endsWith("\n") ? evidenceReadme : `${evidenceReadme}\n`, "utf8"); + +async function sha256(filePath) { + return createHash("sha256").update(await readFile(filePath)).digest("hex"); +} +const internalInputs = [targetImage, targetPatchnote, releaseManifestPath, evidenceReadmePath]; +const internalSums = []; +for (const filePath of internalInputs) { + internalSums.push(`${await sha256(filePath)} ${path.relative(root, filePath).replaceAll("\\\\", "/")}`); +} +await writeFile(internalSumsPath, `${internalSums.join("\n")}\n`, "utf8"); await rm(markerPath); console.log(JSON.stringify({ imageName, @@ -108,5 +137,7 @@ console.log(JSON.stringify({ queuedAt, imageCommit: entry.commit, releaseManifest: path.relative(root, releaseManifestPath), + evidenceReadme: path.relative(root, evidenceReadmePath), + internalSums: path.relative(root, internalSumsPath), archive: releaseManifest.archive, }, null, 2));