Observed since 20 Oct, 21:04 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Runtime affine-style capability handles for Python
One-shot runtime authority for Python.
Python assignment freely creates aliases. That is usually helpful, but it is
awkward when only one contender should be allowed to claim a deployment
approval, publication grant, or destructive-operation authority. affinecap
keeps a value in an interpreter-local registry and gives the application an
opaque handle. The first library-mediated claim of that handle wins; every
alias of the spent handle becomes stale.
from affinecap import CapabilityConsumedError, issue
publish = issue(value=lambda: "published", label="review passed")
assert publish.consume(lambda action: action()) == "published"
try:
publish.consume(lambda action: action())
except CapabilityConsumedError:
print("already used")
This is a small coordination primitive for cooperative Python code. It provides dynamic affine-style behavior; it does not add linear types or a security sandbox to Python.
consume, transfer, or transition claim succeeds for each
issued handle. A successor is a new handle with its own one-shot claim.copy.copy, copy.deepcopy, standard pickle protocols, inherited
fork use, and multiprocessing pickling are rejected.issue.os.register_at_fork callback.The exact guarantee tiers and assumptions are in the semantic contract.
The package supports CPython 3.10 through 3.14 and has no runtime dependencies.
python -m pip install affinecap
From a source checkout:
python -m pip install .
issue(value, *, label=None) -> CapabilityCreates one live capability. The public handle stores no value or lineage in its instance state; the interpreter-local registry retains both and binds the record to the exact handle, current process ID, and runtime epoch.
Labels are application-supplied diagnostic metadata exposed through
cap.lineage. Do not put secrets, credentials, or sensitive personal data in
them.
cap.consume(consumer)Claims the capability before calling consumer(value) and returns the
callback's result. A callback exception propagates and the capability remains
spent. This makes callback admission one-shot; it does not constrain how the
callback uses or retains value after release.
approval = issue(value={"artifact": "sha256:..."})
receipt = approval.consume(deploy)
Callbacks are invoked as ordinary synchronous Python calls. If an async def
function is supplied, calling it merely produces a coroutine; if a generator
function is supplied, calling it merely produces a generator. consume
returns that lazy object without executing it, and any later exception is
outside the capability callback boundary.
cap.transfer(*, label=None) -> CapabilityReturns a new handle for the same value and invalidates the old handle. This is a logical custody handoff, not a Python or Rust memory move. Existing references to the underlying value are unaffected.
worker_cap = approval.transfer(label="approval service -> worker")
cap.transition(transform, *, label=None) -> CapabilityClaims the predecessor, synchronously calls transform(value), and places the
returned value behind a new successor. If the synchronous call raises, there
is no successor and the predecessor stays spent.
verified = acquired.transition(verify, label="verification passed")
As with consume, a coroutine, generator, iterator, or other lazy object
returned by transform is treated as the returned value itself. It becomes
the successor payload; later execution and failures are not observed by
transition. Run all work that must be covered by the fail-closed boundary
before the transform returns.
cap.lineage is an immutable tuple of registry-backed LineageEntry
values.cap.capability_id is the current entry's random public identifier.cap.generation is the number of transfers or transitions since issuance.These properties require a live handle. They raise a lifecycle error after
the handle is spent. A LineageEntry contains capability_id, parent_id,
generation, operation, and optional label. The identifiers and entries
are diagnostic metadata; they do not grant authority and are not authenticity
proofs.
Capability[T] is the final, opaque generic handle type. Applications create
handles with issue, not by calling Capability.LineageEntry is the immutable public lineage record type.CapabilityError is the base class for lifecycle failures.CapabilityConsumedError means the handle is no longer live.CapabilityProcessError means an operation crossed a process or runtime
boundary.CapabilityIntegrityError means a handle is malformed or is not the exact
live object issued by this runtime.__version__ reports the installed distribution version.Construction, subclassing, and default standard-library copying and
serialization raise TypeError rather than a CapabilityError subclass.
examples/deployment_approval.py
checks an artifact against an independently supplied approved digest, hands
the authority to a worker, and records an action in a stateful simulated
deployment backend. It demonstrates at-most-one admission through the handle,
not exactly-once deployment semantics:
python examples/deployment_approval.py
The smaller executable version of the opening example is
examples/minimal.py.
The registry stores the value, immutable lineage, issuing process ID/runtime epoch, and a weak reference to the exact handle. The epoch is an in-memory marker for the current registry instance and is replaced after a fork. A synchronization lock atomically removes a claimed record before any callback is invoked. Transfer and transition issue a new record; they never reactivate the predecessor.
A fork hook replaces the child's lock, epoch, and active registry without touching potentially locked parent state. Inherited records are quarantined until child shutdown so dropping them cannot run arbitrary payload finalizers inside the fork hook. A long-running child may therefore retain inherited payload resources until it exits.
Dropping an unused handle is allowed—that is why the model is affine rather than linear. A weak-reference callback releases the registry's value reference on a best-effort basis. Do not rely on abandonment for resource cleanup. In particular, if the registered value retains its own capability handle, the registry-to-value-to-handle reference path keeps the handle alive and prevents weak-reference abandonment; explicit consumption is required to break that retention.
See prior art
for comparisons with static affine/linear systems, object-capability patterns,
contextvars.Token, state-machine libraries, and once primitives.
The design was generalized from selected mechanisms in Reverse Gravity, a
scientific data-processing system that needed one-shot custody handoffs and
fail-closed phase transitions. affinecap keeps the reusable in-memory
mechanism and omits that system's data formats, network access, artifact
layout, and workflow-specific transition rules. The package implementation is
a compact generalization, not a verbatim source copy. See the
design provenance note.
python -m pip install -e '.[dev]'
pytest
ruff check .
ruff format --check .
mypy
python -m build
twine check dist/*
CI runs the tests on Python 3.10 through 3.14 and checks formatting, lint, typing, distributions, and both examples.
MIT
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.1 | 3 Sept 2026 | 5 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 21:04 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:78ed56081648843118fb615b770022701dfc25290f62d8e1f98962d44f049246