Mark unfinished Python code as structured debt that CI can find and refuse to ship.
Project description
sorry
sorry marks unfinished Python code as structured debt that CI can find and refuse to ship.
It is not a productivity aid. It is a nagging conscience for the build pipeline.
Standard TODO comments are a graveyard of good intentions. sorry makes them
load-bearing: structured metadata, an AST scanner, and a calendar.
Install
pip install pysorry
The distribution is pysorry. The import is sorry.
Mark the hole
Two markers. Use whichever fits the shape of the unfinished thing.
Runtime marker
from sorry import sorry
def reconcile_authority(step):
return sorry(
"corrective monotonicity not discharged",
issue="AG-27",
owner="agent-gov",
expires="2026-06-01",
)
sorry(...) always raises UnprovenObligation (an AssertionError subclass).
The return type is NoReturn, so type checkers treat anything after it as
unreachable.
Decorator marker
from sorry import unfinished
@unfinished(
"corrective monotonicity not discharged",
issue="AG-27",
expires="2026-06-01",
)
def corrective_never_mints_authority(step) -> bool:
...
The decorator runs cleanly at import time. The wrapped function raises
UnprovenObligation when called.
Scan the hole
sorry list src tests
Lists every obligation. Always exits 0.
sorry check src tests
Exits 1 if any obligation exists. No warn-only mode. No per-path policy file. The tool is binary by design: either the debt is discharged, or the build is broken.
Metadata
All fields are optional except the reason. All values must be string literals when scanned — non-literal metadata is reported as invalid.
| field | meaning |
|---|---|
reason |
what is unfinished |
issue |
tracker ID or URL |
owner |
who is responsible |
expires |
ISO date YYYY-MM-DD; valid through that date |
basis |
informal justification or reference |
Status
Each obligation lands in one of three buckets:
- OPEN — unresolved, not yet expired
- EXPIRED —
today > expires - INVALID — non-literal metadata, malformed
expires, missing reason, etc.
sorry check fails on any non-empty bucket. Expired and invalid are visually
separated in output, but they fail the same gate.
Scanner limitations
The V0 scanner is import-aware but not scope-aware. It tracks
from sorry import … and import sorry [as …] at the module level, but it
does not model lexical scope. Two consequences:
- A function-local import (
def f(): from sorry import sorry as s) leaks its alias to the rest of the module — calls tos(...)in sibling functions will be reported as obligations. - A parameter or local variable named
sorry(or matching an alias) shadows the import at runtime, but the scanner will still flag calls to it.
In practice this means: keep sorry imports at the top of the file and
don't shadow them with locals. Scope-aware resolution may land in a later
release; until then, the limitation is documented and the false-positive
shape is predictable.
Non-goals
- No
--warn-onlyflag. - No
pyproject.tomlpolicy section. - No per-path config.
- No pytest plugin.
- No "discharge" subcommand. To discharge an obligation, delete the call.
The tool is meant to stay tiny. If you need governance, build it elsewhere.
License
Licensed under Apache-2.0. See LICENSE and NOTICE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pysorry-0.1.0.tar.gz.
File metadata
- Download URL: pysorry-0.1.0.tar.gz
- Upload date:
- Size: 16.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57fb1bd58d07f136b5034f42fc5c7612490e799e490e4662af8049b0f32a5ae8
|
|
| MD5 |
d642bd78b5dce3688bc8d1ca13c73d3f
|
|
| BLAKE2b-256 |
bfa48bd647b69231a742d5e4f1188c1885e83b003521abab5704f1ddd6eab4b1
|
Provenance
The following attestation bundles were made for pysorry-0.1.0.tar.gz:
Publisher:
publish.yml on unpingable/pysorry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysorry-0.1.0.tar.gz -
Subject digest:
57fb1bd58d07f136b5034f42fc5c7612490e799e490e4662af8049b0f32a5ae8 - Sigstore transparency entry: 1420203472
- Sigstore integration time:
-
Permalink:
unpingable/pysorry@d1a2f7d9b4b97c31a297c243478ede6bca121cd3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/unpingable
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d1a2f7d9b4b97c31a297c243478ede6bca121cd3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pysorry-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pysorry-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7314b1b65875350e87176e49d3526ff21e417d87856004662b1641700aacfb5d
|
|
| MD5 |
323d6a7db906de3eda088e332f65388a
|
|
| BLAKE2b-256 |
69ffd5da300524f083d419861918ec62cb80ba75cb9b223e91cdefdfc3f83ce7
|
Provenance
The following attestation bundles were made for pysorry-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on unpingable/pysorry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pysorry-0.1.0-py3-none-any.whl -
Subject digest:
7314b1b65875350e87176e49d3526ff21e417d87856004662b1641700aacfb5d - Sigstore transparency entry: 1420203578
- Sigstore integration time:
-
Permalink:
unpingable/pysorry@d1a2f7d9b4b97c31a297c243478ede6bca121cd3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/unpingable
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d1a2f7d9b4b97c31a297c243478ede6bca121cd3 -
Trigger Event:
push
-
Statement type: