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.
- previously-0.1.0a1/.dockerignore +3 -0
- previously-0.1.0a1/.github/workflows/audit.yml +72 -0
- previously-0.1.0a1/.github/workflows/gates.yml +103 -0
- previously-0.1.0a1/.github/workflows/release.yml +348 -0
- previously-0.1.0a1/.gitignore +28 -0
- previously-0.1.0a1/.importlinter +116 -0
- previously-0.1.0a1/.pre-commit-config.yaml +55 -0
- previously-0.1.0a1/.vale-styles/config/vocabularies/Previously/accept.txt +19 -0
- previously-0.1.0a1/.vale.ini +45 -0
- previously-0.1.0a1/CLAUDE.md +522 -0
- previously-0.1.0a1/DEPENDENCIES.md +103 -0
- previously-0.1.0a1/Dockerfile +58 -0
- previously-0.1.0a1/LICENSE +661 -0
- previously-0.1.0a1/NOTIZEN.md +926 -0
- previously-0.1.0a1/PKG-INFO +195 -0
- previously-0.1.0a1/README.md +155 -0
- previously-0.1.0a1/alembic.ini +149 -0
- previously-0.1.0a1/docs/Makefile +38 -0
- previously-0.1.0a1/docs/conf.py +59 -0
- previously-0.1.0a1/docs/explanation/backup-encryption.md +165 -0
- previously-0.1.0a1/docs/explanation/blobs.md +243 -0
- previously-0.1.0a1/docs/explanation/canonicalization.md +117 -0
- previously-0.1.0a1/docs/explanation/concurrency.md +216 -0
- previously-0.1.0a1/docs/explanation/delivery.md +100 -0
- previously-0.1.0a1/docs/explanation/design-records.md +146 -0
- previously-0.1.0a1/docs/explanation/erasure.md +196 -0
- previously-0.1.0a1/docs/explanation/hash-chain.md +433 -0
- previously-0.1.0a1/docs/explanation/index.md +19 -0
- previously-0.1.0a1/docs/explanation/module-boundaries.md +339 -0
- previously-0.1.0a1/docs/explanation/projections.md +273 -0
- previously-0.1.0a1/docs/explanation/silent-losses.md +83 -0
- previously-0.1.0a1/docs/how-to/add-a-migration.md +37 -0
- previously-0.1.0a1/docs/how-to/attach-and-fetch-a-file.md +72 -0
- previously-0.1.0a1/docs/how-to/cut-a-release.md +157 -0
- previously-0.1.0a1/docs/how-to/erase-something.md +185 -0
- previously-0.1.0a1/docs/how-to/index.md +18 -0
- previously-0.1.0a1/docs/how-to/keep-the-blob-key-safe.md +156 -0
- previously-0.1.0a1/docs/how-to/rebuild-a-projection.md +89 -0
- previously-0.1.0a1/docs/how-to/restore-from-a-backup.md +202 -0
- previously-0.1.0a1/docs/how-to/run-a-blob-store-on-your-machine.md +110 -0
- previously-0.1.0a1/docs/how-to/run-the-image.md +182 -0
- previously-0.1.0a1/docs/how-to/verify-the-chain.md +114 -0
- previously-0.1.0a1/docs/index.md +53 -0
- previously-0.1.0a1/docs/reference/cli.md +631 -0
- previously-0.1.0a1/docs/reference/configuration.md +99 -0
- previously-0.1.0a1/docs/reference/database-schema.md +195 -0
- previously-0.1.0a1/docs/reference/hash-format.md +245 -0
- previously-0.1.0a1/docs/reference/index.md +12 -0
- previously-0.1.0a1/docs/superpowers/handoffs/2026-10-05-kup6s-delivery.md +252 -0
- previously-0.1.0a1/docs/superpowers/landkarte.md +699 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-02-stufe-1a-log.md +2644 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-03-dokumentation.md +1229 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-aeusserer-anker.md +1354 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1b-projektionen.md +2651 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/blob_spike.py +251 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/blob_spike_run.py +137 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_gcm.py +54 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_guessing.py +38 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_race.py +185 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/measure_rustfs.py +155 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/task-1-hash-v2.patch +395 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-anlagen/vector_v2.py +60 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-04-stufe-1c-blobs-und-tilgung.md +1306 -0
- previously-0.1.0a1/docs/superpowers/plans/2026-10-05-auslieferung.md +796 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/final-fix-report.md +176 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/index.md +67 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/progress.md +1332 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-1-brief.md +327 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-1-report.md +346 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-1-review.md +189 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-2-brief.md +66 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-2-report.md +276 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-3-brief.md +96 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-3-report.md +292 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-4-brief.md +36 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-4-report.md +290 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-4-review.md +122 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-brief.md +60 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-must-survive.md +192 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-report.md +313 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-5-review.md +273 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-brief.md +88 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-fix-1-report.md +276 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-report.md +335 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-6-review.md +549 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-brief.md +338 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-fix-1-report.md +222 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-fix-2-report.md +157 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-report.md +328 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-7-review.md +580 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-8-brief.md +86 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-03-dokumentation/task-8-report.md +129 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-fix-dispatch.md +113 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-fix-report.md +269 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-fix-residuals-dispatch.md +42 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-re-review-dispatch.md +76 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-re-review.md +124 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-review-dispatch.md +121 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/final-review.md +496 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/index.md +263 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/progress.md +578 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-brief.md +565 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-dispatch.md +79 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-mutations.md +24 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-re-review-1-dispatch.md +63 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-re-review-1.md +38 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-report.md +170 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-review-dispatch.md +87 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-1-review.md +101 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-brief.md +537 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-dispatch.md +96 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-fix-1-dispatch.md +126 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-re-review-1-dispatch.md +72 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-re-review-1.md +68 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-report.md +188 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-review-dispatch.md +88 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-2-review.md +76 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-brief.md +135 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-dispatch.md +86 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-fix-1-dispatch.md +115 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-fix-2-dispatch.md +91 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-1-dispatch.md +79 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-1.md +67 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-2-dispatch.md +78 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-re-review-2.md +79 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-report.md +421 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-review-dispatch.md +94 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-aeusserer-anker/task-3-review.md +82 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/bericht-a1-kernmodell-leitsaetze.md +182 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/bericht-a2-storage-und-offenes.md +255 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/bericht-c-messung.md +1506 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/brief-a1-kernmodell-leitsaetze.md +51 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/brief-a2-storage-und-offenes.md +56 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/brief-c-messung.md +44 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-pruefpunkt-teilprojekt-1/index.md +347 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-fix-dispatch.md +96 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-fix-report.md +548 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-re-review-dispatch.md +59 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-re-review.md +88 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-review-dispatch.md +97 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/final-review.md +127 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/index.md +120 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/progress.md +1496 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-1-brief.md +256 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-1-report.md +601 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-2-brief.md +240 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-2-report.md +225 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-3-brief.md +472 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-3-report.md +178 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-4-brief.md +375 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-4-report.md +146 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-brief.md +543 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-fix-1-brief.md +25 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-re-review-1.md +34 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-5-report.md +440 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-brief.md +399 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-dispatch.md +89 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-fix-1-brief.md +17 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-re-review-1.md +31 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-report.md +440 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-review-dispatch.md +84 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-6-review.md +54 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-brief.md +56 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-dispatch.md +77 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-fix-1-brief.md +21 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-re-review-1.md +37 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-report.md +464 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-review-dispatch.md +76 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-7-review.md +61 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-brief.md +87 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-dispatch.md +70 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-fix-1-brief.md +19 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-re-review-1.md +39 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-report.md +598 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-review-dispatch.md +77 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1b-projektionen/task-8-review.md +69 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-code-brief.md +270 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-code-re-review.md +122 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-code-report.md +323 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-docs-brief.md +269 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-docs-handback.md +158 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-fix-docs-re-review.md +51 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-review-code.md +124 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/final-review-docs.md +118 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/global-constraints.md +77 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step1-setup.sh +38 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step10-addendum-smoke-cleanup.sh +53 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step2-bucket-key.sh +59 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step3-attach-rehearse.sh +70 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step4-erase.sh +46 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step5-exit2.sh +39 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step6-restore.sh +66 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step7-failures-cleanup.sh +39 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step8-addendum-setup.sh +50 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/handson-steps/step9-addendum-checks.sh +90 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/index.md +84 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/open-points-from-execution.md +69 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/progress.md +299 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-1-brief.md +115 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-1-report.md +164 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-1-review.md +70 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-2-brief.md +181 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-2-report.md +291 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-2-review.md +103 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-3-brief.md +235 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-3-report.md +218 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-3-review.md +186 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-4-brief.md +58 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-4-report.md +280 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-4-review.md +87 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-5-brief.md +174 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-5-report.md +460 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-5-review.md +103 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-6-brief.md +122 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-6-report.md +334 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-6-review.md +168 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-7-brief.md +130 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-7-report.md +344 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-7-review.md +123 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-brief.md +125 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-fix1-handback.md +83 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-fix1-review.md +20 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-handback.md +480 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-04-stufe-1c-blobs-und-tilgung/task-8-review.md +137 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-fix-re-review.md +85 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-fix-report.md +142 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-review-code.md +96 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/final-review-docs.md +164 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/global-constraints.md +45 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/index.md +63 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/progress.md +138 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-brief.md +103 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-fix1-review.md +23 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-report.md +366 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-1-review.md +58 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-brief.md +139 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix1-review.md +79 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix2-review.md +140 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix3-review.md +156 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix4-review.md +204 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-fix5-review.md +46 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-report.md +1031 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-2-review.md +68 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-brief.md +201 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-fix1-review.md +46 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-fix2-review.md +45 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-report.md +134 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-3-review.md +46 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-4-brief.md +133 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-4-report.md +136 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-4-review.md +67 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-5-brief.md +42 -0
- previously-0.1.0a1/docs/superpowers/sdd/2026-10-05-auslieferung/task-5-report.md +96 -0
- previously-0.1.0a1/docs/superpowers/specs/2026-10-01-architektur.md +1587 -0
- previously-0.1.0a1/docs/superpowers/specs/2026-10-01-previously-design.md +1858 -0
- previously-0.1.0a1/docs/superpowers/specs/2026-10-02-stufe-1a-log.md +1144 -0
- previously-0.1.0a1/docs/superpowers/specs/2026-10-04-aeusserer-anker.md +479 -0
- previously-0.1.0a1/docs/superpowers/specs/2026-10-04-stufe-1b-projektionen.md +801 -0
- previously-0.1.0a1/docs/superpowers/specs/2026-10-04-stufe-1c-blobs-und-tilgung.md +851 -0
- previously-0.1.0a1/docs/superpowers/specs/2026-10-05-auslieferung.md +426 -0
- previously-0.1.0a1/docs/tutorials/index.md +9 -0
- previously-0.1.0a1/docs/tutorials/record-your-first-event.md +250 -0
- previously-0.1.0a1/pyproject.toml +170 -0
- previously-0.1.0a1/renovate.json5 +79 -0
- previously-0.1.0a1/scripts/smoke-image.sh +116 -0
- previously-0.1.0a1/src/previously/__init__.py +3 -0
- previously-0.1.0a1/src/previously/cli.py +1204 -0
- previously-0.1.0a1/src/previously/contract/__init__.py +3 -0
- previously-0.1.0a1/src/previously/contract/blobs.py +98 -0
- previously-0.1.0a1/src/previously/contract/rows.py +112 -0
- previously-0.1.0a1/src/previously/contract/store.py +155 -0
- previously-0.1.0a1/src/previously/contract/types.py +75 -0
- previously-0.1.0a1/src/previously/core/__init__.py +3 -0
- previously-0.1.0a1/src/previously/core/anchor.py +77 -0
- previously-0.1.0a1/src/previously/core/append.py +487 -0
- previously-0.1.0a1/src/previously/core/blob.py +179 -0
- previously-0.1.0a1/src/previously/core/canonical.py +100 -0
- previously-0.1.0a1/src/previously/core/chain.py +318 -0
- previously-0.1.0a1/src/previously/core/errors.py +130 -0
- previously-0.1.0a1/src/previously/core/hashing.py +307 -0
- previously-0.1.0a1/src/previously/core/projection/__init__.py +17 -0
- previously-0.1.0a1/src/previously/core/projection/chronicle.py +134 -0
- previously-0.1.0a1/src/previously/core/projection/source_stats.py +85 -0
- previously-0.1.0a1/src/previously/core/projection/worker.py +170 -0
- previously-0.1.0a1/src/previously/core/redact.py +423 -0
- previously-0.1.0a1/src/previously/core/redaction.py +284 -0
- previously-0.1.0a1/src/previously/core/sealing.py +268 -0
- previously-0.1.0a1/src/previously/core/units.py +45 -0
- previously-0.1.0a1/src/previously/core/verify.py +804 -0
- previously-0.1.0a1/src/previously/migrations/README +1 -0
- previously-0.1.0a1/src/previously/migrations/__init__.py +9 -0
- previously-0.1.0a1/src/previously/migrations/dsn.py +53 -0
- previously-0.1.0a1/src/previously/migrations/env.py +102 -0
- previously-0.1.0a1/src/previously/migrations/script.py.mako +28 -0
- previously-0.1.0a1/src/previously/migrations/versions/0001_log.py +109 -0
- previously-0.1.0a1/src/previously/migrations/versions/0002_projections.py +65 -0
- previously-0.1.0a1/src/previously/migrations/versions/0003_hash_version_2.py +85 -0
- previously-0.1.0a1/src/previously/migrations/versions/0004_event_blob.py +52 -0
- previously-0.1.0a1/src/previously/storage/__init__.py +3 -0
- previously-0.1.0a1/src/previously/storage/errors.py +142 -0
- previously-0.1.0a1/src/previously/storage/keys.py +84 -0
- previously-0.1.0a1/src/previously/storage/migrate.py +154 -0
- previously-0.1.0a1/src/previously/storage/postgres.py +1104 -0
- previously-0.1.0a1/src/previously/storage/s3.py +344 -0
- previously-0.1.0a1/src/previously/storage/schema.py +255 -0
- previously-0.1.0a1/tests/conftest.py +306 -0
- previously-0.1.0a1/tests/test_anchor.py +77 -0
- previously-0.1.0a1/tests/test_append.py +741 -0
- previously-0.1.0a1/tests/test_blob.py +529 -0
- previously-0.1.0a1/tests/test_canonical.py +127 -0
- previously-0.1.0a1/tests/test_chain.py +264 -0
- previously-0.1.0a1/tests/test_cli.py +3479 -0
- previously-0.1.0a1/tests/test_contracts.py +193 -0
- previously-0.1.0a1/tests/test_docs_build.py +33 -0
- previously-0.1.0a1/tests/test_docs_references.py +683 -0
- previously-0.1.0a1/tests/test_docs_typed_output.py +45 -0
- previously-0.1.0a1/tests/test_hashing.py +516 -0
- previously-0.1.0a1/tests/test_keys.py +169 -0
- previously-0.1.0a1/tests/test_migrate.py +729 -0
- previously-0.1.0a1/tests/test_migration_0003.py +137 -0
- previously-0.1.0a1/tests/test_migration_0004.py +57 -0
- previously-0.1.0a1/tests/test_migrations_dsn.py +42 -0
- previously-0.1.0a1/tests/test_projection_derive.py +182 -0
- previously-0.1.0a1/tests/test_projection_store.py +202 -0
- previously-0.1.0a1/tests/test_projection_worker.py +818 -0
- previously-0.1.0a1/tests/test_properties.py +463 -0
- previously-0.1.0a1/tests/test_redact.py +996 -0
- previously-0.1.0a1/tests/test_redaction.py +246 -0
- previously-0.1.0a1/tests/test_rows.py +88 -0
- previously-0.1.0a1/tests/test_s3.py +374 -0
- previously-0.1.0a1/tests/test_schema.py +535 -0
- previously-0.1.0a1/tests/test_sealing.py +263 -0
- previously-0.1.0a1/tests/test_storage.py +798 -0
- previously-0.1.0a1/tests/test_units.py +78 -0
- previously-0.1.0a1/tests/test_verify.py +1368 -0
- previously-0.1.0a1/tests/test_wheel.py +31 -0
- previously-0.1.0a1/uv.lock +1755 -0
|
@@ -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.
|