previously 0.1.0a1__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 (337) hide show
  1. previously-0.1.0a1/.dockerignore +3 -0
  2. previously-0.1.0a1/.github/workflows/audit.yml +72 -0
  3. previously-0.1.0a1/.github/workflows/gates.yml +103 -0
  4. previously-0.1.0a1/.github/workflows/release.yml +348 -0
  5. previously-0.1.0a1/.gitignore +28 -0
  6. previously-0.1.0a1/.importlinter +116 -0
  7. previously-0.1.0a1/.pre-commit-config.yaml +55 -0
  8. previously-0.1.0a1/.vale-styles/config/vocabularies/Previously/accept.txt +19 -0
  9. previously-0.1.0a1/.vale.ini +45 -0
  10. previously-0.1.0a1/CLAUDE.md +522 -0
  11. previously-0.1.0a1/DEPENDENCIES.md +103 -0
  12. previously-0.1.0a1/Dockerfile +58 -0
  13. previously-0.1.0a1/LICENSE +661 -0
  14. previously-0.1.0a1/NOTIZEN.md +926 -0
  15. previously-0.1.0a1/PKG-INFO +195 -0
  16. previously-0.1.0a1/README.md +155 -0
  17. previously-0.1.0a1/alembic.ini +149 -0
  18. previously-0.1.0a1/docs/Makefile +38 -0
  19. previously-0.1.0a1/docs/conf.py +59 -0
  20. previously-0.1.0a1/docs/explanation/backup-encryption.md +165 -0
  21. previously-0.1.0a1/docs/explanation/blobs.md +243 -0
  22. previously-0.1.0a1/docs/explanation/canonicalization.md +117 -0
  23. previously-0.1.0a1/docs/explanation/concurrency.md +216 -0
  24. previously-0.1.0a1/docs/explanation/delivery.md +100 -0
  25. previously-0.1.0a1/docs/explanation/design-records.md +146 -0
  26. previously-0.1.0a1/docs/explanation/erasure.md +196 -0
  27. previously-0.1.0a1/docs/explanation/hash-chain.md +433 -0
  28. previously-0.1.0a1/docs/explanation/index.md +19 -0
  29. previously-0.1.0a1/docs/explanation/module-boundaries.md +339 -0
  30. previously-0.1.0a1/docs/explanation/projections.md +273 -0
  31. previously-0.1.0a1/docs/explanation/silent-losses.md +83 -0
  32. previously-0.1.0a1/docs/how-to/add-a-migration.md +37 -0
  33. previously-0.1.0a1/docs/how-to/attach-and-fetch-a-file.md +72 -0
  34. previously-0.1.0a1/docs/how-to/cut-a-release.md +157 -0
  35. previously-0.1.0a1/docs/how-to/erase-something.md +185 -0
  36. previously-0.1.0a1/docs/how-to/index.md +18 -0
  37. previously-0.1.0a1/docs/how-to/keep-the-blob-key-safe.md +156 -0
  38. previously-0.1.0a1/docs/how-to/rebuild-a-projection.md +89 -0
  39. previously-0.1.0a1/docs/how-to/restore-from-a-backup.md +202 -0
  40. previously-0.1.0a1/docs/how-to/run-a-blob-store-on-your-machine.md +110 -0
  41. previously-0.1.0a1/docs/how-to/run-the-image.md +182 -0
  42. previously-0.1.0a1/docs/how-to/verify-the-chain.md +114 -0
  43. previously-0.1.0a1/docs/index.md +53 -0
  44. previously-0.1.0a1/docs/reference/cli.md +631 -0
  45. previously-0.1.0a1/docs/reference/configuration.md +99 -0
  46. previously-0.1.0a1/docs/reference/database-schema.md +195 -0
  47. previously-0.1.0a1/docs/reference/hash-format.md +245 -0
  48. previously-0.1.0a1/docs/reference/index.md +12 -0
  49. previously-0.1.0a1/docs/superpowers/handoffs/2026-10-05-kup6s-delivery.md +252 -0
  50. previously-0.1.0a1/docs/superpowers/landkarte.md +699 -0
  51. previously-0.1.0a1/docs/superpowers/plans/2026-10-02-stufe-1a-log.md +2644 -0
  52. previously-0.1.0a1/docs/superpowers/plans/2026-10-03-dokumentation.md +1229 -0
  53. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-aeusserer-anker.md +1354 -0
  54. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1b-projektionen.md +2651 -0
  55. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/blob_spike.py +251 -0
  56. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/blob_spike_run.py +137 -0
  57. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_gcm.py +54 -0
  58. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_guessing.py +38 -0
  59. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_race.py +185 -0
  60. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_rustfs.py +155 -0
  61. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/task-1-hash-v2.patch +395 -0
  62. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/vector_v2.py +60 -0
  63. previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-blobs-und-tilgung.md +1306 -0
  64. previously-0.1.0a1/docs/superpowers/plans/2026-10-05-auslieferung.md +796 -0
  65. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/final-fix-report.md +176 -0
  66. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/index.md +67 -0
  67. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/progress.md +1332 -0
  68. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-1-brief.md +327 -0
  69. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-1-report.md +346 -0
  70. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-1-review.md +189 -0
  71. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-2-brief.md +66 -0
  72. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-2-report.md +276 -0
  73. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-3-brief.md +96 -0
  74. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-3-report.md +292 -0
  75. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-4-brief.md +36 -0
  76. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-4-report.md +290 -0
  77. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-4-review.md +122 -0
  78. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-brief.md +60 -0
  79. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-must-survive.md +192 -0
  80. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-report.md +313 -0
  81. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-review.md +273 -0
  82. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-brief.md +88 -0
  83. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-fix-1-report.md +276 -0
  84. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-report.md +335 -0
  85. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-review.md +549 -0
  86. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-brief.md +338 -0
  87. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-fix-1-report.md +222 -0
  88. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-fix-2-report.md +157 -0
  89. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-report.md +328 -0
  90. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-review.md +580 -0
  91. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-8-brief.md +86 -0
  92. previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-8-report.md +129 -0
  93. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-fix-dispatch.md +113 -0
  94. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-fix-report.md +269 -0
  95. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-fix-residuals-dispatch.md +42 -0
  96. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-re-review-dispatch.md +76 -0
  97. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-re-review.md +124 -0
  98. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-review-dispatch.md +121 -0
  99. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-review.md +496 -0
  100. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/index.md +263 -0
  101. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/progress.md +578 -0
  102. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-brief.md +565 -0
  103. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-dispatch.md +79 -0
  104. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-mutations.md +24 -0
  105. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-re-review-1-dispatch.md +63 -0
  106. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-re-review-1.md +38 -0
  107. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-report.md +170 -0
  108. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-review-dispatch.md +87 -0
  109. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-review.md +101 -0
  110. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-brief.md +537 -0
  111. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-dispatch.md +96 -0
  112. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-fix-1-dispatch.md +126 -0
  113. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-re-review-1-dispatch.md +72 -0
  114. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-re-review-1.md +68 -0
  115. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-report.md +188 -0
  116. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-review-dispatch.md +88 -0
  117. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-review.md +76 -0
  118. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-brief.md +135 -0
  119. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-dispatch.md +86 -0
  120. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-fix-1-dispatch.md +115 -0
  121. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-fix-2-dispatch.md +91 -0
  122. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-1-dispatch.md +79 -0
  123. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-1.md +67 -0
  124. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-2-dispatch.md +78 -0
  125. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-2.md +79 -0
  126. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-report.md +421 -0
  127. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-review-dispatch.md +94 -0
  128. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-review.md +82 -0
  129. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/bericht-a1-kernmodell-leitsaetze.md +182 -0
  130. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/bericht-a2-storage-und-offenes.md +255 -0
  131. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/bericht-c-messung.md +1506 -0
  132. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/brief-a1-kernmodell-leitsaetze.md +51 -0
  133. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/brief-a2-storage-und-offenes.md +56 -0
  134. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/brief-c-messung.md +44 -0
  135. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/index.md +347 -0
  136. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-fix-dispatch.md +96 -0
  137. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-fix-report.md +548 -0
  138. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-re-review-dispatch.md +59 -0
  139. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-re-review.md +88 -0
  140. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-review-dispatch.md +97 -0
  141. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-review.md +127 -0
  142. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/index.md +120 -0
  143. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/progress.md +1496 -0
  144. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-1-brief.md +256 -0
  145. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-1-report.md +601 -0
  146. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-2-brief.md +240 -0
  147. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-2-report.md +225 -0
  148. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-3-brief.md +472 -0
  149. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-3-report.md +178 -0
  150. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-4-brief.md +375 -0
  151. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-4-report.md +146 -0
  152. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-brief.md +543 -0
  153. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-fix-1-brief.md +25 -0
  154. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-re-review-1.md +34 -0
  155. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-report.md +440 -0
  156. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-brief.md +399 -0
  157. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-dispatch.md +89 -0
  158. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-fix-1-brief.md +17 -0
  159. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-re-review-1.md +31 -0
  160. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-report.md +440 -0
  161. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-review-dispatch.md +84 -0
  162. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-review.md +54 -0
  163. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-brief.md +56 -0
  164. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-dispatch.md +77 -0
  165. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-fix-1-brief.md +21 -0
  166. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-re-review-1.md +37 -0
  167. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-report.md +464 -0
  168. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-review-dispatch.md +76 -0
  169. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-review.md +61 -0
  170. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-brief.md +87 -0
  171. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-dispatch.md +70 -0
  172. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-fix-1-brief.md +19 -0
  173. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-re-review-1.md +39 -0
  174. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-report.md +598 -0
  175. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-review-dispatch.md +77 -0
  176. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-review.md +69 -0
  177. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-code-brief.md +270 -0
  178. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-code-re-review.md +122 -0
  179. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-code-report.md +323 -0
  180. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-docs-brief.md +269 -0
  181. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-docs-handback.md +158 -0
  182. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-docs-re-review.md +51 -0
  183. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-review-code.md +124 -0
  184. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-review-docs.md +118 -0
  185. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/global-constraints.md +77 -0
  186. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step1-setup.sh +38 -0
  187. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step10-addendum-smoke-cleanup.sh +53 -0
  188. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step2-bucket-key.sh +59 -0
  189. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step3-attach-rehearse.sh +70 -0
  190. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step4-erase.sh +46 -0
  191. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step5-exit2.sh +39 -0
  192. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step6-restore.sh +66 -0
  193. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step7-failures-cleanup.sh +39 -0
  194. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step8-addendum-setup.sh +50 -0
  195. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step9-addendum-checks.sh +90 -0
  196. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/index.md +84 -0
  197. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/open-points-from-execution.md +69 -0
  198. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/progress.md +299 -0
  199. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-1-brief.md +115 -0
  200. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-1-report.md +164 -0
  201. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-1-review.md +70 -0
  202. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-2-brief.md +181 -0
  203. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-2-report.md +291 -0
  204. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-2-review.md +103 -0
  205. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-3-brief.md +235 -0
  206. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-3-report.md +218 -0
  207. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-3-review.md +186 -0
  208. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-4-brief.md +58 -0
  209. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-4-report.md +280 -0
  210. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-4-review.md +87 -0
  211. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-5-brief.md +174 -0
  212. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-5-report.md +460 -0
  213. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-5-review.md +103 -0
  214. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-6-brief.md +122 -0
  215. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-6-report.md +334 -0
  216. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-6-review.md +168 -0
  217. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-7-brief.md +130 -0
  218. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-7-report.md +344 -0
  219. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-7-review.md +123 -0
  220. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-brief.md +125 -0
  221. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-fix1-handback.md +83 -0
  222. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-fix1-review.md +20 -0
  223. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-handback.md +480 -0
  224. previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-review.md +137 -0
  225. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-fix-re-review.md +85 -0
  226. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-fix-report.md +142 -0
  227. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-review-code.md +96 -0
  228. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-review-docs.md +164 -0
  229. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/global-constraints.md +45 -0
  230. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/index.md +63 -0
  231. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/progress.md +138 -0
  232. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-brief.md +103 -0
  233. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-fix1-review.md +23 -0
  234. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-report.md +366 -0
  235. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-review.md +58 -0
  236. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-brief.md +139 -0
  237. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix1-review.md +79 -0
  238. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix2-review.md +140 -0
  239. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix3-review.md +156 -0
  240. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix4-review.md +204 -0
  241. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix5-review.md +46 -0
  242. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-report.md +1031 -0
  243. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-review.md +68 -0
  244. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-brief.md +201 -0
  245. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-fix1-review.md +46 -0
  246. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-fix2-review.md +45 -0
  247. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-report.md +134 -0
  248. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-review.md +46 -0
  249. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-4-brief.md +133 -0
  250. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-4-report.md +136 -0
  251. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-4-review.md +67 -0
  252. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-5-brief.md +42 -0
  253. previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-5-report.md +96 -0
  254. previously-0.1.0a1/docs/superpowers/specs/2026-10-01-architektur.md +1587 -0
  255. previously-0.1.0a1/docs/superpowers/specs/2026-10-01-previously-design.md +1858 -0
  256. previously-0.1.0a1/docs/superpowers/specs/2026-10-02-stufe-1a-log.md +1144 -0
  257. previously-0.1.0a1/docs/superpowers/specs/2026-10-04-aeusserer-anker.md +479 -0
  258. previously-0.1.0a1/docs/superpowers/specs/2026-10-04-stufe-1b-projektionen.md +801 -0
  259. previously-0.1.0a1/docs/superpowers/specs/2026-10-04-stufe-1c-blobs-und-tilgung.md +851 -0
  260. previously-0.1.0a1/docs/superpowers/specs/2026-10-05-auslieferung.md +426 -0
  261. previously-0.1.0a1/docs/tutorials/index.md +9 -0
  262. previously-0.1.0a1/docs/tutorials/record-your-first-event.md +250 -0
  263. previously-0.1.0a1/pyproject.toml +170 -0
  264. previously-0.1.0a1/renovate.json5 +79 -0
  265. previously-0.1.0a1/scripts/smoke-image.sh +116 -0
  266. previously-0.1.0a1/src/previously/__init__.py +3 -0
  267. previously-0.1.0a1/src/previously/cli.py +1204 -0
  268. previously-0.1.0a1/src/previously/contract/__init__.py +3 -0
  269. previously-0.1.0a1/src/previously/contract/blobs.py +98 -0
  270. previously-0.1.0a1/src/previously/contract/rows.py +112 -0
  271. previously-0.1.0a1/src/previously/contract/store.py +155 -0
  272. previously-0.1.0a1/src/previously/contract/types.py +75 -0
  273. previously-0.1.0a1/src/previously/core/__init__.py +3 -0
  274. previously-0.1.0a1/src/previously/core/anchor.py +77 -0
  275. previously-0.1.0a1/src/previously/core/append.py +487 -0
  276. previously-0.1.0a1/src/previously/core/blob.py +179 -0
  277. previously-0.1.0a1/src/previously/core/canonical.py +100 -0
  278. previously-0.1.0a1/src/previously/core/chain.py +318 -0
  279. previously-0.1.0a1/src/previously/core/errors.py +130 -0
  280. previously-0.1.0a1/src/previously/core/hashing.py +307 -0
  281. previously-0.1.0a1/src/previously/core/projection/__init__.py +17 -0
  282. previously-0.1.0a1/src/previously/core/projection/chronicle.py +134 -0
  283. previously-0.1.0a1/src/previously/core/projection/source_stats.py +85 -0
  284. previously-0.1.0a1/src/previously/core/projection/worker.py +170 -0
  285. previously-0.1.0a1/src/previously/core/redact.py +423 -0
  286. previously-0.1.0a1/src/previously/core/redaction.py +284 -0
  287. previously-0.1.0a1/src/previously/core/sealing.py +268 -0
  288. previously-0.1.0a1/src/previously/core/units.py +45 -0
  289. previously-0.1.0a1/src/previously/core/verify.py +804 -0
  290. previously-0.1.0a1/src/previously/migrations/README +1 -0
  291. previously-0.1.0a1/src/previously/migrations/__init__.py +9 -0
  292. previously-0.1.0a1/src/previously/migrations/dsn.py +53 -0
  293. previously-0.1.0a1/src/previously/migrations/env.py +102 -0
  294. previously-0.1.0a1/src/previously/migrations/script.py.mako +28 -0
  295. previously-0.1.0a1/src/previously/migrations/versions/0001_log.py +109 -0
  296. previously-0.1.0a1/src/previously/migrations/versions/0002_projections.py +65 -0
  297. previously-0.1.0a1/src/previously/migrations/versions/0003_hash_version_2.py +85 -0
  298. previously-0.1.0a1/src/previously/migrations/versions/0004_event_blob.py +52 -0
  299. previously-0.1.0a1/src/previously/storage/__init__.py +3 -0
  300. previously-0.1.0a1/src/previously/storage/errors.py +142 -0
  301. previously-0.1.0a1/src/previously/storage/keys.py +84 -0
  302. previously-0.1.0a1/src/previously/storage/migrate.py +154 -0
  303. previously-0.1.0a1/src/previously/storage/postgres.py +1104 -0
  304. previously-0.1.0a1/src/previously/storage/s3.py +344 -0
  305. previously-0.1.0a1/src/previously/storage/schema.py +255 -0
  306. previously-0.1.0a1/tests/conftest.py +306 -0
  307. previously-0.1.0a1/tests/test_anchor.py +77 -0
  308. previously-0.1.0a1/tests/test_append.py +741 -0
  309. previously-0.1.0a1/tests/test_blob.py +529 -0
  310. previously-0.1.0a1/tests/test_canonical.py +127 -0
  311. previously-0.1.0a1/tests/test_chain.py +264 -0
  312. previously-0.1.0a1/tests/test_cli.py +3479 -0
  313. previously-0.1.0a1/tests/test_contracts.py +193 -0
  314. previously-0.1.0a1/tests/test_docs_build.py +33 -0
  315. previously-0.1.0a1/tests/test_docs_references.py +683 -0
  316. previously-0.1.0a1/tests/test_docs_typed_output.py +45 -0
  317. previously-0.1.0a1/tests/test_hashing.py +516 -0
  318. previously-0.1.0a1/tests/test_keys.py +169 -0
  319. previously-0.1.0a1/tests/test_migrate.py +729 -0
  320. previously-0.1.0a1/tests/test_migration_0003.py +137 -0
  321. previously-0.1.0a1/tests/test_migration_0004.py +57 -0
  322. previously-0.1.0a1/tests/test_migrations_dsn.py +42 -0
  323. previously-0.1.0a1/tests/test_projection_derive.py +182 -0
  324. previously-0.1.0a1/tests/test_projection_store.py +202 -0
  325. previously-0.1.0a1/tests/test_projection_worker.py +818 -0
  326. previously-0.1.0a1/tests/test_properties.py +463 -0
  327. previously-0.1.0a1/tests/test_redact.py +996 -0
  328. previously-0.1.0a1/tests/test_redaction.py +246 -0
  329. previously-0.1.0a1/tests/test_rows.py +88 -0
  330. previously-0.1.0a1/tests/test_s3.py +374 -0
  331. previously-0.1.0a1/tests/test_schema.py +535 -0
  332. previously-0.1.0a1/tests/test_sealing.py +263 -0
  333. previously-0.1.0a1/tests/test_storage.py +798 -0
  334. previously-0.1.0a1/tests/test_units.py +78 -0
  335. previously-0.1.0a1/tests/test_verify.py +1368 -0
  336. previously-0.1.0a1/tests/test_wheel.py +31 -0
  337. previously-0.1.0a1/uv.lock +1755 -0
@@ -0,0 +1,3 @@
1
+ *
2
+ !pyproject.toml
3
+ !uv.lock
@@ -0,0 +1,72 @@
1
+ # The dependency audit: `pip-audit` against the locked environment, which
2
+ # the architecture record lists among the tools and which was never built
3
+ # until 2026-10-04.
4
+ #
5
+ # A workflow of its own, not a seventh step in `gates.yml`, because it
6
+ # answers a different question. The six gates judge the tree: their result
7
+ # changes only when the tree does, so a commit that was green stays green.
8
+ # An audit's result changes when an advisory is published, on a day when
9
+ # nobody touched anything. As a gate it would turn a pull request red for a
10
+ # reason that is not in the pull request, and run only when somebody pushes
11
+ # it would miss the day the advisory arrives. Hence the weekly schedule
12
+ # beside the two triggers it shares with the gates.
13
+ #
14
+ # The same care as `gates.yml`: read-only permissions, actions pinned by
15
+ # commit, the locked dependencies, the versions named in the log.
16
+ name: Audit
17
+
18
+ on:
19
+ pull_request:
20
+ push:
21
+ branches: [main]
22
+ schedule:
23
+ # Mondays at 05:17 UTC. Not on the hour: GitHub's documentation of the
24
+ # `schedule` event names the start of every hour as a time of high load,
25
+ # when a scheduled run can be delayed or dropped.
26
+ - cron: "17 5 * * 1"
27
+
28
+ # Nothing here writes: the audit reads the environment and reports its exit
29
+ # code.
30
+ permissions:
31
+ contents: read
32
+
33
+ jobs:
34
+ audit:
35
+ runs-on: ubuntu-latest
36
+
37
+ steps:
38
+ # The same pins as `gates.yml`, by commit with the version beside it.
39
+ - name: Check out
40
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
41
+
42
+ - name: Set up uv and Python 3.14
43
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
44
+ with:
45
+ python-version: "3.14"
46
+ enable-cache: true
47
+ cache-dependency-glob: uv.lock
48
+
49
+ - name: Install the locked dependencies
50
+ # `--locked`, so the run fails when `uv.lock` does not match
51
+ # `pyproject.toml` instead of auditing a fresh resolution.
52
+ # `--all-extras`, so every package the gates install is audited,
53
+ # and `pip-audit` itself, which lives in [dev].
54
+ run: uv sync --locked --all-extras
55
+
56
+ - name: Name the versions
57
+ run: |
58
+ uv --version
59
+ uv run python -VV
60
+ uv run pip-audit --version
61
+
62
+ - name: Audit the dependencies
63
+ # The installed environment, which is the locked one. The project
64
+ # itself is installed editable and is not on PyPI, so there is
65
+ # nothing to look it up against; `--skip-editable` leaves it out and
66
+ # names it as skipped in the log. `--strict` cannot be added: it
67
+ # fails the run on that very skip (measured on 2026-10-04, exit 1
68
+ # with "previously: distribution marked as editable").
69
+ #
70
+ # A finding fails this workflow. Nothing is suppressed here: a
71
+ # vulnerability is answered by a lock update, not by an ignore list.
72
+ run: uv run pip-audit --skip-editable --progress-spinner off
@@ -0,0 +1,103 @@
1
+ # The five gates out of CLAUDE.md, plus a sixth for the documentation build
2
+ # (task 1 of the 2026-10-03 documentation plan), run on a machine that is not
3
+ # the author's.
4
+ #
5
+ # Acceptance condition 4 in §11 of the stage 1a spec reads "a deliberately
6
+ # wrong import lets CI fail". Until this file there was no CI, so the
7
+ # condition was asserted rather than met: `tests/test_contracts.py` ran only
8
+ # when somebody started `pytest` by hand, and `.pre-commit-config.yaml` runs
9
+ # ruff and five hygiene hooks — no pyright, no lint-imports, no pytest.
10
+ #
11
+ # Deliberately nothing beyond the gates: no publishing, no uploading of
12
+ # reports, no secrets, no access to anything outside the repository. The
13
+ # repository is public (AGPL-3.0-or-later) and compute minutes are free, but a
14
+ # workflow with write permissions would be attack surface without a use.
15
+ # `release.yml` calls this file as its first job, through `workflow_call`, and
16
+ # it is there, not here, that anything gets published.
17
+ name: Gates
18
+
19
+ on:
20
+ push:
21
+ pull_request:
22
+ workflow_call:
23
+
24
+ # The minimum, stated explicitly instead of inherited from the repository's
25
+ # default. Nothing here writes: the gates read the tree and report their exit
26
+ # code.
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ gates:
32
+ # Docker is needed, because `tests/conftest.py` raises a real PostgreSQL
33
+ # 17 and a real S3 server, RustFS, via testcontainers — no mock stands in
34
+ # for the database or the blob store (CLAUDE.md).
35
+ # Checked, not assumed: the runner image for ubuntu-24.04, which
36
+ # `ubuntu-latest` currently resolves to, lists "Docker Client 28.0.4",
37
+ # "Docker Server 28.0.4" and "Docker Compose 2.38.2"
38
+ # (actions/runner-images, images/ubuntu/Ubuntu2404-Readme.md). So no
39
+ # service container: `conftest.py` brings its own containers up, and a
40
+ # second route to either server beside it would be a trap for the next
41
+ # reader.
42
+ runs-on: ubuntu-latest
43
+
44
+ steps:
45
+ # Both actions are pinned by commit, not by tag: a tag can be moved, a
46
+ # commit cannot. The version stands beside it so that the pin is
47
+ # legible.
48
+ - name: Check out
49
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
50
+
51
+ - name: Set up uv and Python 3.14
52
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
53
+ with:
54
+ # Python 3.14 is not a preference: the annotations in `canonical.py`
55
+ # and `hashing.py` work only thanks to PEP 649, and an older version
56
+ # fails there. `requires-python = ">=3.14"` says the same thing, but
57
+ # saying it here as well makes the log name the version.
58
+ python-version: "3.14"
59
+ enable-cache: true
60
+ cache-dependency-glob: uv.lock
61
+
62
+ - name: Install the locked dependencies
63
+ # `--locked`, not `--frozen` and not a free resolution: the run fails
64
+ # if `uv.lock` does not match `pyproject.toml`, instead of quietly
65
+ # resolving something else and testing a different project than the
66
+ # development tree. `--all-extras` brings [test], [dev] and [docs],
67
+ # which is where pytest, pyright, ruff, import-linter, sphinx and
68
+ # vale live. So gate 6 installs nothing of its own.
69
+ run: uv sync --locked --all-extras
70
+
71
+ - name: Name the versions
72
+ # So that the log says what was tested, instead of this file claiming
73
+ # it. Two lines, no gate — but the version is part of every result
74
+ # underneath.
75
+ run: |
76
+ uv --version
77
+ uv run python -VV
78
+
79
+ # The six gates, each as its own step: that way the log names which
80
+ # one failed instead of one block going red.
81
+ - name: Gate 1 — ruff check
82
+ run: uv run ruff check .
83
+
84
+ - name: Gate 2 — ruff format
85
+ run: uv run ruff format --check .
86
+
87
+ - name: Gate 3 — pyright
88
+ run: uv run pyright
89
+
90
+ - name: Gate 4 — lint-imports
91
+ run: uv run lint-imports
92
+
93
+ - name: Gate 5 — pytest
94
+ run: uv run pytest --cov --cov-report=term-missing
95
+
96
+ - name: Gate 6 — documentation
97
+ # Warnings are errors (-W in docs/Makefile). linkcheck runs here too:
98
+ # the hosts that rate-limit crawlers are listed in conf.py's
99
+ # linkcheck_ignore, so a failure means a genuinely broken link.
100
+ run: |
101
+ make -C docs html
102
+ make -C docs vale
103
+ make -C docs linkcheck
@@ -0,0 +1,348 @@
1
+ # The way out: the package to PyPI and the image to ghcr.io, built only from
2
+ # a tree that passed the six gates.
3
+ #
4
+ # Three triggers, and what each one reaches:
5
+ #
6
+ # push to main gates -> build -> publish-testpypi
7
+ # manual start the same, when started on main; elsewhere build only
8
+ # published release gates and tag, side by side -> build -> publish-pypi
9
+ # -> image (amd64 and arm64) -> manifest
10
+ #
11
+ # A tag alone publishes nothing, and neither does a merge: only publishing the
12
+ # GitHub release does. The environments hold the second lock: `testpypi`
13
+ # accepts a deployment only from the branch main, `pypi` only from a tag
14
+ # `v*`, and the trusted publishers on PyPI and Test-PyPI name this file and
15
+ # those two environments, so no upload token lies in the repository.
16
+ #
17
+ # Every job after `tag` states its whole condition, `!cancelled()` and the
18
+ # result of the job it needs, instead of leaning on the implicit `success()`.
19
+ # On a push to main `tag` is skipped, and the implicit `success()` counts a
20
+ # skipped job as not passed: directly upstream (actions/runner#491), and also
21
+ # further up, below a job that did run (actions/runner#2205, still open on
22
+ # 2026-10-05) — `publish-testpypi` would never run. Spelled out, a failing
23
+ # gate or a failing tag check leaves `build` skipped, and every job below it
24
+ # is skipped with it, because each one asks for `success` from the one above.
25
+ name: Release
26
+
27
+ on:
28
+ push:
29
+ branches: [main]
30
+ release:
31
+ types: [published]
32
+ workflow_dispatch:
33
+
34
+ # One run per ref at a time, and a running one is never cancelled by a newer
35
+ # one: a run cut off halfway could leave a version on PyPI without its image.
36
+ # A run that still waits can be: GitHub keeps one waiting run per group, and
37
+ # a newer one replaces it. A release has a ref of its own, its tag, so that
38
+ # touches only pushes and manual starts on a branch, where the newer run
39
+ # builds the newer tree.
40
+ concurrency:
41
+ group: release-${{ github.ref }}
42
+ cancel-in-progress: false
43
+
44
+ # The default for every job. A job that sets `permissions:` of its own
45
+ # replaces this default instead of adding to it, so each such job names all
46
+ # it needs, `contents: read` included where it checks out.
47
+ permissions:
48
+ contents: read
49
+
50
+ jobs:
51
+ gates:
52
+ # The same file a push and a pull request run, so the gates stand in one
53
+ # place. On a push to main they therefore run twice, once there and once
54
+ # here: the price of not copying them.
55
+ uses: ./.github/workflows/gates.yml
56
+
57
+ tag:
58
+ # Before anything is uploaded: a tag in another form would otherwise only
59
+ # be refused after the package is on PyPI. The version on PyPI and in the
60
+ # image is the tag without its `v` (hatch-vcs reads it, `image` strips
61
+ # it), so the tag must have a form that PEP 440 normalization leaves as
62
+ # it is. The four forms do; a leading zero (`v1.02.3`) passes this check
63
+ # and does not, because hatch-vcs makes it `1.2.3` while `image` waits for
64
+ # `1.02.3`. That is a mistake in the tag, named here, not tested for.
65
+ if: github.event_name == 'release'
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - name: Check the tag's form
69
+ run: |
70
+ re='^v[0-9]+\.[0-9]+\.[0-9]+((a|b|rc)[0-9]+)?$'
71
+ [[ "$GITHUB_REF_NAME" =~ $re ]] || {
72
+ echo "::error::tag $GITHUB_REF_NAME is not vX.Y.Z, vX.Y.ZaN, vX.Y.ZbN or vX.Y.ZrcN"; exit 1; }
73
+
74
+ build:
75
+ needs: [gates, tag]
76
+ # `tag` is skipped everywhere but on a release, so skipped counts as
77
+ # passed here; failed and cancelled do not.
78
+ if: >-
79
+ ${{ !cancelled() && needs.gates.result == 'success'
80
+ && (needs.tag.result == 'success' || needs.tag.result == 'skipped') }}
81
+ runs-on: ubuntu-latest
82
+ steps:
83
+ # Pinned by commit with the version beside it, as in `gates.yml`, and
84
+ # these two with the same commits.
85
+ - name: Check out
86
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
87
+ with:
88
+ # hatch-vcs reads the version from the tags, so it needs the
89
+ # history: on a release the tag itself; on main a development
90
+ # version, `0.1.devN` before the first tag (`0.1.dev178`, measured
91
+ # on 2026-10-05), and after it one counted from the last tag.
92
+ fetch-depth: 0
93
+
94
+ - name: Set up uv and Python 3.14
95
+ # No `enable-cache`: its default, `auto`, leaves the cache off for
96
+ # release and tag-push events (the input's own description in
97
+ # setup-uv's action.yml), so what goes to PyPI is built without
98
+ # anything an earlier run left behind.
99
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
100
+ with:
101
+ python-version: "3.14"
102
+
103
+ - name: Build the sdist and the wheel
104
+ run: uv build
105
+
106
+ - name: Upload the distributions
107
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
108
+ with:
109
+ name: dist
110
+ path: dist/
111
+ if-no-files-found: error
112
+
113
+ publish-testpypi:
114
+ needs: build
115
+ # `github.ref` and not the event: a manual start on main publishes here
116
+ # as a push does, a manual start anywhere else reaches nothing beyond
117
+ # the build.
118
+ if: ${{ !cancelled() && needs.build.result == 'success' && github.ref == 'refs/heads/main' }}
119
+ runs-on: ubuntu-latest
120
+ environment: testpypi
121
+ # Trusted publishing: the OIDC token is the only credential.
122
+ permissions:
123
+ id-token: write
124
+ steps:
125
+ - name: Download the distributions
126
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
127
+ with:
128
+ name: dist
129
+ path: dist/
130
+
131
+ - name: Publish to Test-PyPI
132
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
133
+ with:
134
+ repository-url: https://test.pypi.org/legacy/
135
+ # A rerun on main would otherwise fail on the files the first run
136
+ # already uploaded.
137
+ skip-existing: true
138
+
139
+ publish-pypi:
140
+ needs: build
141
+ # No `skip-existing` here: a version that is on PyPI never comes back,
142
+ # and a release that finds its version taken has to fail, not pass.
143
+ if: ${{ !cancelled() && needs.build.result == 'success' && github.event_name == 'release' }}
144
+ runs-on: ubuntu-latest
145
+ environment: pypi
146
+ permissions:
147
+ id-token: write
148
+ steps:
149
+ - name: Download the distributions
150
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
151
+ with:
152
+ name: dist
153
+ path: dist/
154
+
155
+ - name: Publish to PyPI
156
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
157
+
158
+ image:
159
+ needs: publish-pypi
160
+ # The image installs Previously from PyPI, so it comes after PyPI and
161
+ # never on main, where `publish-pypi` is skipped.
162
+ if: ${{ !cancelled() && needs.publish-pypi.result == 'success' }}
163
+ strategy:
164
+ matrix:
165
+ include:
166
+ - {platform: linux/amd64, runner: ubuntu-latest}
167
+ - {platform: linux/arm64, runner: ubuntu-24.04-arm}
168
+ # Each platform on a runner of its own architecture: no emulation, and the
169
+ # smoke test runs the image natively.
170
+ runs-on: ${{ matrix.runner }}
171
+ permissions:
172
+ contents: read
173
+ packages: write
174
+ steps:
175
+ - name: Check out
176
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
177
+
178
+ - name: Version from the tag
179
+ id: version
180
+ run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
181
+
182
+ - name: Wait for the version on PyPI
183
+ # The upload has finished, but the index may answer a moment later.
184
+ # 30 attempts, 10 seconds apart: five minutes of waiting at most,
185
+ # plus the time the requests take.
186
+ env:
187
+ VERSION: ${{ steps.version.outputs.version }}
188
+ run: |
189
+ echo "waiting for previously==$VERSION on PyPI"
190
+ for i in $(seq 1 30); do
191
+ if curl -sf "https://pypi.org/pypi/previously/$VERSION/json" > /dev/null; then
192
+ echo "available on PyPI"
193
+ exit 0
194
+ fi
195
+ echo "attempt $i: not yet available, waiting 10 s"
196
+ sleep 10
197
+ done
198
+ echo "::error::previously==$VERSION did not appear on PyPI"
199
+ exit 1
200
+
201
+ - name: Platform suffix and an empty wheels context
202
+ # `linux/amd64` becomes `linux-amd64` for the intermediate tag.
203
+ # `wheels` stays empty, so the Dockerfile has nothing to find there and
204
+ # installs the package from PyPI.
205
+ id: platform
206
+ env:
207
+ PLATFORM: ${{ matrix.platform }}
208
+ run: |
209
+ echo "suffix=${PLATFORM//\//-}" >> "$GITHUB_OUTPUT"
210
+ mkdir -p "$RUNNER_TEMP/wheels"
211
+
212
+ - name: Log in to ghcr.io
213
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
214
+ with:
215
+ registry: ghcr.io
216
+ username: ${{ github.actor }}
217
+ password: ${{ secrets.GITHUB_TOKEN }}
218
+
219
+ - name: Set up Docker Buildx
220
+ uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
221
+
222
+ - name: Build and push the platform image
223
+ # Under an intermediate tag `<version>-linux-<arch>` that nobody is
224
+ # meant to use: the tags people use arise only in `manifest`, once both
225
+ # platforms passed the smoke test.
226
+ id: build
227
+ uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
228
+ with:
229
+ # `.` and not the default, a git context of the ref: the build reads
230
+ # the checked-out `pyproject.toml` and `uv.lock`.
231
+ context: .
232
+ build-contexts: wheels=${{ runner.temp }}/wheels
233
+ push: true
234
+ platforms: ${{ matrix.platform }}
235
+ tags: ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}-${{ steps.platform.outputs.suffix }}
236
+ # The revision and the moment the labels name: the commit of the
237
+ # release and the time it was published, both fixed for the
238
+ # release, so a rerun labels the image the same way.
239
+ build-args: |
240
+ PREVIOUSLY_VERSION=${{ steps.version.outputs.version }}
241
+ PREVIOUSLY_REVISION=${{ github.sha }}
242
+ PREVIOUSLY_CREATED=${{ github.event.release.published_at }}
243
+
244
+ - name: Set up uv
245
+ # For `uv export` in the smoke test, which reads `uv.lock` and needs no
246
+ # Python: measured on 2026-10-05 with UV_PYTHON_DOWNLOADS=never in an
247
+ # image that has uv and no interpreter, exit 0.
248
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
249
+
250
+ - name: Smoke test
251
+ # Against the image as pushed: nothing is loaded locally, so
252
+ # `docker run` pulls it from ghcr.io. If it fails, this job fails,
253
+ # `manifest` does not run, and the version stays without the tags
254
+ # anyone uses — only the intermediate ones exist. The package is on
255
+ # PyPI by then; that is the price of an image installed from PyPI.
256
+ env:
257
+ IMAGE: ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}-${{ steps.platform.outputs.suffix }}
258
+ run: bash scripts/smoke-image.sh "$IMAGE"
259
+
260
+ - name: Export the digest
261
+ # An empty file named after the digest: `manifest` needs the names,
262
+ # not the contents.
263
+ env:
264
+ DIGEST: ${{ steps.build.outputs.digest }}
265
+ run: |
266
+ mkdir -p "$RUNNER_TEMP/digests"
267
+ touch "$RUNNER_TEMP/digests/${DIGEST#sha256:}"
268
+
269
+ - name: Upload the digest
270
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
271
+ with:
272
+ name: digests-${{ steps.platform.outputs.suffix }}
273
+ path: ${{ runner.temp }}/digests/*
274
+ if-no-files-found: error
275
+ # A week: `manifest` reads both digests, also when it is rerun on
276
+ # its own, days later, after a failure. Expired, they are gone, and
277
+ # the rerun fails with the package on PyPI and no image. The files
278
+ # are empty, so keeping them costs nothing.
279
+ retention-days: 7
280
+
281
+ manifest:
282
+ needs: image
283
+ # A matrix job's result is `success` only when every platform succeeded.
284
+ if: ${{ !cancelled() && needs.image.result == 'success' }}
285
+ runs-on: ubuntu-latest
286
+ permissions:
287
+ contents: read
288
+ packages: write
289
+ steps:
290
+ - name: Download the digests
291
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
292
+ with:
293
+ path: ${{ runner.temp }}/digests
294
+ pattern: digests-*
295
+ merge-multiple: true
296
+
297
+ - name: Log in to ghcr.io
298
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
299
+ with:
300
+ registry: ghcr.io
301
+ username: ${{ github.actor }}
302
+ password: ${{ secrets.GITHUB_TOKEN }}
303
+
304
+ - name: Set up Docker Buildx
305
+ uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
306
+
307
+ - name: Image tags
308
+ # `pep440`, not `semver`: `v0.1.0a1` is no valid semver, and the
309
+ # `semver` type skips such a tag with a warning — the first release
310
+ # would get no tag at all. Measured on 2026-10-05 by running this
311
+ # action's own `dist/index.cjs` at the pinned commit with these inputs:
312
+ # `v1.2.3` gives `1.2.3`, `1.2` and `latest`; `v0.1.0a1` and
313
+ # `v1.2.3rc4` give the version alone, because the action extends a
314
+ # pre-release to `{{version}}` only; and the `semver` rules give
315
+ # `v0.1.0a1` no tag, with "No Docker tag has been generated".
316
+ #
317
+ # `latest=false` takes away the automatic `latest`, which the action
318
+ # derives from the tag's form — without it, `v1.2.3` marked as a
319
+ # pre-release still got `latest` in the same measurement. The rule
320
+ # below derives it from the release's mark instead. A tag whose form
321
+ # and mark disagree is a mistake made when publishing, and this file
322
+ # does not second-guess it: the mark decides.
323
+ id: meta
324
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
325
+ with:
326
+ images: ghcr.io/${{ github.repository }}
327
+ flavor: |
328
+ latest=false
329
+ tags: |
330
+ type=pep440,pattern={{version}}
331
+ type=pep440,pattern={{major}}.{{minor}}
332
+ type=raw,value=latest,enable=${{ github.event.release.prerelease == false }}
333
+
334
+ - name: Create and push the manifest list
335
+ # One manifest list over both platform images, under every tag of the
336
+ # step above.
337
+ env:
338
+ IMAGE: ghcr.io/${{ github.repository }}
339
+ run: |
340
+ tags=()
341
+ while IFS= read -r tag; do
342
+ tags+=(-t "$tag")
343
+ done < <(jq -r '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
344
+ sources=()
345
+ for digest in "$RUNNER_TEMP"/digests/*; do
346
+ sources+=("$IMAGE@sha256:${digest##*/}")
347
+ done
348
+ docker buildx imagetools create "${tags[@]}" "${sources[@]}"
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
9
+ # Tools
10
+ .ruff_cache/
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .coverage
14
+ .coverage.*
15
+ htmlcov/
16
+
17
+ # Editor and system
18
+ .idea/
19
+ .vscode/
20
+ .DS_Store
21
+
22
+ # Worktrees and agent workspaces
23
+ .claude/
24
+ .superpowers/
25
+
26
+ # Documentation
27
+ docs/_build/
28
+ .vale-styles/Microsoft/
@@ -0,0 +1,116 @@
1
+ [importlinter]
2
+ root_package = previously
3
+ # Five of the six contracts forbid external packages (sqlalchemy, psycopg,
4
+ # alembic, anthropic, openai, pyrage, boto3, botocore). import-linter requires
5
+ # this setting for that, otherwise it aborts with a configuration error — it
6
+ # is not a loosening of the contracts themselves.
7
+ include_external_packages = True
8
+
9
+ [importlinter:contract:layers]
10
+ name = Layers: core beside migrations, both above storage, contract below all
11
+ type = layers
12
+ layers =
13
+ previously.cli
14
+ previously.core | previously.migrations
15
+ previously.storage
16
+ previously.contract
17
+
18
+ [importlinter:contract:core-is-clean]
19
+ name = core knows no foreign system and no model
20
+ type = forbidden
21
+ source_modules =
22
+ previously.core
23
+ forbidden_modules =
24
+ sqlalchemy
25
+ psycopg
26
+ alembic
27
+ # Until 2026-10-04 this contract carried two named exemptions,
28
+ # `core.append -> storage.postgres` and `core.verify -> storage.postgres`,
29
+ # because both modules typed their `storage` parameter as the concrete
30
+ # `PostgresStorage` under `TYPE_CHECKING`. They were granted by ruling T7-a
31
+ # and by ruling T8-c of the 2026-10-02 stage 1a plan, whose execution ledger
32
+ # was never shipped and is lost, so both labels are provenance and nothing
33
+ # more — the reasoning is the paragraph you are reading.
34
+ #
35
+ # There is no directory to follow them into, and until 2026-10-04 this
36
+ # comment named one. Measured that day against the only record under
37
+ # `docs/superpowers/sdd/`: `2026-10-03-dokumentation/progress.md` carries a
38
+ # `Ruling T7-a` about an unrelated decision and no `Ruling T8-c` at all, so
39
+ # the pointer sent a reader to the wrong decision — worse than a label with
40
+ # no pointer, because the reader stops with an answer.
41
+ #
42
+ # The exemptions were enumerated by name and not matched by a wildcard, so
43
+ # that a third module following the same pattern would break this contract
44
+ # until somebody granted it deliberately — measured with a throwaway module,
45
+ # a wildcard let the new edge through silently.
46
+ #
47
+ # Stage 1b typed `core` against `contract.store.LogStore[Conn]` instead, and
48
+ # the edge is gone. There is nothing to exempt. Should an exemption ever come
49
+ # back here, it is a named edge, never a pattern; {ref}`module-boundaries`
50
+ # carries the measurement that says why.
51
+
52
+ [importlinter:contract:core-and-contract-know-no-sql]
53
+ name = core and contract import no sqlalchemy
54
+ type = forbidden
55
+ source_modules =
56
+ previously.core
57
+ previously.contract
58
+ forbidden_modules =
59
+ sqlalchemy
60
+ psycopg
61
+ # Until 2026-10-04 this contract carried the same two named exemptions as
62
+ # `core-is-clean`: edges enumerated one by one, no wildcard across `core`. See
63
+ # there for the reasoning, and for what the two stage 1a ruling labels are
64
+ # worth. Stage 1b typed `core` against `contract.store.LogStore[Conn]`, the
65
+ # edge `core -> storage.postgres` is gone, and there is nothing left to exempt
66
+ # here either.
67
+ #
68
+ # The contract was named `Only storage imports sqlalchemy`, with the id
69
+ # `only-storage-knows-sql`, until 2026-10-05. That day the migrations moved into
70
+ # the package, and they import SQLAlchemy too, so the old name stated
71
+ # something false while the contract still checked the right thing: its
72
+ # sources were always `core` and `contract`, and the name now says so.
73
+
74
+ [importlinter:contract:no-vendor-sdk]
75
+ name = No vendor SDK in the package
76
+ type = forbidden
77
+ source_modules =
78
+ previously
79
+ forbidden_modules =
80
+ anthropic
81
+ openai
82
+
83
+ [importlinter:contract:only-sealing-knows-age]
84
+ name = Only core.sealing imports pyrage
85
+ type = forbidden
86
+ source_modules =
87
+ previously
88
+ forbidden_modules =
89
+ pyrage
90
+ ignore_imports =
91
+ previously.core.sealing -> pyrage
92
+ # Sealing happens where the rules are, in `core`, so that the store below
93
+ # gets and gives ciphertext only: a module in `storage` that could open what
94
+ # it stores is the mistake this contract exists to catch. The whole package
95
+ # is the source, and the one edge that may exist is named — never a pattern,
96
+ # for the reason `core-is-clean` gives. Should `core.sealing` stop importing
97
+ # `pyrage`, `lint-imports` fails with "No matches for ignored import"
98
+ # (measured on 2026-10-05), so the exemption cannot outlive its edge.
99
+
100
+ [importlinter:contract:only-s3-knows-boto3]
101
+ name = Only storage.s3 imports boto3
102
+ type = forbidden
103
+ source_modules =
104
+ previously
105
+ forbidden_modules =
106
+ boto3
107
+ botocore
108
+ ignore_imports =
109
+ previously.storage.s3 -> boto3
110
+ previously.storage.s3 -> botocore
111
+ # Storing happens where the foreign systems are, in `storage`, the way SQL
112
+ # stays in `storage.postgres`: what reaches `core` is the protocol in
113
+ # `contract.blobs` and the errors in `storage.errors`, no `botocore` type.
114
+ # `botocore` is forbidden beside `boto3` because the exceptions and the
115
+ # client configuration come from there, and an import of either would carry
116
+ # the foreign system upwards. Two named edges, out of one module.