diff --git a/insights/QA_2026-10-31_D08_POSTMERGE_AUDIT.md b/insights/QA_2026-10-31_D08_POSTMERGE_AUDIT.md new file mode 100644 index 00000000..50d4a678 --- /dev/null +++ b/insights/QA_2026-10-31_D08_POSTMERGE_AUDIT.md @@ -0,0 +1,15 @@ +# D08 — actual post-merge artifact audit and setup-stage discrepancy + +Date: 2026-10-31. Base main `6d15d86344ab1281391d3ea1b458dc18ae443985`, open PRs and `AGENTS.md` reviewed. #40 (merge `a671ebc26bdde180602a8df3fa99f8651e1bced5`) and #52 (merge `1e3a1e7131bd6e8d9c9d9d9c93ae5c9203c0f455`) are closed/merged. Only D-owned paths modified. + +## Independently executed readbacks (not inherited status) + +- Directly downloaded `qa-miktex-windows-server` artifact 13044230534 for push-main run 41203529698, checked ZIP SHA-256 `0c6f7a1af3871ab7078d104272c6a1fe221f1bb095747f5468abd58776430eb1`, 44 files. `miktex-qa.json`: exact commit/source head `6d15d86344ab1281391d3ea1b458dc18ae443985`; four results PASS (hand-authored primitive baseline, strict missing-library, strict Lua-only, real N1 app Session-generated source). It explicitly says physical Win10/M1/M4, mouse, help, GUI themes: NOT_RUN. +- Real executable and installer from setup/QA: MiKTeX-XeTeX 4.16 (MiKTeX 25.12), official 25.12 installer SHA `14b42dd9f4b4a7813a8bfd69c8f99316c2888cc4ee26f631f397e163d85d6c62`, private TEXMF roots, `AutoInstall=0` readback, explicit disabled installer in direct XeLaTeX command. `packages-before.txt` and `packages-after.txt` **byte-identical**, exactly 9130 lines. +- Extracted all six actual PDFs and independently ran `pdfinfo`, `pdftotext -enc UTF-8`, `pdffonts`; every PDF is one page, Cyrillic `Примитивы TikZ` or `Геометрия_1 & оптика 25%` and math glyphs `𝛼`/`𝛽` are present, embedded DejaVuSerif/LatinModernMath recorded. All six reported PDF SHA-256 values match PDF bytes; all N1 source SHA-256 values match archived generated `.tex` bytes before/after. Missing-library and graphdrawing Lua-only log errors have exact source-specific diagnostics and `No pages of output`; no corresponding PDFs in ZIP. +- Detected benign copy nuance: top-level `proof with spaces Кириллица/direct-tex.log` is a **UTF-8 reserialization** of `direct/main.log`; SHA in QA correctly refers to original `direct/main.log`, not the normalized copy. Did not count this as a hash failure. +- Real defect: `setup-stage.json` remained `INCOMPLETE` at the earlier `package-metadata-validated` checkpoint after actual success. Unlike provenance/PDF checks, stage metadata is false/stale for completed setup. Need terminal stage and strict QA readback, plus installed-package postcondition, not a weakening of acceptance. + +## D08 correction and proof status + +The D08 branch updates `scripts/tools/qa_setup_miktex.ps1` terminal checkpoint after setup.json, verifies every declared package is now installed, adds `setup_stage_complete()` to the existing XeLaTeX QA report, and adds reject cases in Python stdlib tests. All changes grouped in one D-only commit with this insight and D08 plan. **Modified exact-head hosted MiKTeX, Rust and D QA are PENDING** until their respective workflow logs/artifacts complete. Do not transfer verified main SHA status to new D08 HEAD. No physical user-host or GUI acceptance claimed. diff --git a/plans/QA_2026-10-31_D08_POSTMERGE_MIKTEX.md b/plans/QA_2026-10-31_D08_POSTMERGE_MIKTEX.md new file mode 100644 index 00000000..59d025ac --- /dev/null +++ b/plans/QA_2026-10-31_D08_POSTMERGE_MIKTEX.md @@ -0,0 +1,17 @@ +# D08 — exact-main MiKTeX acceptance and terminal setup evidence + +Date: 2026-10-31. Read fresh `main` SHA `6d15d86344ab1281391d3ea1b458dc18ae443985`, open PRs (#48/#50/#54/#55), `AGENTS.md`, D06 plan and insight, and actual workflow/artifacts before changes. Work only on D-owned scripts, tests and QA documentation; D06 #40 and D07 #52 are already merged, so do not attempt to rewrite closed PRs. This is a narrow fail-closed correction to the **existing real MiKTeX pipeline**, not a standalone TeX fixture. + +## Independently checked post-merge main + +[Run 41203529698](https://github.com/i-shutov/tikzarium/actions/runs/41203529698) on exact **push-main** `6d15d86344ab1281391d3ea1b458dc18ae443985`, hosted Windows Server 2022 x64: SUCCESS, one genuine MiKTeX 25.12 job. [Artifact 13044230534](https://github.com/i-shutov/tikzarium/actions/runs/41203529698/artifacts/13044230534) downloaded and inspected, 44 members, ZIP SHA-256 `0c6f7a1af3871ab7078d104272c6a1fe221f1bb095747f5468abd58776430eb1`. Report `commit_sha` and `source_head_sha` both equal main (no synthetic PR merge). Installer hash `14b42dd9f4b4a7813a8bfd69c8f99316c2888cc4ee26f631f397e163d85d6c62`, private effective roots, `AutoInstall=0`, `--disable-installer`, and engine `MiKTeX-XeTeX 4.16 (MiKTeX 25.12)` proven by setup/engine evidence. Canonical package inventories 9130/9130 lines, byte-identical. Actual six one-page PDFs independently examined with Poppler: text and mathematical italic Greek extract correctly, DejaVuSerif/LatinModernMath embedded. The N1 producer is `tikzarium-app --bin n1-authoring-proof`, using C primitive catalogue; source SHA recorded and unchanged through CLI/direct engine. Lua-only and missing-library both nonzero, required diagnostics and no PDF. Rust [41203529736](https://github.com/i-shutov/tikzarium/actions/runs/41203529736) 3/3 and D QA [41203529679](https://github.com/i-shutov/tikzarium/actions/runs/41203529679) 7/7 also SUCCESS. + +## Genuine evidence defect (discovered in independent archive) + +The post-merge artifact's `setup-stage.json` **still says** `stage=package-metadata-validated, status=INCOMPLETE` despite terminal `setup.json` and four QA results passing. It is an unfinalized *progress checkpoint*, not a MiKTeX setup failure. Consumers must never mistake this stale status for a current setup verdict. D08 updates the existing setup script to atomically publish `stage=setup-complete, status=SETUP_COMPLETE` only after `setup.json` has been written, and makes the existing real PDF test require an exact, matching terminal checkpoint. Also rechecks `isInstalled=true` for all nine declared packages after explicit missing-only installs (not merely successful exit), without auto-install or global state. Add stdlib negative regressions; previous source/glyph/error/embedded-font/package-inventory gates unchanged. + +**Current D08 branch is NOT accepted until its own exact-SHA real MiKTeX CI, new archived setup checkpoint and all six actual PDF proofs have been inspected.** Do not inherit main PASS to modified D08 SHA. No additional full workflow runs for one-file micro-commits; group script/tests/docs into a single commit. + +## Acceptance boundaries + +Hosted Windows Server CI_ONLY; **physical Windows 10 x64** with independently installed external MiKTeX is `NOT_RUN`. M1/M4 with external XeLaTeX and real egui mouse F1/hover/code pane/themes/screenshots are `NOT_RUN` and owned by independent target-host/B/E/F acceptance, not N1 Session emulation. R4 licensed contents not imported; O027/O028/O030/O047 remain open. No changes to A/B/C production files, MiKTeX installer or user TeX configuration. diff --git a/scripts/tools/qa_miktex_vertical.py b/scripts/tools/qa_miktex_vertical.py index 9b8f38e1..90f6eecd 100644 --- a/scripts/tools/qa_miktex_vertical.py +++ b/scripts/tools/qa_miktex_vertical.py @@ -49,6 +49,20 @@ def miktex_engine(version: str) -> bool: return "miktex" in version.casefold() and "xetex" in version.casefold() +def setup_stage_complete(stage: dict, setup: dict) -> bool: + """Require terminal, provenance-matched setup; never accept stale INCOMPLETE.""" + installer = setup.get("installer_sha256") + engine = setup.get("xelatex_path") + return (isinstance(stage, dict) and stage.get("schema_version") == 1 + and stage.get("stage") == "setup-complete" + and stage.get("status") == "SETUP_COMPLETE" + and isinstance(installer, str) and bool(installer) + and isinstance(engine, str) and bool(engine) + and stage.get("installer_sha256") == installer + and stage.get("xelatex_path") == engine + and stage.get("auto_install_config_readback") == "0") + + def poppler_version(tool: str, transcript: str) -> bool: """A real Poppler auditor, not MiKTeX's same-named TeX shim.""" return bool(re.search(rf"(?m)^{re.escape(tool)} version [0-9]+(?:[.][0-9]+)+", transcript)) @@ -336,7 +350,11 @@ def main(argv: list[str] | None = None) -> int: or setup.get("auto_install_disabled") is not True or Path(setup.get("user_install", "")).resolve() != prefix): raise RuntimeError("Isolated MiKTeX setup provenance or auto-install-off proof absent") + stage = json.loads(args.setup_record.with_name("setup-stage.json").read_text(encoding="utf-8-sig")) + if not setup_stage_complete(stage, setup): + raise RuntimeError("MiKTeX private setup-stage is incomplete or provenance-mismatched") report["setup"] = setup + report["setup_stage"] = stage report["pdf_tools"] = select_poppler(args.poppler_dir) if os.environ.get("TIKZARIUM_XELATEX", "").casefold() != str(engine).casefold(): raise RuntimeError("CLI must be explicitly pointed to this MiKTeX XeLaTeX binary") @@ -348,7 +366,7 @@ def main(argv: list[str] | None = None) -> int: root / "extensions/fixtures/graphdrawing-lua.tex", "lua-only", "lua-only", output, engine)) report["results"].append(n1_positive(root, output, engine, cli, args.n1_proof.resolve())) - except (OSError, RuntimeError, subprocess.TimeoutExpired) as exc: + except (OSError, RuntimeError, json.JSONDecodeError, subprocess.TimeoutExpired) as exc: report["fatal"] = f"{type(exc).__name__}: {exc}" finally: report["accepted"] = not report.get("fatal") and len(report["results"]) == 4 and all( diff --git a/scripts/tools/qa_setup_miktex.ps1 b/scripts/tools/qa_setup_miktex.ps1 index 7d1dbfd4..7ea4e041 100644 --- a/scripts/tools/qa_setup_miktex.ps1 +++ b/scripts/tools/qa_setup_miktex.ps1 @@ -127,6 +127,18 @@ $packageVersionFile = Join-Path $recordDir 'explicit-package-versions.txt' foreach ($package in $packages) { $info = (& $miktexExe packages info --template '{id}|{version}|{timePackaged}|{isInstalled}' $package 2>&1 | Out-String).Trim() if ($LASTEXITCODE -ne 0) { throw "Cannot inspect installed package version for $package" } + # A successful explicit install is not a proof that *each* dependency is now installed. + # Reject unknown/duplicate/malformed post-provision metadata, not just absent TeX files. + $installedRows = @($info -split '(?:\r\n|\r|\n)' | + Where-Object { $_ -match ('^' + [regex]::Escape($package) + '\|') }) + if ($installedRows.Count -ne 1) { + throw "MiKTeX post-provision package metadata missing/ambiguous for $package" + } + $installedFields = @($installedRows[0].Trim() -split '\|', 4) + if ($installedFields.Count -ne 4 -or $installedFields[0] -cne $package -or + $installedFields[3] -cne 'true') { + throw "MiKTeX declared package $package is not confirmed installed after provisioning" + } "$package => $info" | Out-File -FilePath $packageVersionFile -Append -Encoding utf8 } @@ -155,4 +167,11 @@ $setupRecord = @{ ci_host = 'windows-2022'; target_windows10 = 'NOT_RUN'; physical_mac_arm = 'NOT_RUN' } $setupRecord | ConvertTo-Json -Depth 5 | Out-File -FilePath (Join-Path $recordDir 'setup.json') -Encoding utf8 +# Terminal status is for the *setup stage*, not the XeLaTeX/PDF QA job. +# An incomplete checkpoint must not survive a successfully committed setup.json. +@{ schema_version = 1; stage = 'setup-complete'; status = 'SETUP_COMPLETE' + installer_sha256 = $downloadedHash; xelatex_path = $xelatex + auto_install_config_readback = $configValue; host = 'windows-2022' + physical_windows10 = 'NOT_RUN' } | + ConvertTo-Json | Set-Content -LiteralPath (Join-Path $recordDir 'setup-stage.json') -Encoding utf8 Write-Host "MiKTeX private XeLaTeX: $xelatex" diff --git a/scripts/tools/test_qa_miktex_vertical.py b/scripts/tools/test_qa_miktex_vertical.py index 26b96d1f..a0ada292 100644 --- a/scripts/tools/test_qa_miktex_vertical.py +++ b/scripts/tools/test_qa_miktex_vertical.py @@ -4,7 +4,7 @@ from pathlib import Path import unittest -from qa_miktex_vertical import MISSING_LIBRARY, PRIMITIVE_TOKENS, N1_TOOLS, miktex_engine, n1_state_contract, poppler_version, tex_failure +from qa_miktex_vertical import MISSING_LIBRARY, PRIMITIVE_TOKENS, N1_TOOLS, miktex_engine, n1_state_contract, poppler_version, setup_stage_complete, tex_failure class MiKTeXVerticalContract(unittest.TestCase): @@ -21,6 +21,22 @@ def test_miktex_version_must_be_xetex(self): self.assertFalse(miktex_engine("XeTeX 3.14159 (TeX Live 2026)")) self.assertFalse(miktex_engine("MiKTeX-pdfTeX 4.0")) + def test_terminal_setup_provenance_rejects_stale_or_mismatched_checkpoint(self): + setup = {"installer_sha256": "0" * 64, "xelatex_path": r"D:\private\xelatex.exe"} + stage = {"schema_version": 1, "stage": "setup-complete", + "status": "SETUP_COMPLETE", "installer_sha256": setup["installer_sha256"], + "xelatex_path": setup["xelatex_path"], "auto_install_config_readback": "0"} + self.assertTrue(setup_stage_complete(stage, setup)) + for changed in ( + {"status": "INCOMPLETE"}, {"stage": "package-metadata-validated"}, + {"installer_sha256": "1" * 64}, {"xelatex_path": r"C:\TeXLive\xelatex.exe"}, + {"auto_install_config_readback": "1"}, {"schema_version": 0}, + ): + with self.subTest(changed=changed): + self.assertFalse(setup_stage_complete({**stage, **changed}, setup)) + self.assertFalse(setup_stage_complete({}, setup)) + self.assertFalse(setup_stage_complete(stage, {**setup, "xelatex_path": ""})) + def test_lua_negative_requires_complete_message_and_absence_pdf(self): message = "You need to run LuaTeX to use the grap\nh drawing library." self.assertTrue(tex_failure("lua-only", 1, message, False)) @@ -53,6 +69,8 @@ def test_private_index_refresh_is_fail_closed_and_precedes_package_install(self) self.assertIn("package-metadata-before-install.txt", setup) self.assertIn("private-package-index-refreshed", setup) self.assertNotIn("packages update @packages", setup) + self.assertIn("MiKTeX declared package $package is not confirmed installed", setup) + self.assertIn("stage = 'setup-complete'; status = 'SETUP_COMPLETE'", setup) def test_n1_session_contract_rejects_missing_ownership_and_undo(self): source = (r"\newcommand{\UserPath}{\path (0,0);}"