brewdoc 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (453) hide show
  1. brewdoc-0.3.0/.gitattributes +4 -0
  2. brewdoc-0.3.0/.github/workflows/ci.yml +124 -0
  3. brewdoc-0.3.0/.github/workflows/recover_v020.py +514 -0
  4. brewdoc-0.3.0/.github/workflows/release.yml +197 -0
  5. brewdoc-0.3.0/.github/workflows/verify_install.py +460 -0
  6. brewdoc-0.3.0/FORMATS.md +364 -0
  7. {brewdoc-0.2.0 → brewdoc-0.3.0}/PKG-INFO +101 -22
  8. {brewdoc-0.2.0 → brewdoc-0.3.0}/README.md +98 -21
  9. brewdoc-0.3.0/RELEASING.md +88 -0
  10. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/CATALOG.md +21 -8
  11. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/METHODOLOGY.md +6 -4
  12. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/README.md +41 -11
  13. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/README.md +23 -8
  14. brewdoc-0.3.0/benchmarks/annotations/epub-epubcheck-minimal.json +56 -0
  15. brewdoc-0.3.0/benchmarks/annotations/epub-standardebooks-herland.json +157 -0
  16. brewdoc-0.3.0/benchmarks/compare_latency.py +672 -0
  17. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/corpus.json +62 -1
  18. {brewdoc-0.2.0 → brewdoc-0.3.0}/pyproject.toml +6 -3
  19. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/__init__.py +3 -2
  20. brewdoc-0.3.0/src/brewdoc/_urljs/__init__.py +1 -0
  21. brewdoc-0.3.0/src/brewdoc/_urljs/glue.js +858 -0
  22. brewdoc-0.3.0/src/brewdoc/_urljs/native.py +922 -0
  23. brewdoc-0.3.0/src/brewdoc/_vendor/THIRD_PARTY_NOTICES.txt +342 -0
  24. brewdoc-0.3.0/src/brewdoc/_vendor/linkedom-worker.js +12650 -0
  25. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/cli.py +48 -16
  26. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/common.py +228 -41
  27. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/docx.py +6 -2
  28. brewdoc-0.3.0/src/brewdoc/epub.py +1099 -0
  29. brewdoc-0.3.0/src/brewdoc/htmljs.py +776 -0
  30. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/htmltext.py +6 -2
  31. brewdoc-0.3.0/src/brewdoc/htmlurl.py +719 -0
  32. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/pdf.py +16 -9
  33. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/selfcheck.py +46 -0
  34. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/service.py +7 -3
  35. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/sheets.py +4 -3
  36. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/SOURCES.md +11 -11
  37. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/receipts.json +150 -0
  38. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_benchmark_metadata.py +13 -7
  39. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_capture_results.py +3 -0
  40. brewdoc-0.3.0/tests/test_compare_latency.py +630 -0
  41. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_contract.py +233 -3
  42. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_documents.py +47 -0
  43. brewdoc-0.3.0/tests/test_epub.py +713 -0
  44. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_fixtures.py +59 -14
  45. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_html.py +64 -0
  46. brewdoc-0.3.0/tests/test_html_js.py +336 -0
  47. brewdoc-0.3.0/tests/test_html_js_a10.py +635 -0
  48. brewdoc-0.3.0/tests/test_html_js_bootstrap.py +348 -0
  49. brewdoc-0.3.0/tests/test_html_js_content.py +207 -0
  50. brewdoc-0.3.0/tests/test_html_js_inner_text.py +195 -0
  51. brewdoc-0.3.0/tests/test_html_js_promises.py +600 -0
  52. brewdoc-0.3.0/tests/test_html_js_viewport.py +61 -0
  53. brewdoc-0.3.0/tests/test_html_native.py +312 -0
  54. brewdoc-0.3.0/tests/test_html_search_post.py +512 -0
  55. brewdoc-0.3.0/tests/test_html_search_post_js.py +281 -0
  56. brewdoc-0.3.0/tests/test_html_snapshot_guard.py +423 -0
  57. brewdoc-0.3.0/tests/test_html_snapshot_ownership.py +140 -0
  58. brewdoc-0.3.0/tests/test_html_timeout.py +568 -0
  59. brewdoc-0.3.0/tests/test_html_timeout_guards.py +509 -0
  60. brewdoc-0.3.0/tests/test_html_url.py +648 -0
  61. brewdoc-0.3.0/tests/test_html_url_cli.py +109 -0
  62. brewdoc-0.3.0/tests/test_install_verification.py +310 -0
  63. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_output.py +51 -0
  64. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_pdf_regressions.py +25 -0
  65. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_presentations.py +34 -0
  66. brewdoc-0.3.0/tests/test_release_recovery.py +388 -0
  67. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/test_spreadsheets.py +135 -0
  68. {brewdoc-0.2.0 → brewdoc-0.3.0}/uv.lock +206 -169
  69. brewdoc-0.2.0/.gitattributes +0 -1
  70. brewdoc-0.2.0/.github/workflows/ci.yml +0 -27
  71. brewdoc-0.2.0/.github/workflows/release.yml +0 -57
  72. brewdoc-0.2.0/FORMATS.md +0 -189
  73. brewdoc-0.2.0/RELEASING.md +0 -31
  74. {brewdoc-0.2.0 → brewdoc-0.3.0}/.github/dependabot.yml +0 -0
  75. {brewdoc-0.2.0 → brewdoc-0.3.0}/.gitignore +0 -0
  76. {brewdoc-0.2.0 → brewdoc-0.3.0}/.python-version +0 -0
  77. {brewdoc-0.2.0 → brewdoc-0.3.0}/LICENSE +0 -0
  78. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/LICENSES/apache-httpd-LICENSE.txt +0 -0
  79. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/LICENSES/apache-httpd-NOTICE.txt +0 -0
  80. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/LICENSES/calamine-MIT.txt +0 -0
  81. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/docx-libreoffice-negative-cell-margin-twips.json +0 -0
  82. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/docx-poi-deep-table-cell.json +0 -0
  83. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/docx-poi-table-alignment.json +0 -0
  84. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/docx-poi-three-col-head.json +0 -0
  85. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/docx-tika-testword-various.json +0 -0
  86. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/docx-tika-testword.json +0 -0
  87. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/html-apache-httpd-allowmethods.json +0 -0
  88. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/html-mdn-document-structure.json +0 -0
  89. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/html-mdn-first-form-styled.json +0 -0
  90. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/html-mdn-items-sold-headers.json +0 -0
  91. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/html-mdn-planets-data.json +0 -0
  92. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/html-mdn-timetable-caption.json +0 -0
  93. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/ods-calamine-any-sheets.json +0 -0
  94. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/ods-calamine-date.json +0 -0
  95. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/ods-calamine-issues.json +0 -0
  96. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/ods-calamine-merged-cells.json +0 -0
  97. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/ods-calamine-special-cells.json +0 -0
  98. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-arxiv-2302.13971v1.json +0 -0
  99. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-arxiv-2305.18290v3.json +0 -0
  100. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-arxiv-2402.00838v4.json +0 -0
  101. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-eia-steo-table1.json +0 -0
  102. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-fbi-nics-background-checks-2015-11.json +0 -0
  103. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-federal-register-2020-17221.json +0 -0
  104. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-nist-sp800-145.json +0 -0
  105. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-us-patent-223898-scan.json +0 -0
  106. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pdf-us-senate-expenditures.json +0 -0
  107. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pptx-poi-table-test.json +0 -0
  108. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pptx-poi-testppt.json +0 -0
  109. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pptx-python-pptx-test.json +0 -0
  110. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/pptx-tika-testppt-various2.json +0 -0
  111. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xls-calamine-any-sheets.json +0 -0
  112. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xls-calamine-date.json +0 -0
  113. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xls-calamine-merge-cells.json +0 -0
  114. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xls-calamine-xls-formula.json +0 -0
  115. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xls-census-st-est00int-01.json +0 -0
  116. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsb-calamine-date.json +0 -0
  117. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsb-calamine-issue127.json +0 -0
  118. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsb-calamine-issues.json +0 -0
  119. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsm-calamine-issue221.json +0 -0
  120. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsm-calamine-issue3.json +0 -0
  121. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsm-calamine-vba.json +0 -0
  122. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsx-calamine-any-sheets.json +0 -0
  123. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsx-calamine-date.json +0 -0
  124. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsx-calamine-inventory-table.json +0 -0
  125. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsx-calamine-merge-cells.json +0 -0
  126. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsx-calamine-temperature-table.json +0 -0
  127. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/annotations/xlsx-census-nst-est2024-pop.json +0 -0
  128. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/capture_results.py +0 -0
  129. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/README.md +0 -0
  130. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/ods-calamine-any-sheets.json +0 -0
  131. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/ods-calamine-date.json +0 -0
  132. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/ods-calamine-issues.json +0 -0
  133. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/ods-calamine-merged-cells.json +0 -0
  134. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/ods-calamine-special-cells.json +0 -0
  135. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-arxiv-2302.13971v1.json +0 -0
  136. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-arxiv-2305.18290v3.json +0 -0
  137. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-arxiv-2402.00838v4.json +0 -0
  138. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-eia-steo-table1.json +0 -0
  139. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-fbi-nics-background-checks-2015-11.json +0 -0
  140. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-federal-register-2020-17221.json +0 -0
  141. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-nist-sp800-145.json +0 -0
  142. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-us-patent-223898-scan.json +0 -0
  143. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/pdf-us-senate-expenditures.json +0 -0
  144. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xls-calamine-any-sheets.json +0 -0
  145. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xls-calamine-date.json +0 -0
  146. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xls-calamine-merge-cells.json +0 -0
  147. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xls-calamine-xls-formula.json +0 -0
  148. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xls-census-st-est00int-01.json +0 -0
  149. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsb-calamine-date.json +0 -0
  150. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsb-calamine-issue127.json +0 -0
  151. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsb-calamine-issues.json +0 -0
  152. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsm-calamine-issue221.json +0 -0
  153. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsm-calamine-issue3.json +0 -0
  154. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsm-calamine-vba.json +0 -0
  155. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsx-calamine-any-sheets.json +0 -0
  156. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsx-calamine-date.json +0 -0
  157. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsx-calamine-inventory-table.json +0 -0
  158. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsx-calamine-merge-cells.json +0 -0
  159. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsx-calamine-temperature-table.json +0 -0
  160. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/annotations/xlsx-census-nst-est2024-pop.json +0 -0
  161. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/context.json +0 -0
  162. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/corpus-v1.json +0 -0
  163. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/corpus.json +0 -0
  164. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/markdown-integrity.json +0 -0
  165. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-any-sheets/artifacts/document.md +0 -0
  166. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-any-sheets/receipt.json +0 -0
  167. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-any-sheets/result.json +0 -0
  168. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-date/artifacts/document.md +0 -0
  169. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-date/receipt.json +0 -0
  170. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-date/result.json +0 -0
  171. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-issues/artifacts/document.md +0 -0
  172. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-issues/receipt.json +0 -0
  173. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-issues/result.json +0 -0
  174. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-merged-cells/artifacts/document.md +0 -0
  175. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-merged-cells/receipt.json +0 -0
  176. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-merged-cells/result.json +0 -0
  177. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-special-cells/artifacts/document.md +0 -0
  178. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-special-cells/receipt.json +0 -0
  179. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/ods-calamine-special-cells/result.json +0 -0
  180. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2302.13971v1/artifacts/document.md +0 -0
  181. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2302.13971v1/receipt.json +0 -0
  182. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2302.13971v1/result.json +0 -0
  183. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2305.18290v3/artifacts/document.md +0 -0
  184. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2305.18290v3/receipt.json +0 -0
  185. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2305.18290v3/result.json +0 -0
  186. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2402.00838v4/artifacts/document.md +0 -0
  187. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2402.00838v4/receipt.json +0 -0
  188. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-arxiv-2402.00838v4/result.json +0 -0
  189. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-eia-steo-table1/artifacts/document.md +0 -0
  190. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-eia-steo-table1/receipt.json +0 -0
  191. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-eia-steo-table1/result.json +0 -0
  192. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-fbi-nics-background-checks-2015-11/artifacts/document.md +0 -0
  193. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-fbi-nics-background-checks-2015-11/receipt.json +0 -0
  194. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-fbi-nics-background-checks-2015-11/result.json +0 -0
  195. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-federal-register-2020-17221/artifacts/document.md +0 -0
  196. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-federal-register-2020-17221/receipt.json +0 -0
  197. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-federal-register-2020-17221/result.json +0 -0
  198. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-nist-sp800-145/artifacts/document.md +0 -0
  199. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-nist-sp800-145/receipt.json +0 -0
  200. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-nist-sp800-145/result.json +0 -0
  201. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-us-patent-223898-scan/receipt.json +0 -0
  202. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-us-patent-223898-scan/result.json +0 -0
  203. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-us-senate-expenditures/artifacts/document.md +0 -0
  204. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-us-senate-expenditures/receipt.json +0 -0
  205. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/pdf-us-senate-expenditures/result.json +0 -0
  206. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-any-sheets/artifacts/document.md +0 -0
  207. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-any-sheets/receipt.json +0 -0
  208. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-any-sheets/result.json +0 -0
  209. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-date/artifacts/document.md +0 -0
  210. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-date/receipt.json +0 -0
  211. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-date/result.json +0 -0
  212. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-merge-cells/artifacts/document.md +0 -0
  213. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-merge-cells/receipt.json +0 -0
  214. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-merge-cells/result.json +0 -0
  215. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-xls-formula/artifacts/document.md +0 -0
  216. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-xls-formula/receipt.json +0 -0
  217. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-calamine-xls-formula/result.json +0 -0
  218. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-census-st-est00int-01/artifacts/document.md +0 -0
  219. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-census-st-est00int-01/receipt.json +0 -0
  220. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xls-census-st-est00int-01/result.json +0 -0
  221. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-date/artifacts/document.md +0 -0
  222. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-date/receipt.json +0 -0
  223. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-date/result.json +0 -0
  224. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-issue127/artifacts/document.md +0 -0
  225. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-issue127/receipt.json +0 -0
  226. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-issue127/result.json +0 -0
  227. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-issues/artifacts/document.md +0 -0
  228. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-issues/receipt.json +0 -0
  229. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsb-calamine-issues/result.json +0 -0
  230. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-issue221/artifacts/document.md +0 -0
  231. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-issue221/receipt.json +0 -0
  232. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-issue221/result.json +0 -0
  233. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-issue3/artifacts/document.md +0 -0
  234. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-issue3/receipt.json +0 -0
  235. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-issue3/result.json +0 -0
  236. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-vba/artifacts/document.md +0 -0
  237. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-vba/receipt.json +0 -0
  238. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsm-calamine-vba/result.json +0 -0
  239. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-any-sheets/artifacts/document.md +0 -0
  240. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-any-sheets/receipt.json +0 -0
  241. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-any-sheets/result.json +0 -0
  242. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-date/artifacts/document.md +0 -0
  243. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-date/receipt.json +0 -0
  244. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-date/result.json +0 -0
  245. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-inventory-table/artifacts/document.md +0 -0
  246. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-inventory-table/receipt.json +0 -0
  247. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-inventory-table/result.json +0 -0
  248. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-merge-cells/artifacts/document.md +0 -0
  249. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-merge-cells/receipt.json +0 -0
  250. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-merge-cells/result.json +0 -0
  251. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-temperature-table/artifacts/document.md +0 -0
  252. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-temperature-table/receipt.json +0 -0
  253. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-calamine-temperature-table/result.json +0 -0
  254. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-census-nst-est2024-pop/artifacts/document.md +0 -0
  255. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-census-nst-est2024-pop/receipt.json +0 -0
  256. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-1/xlsx-census-nst-est2024-pop/result.json +0 -0
  257. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-any-sheets/artifacts/document.md +0 -0
  258. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-any-sheets/receipt.json +0 -0
  259. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-any-sheets/result.json +0 -0
  260. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-date/artifacts/document.md +0 -0
  261. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-date/receipt.json +0 -0
  262. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-date/result.json +0 -0
  263. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-issues/artifacts/document.md +0 -0
  264. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-issues/receipt.json +0 -0
  265. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-issues/result.json +0 -0
  266. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-merged-cells/artifacts/document.md +0 -0
  267. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-merged-cells/receipt.json +0 -0
  268. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-merged-cells/result.json +0 -0
  269. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-special-cells/artifacts/document.md +0 -0
  270. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-special-cells/receipt.json +0 -0
  271. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/ods-calamine-special-cells/result.json +0 -0
  272. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2302.13971v1/artifacts/document.md +0 -0
  273. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2302.13971v1/receipt.json +0 -0
  274. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2302.13971v1/result.json +0 -0
  275. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2305.18290v3/artifacts/document.md +0 -0
  276. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2305.18290v3/receipt.json +0 -0
  277. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2305.18290v3/result.json +0 -0
  278. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2402.00838v4/artifacts/document.md +0 -0
  279. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2402.00838v4/receipt.json +0 -0
  280. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-arxiv-2402.00838v4/result.json +0 -0
  281. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-eia-steo-table1/artifacts/document.md +0 -0
  282. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-eia-steo-table1/receipt.json +0 -0
  283. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-eia-steo-table1/result.json +0 -0
  284. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-fbi-nics-background-checks-2015-11/artifacts/document.md +0 -0
  285. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-fbi-nics-background-checks-2015-11/receipt.json +0 -0
  286. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-fbi-nics-background-checks-2015-11/result.json +0 -0
  287. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-federal-register-2020-17221/artifacts/document.md +0 -0
  288. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-federal-register-2020-17221/receipt.json +0 -0
  289. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-federal-register-2020-17221/result.json +0 -0
  290. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-nist-sp800-145/artifacts/document.md +0 -0
  291. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-nist-sp800-145/receipt.json +0 -0
  292. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-nist-sp800-145/result.json +0 -0
  293. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-us-patent-223898-scan/receipt.json +0 -0
  294. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-us-patent-223898-scan/result.json +0 -0
  295. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-us-senate-expenditures/artifacts/document.md +0 -0
  296. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-us-senate-expenditures/receipt.json +0 -0
  297. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/pdf-us-senate-expenditures/result.json +0 -0
  298. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-any-sheets/artifacts/document.md +0 -0
  299. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-any-sheets/receipt.json +0 -0
  300. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-any-sheets/result.json +0 -0
  301. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-date/artifacts/document.md +0 -0
  302. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-date/receipt.json +0 -0
  303. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-date/result.json +0 -0
  304. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-merge-cells/artifacts/document.md +0 -0
  305. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-merge-cells/receipt.json +0 -0
  306. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-merge-cells/result.json +0 -0
  307. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-xls-formula/artifacts/document.md +0 -0
  308. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-xls-formula/receipt.json +0 -0
  309. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-calamine-xls-formula/result.json +0 -0
  310. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-census-st-est00int-01/artifacts/document.md +0 -0
  311. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-census-st-est00int-01/receipt.json +0 -0
  312. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xls-census-st-est00int-01/result.json +0 -0
  313. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-date/artifacts/document.md +0 -0
  314. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-date/receipt.json +0 -0
  315. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-date/result.json +0 -0
  316. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-issue127/artifacts/document.md +0 -0
  317. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-issue127/receipt.json +0 -0
  318. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-issue127/result.json +0 -0
  319. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-issues/artifacts/document.md +0 -0
  320. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-issues/receipt.json +0 -0
  321. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsb-calamine-issues/result.json +0 -0
  322. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-issue221/artifacts/document.md +0 -0
  323. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-issue221/receipt.json +0 -0
  324. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-issue221/result.json +0 -0
  325. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-issue3/artifacts/document.md +0 -0
  326. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-issue3/receipt.json +0 -0
  327. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-issue3/result.json +0 -0
  328. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-vba/artifacts/document.md +0 -0
  329. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-vba/receipt.json +0 -0
  330. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsm-calamine-vba/result.json +0 -0
  331. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-any-sheets/artifacts/document.md +0 -0
  332. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-any-sheets/receipt.json +0 -0
  333. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-any-sheets/result.json +0 -0
  334. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-date/artifacts/document.md +0 -0
  335. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-date/receipt.json +0 -0
  336. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-date/result.json +0 -0
  337. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-inventory-table/artifacts/document.md +0 -0
  338. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-inventory-table/receipt.json +0 -0
  339. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-inventory-table/result.json +0 -0
  340. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-merge-cells/artifacts/document.md +0 -0
  341. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-merge-cells/receipt.json +0 -0
  342. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-merge-cells/result.json +0 -0
  343. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-temperature-table/artifacts/document.md +0 -0
  344. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-temperature-table/receipt.json +0 -0
  345. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-calamine-temperature-table/result.json +0 -0
  346. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-census-nst-est2024-pop/artifacts/document.md +0 -0
  347. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-census-nst-est2024-pop/receipt.json +0 -0
  348. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/pass-2/xlsx-census-nst-est2024-pop/result.json +0 -0
  349. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/quality.json +0 -0
  350. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/results.json +0 -0
  351. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/ods-calamine-any-sheets.json +0 -0
  352. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/ods-calamine-date.json +0 -0
  353. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/ods-calamine-issues.json +0 -0
  354. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/ods-calamine-merged-cells.json +0 -0
  355. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/ods-calamine-special-cells.json +0 -0
  356. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-arxiv-2302.13971v1.json +0 -0
  357. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-arxiv-2305.18290v3.json +0 -0
  358. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-arxiv-2402.00838v4.json +0 -0
  359. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-eia-steo-table1.json +0 -0
  360. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-fbi-nics-background-checks-2015-11.json +0 -0
  361. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-federal-register-2020-17221.json +0 -0
  362. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-nist-sp800-145.json +0 -0
  363. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-us-patent-223898-scan.json +0 -0
  364. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/pdf-us-senate-expenditures.json +0 -0
  365. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xls-calamine-any-sheets.json +0 -0
  366. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xls-calamine-date.json +0 -0
  367. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xls-calamine-merge-cells.json +0 -0
  368. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xls-calamine-xls-formula.json +0 -0
  369. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xls-census-st-est00int-01.json +0 -0
  370. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsb-calamine-date.json +0 -0
  371. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsb-calamine-issue127.json +0 -0
  372. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsb-calamine-issues.json +0 -0
  373. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsm-calamine-issue221.json +0 -0
  374. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsm-calamine-issue3.json +0 -0
  375. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsm-calamine-vba.json +0 -0
  376. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsx-calamine-any-sheets.json +0 -0
  377. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsx-calamine-date.json +0 -0
  378. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsx-calamine-inventory-table.json +0 -0
  379. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsx-calamine-merge-cells.json +0 -0
  380. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsx-calamine-temperature-table.json +0 -0
  381. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/reviews/xlsx-census-nst-est2024-pop.json +0 -0
  382. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/selection.json +0 -0
  383. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/20260913-133006/validation.json +0 -0
  384. {brewdoc-0.2.0 → brewdoc-0.3.0}/benchmarks/results/README.md +0 -0
  385. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/output.py +0 -0
  386. {brewdoc-0.2.0 → brewdoc-0.3.0}/src/brewdoc/pptx.py +0 -0
  387. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/csv/vega-global-temp.csv +0 -0
  388. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/csv/vega-iowa-electricity.csv +0 -0
  389. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/csv/vega-seattle-weather.csv +0 -0
  390. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/csv/vega-us-employment.csv +0 -0
  391. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/docx/libreoffice-negative-cell-margin-twips.docx +0 -0
  392. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/docx/poi-ThreeColHead.docx +0 -0
  393. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/docx/poi-deep-table-cell.docx +0 -0
  394. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/docx/poi-table-alignment.docx +0 -0
  395. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/docx/tika-testWORD.docx +0 -0
  396. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/docx/tika-testWORD_various.docx +0 -0
  397. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/eml/tika-testGroupWiseEml.eml +0 -0
  398. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/eml/tika-testRFC822_dkim.eml +0 -0
  399. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/eml/tika-testRFC822_multipart_attachments.eml +0 -0
  400. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/epub/epubcheck-minimal.epub +0 -0
  401. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/epub/standardebooks-herland.epub +0 -0
  402. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/html/apache-httpd-allowmethods.html +0 -0
  403. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/html/mdn-document-structure.html +0 -0
  404. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/html/mdn-first-form-styled.html +0 -0
  405. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/html/mdn-items-sold-headers.html +0 -0
  406. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/html/mdn-planets-data.html +0 -0
  407. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/html/mdn-timetable-caption.html +0 -0
  408. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/md/calamine-README.md +0 -0
  409. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/md/pdfplumber-README.md +0 -0
  410. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/odp/tika-testMasterFooter.odp +0 -0
  411. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/odp/tika-testODPMacro.odp +0 -0
  412. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/ods/calamine-any_sheets.ods +0 -0
  413. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/ods/calamine-date.ods +0 -0
  414. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/ods/calamine-issues.ods +0 -0
  415. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/ods/calamine-merged_cells.ods +0 -0
  416. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/ods/calamine-special_cells.ods +0 -0
  417. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/odt/tika-testODFwithOOo3.odt +0 -0
  418. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/odt/tika-testODTStyles2.odt +0 -0
  419. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/odt/tika-testOpenOffice2.odt +0 -0
  420. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/arxiv-2302.13971v1.pdf +0 -0
  421. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/arxiv-2305.18290v3.pdf +0 -0
  422. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/arxiv-2402.00838v4.pdf +0 -0
  423. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/eia-steo-table1.pdf +0 -0
  424. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/fbi-nics-background-checks-2015-11.pdf +0 -0
  425. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/federal-register-2020-17221.pdf +0 -0
  426. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/nist-sp800-145.pdf +0 -0
  427. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/us-patent-223898-scan.pdf +0 -0
  428. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pdf/us-senate-expenditures.pdf +0 -0
  429. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pptx/poi-table_test.pptx +0 -0
  430. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pptx/poi-testPPT.pptx +0 -0
  431. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pptx/python-pptx-test.pptx +0 -0
  432. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/pptx/tika-testPPT_various2.pptx +0 -0
  433. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/tsv/miller-abixy.tsv +0 -0
  434. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/tsv/vega-unemployment.tsv +0 -0
  435. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/txt/nasa-giss-global-temperature-index.txt +0 -0
  436. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/txt/noaa-ghcn-daily-readme.txt +0 -0
  437. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xls/calamine-any_sheets.xls +0 -0
  438. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xls/calamine-date.xls +0 -0
  439. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xls/calamine-merge_cells.xls +0 -0
  440. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xls/calamine-xls_formula.xls +0 -0
  441. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xls/census-st-est00int-01.xls +0 -0
  442. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsb/calamine-date.xlsb +0 -0
  443. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsb/calamine-issue127.xlsb +0 -0
  444. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsb/calamine-issues.xlsb +0 -0
  445. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsm/calamine-issue221.xlsm +0 -0
  446. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsm/calamine-issue3.xlsm +0 -0
  447. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsm/calamine-vba.xlsm +0 -0
  448. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsx/calamine-any_sheets.xlsx +0 -0
  449. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsx/calamine-date.xlsx +0 -0
  450. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsx/calamine-inventory-table.xlsx +0 -0
  451. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsx/calamine-merge_cells.xlsx +0 -0
  452. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsx/calamine-temperature-table.xlsx +0 -0
  453. {brewdoc-0.2.0 → brewdoc-0.3.0}/tests/fixtures/xlsx/census-NST-EST2024-POP.xlsx +0 -0
@@ -0,0 +1,4 @@
1
+ tests/fixtures/** -text
2
+ pyproject.toml text eol=lf
3
+ uv.lock text eol=lf
4
+ benchmarks/corpus.json text eol=lf
@@ -0,0 +1,124 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ latency:
17
+ name: isolated elapsed comparison (Linux, Python 3.12)
18
+ if: github.event_name == 'pull_request' && github.event.action == 'opened' && github.head_ref == 'perf/full-performance-review' && github.run_attempt == '1'
19
+ runs-on: ubuntu-26.04
20
+ timeout-minutes: 45
21
+ steps:
22
+ - uses: actions/checkout@v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+ fetch-depth: 0
26
+ - uses: astral-sh/setup-uv@v10.2.0
27
+ with:
28
+ version: "0.12.21"
29
+ python-version: "3.12"
30
+ enable-cache: false
31
+ - run: uv sync --locked --group dev --no-install-project
32
+ - name: Compare sealed sources in one locked runtime
33
+ timeout-minutes: 40
34
+ env:
35
+ BREWDOC_PR_HEAD: ${{ github.event.pull_request.head.sha }}
36
+ run: uv run --no-sync python benchmarks/compare_latency.py compare --baseline-ref dc1f0c7fdedeec024840127ed989e1f58416762a --output "${{ runner.temp }}/isolated-latency"
37
+ - name: Preserve complete or partial elapsed evidence
38
+ if: always()
39
+ uses: actions/upload-artifact@v7.0.2
40
+ with:
41
+ name: isolated-latency-${{ github.sha }}-${{ github.run_id }}
42
+ path: ${{ runner.temp }}/isolated-latency/
43
+ if-no-files-found: warn
44
+ retention-days: 7
45
+
46
+ package:
47
+ name: package (Python 3.12)
48
+ runs-on: ubuntu-26.04
49
+ timeout-minutes: 10
50
+ steps:
51
+ - uses: actions/checkout@v7.0.1
52
+ with:
53
+ persist-credentials: false
54
+ - uses: astral-sh/setup-uv@v10.2.0
55
+ id: uv
56
+ with:
57
+ version: "0.12.21"
58
+ python-version: "3.12"
59
+ cache-suffix: uv-0.12.21-ci-build
60
+ - run: uv lock --check
61
+ - run: uv build --no-sources --no-create-gitignore --out-dir dist --verbose
62
+ - name: Record package identity and installation constraints
63
+ env:
64
+ BREWDOC_CACHE_HIT: ${{ steps.uv.outputs.cache-hit }}
65
+ BREWDOC_CACHE_KEY: ${{ steps.uv.outputs.cache-key }}
66
+ run: uv run --no-project --python 3.12 python .github/workflows/verify_install.py prepare
67
+ - uses: actions/upload-artifact@v7.0.2
68
+ with:
69
+ name: ci-dist
70
+ path: |
71
+ dist/
72
+ ci-package/
73
+ if-no-files-found: error
74
+ retention-days: 7
75
+
76
+ test:
77
+ name: test (${{ matrix.platform }}, Python 3.12)
78
+ needs: package
79
+ runs-on: ${{ matrix.os }}
80
+ timeout-minutes: 15
81
+ strategy:
82
+ fail-fast: false
83
+ max-parallel: 2
84
+ matrix:
85
+ include:
86
+ - platform: Linux
87
+ os: ubuntu-26.04
88
+ - platform: macOS
89
+ os: macos-26
90
+ - platform: Windows
91
+ os: windows-2025
92
+ steps:
93
+ - uses: actions/checkout@v7.0.1
94
+ with:
95
+ persist-credentials: false
96
+ - uses: astral-sh/setup-uv@v10.2.0
97
+ id: uv
98
+ with:
99
+ version: "0.12.21"
100
+ python-version: "3.12"
101
+ cache-suffix: uv-0.12.21-ci-test
102
+ - run: uv sync --locked --group dev --extra render-js
103
+ - name: Run all source tests, including corpus
104
+ run: uv run --locked --extra render-js pytest -q --strict-markers -n 4 --randomly-seed=0 --junitxml=ci-results.xml
105
+ - uses: actions/download-artifact@v8.0.2
106
+ with:
107
+ name: ci-dist
108
+ path: ${{ runner.temp }}/ci-dist
109
+ digest-mismatch: error
110
+ - name: Check base-only and optional wheel and sdist installations
111
+ env:
112
+ BREWDOC_CACHE_HIT: ${{ steps.uv.outputs.cache-hit }}
113
+ BREWDOC_CACHE_KEY: ${{ steps.uv.outputs.cache-key }}
114
+ run: uv run --no-project --python 3.12 python .github/workflows/verify_install.py verify --bundle "${{ runner.temp }}/ci-dist"
115
+ - name: Preserve test and installed-package evidence
116
+ if: always()
117
+ uses: actions/upload-artifact@v7.0.2
118
+ with:
119
+ name: ci-results-${{ matrix.platform }}
120
+ path: |
121
+ ci-results.xml
122
+ ${{ runner.temp }}/ci-dist/ci-package/verification-*.json
123
+ if-no-files-found: warn
124
+ retention-days: 7
@@ -0,0 +1,514 @@
1
+ """Prepare and admit only immutable v0.2.0 release recovery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import ast
7
+ import datetime as dt
8
+ from email.parser import BytesParser
9
+ import hashlib
10
+ import io
11
+ import json
12
+ import os
13
+ from pathlib import Path, PurePosixPath
14
+ import re
15
+ import shutil
16
+ import stat
17
+ import subprocess
18
+ import tarfile
19
+ import tempfile
20
+ import tomllib
21
+ import urllib.error
22
+ import urllib.parse
23
+ import urllib.request
24
+ import zipfile
25
+
26
+ REPOSITORY = "kochetkov-ma/brewdoc"
27
+ SOURCE_SHA = "dc1f0c7fdedeec024840127ed989e1f58416762a"
28
+ TAG = "v0.2.0"
29
+ FILENAMES = ("brewdoc-0.2.0-py3-none-any.whl", "brewdoc-0.2.0.tar.gz")
30
+ ORIGINAL_HASHES = (
31
+ "234a5c83497ac9f70e1530c0e36920657df93220539d3935fa632e3c7fe189de",
32
+ "7085c35212568b92e4a2cea465f723ee3722abb70f4885e12c6f0d8231d9dcb2",
33
+ )
34
+ ORIGINAL_ARTIFACT = 11112736558
35
+ ORIGINAL_DIGEST = "2e83b3ae22ffb59e7ecdd73eacc10b6770965f98f091a6a84249ccc8caba2bea"
36
+ ARTIFACT_NAME = "recovery-v020"
37
+ MANIFEST_NAME = "recovery-manifest.json"
38
+ HEX = re.compile(r"[0-9a-f]{64}\Z")
39
+ MAX_DOWNLOAD = 64 * 1024 * 1024
40
+
41
+
42
+ def command(argv: list[str], cwd: Path | None = None) -> bytes:
43
+ """Run a bounded command without inheriting source-import overrides."""
44
+ environment = {key: value for key, value in os.environ.items() if key not in {
45
+ "PYTHONPATH", "PYTHONHOME", "VIRTUAL_ENV", "UV_PROJECT_ENVIRONMENT", "UV_PYTHON"}}
46
+ completed = subprocess.run(argv, cwd=cwd, env=environment, capture_output=True, timeout=600)
47
+ if completed.returncode:
48
+ detail = (completed.stderr or completed.stdout).decode("utf-8", errors="replace")
49
+ detail = re.sub(r"(https?://)[^/\s@]+@", r"\1[redacted]@", detail)
50
+ detail = re.sub(r"(?i)\bbearer\s+\S+", "Bearer [redacted]", detail)
51
+ detail = re.sub(r"(?i)\b((?:[a-z_]+_)?(?:token|password|secret|api_key)|authorization)\s*[:=]\s*[^\r\n]+",
52
+ r"\1=[redacted]", detail)
53
+ detail = re.sub(r"\b(?:gh[pousr]_[A-Za-z0-9]+|github_pat_[A-Za-z0-9_]+|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)\b",
54
+ "[redacted]", detail)
55
+ raise ValueError(f"Recovery command failed: {Path(argv[0]).name} (exit {completed.returncode})\n"
56
+ + detail[-2048:].strip())
57
+ return completed.stdout
58
+
59
+
60
+ def api(endpoint: str, *, missing: bool = False) -> dict | list | None:
61
+ """Read repository metadata, allowing only an explicit HTTP 404 absence."""
62
+ completed = subprocess.run(["gh", "api", f"repos/{REPOSITORY}/{endpoint}"],
63
+ capture_output=True, timeout=60)
64
+ payload = json.loads(completed.stdout)
65
+ if completed.returncode:
66
+ if missing and isinstance(payload, dict) and str(payload.get("status")) == "404":
67
+ return None
68
+ raise ValueError(f"Repository preflight failed: {endpoint}")
69
+ return payload
70
+
71
+
72
+ def validate_inputs(inputs: dict) -> tuple[str, int | None, tuple[str, str] | None]:
73
+ """Require the four fixed dispatch inputs before any acquisition or publication."""
74
+ expected = {"operation", "prepared_run_id", "approved_wheel_sha256", "approved_sdist_sha256"}
75
+ if set(inputs) != expected or any(not isinstance(value, str) for value in inputs.values()):
76
+ raise ValueError("Recovery inputs must contain exactly the four declared strings")
77
+ operation = inputs["operation"]
78
+ values = tuple(inputs[key] for key in (
79
+ "prepared_run_id", "approved_wheel_sha256", "approved_sdist_sha256"))
80
+ if operation == "prepare-v020":
81
+ if values != ("", "", ""):
82
+ raise ValueError("Preparation inputs must leave run and approved hashes empty")
83
+ return operation, None, None
84
+ if operation != "publish-v020" or not re.fullmatch(r"[1-9][0-9]*", values[0]):
85
+ raise ValueError("Publication requires a positive decimal preparation run")
86
+ if not all(HEX.fullmatch(value) for value in values[1:]):
87
+ raise ValueError("Publication requires two lowercase SHA-256 approvals")
88
+ return operation, int(values[0]), values[1:]
89
+
90
+
91
+ def validate_tag(reference: dict) -> None:
92
+ """Refuse missing, moved or annotated substitutions for the original tag."""
93
+ if reference.get("ref") != f"refs/tags/{TAG}" or reference.get("object") != {
94
+ "sha": SOURCE_SHA, "type": "commit", "url": f"https://api.github.com/repos/{REPOSITORY}/git/commits/{SOURCE_SHA}"}:
95
+ raise ValueError("Original v0.2.0 tag changed")
96
+
97
+
98
+ def context() -> tuple[dict, Path, Path]:
99
+ """Admit dispatches only from the current reviewed main workflow commit."""
100
+ event = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text(encoding="utf-8"))
101
+ if (os.environ.get("GITHUB_REPOSITORY") != REPOSITORY
102
+ or event.get("repository", {}).get("full_name") != REPOSITORY
103
+ or os.environ.get("GITHUB_EVENT_NAME") != "workflow_dispatch"
104
+ or os.environ.get("GITHUB_REF") != "refs/heads/main"
105
+ or os.environ.get("GITHUB_WORKFLOW_REF") != f"{REPOSITORY}/.github/workflows/release.yml@refs/heads/main"):
106
+ raise ValueError("Recovery must run from release.yml on repository main")
107
+ validate_inputs(event.get("inputs", {}))
108
+ if api("git/ref/heads/main")["object"]["sha"] != os.environ["GITHUB_SHA"]:
109
+ raise ValueError("Recovery workflow commit is no longer current main")
110
+ validate_tag(api(f"git/ref/tags/{TAG}"))
111
+ workspace = Path(os.environ["GITHUB_WORKSPACE"]).resolve()
112
+ return event, workspace / "package-source", workspace / ARTIFACT_NAME
113
+
114
+
115
+ def validate_run(run: dict, jobs: dict, current_sha: str, run_id: int) -> None:
116
+ """Reject foreign, stale, failed or non-preparation workflow provenance."""
117
+ if (run.get("id") != run_id or run.get("repository", {}).get("full_name") != REPOSITORY
118
+ or run.get("head_repository", {}).get("full_name") != REPOSITORY
119
+ or run.get("path") != ".github/workflows/release.yml"
120
+ or run.get("event") != "workflow_dispatch" or run.get("head_branch") != "main"
121
+ or run.get("head_sha") != current_sha or run.get("status") != "completed"
122
+ or run.get("conclusion") != "success"):
123
+ raise ValueError("Preparation run is foreign, stale or unsuccessful")
124
+ prepared = [job for job in jobs.get("jobs", []) if job.get("name") == "recovery-prepare"]
125
+ if len(prepared) != 1 or prepared[0].get("conclusion") != "success":
126
+ raise ValueError("Preparation job did not succeed")
127
+
128
+
129
+ def validate_artifact(artifact: dict, run_id: int, sha: str, name: str) -> str:
130
+ """Require an unexpired artifact with matching run, source and archive digest."""
131
+ provenance = artifact.get("workflow_run", {})
132
+ digest = artifact.get("digest") or ""
133
+ if (artifact.get("name") != name or artifact.get("expired") is not False
134
+ or provenance.get("id") != run_id or provenance.get("head_sha") != sha
135
+ or not digest.startswith("sha256:") or not HEX.fullmatch(digest[7:])
136
+ or dt.datetime.fromisoformat(artifact["expires_at"].replace("Z", "+00:00")) <= dt.datetime.now(dt.timezone.utc)):
137
+ raise ValueError("Recovery artifact provenance, expiry or digest is invalid")
138
+ return digest[7:]
139
+
140
+
141
+ def unpack_artifact(payload: bytes, expected_digest: str, names: tuple[str, ...], folder: Path) -> None:
142
+ """Verify the archive before writing only its exact ordinary top-level files."""
143
+ if len(payload) > MAX_DOWNLOAD or hashlib.sha256(payload).hexdigest() != expected_digest:
144
+ raise ValueError("Recovery artifact archive digest changed")
145
+ with zipfile.ZipFile(io.BytesIO(payload)) as archive:
146
+ members = archive.infolist()
147
+ if sorted(item.filename for item in members) != sorted(names):
148
+ raise ValueError("Recovery artifact contains unexpected or duplicate files")
149
+ if any(item.is_dir() or item.flag_bits & 1
150
+ or stat.S_IFMT(item.external_attr >> 16) not in {0, stat.S_IFREG}
151
+ or item.file_size > MAX_DOWNLOAD for item in members):
152
+ raise ValueError("Recovery artifact contains unsafe members")
153
+ if sum(item.file_size for item in members) > MAX_DOWNLOAD:
154
+ raise ValueError("Recovery artifact expanded bytes exceed the limit")
155
+ contents = {item.filename: archive.read(item) for item in members}
156
+ folder.mkdir(parents=True, exist_ok=False)
157
+ for name, content in contents.items():
158
+ (folder / name).write_bytes(content)
159
+
160
+
161
+ def file_identities(folder: Path) -> dict:
162
+ """Identify the exact two ordinary package files without accepting path substitutions."""
163
+ identities = {}
164
+ for name in FILENAMES:
165
+ path = folder / name
166
+ if path.is_symlink() or not path.is_file():
167
+ raise ValueError("Recovery package is missing or linked")
168
+ payload = path.read_bytes()
169
+ if len(payload) > MAX_DOWNLOAD:
170
+ raise ValueError("Recovery package exceeds the size limit")
171
+ identities[name] = {"size": len(payload), "sha256": hashlib.sha256(payload).hexdigest()}
172
+ return identities
173
+
174
+
175
+ def check_names(names: list[str]) -> None:
176
+ """Reject duplicate and escaping package archive member paths."""
177
+ if len(names) != len(set(names)) or any(
178
+ PurePosixPath(name).is_absolute() or ".." in PurePosixPath(name).parts
179
+ or "\\" in name for name in names):
180
+ raise ValueError("Recovery package contains unsafe or duplicate names")
181
+
182
+
183
+ def verify_source(source: Path) -> None:
184
+ """Require an unmodified original checkout before its build or package comparison."""
185
+ if command(["git", "rev-parse", "HEAD"], source).decode().strip() != SOURCE_SHA:
186
+ raise ValueError("Recovery package checkout is not the original source")
187
+ command(["git", "diff", "--exit-code", SOURCE_SHA], source)
188
+ if command(["git", "ls-files", "--others", "--exclude-standard"], source):
189
+ raise ValueError("Recovery package checkout contains untracked files")
190
+
191
+
192
+ def verify_packages(folder: Path, source: Path) -> None:
193
+ """Require original source, metadata, safe archives and the original console entry."""
194
+ verify_source(source)
195
+ tracked = command(["git", "ls-tree", "-r", "--name-only", SOURCE_SHA], source).decode().splitlines()
196
+ original = {name.removeprefix("src/"): (source / name).read_bytes()
197
+ for name in tracked if name.startswith("src/brewdoc/")}
198
+ project = tomllib.loads((source / "pyproject.toml").read_text(encoding="utf-8"))["project"]
199
+ if project["version"] != "0.2.0" or len(original) != 11:
200
+ raise ValueError("Original package metadata changed")
201
+ prefix = "brewdoc-0.2.0.dist-info/"
202
+ allowed = set(original) | {prefix + name for name in (
203
+ "METADATA", "WHEEL", "entry_points.txt", "licenses/LICENSE", "RECORD")}
204
+ with zipfile.ZipFile(folder / FILENAMES[0]) as wheel:
205
+ check_names(wheel.namelist())
206
+ if set(wheel.namelist()) != allowed or any(
207
+ stat.S_IFMT(item.external_attr >> 16) not in {0, stat.S_IFREG}
208
+ or item.flag_bits & 1 for item in wheel.infolist()):
209
+ raise ValueError("Original wheel member set changed")
210
+ if sum(item.file_size for item in wheel.infolist()) > MAX_DOWNLOAD:
211
+ raise ValueError("Original wheel expanded bytes exceed the limit")
212
+ if {name: wheel.read(name) for name in original} != original:
213
+ raise ValueError("Wheel source differs from the original tag")
214
+ if wheel.read(prefix + "entry_points.txt") != b"[console_scripts]\nbrewdoc = brewdoc.cli:main\n":
215
+ raise ValueError("Original console entry changed")
216
+ if wheel.read(prefix + "licenses/LICENSE") != (source / "LICENSE").read_bytes():
217
+ raise ValueError("Original wheel license changed")
218
+ metadata = BytesParser().parsebytes(wheel.read(prefix + "METADATA"))
219
+ with tarfile.open(folder / FILENAMES[1]) as sdist:
220
+ members = sdist.getmembers()
221
+ check_names([member.name for member in members])
222
+ if any(not (member.isfile() or member.isdir()) for member in members):
223
+ raise ValueError("Original sdist contains links or special members")
224
+ if sum(member.size for member in members) > MAX_DOWNLOAD:
225
+ raise ValueError("Original sdist expanded bytes exceed the limit")
226
+ seen = set()
227
+ for member in members:
228
+ if not member.isfile():
229
+ continue
230
+ name = member.name.removeprefix("brewdoc-0.2.0/")
231
+ if name == "PKG-INFO":
232
+ continue
233
+ if name not in tracked or sdist.extractfile(member).read() != (source / name).read_bytes():
234
+ raise ValueError("Sdist files differ from the original tag")
235
+ seen.add(name)
236
+ required = {"pyproject.toml", "uv.lock", "LICENSE", "README.md"} | {"src/" + name for name in original}
237
+ if not required.issubset(seen):
238
+ raise ValueError("Original sdist required files are missing")
239
+ source_metadata = BytesParser().parsebytes(sdist.extractfile("brewdoc-0.2.0/PKG-INFO").read())
240
+ for document in (metadata, source_metadata):
241
+ if (document["Name"], document["Version"], document["Requires-Python"],
242
+ document.get_all("Requires-Dist")) != (
243
+ "brewdoc", "0.2.0", project["requires-python"], project["dependencies"]):
244
+ raise ValueError("Original distribution metadata changed")
245
+
246
+
247
+ def installed_checks(folder: Path, source: Path) -> list[dict]:
248
+ """Install wheel and sdist independently with original locked runtime/build constraints."""
249
+ with tempfile.TemporaryDirectory(prefix="brewdoc-v020-") as temporary:
250
+ root = Path(temporary)
251
+ runtime = root / "runtime.txt"
252
+ command(["uv", "export", "--locked", "--no-dev", "--no-emit-project",
253
+ "--output-file", str(runtime)], source)
254
+ project = tomllib.loads((source / "pyproject.toml").read_text(encoding="utf-8"))
255
+ build = root / "build.txt"
256
+ build.write_text("\n".join(project["build-system"]["requires"]
257
+ + project["tool"]["uv"]["build-constraint-dependencies"]) + "\n")
258
+ exports = next(ast.literal_eval(node.value) for node in ast.parse(
259
+ (source / "src/brewdoc/__init__.py").read_bytes()).body if isinstance(node, ast.Assign)
260
+ and any(isinstance(target, ast.Name) and target.id == "__all__" for target in node.targets))
261
+ expected = root / "exports.json"
262
+ expected.write_text(json.dumps(exports))
263
+ script = root / "probe.py"
264
+ script.write_text(INSTALL_PROBE, encoding="utf-8")
265
+ results = []
266
+ for index, name in enumerate(FILENAMES):
267
+ environment = root / f"environment-{index}"
268
+ command(["uv", "venv", "--python", "3.12", str(environment)], root)
269
+ python = environment / "bin/python"
270
+ command(["uv", "pip", "install", "--python", str(python), "--require-hashes",
271
+ "-r", str(runtime)], root)
272
+ command(["uv", "pip", "install", "--python", str(python), "--no-deps",
273
+ "--build-constraints", str(build), str((folder / name).resolve())], root)
274
+ result = json.loads(command([str(python), "-I", str(script), str(expected)], root))
275
+ results.append({"filename": name, **result})
276
+ return results
277
+
278
+
279
+ INSTALL_PROBE = '''import contextlib, importlib.metadata, io, json, pathlib, subprocess, sys
280
+ import brewdoc
281
+ expected = json.loads(pathlib.Path(sys.argv[1]).read_text())
282
+ assert brewdoc.__version__ == importlib.metadata.version("brewdoc") == "0.2.0", "original package version differs"
283
+ assert brewdoc.__all__ == expected, "original public exports differ"
284
+ assert pathlib.Path(brewdoc.__file__).resolve().is_relative_to(pathlib.Path(sys.prefix).resolve()), "original package imports outside its installed environment"
285
+ capture = io.StringIO()
286
+ with contextlib.redirect_stdout(capture):
287
+ verdict = brewdoc.self_check()
288
+ assert verdict == 0 and capture.getvalue() == "self-check: ok (34 checks)\\n", "original self-check failed or changed"
289
+ path = pathlib.Path.cwd() / "probe.html"
290
+ path.write_text("<title>Original</title><p>caf\u00e9 \u6771\u4eac</p>", encoding="utf-8")
291
+ result = brewdoc.run(path)
292
+ assert result[0] == 0 and result[1]["route"] == "html" and result[1]["units"] == 1, "original HTML conversion receipt differs"
293
+ assert brewdoc.render_html(path)[0] == result[2], "original HTML wrapper differs from run"
294
+ assert "caf\u00e9 \u6771\u4eac" in result[2], "original HTML conversion lost Unicode"
295
+ console = pathlib.Path(sys.prefix) / "bin/brewdoc"
296
+ completed = subprocess.run([str(console), str(path)], capture_output=True, timeout=60)
297
+ expected_line = (json.dumps(result[1], ensure_ascii=True, sort_keys=True) + "\\n").encode("ascii")
298
+ assert (completed.returncode, completed.stdout, completed.stderr) == (0, expected_line + result[2].encode("utf-8"), b""), "original CLI receipt or UTF-8 stdout differs"
299
+ out = pathlib.Path.cwd() / "out.md"
300
+ written = brewdoc.run(path, out)
301
+ completed = subprocess.run([str(console), str(path), "--out", str(out)], capture_output=True, timeout=60)
302
+ assert (completed.returncode, completed.stdout, completed.stderr, out.read_bytes()) == (0, (json.dumps(written[1], ensure_ascii=True, sort_keys=True) + "\\n").encode("ascii"), b"", result[2].encode("utf-8")), "original CLI output path or bytes differ"
303
+ assert brewdoc.run(path) == result, "original conversion is not deterministic"
304
+ print(json.dumps({"version": "0.2.0", "origin_verified": True, "contracts_verified": True, "self_check": "self-check: ok (34 checks)", "dependencies": {name: importlib.metadata.version(name) for name in ("pdfplumber", "python-calamine", "selectolax")}}))
305
+ '''
306
+
307
+
308
+ def prepare(source: Path, folder: Path) -> None:
309
+ """Prefer verified retained packages; rebuild only confirmed absent or expired artifacts."""
310
+ artifact = api(f"actions/artifacts/{ORIGINAL_ARTIFACT}", missing=True)
311
+ method = "retained"
312
+ if artifact is not None and not artifact.get("expired"):
313
+ digest = validate_artifact(artifact, 36746089723, SOURCE_SHA, "dist")
314
+ if digest != ORIGINAL_DIGEST:
315
+ raise ValueError("Original artifact digest changed")
316
+ unpack_artifact(command(["gh", "api", f"repos/{REPOSITORY}/actions/artifacts/{ORIGINAL_ARTIFACT}/zip"]),
317
+ digest, FILENAMES, folder)
318
+ identities = file_identities(folder)
319
+ if tuple(identities[name]["sha256"] for name in FILENAMES) != ORIGINAL_HASHES:
320
+ raise ValueError("Original retained package hashes changed")
321
+ else:
322
+ method = "rebuilt"
323
+ verify_source(source)
324
+ folder.mkdir(parents=True, exist_ok=False)
325
+ command(["uv", "lock", "--check"], source)
326
+ command(["uv", "build", "--no-sources", "--no-create-gitignore",
327
+ "--out-dir", str(folder)], source)
328
+ if set(path.name for path in folder.iterdir()) != set(FILENAMES):
329
+ raise ValueError("Preparation must produce only the original package pair")
330
+ verify_packages(folder, source)
331
+ verification = installed_checks(folder, source)
332
+ manifest = {"schema": 1, "operation": "prepare-v020", "repository": REPOSITORY,
333
+ "tag": TAG, "source_sha": SOURCE_SHA, "version": "0.2.0",
334
+ "control_sha": os.environ["GITHUB_SHA"], "prepared_run_id": int(os.environ["GITHUB_RUN_ID"]),
335
+ "prepared_run_attempt": int(os.environ["GITHUB_RUN_ATTEMPT"]), "method": method,
336
+ "files": file_identities(folder), "verification": verification}
337
+ (folder / MANIFEST_NAME).write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
338
+
339
+
340
+ def validate_manifest(manifest: dict, folder: Path, run: dict, approvals: tuple[str, str]) -> None:
341
+ """Bind approval hashes and installed original-package proof to the preparation run."""
342
+ identity = {key: manifest.get(key) for key in (
343
+ "schema", "operation", "repository", "tag", "source_sha", "version", "control_sha",
344
+ "prepared_run_id", "prepared_run_attempt")}
345
+ if identity != {"schema": 1, "operation": "prepare-v020", "repository": REPOSITORY,
346
+ "tag": TAG, "source_sha": SOURCE_SHA, "version": "0.2.0",
347
+ "control_sha": run["head_sha"], "prepared_run_id": run["id"],
348
+ "prepared_run_attempt": run["run_attempt"]}:
349
+ raise ValueError("Recovery manifest identity differs from the preparation run")
350
+ actual = file_identities(folder)
351
+ if manifest.get("files") != actual or tuple(actual[name]["sha256"] for name in FILENAMES) != approvals:
352
+ raise ValueError("Recovery package bytes differ from manifest or approved hashes")
353
+ verification = manifest.get("verification", [])
354
+ if ([entry.get("filename") for entry in verification] != list(FILENAMES)
355
+ or any(entry.get("version") != "0.2.0" or entry.get("origin_verified") is not True
356
+ or entry.get("contracts_verified") is not True for entry in verification)
357
+ or manifest.get("method") not in {"retained", "rebuilt"}):
358
+ raise ValueError("Original package installation verification is incomplete")
359
+
360
+
361
+ def admit(source: Path, folder: Path, inputs: dict) -> None:
362
+ """Reacquire accepted provenance and packages before each publication boundary."""
363
+ operation, run_id, approvals = validate_inputs(inputs)
364
+ if operation != "publish-v020":
365
+ raise ValueError("Only publish-v020 admits prepared artifacts")
366
+ run = api(f"actions/runs/{run_id}")
367
+ validate_run(run, api(f"actions/runs/{run_id}/jobs"), os.environ["GITHUB_SHA"], run_id)
368
+ listing = api(f"actions/runs/{run_id}/artifacts")
369
+ if listing.get("total_count") != 1 or len(listing.get("artifacts", [])) != 1:
370
+ raise ValueError("Preparation must contain exactly one recovery artifact")
371
+ artifact = api(f"actions/artifacts/{listing['artifacts'][0]['id']}")
372
+ digest = validate_artifact(artifact, run_id, os.environ["GITHUB_SHA"], ARTIFACT_NAME)
373
+ unpack_artifact(command(["gh", "api", f"repos/{REPOSITORY}/actions/artifacts/{artifact['id']}/zip"]),
374
+ digest, (*FILENAMES, MANIFEST_NAME), folder)
375
+ manifest = json.loads((folder / MANIFEST_NAME).read_text(encoding="utf-8"))
376
+ validate_manifest(manifest, folder, run, approvals)
377
+ verify_packages(folder, source)
378
+
379
+
380
+ def public_bytes(url: str) -> bytes:
381
+ """Fetch only public package hosts with a bounded response and no credentials."""
382
+ allowed = {"pypi.org", "files.pythonhosted.org", "github.com", "release-assets.githubusercontent.com",
383
+ "objects.githubusercontent.com"}
384
+
385
+ def check(target: str) -> None:
386
+ """Refuse targets outside the fixed public distribution hosts."""
387
+ parsed = urllib.parse.urlsplit(target)
388
+ if parsed.scheme != "https" or parsed.hostname not in allowed or parsed.username or parsed.password:
389
+ raise ValueError("Public package URL is not an admitted HTTPS host")
390
+
391
+ class Redirect(urllib.request.HTTPRedirectHandler):
392
+ """Validate public download redirects without carrying authentication."""
393
+
394
+ def redirect_request(self, request, response, code, message, headers, target):
395
+ """Apply the public host policy to every redirect."""
396
+ check(target)
397
+ return super().redirect_request(request, response, code, message, headers, target)
398
+
399
+ check(url)
400
+ with urllib.request.build_opener(Redirect).open(url, timeout=60) as response:
401
+ payload = response.read(MAX_DOWNLOAD + 1)
402
+ if len(payload) > MAX_DOWNLOAD:
403
+ raise ValueError("Public package response exceeds the size limit")
404
+ return payload
405
+
406
+
407
+ def missing_public_files(expected: dict, entries: list[dict], fetch) -> list[str]:
408
+ """Accept exact existing bytes and return only absent approved filenames."""
409
+ names = [entry.get("filename") for entry in entries]
410
+ if len(names) != len(set(names)) or not set(names).issubset(expected):
411
+ raise ValueError("Public release contains unexpected or duplicate files")
412
+ for entry in entries:
413
+ name = entry["filename"]
414
+ payload = fetch(entry["url"])
415
+ actual = {"size": len(payload), "sha256": hashlib.sha256(payload).hexdigest()}
416
+ if actual != expected[name] or entry.get("size") != actual["size"]:
417
+ raise ValueError("Public release bytes differ from approved packages")
418
+ return [name for name in FILENAMES if name not in names]
419
+
420
+
421
+ def pypi_missing(folder: Path) -> list[str]:
422
+ """Treat only public HTTP 404 as absence; all other preflight errors stop recovery."""
423
+ try:
424
+ metadata = json.loads(public_bytes("https://pypi.org/pypi/brewdoc/0.2.0/json"))
425
+ except urllib.error.HTTPError as exc:
426
+ if exc.code == 404:
427
+ return list(FILENAMES)
428
+ raise ValueError("PyPI preflight is unavailable") from exc
429
+ if not isinstance(metadata, dict) or not isinstance(metadata.get("info"), dict):
430
+ raise ValueError("PyPI public-file metadata is malformed")
431
+ if (metadata["info"].get("name"), metadata["info"].get("version")) != ("brewdoc", "0.2.0"):
432
+ raise ValueError("PyPI release identity changed")
433
+ entries = metadata.get("urls")
434
+ if not isinstance(entries, list) or any(not isinstance(entry, dict) for entry in entries):
435
+ raise ValueError("PyPI public-file metadata is malformed")
436
+ expected = file_identities(folder)
437
+ for entry in entries:
438
+ if (not isinstance(entry.get("filename"), str) or entry["filename"] not in expected
439
+ or type(entry.get("size")) is not int or entry["size"] < 0
440
+ or not isinstance(entry.get("url"), str) or not isinstance(entry.get("digests"), dict)
441
+ or not isinstance(entry["digests"].get("sha256"), str)
442
+ or not HEX.fullmatch(entry["digests"]["sha256"])):
443
+ raise ValueError("PyPI public-file metadata is malformed")
444
+ target = urllib.parse.urlsplit(entry["url"])
445
+ if (target.scheme != "https" or target.hostname != "files.pythonhosted.org"
446
+ or target.username or target.password or target.port not in {None, 443} or target.fragment):
447
+ raise ValueError("PyPI public-file metadata is malformed")
448
+ if {"size": entry["size"], "sha256": entry["digests"]["sha256"]} != expected[entry["filename"]]:
449
+ raise ValueError("Public release bytes differ from approved packages")
450
+ return missing_public_files(expected, entries, public_bytes)
451
+
452
+
453
+ def stage_upload(folder: Path) -> None:
454
+ """Stage only missing exact PyPI files and expose whether upload is needed."""
455
+ missing = pypi_missing(folder)
456
+ upload = folder.parent / "recovery-upload"
457
+ upload.mkdir(exist_ok=False)
458
+ for name in missing:
459
+ shutil.copyfile(folder / name, upload / name)
460
+ with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output:
461
+ output.write(f"upload_needed={'true' if missing else 'false'}\n")
462
+
463
+
464
+ def complete_github(folder: Path) -> None:
465
+ """Verify PyPI, then add only missing old-tag assets without promoting latest."""
466
+ if pypi_missing(folder):
467
+ raise ValueError("PyPI original release is incomplete")
468
+ release = api(f"releases/tags/{TAG}", missing=True)
469
+ if release is None:
470
+ command(["gh", "release", "create", TAG, "--repo", REPOSITORY, "--verify-tag",
471
+ "--target", SOURCE_SHA, "--latest=false", "--title", TAG,
472
+ "--notes", "Original immutable 0.2.0 release."])
473
+ release = api(f"releases/tags/{TAG}")
474
+ if (release.get("tag_name") != TAG or release.get("draft") is not False
475
+ or release.get("prerelease") is not False or len(release.get("assets", [])) > 2):
476
+ raise ValueError("GitHub original release identity is invalid")
477
+ entries = [{"filename": asset["name"], "size": asset["size"], "url": asset["browser_download_url"]}
478
+ for asset in release["assets"]]
479
+ for entry in entries:
480
+ if entry["url"] != f"https://github.com/{REPOSITORY}/releases/download/{TAG}/{entry['filename']}":
481
+ raise ValueError("GitHub release asset URL differs from the original tag")
482
+ missing = missing_public_files(file_identities(folder), entries, public_bytes)
483
+ if missing:
484
+ command(["gh", "release", "upload", TAG, "--repo", REPOSITORY,
485
+ *(str(folder / name) for name in missing)])
486
+ release = api(f"releases/tags/{TAG}")
487
+ final = [{"filename": asset["name"], "size": asset["size"], "url": asset["browser_download_url"]}
488
+ for asset in release["assets"]]
489
+ if missing_public_files(file_identities(folder), final, public_bytes):
490
+ raise ValueError("GitHub original release assets remain incomplete")
491
+
492
+
493
+ def main() -> None:
494
+ """Run one fixed recovery phase using event inputs and fixed workspace paths."""
495
+ parser = argparse.ArgumentParser(description=__doc__)
496
+ parser.add_argument("phase", choices=("prepare", "admit", "preflight", "github"))
497
+ phase = parser.parse_args().phase
498
+ event, source, folder = context()
499
+ operation, _, _ = validate_inputs(event["inputs"])
500
+ if phase == "prepare":
501
+ if operation != "prepare-v020":
502
+ raise ValueError("Preparation requires prepare-v020")
503
+ prepare(source, folder)
504
+ return
505
+ admit(source, folder, event["inputs"])
506
+ if phase in {"admit", "preflight"}:
507
+ stage_upload(folder)
508
+ context()
509
+ else:
510
+ complete_github(folder)
511
+
512
+
513
+ if __name__ == "__main__":
514
+ main()