diff --git a/changes/cc-g00-legacy-reference-reconcile.evidence.yaml b/changes/cc-g00-legacy-reference-reconcile.evidence.yaml new file mode 100644 index 000000000..6c04b5c51 --- /dev/null +++ b/changes/cc-g00-legacy-reference-reconcile.evidence.yaml @@ -0,0 +1,11 @@ +format_version: 1 +kind: evidence +claims: + - id: overlay-records-separate-live-and-legacy-issues + evidence: + - unit:scripts/ci/test_preflight_check_catalog.py + - architecture:scripts/generate-architecture-catalogs.py + - id: backlog-rows-point-at-live-trackers + evidence: + - architecture:scripts/generate-architecture-catalogs.py +unresolved: [] diff --git a/changes/cc-g00-legacy-reference-reconcile.md b/changes/cc-g00-legacy-reference-reconcile.md new file mode 100644 index 000000000..69a7a4009 --- /dev/null +++ b/changes/cc-g00-legacy-reference-reconcile.md @@ -0,0 +1,4 @@ +Category: internal +Audience: developers +Breaking-Change: no +Summary: Separate live studio-berry/loop issue records from frozen legacy ones in the preflight catalog overlay (#111) — github_issues entries now carry `repository`, legacy snapshots are keyed `legacy#` and can never be an open row's `closed_by`, the generator adds an opt-in `--verify-github` read-back that rejects a missing issue, a pull request, or a changed title, state, or milestone, sixteen open backlog rows re-point to the reset issues filed for them, and 25 of the 28 legacy issue and pull-request links under docs/ become `legacy #` text (the three in docs/GOVERNED_EXECUTION.md and ADR-011 wait for a change that can carry the governed-execution proof lanes), with the convention recorded in docs/LEGACY_ISSUE_PROVENANCE.md. diff --git a/docs/CORRECTION_COVERAGE_MATRIX.md b/docs/CORRECTION_COVERAGE_MATRIX.md index 6ebfd2c50..5b0bfd264 100644 --- a/docs/CORRECTION_COVERAGE_MATRIX.md +++ b/docs/CORRECTION_COVERAGE_MATRIX.md @@ -42,7 +42,7 @@ Save-mode semantics for the source artifact are defined once in the generated catalog under `save_modes` and referenced per operation. Target scopes describe the current implicit selector behaviour. The shared -selector AST tracked in GitHub #587 is not yet wired into repair plans; until it +selector AST tracked in legacy #587 is not yet wired into repair plans; until it lands, operations declare whole-document, page, resource, or production-geometry scopes resolved during `analyze()`. diff --git a/docs/EDITOR_RECOVERY.md b/docs/EDITOR_RECOVERY.md index 5d724ddfe..86ee3a3f6 100644 --- a/docs/EDITOR_RECOVERY.md +++ b/docs/EDITOR_RECOVERY.md @@ -26,7 +26,7 @@ only half reachable today: the approval half is pinned by `UnitTestsOperationHistory::noSavePathProducesAnApprovedOutputRecord` (no save path records an approval or an approved output, so a recovered file cannot be presented as approved), and the restore half is tracked by -[#575](https://github.com/studio-berry/loop/issues/575). +legacy #575. ## Safety contract diff --git a/docs/EVIDENCE_CORE_RESET_INVENTORY.md b/docs/EVIDENCE_CORE_RESET_INVENTORY.md index 260f1c669..f2e9a27b3 100644 --- a/docs/EVIDENCE_CORE_RESET_INVENTORY.md +++ b/docs/EVIDENCE_CORE_RESET_INVENTORY.md @@ -27,7 +27,7 @@ The catalog has **22 registered checks**: 5 `covered`, 17 `partial`, and no `not | Canonical Pass/Fail/Incomplete/Error reducer and certificate gate | **Reuse; prove** all four states and no zero-finding budget PASS under [#16](https://github.com/studio-berry/loop2/issues/16). Core verdict. | [`pdfpreflightverdict.cpp`](../LoopLibCore/sources/pdfpreflightverdict.cpp), [verdict contract](PREFLIGHT_VERDICT.md); `UnitTestsPreflightVerdict`, `UnitTestsPreflightEngine`. `PreflightResult::pass` is derived compatibility data. | | Fixed-capacity job scheduler, cancellation, stale-result discard | **Reuse** the existing scheduler; **repair** producer/result fencing under [#17](https://github.com/studio-berry/loop2/issues/17). Core scheduling. | [`pdfjobscheduler.cpp`](../LoopLibCore/sources/pdfjobscheduler.cpp), [scheduler contract](JOB_SCHEDULER.md); `UnitTestsJobScheduler`, `UnitTestsRevisionStress`, `scripts/ci/check_unmanaged_async.py`. Caller coverage is not complete merely because the scheduler exists. | | Parser, reader, renderer, session, processing and resource budgets | **Reuse** Core primitives; **prove** hostile and production envelopes under [#19](https://github.com/studio-berry/loop2/issues/19). Core PDF. | [`pdfdocumentreader.cpp`](../LoopLibCore/sources/pdfdocumentreader.cpp) calls [`pdfparser.cpp`](../LoopLibCore/sources/pdfparser.cpp); [`pdfrenderer.cpp`](../LoopLibCore/sources/pdfrenderer.cpp) and [budget contract](RESOURCE_BUDGETS.md) bound work. `UnitTestsProcessingBudget`, `UnitTestsResourceBudget`, `UnitTestsBudgetExhaustion`, `UnitTestsBudgetCorpus` are mapped tests. The unbudgeted cumulative `PDFFunction::createFunction()` path remains an explicit deferred contract-level gap in that document. | -| PdfTool open/preflight process boundary | **Reuse** the Linux-first worker proof from [legacy #618](https://github.com/studio-berry/loop/issues/618); **repair/audit** remaining privileged-host paths under [#20](https://github.com/studio-berry/loop2/issues/20). PdfTool supervisor and Core. | [`pdfworkerprotocol.h`](../PdfTool/pdfworkerprotocol.h) allowlists `ping`, `open`, `preflight`, `cancel`; [`pdfworkerclient.cpp`](../PdfTool/pdfworkerclient.cpp) maps worker failure/timeout to unavailable/incomplete; [`pdfworkersandbox.cpp`](../PdfTool/pdfworkersandbox.cpp), `UnitTestsPdfWorkerIsolation`, `scripts/ci/check_pdf_worker_isolation.py`. Windows runtime tests skip the Linux sandbox proof; [`editorhost.cpp`](../LoopEditor/editorhost.cpp) still constructs an in-process `PreflightEngine`. The open [legacy #619](https://github.com/studio-berry/loop/issues/619) does not justify a replacement worker primitive. | +| PdfTool open/preflight process boundary | **Reuse** the Linux-first worker proof from legacy #618; **repair/audit** remaining privileged-host paths under [#20](https://github.com/studio-berry/loop2/issues/20). PdfTool supervisor and Core. | [`pdfworkerprotocol.h`](../PdfTool/pdfworkerprotocol.h) allowlists `ping`, `open`, `preflight`, `cancel`; [`pdfworkerclient.cpp`](../PdfTool/pdfworkerclient.cpp) maps worker failure/timeout to unavailable/incomplete; [`pdfworkersandbox.cpp`](../PdfTool/pdfworkersandbox.cpp), `UnitTestsPdfWorkerIsolation`, `scripts/ci/check_pdf_worker_isolation.py`. Windows runtime tests skip the Linux sandbox proof; [`editorhost.cpp`](../LoopEditor/editorhost.cpp) still constructs an in-process `PreflightEngine`. The open legacy #619 does not justify a replacement worker primitive. | | Independent standards/rendering validation | **Reuse** the validation harness; **prove** independent oracle outputs and fidelity claims under [#18](https://github.com/studio-berry/loop2/issues/18). Core qualification. | [`check_independent_validation_gate.py`](../scripts/ci/check_independent_validation_gate.py), [independent evidence schema](schemas/independent-validation-evidence.schema.json), [coverage matrix](PREFLIGHT_COVERAGE_MATRIX.md), `UnitTestsConversionOracle`. The source gate checks presence/guards; it is not a current installed-runtime oracle result. | | Cross-platform exact-SHA admission | **Defer** release admission to [#21](https://github.com/studio-berry/loop2/issues/21). Core qualification with CI owners. | [Proof lanes](../architecture/proof-lanes.yaml) bind `linux-build` and `windows-build`; [parent exit gate](https://github.com/studio-berry/loop2/issues/2) requires one exact-SHA packet. No such packet is asserted by this inventory. | @@ -48,14 +48,14 @@ These are **all 18 `open` rows** in the [generated coverage backlog](generated/p | Open gap ID | Priority | Disposition; evidence/legacy owner | | --- | --- | --- | -| `barcode-slug-braille` | P1 | **Defer**; backlog row, [Loop #604](https://github.com/studio-berry/loop/issues/604). | -| `devicen-per-colorant-ink-limit` | P1 | **Defer**; backlog row, [Loop #600](https://github.com/studio-berry/loop/issues/600). | -| `gwg-2022-2024-certificates` | P1 | **Defer**; backlog row, [Loop #664](https://github.com/studio-berry/loop/issues/664). | -| `imposition-and-reader-spreads` | P1 | **Defer**; backlog row, [Loop #603](https://github.com/studio-berry/loop/issues/603). | -| `pdfvt-variable-data` | P1 | **Defer**; backlog row, [Loop #605](https://github.com/studio-berry/loop/issues/605). | -| `bleed-raster-strip-depth` | P2 | **Defer**; backlog row, [Loop #47](https://github.com/studio-berry/loop/issues/47). | +| `barcode-slug-braille` | P1 | **Defer**; backlog row, legacy #604. | +| `devicen-per-colorant-ink-limit` | P1 | **Defer**; backlog row, legacy #600. | +| `gwg-2022-2024-certificates` | P1 | **Defer**; backlog row, legacy #664. | +| `imposition-and-reader-spreads` | P1 | **Defer**; backlog row, legacy #603. | +| `pdfvt-variable-data` | P1 | **Defer**; backlog row, legacy #605. | +| `bleed-raster-strip-depth` | P2 | **Defer**; backlog row, legacy #47. | | `color-mode-icc-alternate` | P2 | **Defer**; backlog row, unfiled. | -| `dieline-geometry` | P2 | **Defer**; backlog row, [Loop #604](https://github.com/studio-berry/loop/issues/604). | +| `dieline-geometry` | P2 | **Defer**; backlog row, legacy #604. | | `font-glyph-coverage` | P2 | **Defer**; backlog row, unfiled. | | `hidden-layers-ocmd` | P2 | **Defer**; backlog row, unfiled. | | `ink-coverage-raster-tac` | P2 | **Defer**; backlog row, unfiled. | @@ -63,7 +63,7 @@ These are **all 18 `open` rows** in the [generated coverage backlog](generated/p | `obscured-content-occlusion` | P2 | **Defer**; backlog row, unfiled. | | `off-page-content-clipping` | P2 | **Defer**; backlog row, unfiled. | | `transparency-rip-interaction` | P2 | **Defer**; backlog row, unfiled. | -| `white-overprint-renderer` | P2 | **Defer**; backlog row, [Loop #49](https://github.com/studio-berry/loop/issues/49). | +| `white-overprint-renderer` | P2 | **Defer**; backlog row, legacy #49. | | `color-inventory-probe-depth` | P3 | **Defer**; backlog row, unfiled. | | `thin-parts-raster-budget` | P3 | **Defer**; backlog row, unfiled; current failure is incomplete rather than a silent PASS. | diff --git a/docs/LEGACY_ISSUE_PROVENANCE.md b/docs/LEGACY_ISSUE_PROVENANCE.md new file mode 100644 index 000000000..5acad59e7 --- /dev/null +++ b/docs/LEGACY_ISSUE_PROVENANCE.md @@ -0,0 +1,94 @@ +# Legacy issue provenance + +Loop's planning history predates the reset repository. This page records what a bare +issue number means in this tree, where the retired repository's content can still be +recovered, and how references are written from now on. + +## What happened + +`studio-berry/loop2` was created on 2026-09-24 as the reset codebase and now serves as +`studio-berry/loop`. The repository that held `studio-berry/loop` before the rename is +not reachable from either the `studio-berry` or the `mberrys` account as of 2026-09-28: +`gh repo view` finds no `mberrys/loop`, `mberrys/loop2`, `mberrys/Loop-pdf`, or +`studio-berry/Loop-pdf`, and `studio-berry` lists no repository with that history. Its +issue and pull-request numbers (at least through #686) overlap the reset repository's +sequence, which started again at 1. + +Consequences: + +- A link to `github.com/studio-berry/loop/issues/` written before the rename either + returns 404 or opens an unrelated reset issue. Neither is the issue the author meant. +- A bare `#` in a file dated before 2026-09-24 means the legacy issue or pull request + unless the file says otherwise (`loop2 #15`, `#15`, and every issue in the roadmap's + L01–L12 suite are reset issues). +- Issue and pull-request numbers share one sequence, so the collision surface grows with + every new issue or PR. + +## Where legacy content is recoverable + +The retired repository itself cannot be recovered from GitHub here. The specifications +and dispositions survive in Notion: + +- the *Loop Issues* ledger and the *Sessions* ledger, linked from the master roadmap + (§2) — the source of record for legacy titles, bodies, and status; +- `docs/ROADMAP_0.5.0-0.8.0.md`, `docs/github-milestones/`, and the handoff documents + under `docs/`, which quote legacy numbers as they were written. + +Do not treat a legacy issue's status as reset-repository status. Reconcile against code, +tests, and exact-SHA evidence, and write a new issue for a demonstrated remaining gap. + +## How references are written + +| Reference | Meaning | +| --- | --- | +| `#` in an issue, PR, or code comment | An issue or PR in `studio-berry/loop` (the reset repository), resolved by GitHub. | +| `legacy #` in prose, `legacy#` in machine-read files | A retired-repository issue. Never linked; never a live tracker. | +| `studio-berry/loop#` or the full URL | A reset issue, when a file could be read outside the repository. | + +Rules: + +1. **Never link a legacy number.** No `github.com/studio-berry/loop/issues/` URL may + point at a legacy issue. Existing ones were rewritten to `legacy #` text, except + the links to legacy #656 and #675 in `docs/GOVERNED_EXECUTION.md` and + `docs/adr/adr-011-architecture-contracts-d1-d5.md`: both belong to the + governed-execution subsystem, whose binding proof lanes (build, packaging, unit) + cannot be produced by a documentation change, so they are rewritten with the next + change that carries them. +2. **Machine-read records name their repository.** `github_issues` entries in + `docs/preflight-check-catalog-overlay.json` carry `repository`. Live records are + `#` with `studio-berry/loop`; frozen snapshots are `legacy#` with `legacy`. +3. **A legacy record can document closed work, never open a gap.** The catalog generator + refuses an open legacy issue as a backlog row's `closed_by`. Re-point the row to a + live issue. +4. **Live records are read back before promotion.** + `python3 scripts/generate-architecture-catalogs.py --check --verify-github` compares + title, state, and milestone with GitHub and rejects a number that now resolves to a + pull request or another issue. + +## Preflight backlog re-pointing + +Open backlog rows used to cite legacy trackers. Each now cites the reset issue filed for +the gap; the legacy number is kept here as provenance. + +| Backlog row | Legacy tracker | Live tracker | +| --- | --- | --- | +| `barcode-slug-braille`, `dieline-geometry` | legacy #604 | #143 (X00-04) | +| `devicen-per-colorant-ink-limit` | legacy #600 | #142 (X00-03) | +| `gwg-2022-2024-certificates` | legacy #664 | #144 (X00-05) | +| `imposition-and-reader-spreads`, `pdfvt-variable-data` | legacy #603, legacy #605 | #141 (X00-02) | +| `bleed-raster-strip-depth` | legacy #47 | #120 (L01-15) | +| `white-overprint-renderer` | legacy #49 | #119 (L01-14) | +| `transparency-rip-interaction` | unfiled | #119 (L01-14) | +| `color-mode-icc-alternate` | unfiled | #113 (L01-08) | +| `font-glyph-coverage` | unfiled | #114 (L01-09) | +| `hidden-layers-ocmd` | unfiled | #115 (L01-10) | +| `ink-coverage-raster-tac` | unfiled | #116 (L01-11) | +| `invisible-content-breadth` | unfiled | #117 (L01-12) | +| `obscured-content-occlusion`, `off-page-content-clipping` | unfiled | #118 (L01-13) | + +Rows that landed (`corrupt-embedded-fonts`, `nested-font-resources`, +`output-intent-identity`, `thin-filled-parts`, `pdfx5-pdfa3-output`) and the closed +`devicen-dieline-detection` row keep their legacy references as `legacy#` snapshots, +as does the `invisible-content-breadth` gap text for its earlier detector. +`color-inventory-probe-depth` and `thin-parts-raster-budget` stay register-only with a +reviewed deferral. diff --git a/docs/PREFLIGHT_COVERAGE_MATRIX.md b/docs/PREFLIGHT_COVERAGE_MATRIX.md index 0c2fba35c..9c9c5adf4 100644 --- a/docs/PREFLIGHT_COVERAGE_MATRIX.md +++ b/docs/PREFLIGHT_COVERAGE_MATRIX.md @@ -143,12 +143,34 @@ State and closure (`state_rule` in the generated file): `closed_by` is a verified GitHub issue (`#`), a registered check id, or the literal `unfiled` when neither exists. Issue numbers are never inferred: the -overlay records each one in `github_issues` with the number, title, state, and -milestone read back from `gh issue view`, the generator refuses a reference with -no verified record, and it refuses a row whose `state` disagrees with the -recorded issue state — so a closed issue forces a row to be re-triaged rather -than left stale. Every `not_covered` class must appear in a P1 row's `gap`, and -no P1 row may invent a class the matrix does not list. +overlay records each one in `github_issues` with the number, title, state, +milestone, and `repository` read back from `gh issue view`, the generator +refuses a reference with no verified record, and it refuses a row whose `state` +disagrees with the recorded issue state — so a closed issue forces a row to be +re-triaged rather than left stale. Every `not_covered` class must appear in a P1 +row's `gap`, and no P1 row may invent a class the matrix does not list. + +`github_issues` holds two kinds of record, keyed by how they are written: + +- `#` is an issue in `studio-berry/loop`. It may be a row's `closed_by` + and can be read back at any time. +- `legacy#` is a frozen snapshot from the retired repository, whose + numbers overlap the live ones and can no longer be read back (see + [`LEGACY_ISSUE_PROVENANCE.md`](LEGACY_ISSUE_PROVENANCE.md)). It may document a + row that was closed there, in `closed_by` or in `gap` text, but the generator + refuses a legacy record that is still `OPEN` as a row's `closed_by`, and a + bare `#` in `gap` text never satisfies a `legacy#` record. + +Issue and pull-request numbers share one sequence, so a live record can drift or +collide as the repository grows. `--check` stays offline; run + +```text +python3 scripts/generate-architecture-catalogs.py --check --verify-github +``` + +to read every live record back with `gh` and fail on a missing issue, a pull +request, or a changed title, state, or milestone. Run it before a promotion and +after retitling or closing a cited issue. A row that is not filed carries a `deferral` reason instead, and the generator refuses both an unfiled row without one and a filed row that still carries one — diff --git a/docs/REPO_MAP.md b/docs/REPO_MAP.md index 534583be0..89e630ab1 100644 --- a/docs/REPO_MAP.md +++ b/docs/REPO_MAP.md @@ -26,7 +26,7 @@ tooling. Do not infer Loop branch policy from upstream's `master` branch. The reviewed machine-readable policy is [`branch-policy.json`](branch-policy.json). The current factual branch and -workflow audit is tracked in GitHub issue [#232](https://github.com/studio-berry/loop/issues/232). +workflow audit is tracked in legacy issue #232. ## Versioning diff --git a/docs/SESSION_09_HANDOFF.md b/docs/SESSION_09_HANDOFF.md index d2f171898..4ec38d87f 100644 --- a/docs/SESSION_09_HANDOFF.md +++ b/docs/SESSION_09_HANDOFF.md @@ -49,9 +49,9 @@ Deleted Phase 5 identities remain recorded in `docs/product-surface.json` with packaging and budget work beyond this ledger-closeout diff; qualification lanes should not treat it as the Session 09 ledger baseline. -**Dev integration:** merged via [PR #535](https://github.com/studio-berry/loop/pull/535) @ +**Dev integration:** merged via legacy PR #535 @ `1f69bdf8bff037e5cae2d37e3c2e3eae8b2ca6b5`. Session 13 scaffolding landed on -`dev` via [PR #539](https://github.com/studio-berry/loop/pull/539) @ +`dev` via legacy PR #539 @ `ebde8661bff037e5cae2d37e3c2e3eae8b2ca6b5` (current qualification `candidate_sha`). diff --git a/docs/adr/adr-007-qt-quick-controls-shell.md b/docs/adr/adr-007-qt-quick-controls-shell.md index 437abdc09..31b463aa7 100644 --- a/docs/adr/adr-007-qt-quick-controls-shell.md +++ b/docs/adr/adr-007-qt-quick-controls-shell.md @@ -169,4 +169,4 @@ adoption preserves feature delivery while those risks are measured. - [Qt Quick Controls](https://doc.qt.io/qt-6/qtquickcontrols-index.html) - [Qt 6.11 changes to Qt Quick](https://doc.qt.io/qt-6/quick-changes-qt6.html) - [QQuickWindow scene-graph backend selection](https://doc.qt.io/qt-6/qquickwindow.html) -- [Loop issue #178](https://github.com/studio-berry/loop/issues/178) +- legacy issue #178 diff --git a/docs/adr/adr-008-generated-history-rewrite.md b/docs/adr/adr-008-generated-history-rewrite.md index 10dc3fe1d..89296b667 100644 --- a/docs/adr/adr-008-generated-history-rewrite.md +++ b/docs/adr/adr-008-generated-history-rewrite.md @@ -9,18 +9,18 @@ ## Context -[#265](https://github.com/studio-berry/loop/issues/265) asked to decide +legacy #265 asked to decide whether to rewrite or retain generated dependency and build blobs already present in the 195 unreleased `dev` commits, and originally recommended a `dev`-only rewrite because `stable` had not yet received that history. -That window closed when [PR #188](https://github.com/studio-berry/loop/pull/188) +That window closed when legacy PR #188 merged to `stable` on 2026-08-13. After the merge, both `origin/dev` and `origin/stable` still contained the same 982 blobs (~400.5 MiB): `.docker-vcpkg*`, `build-fuzz-docker/` (including a 45 MiB `libLoopLibCore.so`), `debug-b0e75b.log`, `scripts/debug-pr188.*`, and stray -`loop-ocr` bytecode. Branch tips were already clean ([#249](https://github.com/studio-berry/loop/pull/249), -[#258](https://github.com/studio-berry/loop/pull/258)); only history held the +`loop-ocr` bytecode. Branch tips were already clean (legacy #249, +legacy #258); only history held the blobs. Rewriting only `dev` would not reclaim GitHub storage. Rewriting `stable` diff --git a/docs/adr/adr-009-canvas-hosting-benchmark.md b/docs/adr/adr-009-canvas-hosting-benchmark.md index 3f5e7a029..1eee1dde6 100644 --- a/docs/adr/adr-009-canvas-hosting-benchmark.md +++ b/docs/adr/adr-009-canvas-hosting-benchmark.md @@ -147,4 +147,4 @@ Those remain explicit later gates in ADR-007 and ADR-010. - [Quick-root admission](adr-010-quick-root-admission.md) - [Qt Quick Controls shell](adr-007-qt-quick-controls-shell.md) - [Quick composition contract](../QUICK_COMPOSITION.md) -- [Issue #247](https://github.com/studio-berry/loop/issues/247) +- legacy issue #247 diff --git a/docs/generated/preflight-coverage-backlog.json b/docs/generated/preflight-coverage-backlog.json index 4813743e6..8c428da56 100644 --- a/docs/generated/preflight-coverage-backlog.json +++ b/docs/generated/preflight-coverage-backlog.json @@ -15,87 +15,136 @@ "format_version": 1, "generated_by": "scripts/generate-architecture-catalogs.py", "github_issues": { - "#12": { - "milestone": "0.0.1", - "number": 12, - "state": "CLOSED", - "title": "Validate output-intent and ICC-profile presence, identity, and color-space consistency" + "#113": { + "milestone": "L01 - Evidence Core", + "number": 113, + "repository": "studio-berry/loop", + "state": "OPEN", + "title": "L01-08 \u2014 Classify ICC-based color by its painted space" }, - "#124": { - "milestone": null, - "number": 124, - "state": "CLOSED", - "title": "Detect hidden, invisible, and off-page content in preflight" + "#114": { + "milestone": "L01 - Evidence Core", + "number": 114, + "repository": "studio-berry/loop", + "state": "OPEN", + "title": "L01-09 \u2014 Audit glyph coverage in embedded fonts" }, - "#130": { - "milestone": null, - "number": 130, - "state": "CLOSED", - "title": "Detect corrupt fonts, not just embedded/unembedded status" + "#115": { + "milestone": "L01 - Evidence Core", + "number": 115, + "repository": "studio-berry/loop", + "state": "OPEN", + "title": "L01-10 \u2014 Evaluate optional-content membership for hidden content" }, - "#131": { - "milestone": null, - "number": 131, - "state": "CLOSED", - "title": "Extend thin-part detection beyond simple strokes (expand #23)" + "#116": { + "milestone": "L01 - Evidence Core", + "number": 116, + "repository": "studio-berry/loop", + "state": "OPEN", + "title": "L01-11 \u2014 Stop isolated over-limit ink regions passing ink coverage" }, - "#47": { - "milestone": null, - "number": 47, + "#117": { + "milestone": "L01 - Evidence Core", + "number": 117, + "repository": "studio-berry/loop", + "state": "OPEN", + "title": "L01-12 \u2014 Broaden invisible-content detection" + }, + "#118": { + "milestone": "L01 - Evidence Core", + "number": 118, + "repository": "studio-berry/loop", + "state": "OPEN", + "title": "L01-13 \u2014 Make off-page and obscured-content geometry clip-aware" + }, + "#119": { + "milestone": "L01 - Evidence Core", + "number": 119, + "repository": "studio-berry/loop", "state": "OPEN", - "title": "[Residual] Tier-2 bleed raster golden corpus + strip calibration" + "title": "L01-14 \u2014 Judge white overprint and transparency interaction on the authoritative renderer" }, - "#49": { - "milestone": "0.8.0", - "number": 49, + "#120": { + "milestone": "L01 - Evidence Core", + "number": 120, + "repository": "studio-berry/loop", "state": "OPEN", - "title": "[0.8.0] Overprint-correct rendering in standard and advanced renderers" + "title": "L01-15 \u2014 Close the sparse-mark bleed raster false pass" }, - "#600": { + "#141": { "milestone": null, - "number": 600, + "number": 141, + "repository": "studio-berry/loop", "state": "OPEN", - "title": "Ink Manager: separation management and spot-color library" + "title": "X00-02 \u2014 Re-file parked legacy expansion candidates with value cases" }, - "#603": { + "#142": { "milestone": null, - "number": 603, + "number": 142, + "repository": "studio-berry/loop", "state": "OPEN", - "title": "Imposition: n-up, gang-up, booklets, creep, and sheet furniture" + "title": "X00-03 \u2014 Per-named-colorant ink limits for DeviceN and spot separations" }, - "#604": { + "#143": { "milestone": null, - "number": 604, + "number": 143, + "repository": "studio-berry/loop", "state": "OPEN", - "title": "Packaging production semantics: ISO 19593 Processing Steps, dielines, varnish/foil/white, barcode validation" + "title": "X00-04 \u2014 Barcode, slug, Braille, and dieline geometry validation" }, - "#605": { + "#144": { "milestone": null, - "number": 605, + "number": 144, + "repository": "studio-berry/loop", "state": "OPEN", - "title": "PDF/VT variable-data production: understanding, validation, and preservation" + "title": "X00-05 \u2014 Standards currency: PDF/X-6, PDF 2.0 output intents, and GWG 2022 check sets" + }, + "legacy#12": { + "milestone": "0.0.1", + "number": 12, + "repository": "legacy", + "state": "CLOSED", + "title": "Validate output-intent and ICC-profile presence, identity, and color-space consistency" + }, + "legacy#124": { + "milestone": null, + "number": 124, + "repository": "legacy", + "state": "CLOSED", + "title": "Detect hidden, invisible, and off-page content in preflight" + }, + "legacy#130": { + "milestone": null, + "number": 130, + "repository": "legacy", + "state": "CLOSED", + "title": "Detect corrupt fonts, not just embedded/unembedded status" }, - "#628": { + "legacy#131": { + "milestone": null, + "number": 131, + "repository": "legacy", + "state": "CLOSED", + "title": "Extend thin-part detection beyond simple strokes (expand #23)" + }, + "legacy#628": { "milestone": "0.3.0", "number": 628, + "repository": "legacy", "state": "CLOSED", "title": "DeviceN dieline / spot geometry not detected (Separation-only collector)" }, - "#664": { - "milestone": null, - "number": 664, - "state": "OPEN", - "title": "[0.3.0] No check inspects formal GWG 2022/2024 sheetfed or packaging conformance" - }, - "#665": { + "legacy#665": { "milestone": null, "number": 665, + "repository": "legacy", "state": "OPEN", "title": "[0.3.0] No check validates or produces PDF/X-5 and PDF/A-3 output" }, - "#70": { + "legacy#70": { "milestone": null, "number": 70, + "repository": "legacy", "state": "CLOSED", "title": "Traverse nested Form XObjects, inherited resources, and appearance streams for embedded fonts" } @@ -111,7 +160,7 @@ "priority_rule": "P1 - no registered check inspects the defect class at all (a matrix not_covered entry), so a clean run is silent about it; P2 - a registered check inspects the class but its named limitation can suppress or misclassify a finding on defective content, which would be a false clean pass; P3 - a registered check inspects the class and the named limitation only narrows reported detail or fails closed as incomplete, so it cannot turn a defect into a silent pass.", "rows": [ { - "closed_by": "#604", + "closed_by": "#143", "deferral": null, "families": [ "packaging", @@ -124,7 +173,7 @@ "state": "open" }, { - "closed_by": "#600", + "closed_by": "#142", "deferral": null, "families": [ "packaging", @@ -138,7 +187,7 @@ "state": "open" }, { - "closed_by": "#664", + "closed_by": "#144", "deferral": null, "families": [ "sheetfed-offset", @@ -150,7 +199,7 @@ "state": "open" }, { - "closed_by": "#603", + "closed_by": "#141", "deferral": null, "families": [ "sheetfed-offset", @@ -163,7 +212,7 @@ "state": "open" }, { - "closed_by": "#605", + "closed_by": "#141", "deferral": null, "families": [ "web-offset", @@ -176,7 +225,7 @@ "state": "open" }, { - "closed_by": "#47", + "closed_by": "#120", "deferral": null, "families": [ "sheetfed-offset", @@ -189,8 +238,8 @@ "state": "open" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture whose ICC space resolves to an allowed alternate, so the false pass is reproducible rather than argued.", + "closed_by": "#113", + "deferral": null, "families": [ "sheetfed-offset", "web-offset", @@ -210,13 +259,13 @@ "sheetfed-offset", "digital" ], - "gap": "embedded font programs that fail to parse, or lose their cmap, were unchecked; font-integrity now reports them (filed as #130)", + "gap": "embedded font programs that fail to parse, or lose their cmap, were unchecked; font-integrity now reports them (filed as legacy#130)", "id": "corrupt-embedded-fonts", "priority": "P2", "state": "landed" }, { - "closed_by": "#628", + "closed_by": "legacy#628", "deferral": null, "families": [ "packaging" @@ -227,7 +276,7 @@ "state": "closed" }, { - "closed_by": "#604", + "closed_by": "#143", "deferral": null, "families": [ "packaging" @@ -238,8 +287,8 @@ "state": "open" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture with a parsable embedded program that is missing a used glyph.", + "closed_by": "#114", + "deferral": null, "families": [ "sheetfed-offset", "digital" @@ -262,8 +311,8 @@ "state": "landed" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture with an optional-content membership configuration that hides print content.", + "closed_by": "#115", + "deferral": null, "families": [ "packaging" ], @@ -273,8 +322,8 @@ "state": "open" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture with an isolated over-limit element below min_region_area_pct.", + "closed_by": "#116", + "deferral": null, "families": [ "sheetfed-offset", "web-offset", @@ -286,13 +335,13 @@ "state": "open" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture painting an invisible class outside text render mode 3 and zero-alpha graphics state.", + "closed_by": "#117", + "deferral": null, "families": [ "sheetfed-offset", "packaging" ], - "gap": "invisible-content covers text render mode 3 and zero-alpha graphics state only, so other invisible classes pass clean (the wider detector is filed as #124)", + "gap": "invisible-content covers text render mode 3 and zero-alpha graphics state only, so other invisible classes pass clean (the wider detector is filed as legacy#124)", "id": "invisible-content-breadth", "priority": "P2", "state": "open" @@ -307,14 +356,14 @@ "newspaper", "digital" ], - "gap": "fonts used only inside nested Form XObjects, inherited resources, or annotation appearance streams were unchecked; embedded-fonts now traverses them (filed as #70)", + "gap": "fonts used only inside nested Form XObjects, inherited resources, or annotation appearance streams were unchecked; embedded-fonts now traverses them (filed as legacy#70)", "id": "nested-font-resources", "priority": "P2", "state": "landed" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture where a bounding-box heuristic cannot see the occlusion a RIP would.", + "closed_by": "#118", + "deferral": null, "families": [ "sheetfed-offset" ], @@ -324,8 +373,8 @@ "state": "open" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture whose clipped mark exposes the pre-clip bounds comparison.", + "closed_by": "#118", + "deferral": null, "families": [ "sheetfed-offset", "digital" @@ -344,7 +393,7 @@ "packaging", "digital" ], - "gap": "output intent presence, identity, subtype, and ICC payload were unchecked; output-intent now validates them (filed as #12)", + "gap": "output intent presence, identity, subtype, and ICC payload were unchecked; output-intent now validates them (filed as legacy#12)", "id": "output-intent-identity", "priority": "P2", "state": "landed" @@ -355,14 +404,14 @@ "families": [ "packaging" ], - "gap": "thin filled parts, not only strokes, were unchecked for a minimum width; thin-parts now measures them (filed as #131)", + "gap": "thin filled parts, not only strokes, were unchecked for a minimum width; thin-parts now measures them (filed as legacy#131)", "id": "thin-filled-parts", "priority": "P2", "state": "landed" }, { - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture reproducing an overprint-plus-transparency interaction only a RIP flattens.", + "closed_by": "#119", + "deferral": null, "families": [ "sheetfed-offset", "packaging", @@ -374,7 +423,7 @@ "state": "open" }, { - "closed_by": "#49", + "closed_by": "#119", "deferral": null, "families": [ "sheetfed-offset", @@ -419,7 +468,7 @@ "packaging", "digital" ], - "gap": "PDF/X-5n, PDF/X-5g, and PDF/A-3 are not produced or validated; conformance-claims reports a declared level as unsupported instead of a silent pass (filed as #665)", + "gap": "PDF/X-5n, PDF/X-5g, and PDF/A-3 are not produced or validated; conformance-claims reports a declared level as unsupported instead of a silent pass (filed as legacy#665)", "id": "pdfx5-pdfa3-output", "priority": "P3", "state": "landed" diff --git a/docs/handoffs/0.2.0-gh143-parity-checklist.md b/docs/handoffs/0.2.0-gh143-parity-checklist.md index aa7edf41e..00d87fa9a 100644 --- a/docs/handoffs/0.2.0-gh143-parity-checklist.md +++ b/docs/handoffs/0.2.0-gh143-parity-checklist.md @@ -4,16 +4,16 @@ Status: tracks harvest from PR #358 / `gh-143` into the Qt Quick architecture on `dev`. Widgets implementation code from that branch is superseded; this checklist maps each gh-143 test and contract to its Quick/interaction target. -Parent epic: [#356](https://github.com/studio-berry/loop/issues/356). +Parent epic: legacy #356. ## Notion mapping (0.2.0–0.4.0) | GitHub | Notion anchor | Release | | --- | --- | --- | -| [#361](https://github.com/studio-berry/loop/issues/361) | [0.2.0 QT Quick](https://app.notion.com/p/3c39cb079ddb80c29549dfe9abace836) → P4-S8 | 0.2.0 | -| [#362](https://github.com/studio-berry/loop/issues/362) | [0.2.0 QT Quick](https://app.notion.com/p/3c39cb079ddb80c29549dfe9abace836) → harvest / DenyExtraGraphics | 0.2.0 | -| [#360](https://github.com/studio-berry/loop/issues/360) | [0.2.0 QT Quick](https://app.notion.com/p/3c39cb079ddb80c29549dfe9abace836) → cache budget harvest; [migration handoff](https://app.notion.com/p/3c39cb079ddb8123a4defd5740d4815b) | 0.2.0 | -| [#363](https://github.com/studio-berry/loop/issues/363) | [P4-S7 handoff](https://app.notion.com/p/3c69cb079ddb814e85d6e571a29740ab); [0.2.0](https://app.notion.com/p/3c09cb079ddb80dfa2f9d6d5a15f2d8e) harvest index | 0.2.0 | +| legacy #361 | [0.2.0 QT Quick](https://app.notion.com/p/3c39cb079ddb80c29549dfe9abace836) → P4-S8 | 0.2.0 | +| legacy #362 | [0.2.0 QT Quick](https://app.notion.com/p/3c39cb079ddb80c29549dfe9abace836) → harvest / DenyExtraGraphics | 0.2.0 | +| legacy #360 | [0.2.0 QT Quick](https://app.notion.com/p/3c39cb079ddb80c29549dfe9abace836) → cache budget harvest; [migration handoff](https://app.notion.com/p/3c39cb079ddb8123a4defd5740d4815b) | 0.2.0 | +| legacy #363 | [P4-S7 handoff](https://app.notion.com/p/3c69cb079ddb814e85d6e571a29740ab); [0.2.0](https://app.notion.com/p/3c09cb079ddb80dfa2f9d6d5a15f2d8e) harvest index | 0.2.0 | **0.3.0** ([Governed Corrections](https://app.notion.com/p/3c39cb079ddb8152b9f1f16d2fa2bacd)): lists **#361** as upstream P4-S8 dependency for detect→pinpoint, not 0.3.0 implementation. diff --git a/docs/preflight-check-catalog-overlay.json b/docs/preflight-check-catalog-overlay.json index db2905a89..916f07a9d 100644 --- a/docs/preflight-check-catalog-overlay.json +++ b/docs/preflight-check-catalog-overlay.json @@ -17,89 +17,138 @@ "backlog_priority_rule": "P1 - no registered check inspects the defect class at all (a matrix not_covered entry), so a clean run is silent about it; P2 - a registered check inspects the class but its named limitation can suppress or misclassify a finding on defective content, which would be a false clean pass; P3 - a registered check inspects the class and the named limitation only narrows reported detail or fails closed as incomplete, so it cannot turn a defect into a silent pass.", "backlog_state_rule": "open - the gap is still present; landed - a registered check now covers the class and closed_by names that check id; closed - the filed issue that tracked the gap is closed.", "github_issues": { - "#12": { + "legacy#12": { "number": 12, "title": "Validate output-intent and ICC-profile presence, identity, and color-space consistency", "state": "CLOSED", - "milestone": "0.0.1" + "milestone": "0.0.1", + "repository": "legacy" }, - "#47": { - "number": 47, - "title": "[Residual] Tier-2 bleed raster golden corpus + strip calibration", - "state": "OPEN", - "milestone": null - }, - "#49": { - "number": 49, - "title": "[0.8.0] Overprint-correct rendering in standard and advanced renderers", - "state": "OPEN", - "milestone": "0.8.0" - }, - "#70": { + "legacy#70": { "number": 70, "title": "Traverse nested Form XObjects, inherited resources, and appearance streams for embedded fonts", "state": "CLOSED", - "milestone": null + "milestone": null, + "repository": "legacy" }, - "#124": { + "legacy#124": { "number": 124, "title": "Detect hidden, invisible, and off-page content in preflight", "state": "CLOSED", - "milestone": null + "milestone": null, + "repository": "legacy" }, - "#130": { + "legacy#130": { "number": 130, "title": "Detect corrupt fonts, not just embedded/unembedded status", "state": "CLOSED", - "milestone": null + "milestone": null, + "repository": "legacy" }, - "#131": { + "legacy#131": { "number": 131, "title": "Extend thin-part detection beyond simple strokes (expand #23)", "state": "CLOSED", - "milestone": null + "milestone": null, + "repository": "legacy" }, - "#600": { - "number": 600, - "title": "Ink Manager: separation management and spot-color library", + "legacy#628": { + "number": 628, + "title": "DeviceN dieline / spot geometry not detected (Separation-only collector)", + "state": "CLOSED", + "milestone": "0.3.0", + "repository": "legacy" + }, + "legacy#665": { + "number": 665, + "title": "[0.3.0] No check validates or produces PDF/X-5 and PDF/A-3 output", "state": "OPEN", - "milestone": null + "milestone": null, + "repository": "legacy" }, - "#603": { - "number": 603, - "title": "Imposition: n-up, gang-up, booklets, creep, and sheet furniture", + "#113": { + "number": 113, + "title": "L01-08 \u2014 Classify ICC-based color by its painted space", "state": "OPEN", - "milestone": null + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" }, - "#604": { - "number": 604, - "title": "Packaging production semantics: ISO 19593 Processing Steps, dielines, varnish/foil/white, barcode validation", + "#114": { + "number": 114, + "title": "L01-09 \u2014 Audit glyph coverage in embedded fonts", "state": "OPEN", - "milestone": null + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" }, - "#605": { - "number": 605, - "title": "PDF/VT variable-data production: understanding, validation, and preservation", + "#115": { + "number": 115, + "title": "L01-10 \u2014 Evaluate optional-content membership for hidden content", "state": "OPEN", - "milestone": null + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" }, - "#628": { - "number": 628, - "title": "DeviceN dieline / spot geometry not detected (Separation-only collector)", - "state": "CLOSED", - "milestone": "0.3.0" + "#116": { + "number": 116, + "title": "L01-11 \u2014 Stop isolated over-limit ink regions passing ink coverage", + "state": "OPEN", + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" }, - "#664": { - "number": 664, - "title": "[0.3.0] No check inspects formal GWG 2022/2024 sheetfed or packaging conformance", + "#117": { + "number": 117, + "title": "L01-12 \u2014 Broaden invisible-content detection", "state": "OPEN", - "milestone": null + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" }, - "#665": { - "number": 665, - "title": "[0.3.0] No check validates or produces PDF/X-5 and PDF/A-3 output", + "#118": { + "number": 118, + "title": "L01-13 \u2014 Make off-page and obscured-content geometry clip-aware", + "state": "OPEN", + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" + }, + "#119": { + "number": 119, + "title": "L01-14 \u2014 Judge white overprint and transparency interaction on the authoritative renderer", + "state": "OPEN", + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" + }, + "#120": { + "number": 120, + "title": "L01-15 \u2014 Close the sparse-mark bleed raster false pass", + "state": "OPEN", + "milestone": "L01 - Evidence Core", + "repository": "studio-berry/loop" + }, + "#141": { + "number": 141, + "title": "X00-02 \u2014 Re-file parked legacy expansion candidates with value cases", "state": "OPEN", - "milestone": null + "milestone": null, + "repository": "studio-berry/loop" + }, + "#142": { + "number": 142, + "title": "X00-03 \u2014 Per-named-colorant ink limits for DeviceN and spot separations", + "state": "OPEN", + "milestone": null, + "repository": "studio-berry/loop" + }, + "#143": { + "number": 143, + "title": "X00-04 \u2014 Barcode, slug, Braille, and dieline geometry validation", + "state": "OPEN", + "milestone": null, + "repository": "studio-berry/loop" + }, + "#144": { + "number": 144, + "title": "X00-05 \u2014 Standards currency: PDF/X-6, PDF 2.0 output intents, and GWG 2022 check sets", + "state": "OPEN", + "milestone": null, + "repository": "studio-berry/loop" } }, "checks": { @@ -1323,7 +1372,7 @@ "newspaper" ], "state": "open", - "closed_by": "#604", + "closed_by": "#143", "deferral": null }, { @@ -1337,7 +1386,7 @@ "newspaper" ], "state": "open", - "closed_by": "#600", + "closed_by": "#142", "deferral": null }, { @@ -1349,7 +1398,7 @@ "packaging" ], "state": "open", - "closed_by": "#664", + "closed_by": "#144", "deferral": null }, { @@ -1362,7 +1411,7 @@ "newspaper" ], "state": "open", - "closed_by": "#603", + "closed_by": "#141", "deferral": null }, { @@ -1375,13 +1424,13 @@ "digital" ], "state": "open", - "closed_by": "#605", + "closed_by": "#141", "deferral": null }, { "id": "pdfx5-pdfa3-output", "priority": "P3", - "gap": "PDF/X-5n, PDF/X-5g, and PDF/A-3 are not produced or validated; conformance-claims reports a declared level as unsupported instead of a silent pass (filed as #665)", + "gap": "PDF/X-5n, PDF/X-5g, and PDF/A-3 are not produced or validated; conformance-claims reports a declared level as unsupported instead of a silent pass (filed as legacy#665)", "families": [ "sheetfed-offset", "packaging", @@ -1401,7 +1450,7 @@ "digital" ], "state": "open", - "closed_by": "#47", + "closed_by": "#120", "deferral": null }, { @@ -1416,8 +1465,8 @@ "digital" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture whose ICC space resolves to an allowed alternate, so the false pass is reproducible rather than argued." + "closed_by": "#113", + "deferral": null }, { "id": "dieline-geometry", @@ -1427,7 +1476,7 @@ "packaging" ], "state": "open", - "closed_by": "#604", + "closed_by": "#143", "deferral": null }, { @@ -1439,8 +1488,8 @@ "digital" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture with a parsable embedded program that is missing a used glyph." + "closed_by": "#114", + "deferral": null }, { "id": "hidden-layers-ocmd", @@ -1450,8 +1499,8 @@ "packaging" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture with an optional-content membership configuration that hides print content." + "closed_by": "#115", + "deferral": null }, { "id": "ink-coverage-raster-tac", @@ -1463,20 +1512,20 @@ "newspaper" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture with an isolated over-limit element below min_region_area_pct." + "closed_by": "#116", + "deferral": null }, { "id": "invisible-content-breadth", "priority": "P2", - "gap": "invisible-content covers text render mode 3 and zero-alpha graphics state only, so other invisible classes pass clean (the wider detector is filed as #124)", + "gap": "invisible-content covers text render mode 3 and zero-alpha graphics state only, so other invisible classes pass clean (the wider detector is filed as legacy#124)", "families": [ "sheetfed-offset", "packaging" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture painting an invisible class outside text render mode 3 and zero-alpha graphics state." + "closed_by": "#117", + "deferral": null }, { "id": "obscured-content-occlusion", @@ -1486,8 +1535,8 @@ "sheetfed-offset" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture where a bounding-box heuristic cannot see the occlusion a RIP would." + "closed_by": "#118", + "deferral": null }, { "id": "off-page-content-clipping", @@ -1498,8 +1547,8 @@ "digital" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture whose clipped mark exposes the pre-clip bounds comparison." + "closed_by": "#118", + "deferral": null }, { "id": "transparency-rip-interaction", @@ -1511,8 +1560,8 @@ "digital" ], "state": "open", - "closed_by": "unfiled", - "deferral": "Deferred: filing needs a fixture reproducing an overprint-plus-transparency interaction only a RIP flattens." + "closed_by": "#119", + "deferral": null }, { "id": "white-overprint-renderer", @@ -1523,13 +1572,13 @@ "packaging" ], "state": "open", - "closed_by": "#49", + "closed_by": "#119", "deferral": null }, { "id": "corrupt-embedded-fonts", "priority": "P2", - "gap": "embedded font programs that fail to parse, or lose their cmap, were unchecked; font-integrity now reports them (filed as #130)", + "gap": "embedded font programs that fail to parse, or lose their cmap, were unchecked; font-integrity now reports them (filed as legacy#130)", "families": [ "sheetfed-offset", "digital" @@ -1546,7 +1595,7 @@ "packaging" ], "state": "closed", - "closed_by": "#628", + "closed_by": "legacy#628", "deferral": null }, { @@ -1564,7 +1613,7 @@ { "id": "nested-font-resources", "priority": "P2", - "gap": "fonts used only inside nested Form XObjects, inherited resources, or annotation appearance streams were unchecked; embedded-fonts now traverses them (filed as #70)", + "gap": "fonts used only inside nested Form XObjects, inherited resources, or annotation appearance streams were unchecked; embedded-fonts now traverses them (filed as legacy#70)", "families": [ "sheetfed-offset", "web-offset", @@ -1579,7 +1628,7 @@ { "id": "output-intent-identity", "priority": "P2", - "gap": "output intent presence, identity, subtype, and ICC payload were unchecked; output-intent now validates them (filed as #12)", + "gap": "output intent presence, identity, subtype, and ICC payload were unchecked; output-intent now validates them (filed as legacy#12)", "families": [ "sheetfed-offset", "web-offset", @@ -1593,7 +1642,7 @@ { "id": "thin-filled-parts", "priority": "P2", - "gap": "thin filled parts, not only strokes, were unchecked for a minimum width; thin-parts now measures them (filed as #131)", + "gap": "thin filled parts, not only strokes, were unchecked for a minimum width; thin-parts now measures them (filed as legacy#131)", "families": [ "packaging" ], diff --git a/scripts/ci/test_preflight_check_catalog.py b/scripts/ci/test_preflight_check_catalog.py index 0fdd967cc..b1c065371 100644 --- a/scripts/ci/test_preflight_check_catalog.py +++ b/scripts/ci/test_preflight_check_catalog.py @@ -165,11 +165,103 @@ def invent(overlay: dict) -> None: def test_state_disagreeing_with_issue_state_fails(self) -> None: def mismatch(overlay: dict) -> None: for row in overlay["backlog"]: - if row["closed_by"] == "#605": + if row["closed_by"] == "#141": row["state"] = "closed" break - self.assert_overlay_fails(mismatch, "disagrees with #605", target="backlog") + self.assert_overlay_fails(mismatch, "disagrees with #141", target="backlog") + + def test_record_without_a_repository_fails(self) -> None: + self.assert_overlay_fails( + lambda overlay: overlay["github_issues"]["#141"].pop("repository"), + "github_issues entry '#141' missing repository", + target="backlog", + ) + + def test_live_record_naming_another_repository_fails(self) -> None: + self.assert_overlay_fails( + lambda overlay: overlay["github_issues"]["#141"].__setitem__("repository", "legacy"), + "'#141' must record repository 'studio-berry/loop'", + target="backlog", + ) + + def test_legacy_record_naming_the_live_repository_fails(self) -> None: + self.assert_overlay_fails( + lambda overlay: overlay["github_issues"]["legacy#124"].__setitem__( + "repository", "studio-berry/loop" + ), + "'legacy#124' must record repository 'legacy'", + target="backlog", + ) + + def test_open_legacy_issue_cannot_track_a_gap_fails(self) -> None: + def reopen(overlay: dict) -> None: + overlay["github_issues"]["legacy#124"]["state"] = "OPEN" + for row in overlay["backlog"]: + if row["id"] == "devicen-dieline-detection": + row["closed_by"] = "legacy#124" + row["state"] = "open" + + self.assert_overlay_fails(reopen, "cites open legacy issue legacy#124", target="backlog") + + def test_bare_number_does_not_satisfy_a_legacy_record_fails(self) -> None: + def collide(overlay: dict) -> None: + for row in overlay["backlog"]: + if row["id"] == "invisible-content-breadth": + row["gap"] += " (see #124)" + + self.assert_overlay_fails(collide, "cite issues with no verified record: #124", target="backlog") + + def test_gap_text_citing_an_unrecorded_issue_fails(self) -> None: + def cite(overlay: dict) -> None: + overlay["backlog"][0]["gap"] += " (filed as #98765)" + + self.assert_overlay_fails(cite, "cite issues with no verified record: #98765", target="backlog") + + def test_committed_overlay_records_the_live_repository_only_for_live_numbers(self) -> None: + for reference, entry in self.overlay()["github_issues"].items(): + expected = "legacy" if reference.startswith("legacy#") else "studio-berry/loop" + self.assertEqual(entry["repository"], expected, reference) + + def test_github_read_back_accepts_matching_records(self) -> None: + live = {"title": "t", "state": "OPEN", "milestone": None, "is_pull_request": False} + records = {"#7": {"number": 7, "title": "t", "state": "OPEN", "milestone": None, "repository": "studio-berry/loop"}} + self.assertEqual(generator.verify_issue_records_against_github(records, lambda number: live), []) + + def test_github_read_back_skips_legacy_snapshots(self) -> None: + def unreachable(number: int) -> dict: + raise AssertionError("a legacy snapshot must not be read back") + + records = { + "legacy#7": {"number": 7, "title": "t", "state": "CLOSED", "milestone": None, "repository": "legacy"} + } + self.assertEqual(generator.verify_issue_records_against_github(records, unreachable), []) + + def test_github_read_back_reports_every_collision_kind(self) -> None: + live = { + 1: {"title": "other", "state": "OPEN", "milestone": None, "is_pull_request": False}, + 2: {"title": "t", "state": "CLOSED", "milestone": None, "is_pull_request": False}, + 3: {"title": "t", "state": "OPEN", "milestone": "L01", "is_pull_request": False}, + 4: {"title": "t", "state": "OPEN", "milestone": None, "is_pull_request": True}, + 5: None, + } + records = { + f"#{number}": { + "number": number, + "title": "t", + "state": "OPEN", + "milestone": None, + "repository": "studio-berry/loop", + } + for number in live + } + problems = generator.verify_issue_records_against_github(records, live.__getitem__) + self.assertEqual(len(problems), 5) + self.assertIn("#1: title is 'other'", problems[0]) + self.assertIn("#2: state is 'CLOSED'", problems[1]) + self.assertIn("#3: milestone is 'L01'", problems[2]) + self.assertIn("#4: resolves to a pull request", problems[3]) + self.assertIn("#5: no such issue", problems[4]) def test_uncovered_class_missing_from_backlog_fails(self) -> None: def drop(overlay: dict) -> None: diff --git a/scripts/generate-architecture-catalogs.py b/scripts/generate-architecture-catalogs.py index 4ee804409..c342493c5 100644 --- a/scripts/generate-architecture-catalogs.py +++ b/scripts/generate-architecture-catalogs.py @@ -13,6 +13,7 @@ import difflib import json import re +import subprocess import sys from pathlib import Path from typing import Any, Iterable @@ -167,7 +168,12 @@ def parse_engine_matrix_id() -> str: PREFLIGHT_BACKLOG_PRIORITIES = {"P1", "P2", "P3"} PREFLIGHT_BACKLOG_STATES = {"open", "landed", "closed"} PREFLIGHT_BACKLOG_UNFILED = "unfiled" +GITHUB_ISSUE_REPOSITORY = "studio-berry/loop" +LEGACY_ISSUE_REPOSITORY = "legacy" GITHUB_ISSUE_REF = re.compile(r"#[1-9][0-9]*") +LEGACY_ISSUE_REF = re.compile(r"legacy#[1-9][0-9]*") +ISSUE_RECORD_KEY = re.compile(r"(?:legacy)?#[1-9][0-9]*") +ISSUE_REF_IN_TEXT = re.compile(r"(?`` is an issue in + ``studio-berry/loop`` that ``--verify-github`` can read back. ``legacy#`` + is a frozen snapshot from the retired repository, whose numbers overlap the + live ones and can no longer be read back: it may document a row that was + closed there, but it can never be the open tracker of a gap. """ issues = overlay.get("github_issues") if not isinstance(issues, dict) or not issues: raise ValueError("preflight catalog overlay is missing github_issues") verified: dict[str, dict[str, Any]] = {} for reference, entry in issues.items(): - if not isinstance(reference, str) or not GITHUB_ISSUE_REF.fullmatch(reference): - raise ValueError(f"github_issues key '{reference}' is not a '#' reference") + if not isinstance(reference, str) or not ISSUE_RECORD_KEY.fullmatch(reference): + raise ValueError( + f"github_issues key '{reference}' is not a '#' or 'legacy#' reference" + ) if not isinstance(entry, dict): raise ValueError(f"github_issues entry '{reference}' must be an object") - absent = sorted({"number", "title", "state", "milestone"} - set(entry)) + absent = sorted({"number", "title", "state", "milestone", "repository"} - set(entry)) if absent: raise ValueError(f"github_issues entry '{reference}' missing {', '.join(absent)}") - if entry["number"] != int(reference[1:]): + expected_repository = ( + LEGACY_ISSUE_REPOSITORY if LEGACY_ISSUE_REF.fullmatch(reference) else GITHUB_ISSUE_REPOSITORY + ) + if entry["repository"] != expected_repository: + raise ValueError( + f"github_issues entry '{reference}' must record repository '{expected_repository}'" + ) + if entry["number"] != int(reference.split("#")[1]): raise ValueError(f"github_issues entry '{reference}' records a different number") if entry["state"] not in {"OPEN", "CLOSED"}: raise ValueError(f"github_issues entry '{reference}' must record the GitHub state verbatim") @@ -434,12 +455,17 @@ def build_preflight_backlog( if closed_by == PREFLIGHT_BACKLOG_UNFILED: if state != "open": raise ValueError(f"backlog row '{identifier}' is unfiled but its state is '{state}'") - elif isinstance(closed_by, str) and GITHUB_ISSUE_REF.fullmatch(closed_by): + elif isinstance(closed_by, str) and ISSUE_RECORD_KEY.fullmatch(closed_by): if closed_by not in verified: raise ValueError( f"backlog row '{identifier}' references unverified issue {closed_by}; " "confirm it with 'gh issue view' and record it in github_issues first" ) + if LEGACY_ISSUE_REF.fullmatch(closed_by) and verified[closed_by]["state"] != "CLOSED": + raise ValueError( + f"backlog row '{identifier}' cites open legacy issue {closed_by}, which can no " + "longer be read back; re-point it to a live issue or unfile it with a deferral" + ) if state == "landed": raise ValueError(f"backlog row '{identifier}' is landed by a check, not by {closed_by}") expected = "open" if verified[closed_by]["state"] == "OPEN" else "closed" @@ -487,9 +513,9 @@ def build_preflight_backlog( referenced: set[str] = set() for row in parsed: - if GITHUB_ISSUE_REF.fullmatch(row["closed_by"]): + if ISSUE_RECORD_KEY.fullmatch(row["closed_by"]): referenced.add(row["closed_by"]) - referenced.update(re.findall(GITHUB_ISSUE_REF, row["gap"])) + referenced.update(ISSUE_REF_IN_TEXT.findall(row["gap"])) unused = sorted(reference for reference in verified if reference not in referenced) if unused: raise ValueError("github_issues entries cited by no backlog row: " + ", ".join(unused)) @@ -1170,6 +1196,57 @@ def serialized_preflight_catalog() -> str: return json.dumps(build_preflight_check_catalog(registry, parse_repair_operations()), indent=2, sort_keys=True) + "\n" +def fetch_github_issue(number: int) -> dict[str, Any] | None: + """Read one issue back from GitHub, or None when the number does not exist.""" + result = subprocess.run( + ["gh", "api", f"repos/{GITHUB_ISSUE_REPOSITORY}/issues/{number}"], + capture_output=True, + text=True, + encoding="utf-8", + check=False, + ) + if result.returncode != 0: + if "Not Found" in result.stdout or "Not Found" in result.stderr: + return None + raise OSError(f"gh api issue {number} failed: {result.stderr.strip()}") + live = json.loads(result.stdout) + return { + "title": live["title"], + "state": live["state"].upper(), + "milestone": (live.get("milestone") or {}).get("title"), + "is_pull_request": "pull_request" in live, + } + + +def verify_issue_records_against_github( + records: dict[str, dict[str, Any]], fetch: Any = fetch_github_issue +) -> list[str]: + """Compare every live ``#`` record with GitHub; legacy snapshots are not re-read. + + Issue and pull-request numbers share one sequence, so a record that resolves + to a pull request, or to an issue with another title, is a collision rather + than a verified reference. + """ + problems: list[str] = [] + live_records = [ + (reference, entry) + for reference, entry in records.items() + if entry["repository"] == GITHUB_ISSUE_REPOSITORY + ] + for reference, entry in sorted(live_records, key=lambda item: item[1]["number"]): + live = fetch(entry["number"]) + if live is None: + problems.append(f"{reference}: no such issue in {GITHUB_ISSUE_REPOSITORY}") + continue + if live["is_pull_request"]: + problems.append(f"{reference}: resolves to a pull request, not an issue") + continue + for field in ("title", "state", "milestone"): + if live[field] != entry[field]: + problems.append(f"{reference}: {field} is {live[field]!r} on GitHub, recorded {entry[field]!r}") + return problems + + def serialized_preflight_backlog() -> str: overlay = json.loads(read(PREFLIGHT_OVERLAY_PATH)) registry = parse_preflight_checks() @@ -1212,6 +1289,11 @@ def main() -> int: mode = parser.add_mutually_exclusive_group(required=True) mode.add_argument("--check", action="store_true", help="validate ADRs and the committed catalog") mode.add_argument("--write", action="store_true", help="validate ADRs and write the catalog") + parser.add_argument( + "--verify-github", + action="store_true", + help="read every live github_issues record back from GitHub (needs gh; not run in CI)", + ) args = parser.parse_args() errors = validate_adrs() @@ -1230,6 +1312,19 @@ def main() -> int: print(f"error: cannot generate architecture catalog: {error}", file=sys.stderr) return 1 + if args.verify_github: + try: + problems = verify_issue_records_against_github( + json.loads(read(PREFLIGHT_OVERLAY_PATH))["github_issues"] + ) + except OSError as error: + print(f"error: cannot read issues back from GitHub: {error}", file=sys.stderr) + return 1 + if problems: + for problem in problems: + print(f"error: github_issues {problem}", file=sys.stderr) + return 1 + if args.write: CATALOG_PATH.parent.mkdir(parents=True, exist_ok=True) CATALOG_PATH.write_text(expected, encoding="utf-8", newline="\n")