Offline acquisition input store¶
pycharter.acquisition_store retains immutable watchlist generations and frozen
run inputs with exact supplied requirements. It provides checked head changes,
bounded staging, command recovery and authority-generation changes. Agent
execution remains unavailable; the Store supplies no provider submission,
evidence retention, worker lease, publication or published-history API. See
agent step definitions for the execution boundary.
The optional target-attempt intent journal
adds verified metadata-only intentions through a separate interface and explicit
permission adapter; it does not enable execution.
Connected setup¶
open_acquisition_store(identity, limits, grants, *, handle, owns_handle=False,
initialize=False) opens one explicitly selected Store. The caller supplies an
AcquisitionIdentity, every AcquisitionLimits field, a trusted actor-bound
CurrentAcquisitionGrants adapter and the actual connected backend handle.
No database URL, credential or environment setting is resolved by this factory.
Its connected identity and limits are read-only snapshots.
| Backend | Required handle and profile |
|---|---|
| SQLite | The supplied SQLAlchemy Engine, with its canonical absolute persistent file identity or explicitly supplied memory Engine with database=":memory:" |
| PostgreSQL | The supplied SQLAlchemy Engine, matching the selected database; operational tables use the pycharter schema |
| MongoDB | An explicitly selected PyMongo Database matching the identity, on an unsharded replica set supporting transactions; standalone MongoDB refuses |
| Redis | The supplied standalone Redis client, matching the configured database and key prefix; primary role, no replicas and noeviction are required |
| In-memory | The same supplied AcquisitionMemoryState instance; process-local conformance only |
These profiles apply to this Store. Existing artifact-store constructors retain their own behavior. Replica-set transaction tests and Redis atomicity tests do not establish deployment durability; disk, failover and restore guarantees need separate qualification before managed execution.
Opening normally requires the existing compatible operational format. Explicit
initialize=True permits additive initialization of this Store's own format.
SQL initialization creates the two owned tables when both are absent, then
initializes the selected scope. A partial table pair or incompatible schema
raises AcquisitionInputError. It does not stamp, upgrade or certify the
application's Alembic revision. Use the normal migration
workflow for application deployment. Incompatible or partially incompatible
schema/profile state refuses rather than being repaired implicitly.
Ownership transfers only after the factory returns successfully; otherwise
the caller remains responsible for its handle. Closing the Store is idempotent. It closes/disposes the supplied handle only
when owns_handle=True; a borrowed engine/client remains usable. The context
manager follows the same rule.
The backend selector labels are sqlite, postgres, mongodb, redis and
in_memory. Database identity is the selected SQL/Mongo database name, SQLite
filename or :memory:, Redis database number as text, or memory state's
instance_id. The storage namespace/prefix is separate from artifact and
dataset namespaces. Request fields cannot retarget a connected Store.
Content and exact identities¶
A WatchlistContent contains canonical JSON target bytes, ordered key fields,
an exact native DataContract snapshot and ordered retained byte parts. Targets
are checked against the retained contract's embedded JSON Schema and must
have complete, non-null, unique keys. The Store does not run contract evaluation,
coercion bindings or format checking. Empty input is valid;
a corresponding frozen run records no_work.
FreezeRun copies the retained generation into run-owned content together with
supplied requirements bytes, exact native artifact snapshots and retained parts.
Changing a file, alias or current artifact later does not change that content.
Requirements are retained as supplied configuration; this API does not decide
whether they are sufficient for live execution.
ArtifactSnapshot accepts the existing DataContract and Pipeline kinds with
matching namespace, name and exact version. Global namespace None is exact,
not a wildcard. Aliases such as latest refuse. Native configuration is
validated locally; remote schema resolution and file/provider fetching are
unavailable. Other original-source/code/dependency bytes belong in bounded
RetainedPart values, supplied by the trusted caller.
Canonical JSON uses UTF-8, sorted object keys, compact separators, ordered arrays and finite JSON values, with no Unicode normalization. Duplicate keys and noncanonical byte encodings refuse. Returned DTOs are frozen and slotted; nested arrays are tuples and documents/content are bytes.
Current access and authority changes¶
The setup-bound adapter supplies current decisions for the exact resource.
Request fields cannot supply an actor, grant or authorized flag.
| Operation | Required adapter operation |
|---|---|
| Operational state, command recovery and completed receipt replay | inspect |
| Generation content, frozen content and watchlist head reads | read_inputs |
| New generation and head writes | write_inputs |
| Freeze | freeze for the referenced generation, plus read_inputs for its content |
| Abandoned staging cleanup | maintain for the original command |
| Authority generation advance | Independent authorize_authority_change decision |
The decision scope and authority generation must match the serialized Store boundary. A resource ID or namespace alone grants no access. Read permission for receipt metadata does not grant raw input or frozen-content access.
advance_authority_generation(AuthorityGenerationChange(expected, new)) performs
a compare-and-swap from the expected generation to a strictly greater one. It
uses a pre-reserved result slot, including when ordinary admission is full.
An exact retry returns the same receipt until a later advance replaces that
slot; a superseded request conflicts and cannot restore old grants. The
operation changes Store-effective authority. Coordinating revocation in an
external grant source remains the trusted adapter's responsibility.
Stored authority generations must be native integers. Boolean or numeric-equality aliases cannot authorize reads, writes, inspection or replay. Authority maintenance also validates this before comparing or replaying its retained receipt; malformed backing state raises the existing fixed persistence error without applying a transition. This adds no new generation range, permission, public method or policy default.
Bounded writes and recovery¶
Each generation, head and freeze command has a caller-known CommandKey binding the Store, epoch, scope and
monotonic sequence. Compute identify_command(request) before dispatch so a
lost acknowledgement can be recovered without a returned ID. Use the same
original request and identity when reconciling; do not allocate another key
for an uncertain write.
put_watchlist_generation and freeze_run reserve all required payload slots,
receipt/header metadata and cleanup capacity before admitting the command.
Their per-call work is finite. Results distinguish committed and partial work:
Completed(applied)acknowledges the committed original receipt.Completed(replayed)returns that same immutable receipt.Stagingidentifies charged partial work and its current continuation.Refusedreports an established conflict, access, capacity, corruption or other authored refusal reason.
Invalid DTOs, canonical bytes, setup, limits or schema/profile definitions raise
AcquisitionInputError with CONFIG. Expected access/conflict/capacity states
return Refused; required database/acknowledgement failures raise
AcquisitionPersistenceError. These outcomes are separate.
Repeating an admitted request with continuation=None reports its current state;
it does not resume partial writes. To resume, supply the full original request
and its current StagingRef. A stale continuation conflicts. recover_command
reads the current state without allocating or resuming work. An observed absent
command does not prove that an outstanding original request cannot still arrive.
For an already prepared immutable request and connected Store, retain the identity before dispatch and resume only its original bytes:
from pycharter.acquisition_store import (
AcquisitionPersistenceError,
RecoveryAbsent,
Staging,
identify_command,
)
command = identify_command(request)
try:
outcome = store.put_watchlist_generation(request)
except AcquisitionPersistenceError:
outcome = store.recover_command(command)
if isinstance(outcome, Staging):
outcome = store.put_watchlist_generation(
request, continuation=outcome.continuation
)
elif isinstance(outcome, RecoveryAbsent):
# Reconcile the outstanding original call; keep its command and bytes.
pass
A bounded resume can return another Staging. Let the caller's explicit work
policy decide when to continue; receipt recovery does not resume automatically.
set_watchlist_head is one atomic operation with an expected head revision.
It points only to a committed generation and returns a checked receipt or refusal.
Generation and frozen-run reads return complete bounded content after manifest,
resource identity, part ownership and digest checks; partial staging is hidden.
Required persistence failures raise AcquisitionPersistenceError with a safe
package error code, including uncertain commit acknowledgement. They do not
become successful receipts or expected refusals. Recover the original identity
before any downstream action that requires the write.
Capacity and abandoned staging¶
Every operator bound is explicit: retained generations/runs, commands/pending commands, targets, parts/slots, payload/request/control bytes and per-call upload and cleanup work. Limits apply to the configured scope across runs, including pending uploads and retained command receipts. Encoded payload and fixed control reservations are charged; this is not a physical cap on database files, WAL, AOF or database engine overhead.
WatchlistReceipt.encoded_payload_bytes measures the encoded content manifest.
Payload quota counters additionally charge base64 bodies per chunk, using
sum(4 * ceil(chunk_length / 3)); those counts differ from receipt bytes and raw
file lengths. Control reservations are separate.
The portable projected-input ceiling is 16 MiB; parts, slots and commands are
each capped at 256, part size at 8 MiB and storage namespace at 120 UTF-8 bytes.
Counters and DTO nesting are also bounded. Control budgets must cover the
derived fixed reservation. These are technical ceilings, not live defaults;
max_targets remains a separate operator bound.
collect_abandoned_staging(current_staging_ref) first prevents further writes
to that command, then clears and refunds a bounded subset of its original slots.
It allocates no new command, receipt or cleanup sequence, so full ordinary
admission does not block cleanup. Repeated completed cleanup returns its same
cumulative result without another refund. While cleanup returns
CleanupProgress(status="cleanup_pending"), use its newly returned continuation
for the next bounded call. An older revision conflicts; recover_command can
recover the current cleanup continuation. Late uploaders cannot recreate a
retired or reused body. If finalization won first, cleanup refuses and leaves the
committed content intact.
Original command/cleanup metadata remains retained and charged. Cleanup can restore payload/pending capacity while new commands still refuse at the retained command limit. This API provides no automatic receipt retirement or evidence expiry policy; the pilot's approved 30-day evidence requirement belongs to the later runtime/evidence implementation.
See retained request content for the optional exact-body journal and its separate current-content permissions.