aeat.application.bucket_maintenance._sandbox module¶
Sandbox workspace lifecycle: create, activate, and discard an isolated bucket.
A sandbox is an ordinary profile bucket distinguished only by a reserved
operator-visible label prefix (SANDBOX_LABEL_PREFIX). It rides the
exact same encrypted per-bucket storage substrate every profile uses
(SecureObjectRepository via the
bucket-scoped runtime wrapper): there is no plaintext scratch directory and no
parallel storage backend. Isolation is the pre-existing per-bucket engine
guarantee (see
test_per_bucket_engine_isolation);
this module adds nothing to that guarantee, it only composes the existing
single-writer primitives behind an experiment-lifecycle vocabulary.
The service delegates every write to an existing primitive
(composition-service-no-parallel-write-path):
register_active_profile()(via theconfig profile create/duplicateatomic-create span) provisions the sandbox bucket, optionally seeded from a source profile’s live facts — exactly the same forkconfig profile duplicatealready performs. The source bucket is opened read-only (a lifecycle-serviceread) and is never written to, so seeding cannot mutate main state.select_profile_with_lifecycle_span()activates a named sandbox as the current session (config switch’s primitive).BucketMaintenanceService.delete composes the existing soft-tombstone-then-hard-erase primitives to discard the sandbox bucket entirely.BucketMaintenanceService.archive composes the soft-tombstone-only primitive to move a sandbox into reversible dormancy (the bucket directory, manifest, and encrypted record survive intact);BucketMaintenanceService().restore is its symmetric inverse.BucketMaintenanceService.browse (via a read-only lifecycle session, not the active bucket) backspreview_discard_sandbox(), so an operator can see what a discard would remove before confirming it.
list_sandboxes() is the read-only enumeration shared by sandbox list
and sandbox prune — the composition-verb pattern this module follows: a
bulk verb composes the same single-bucket primitives per row rather than
re-implementing bucket erasure.
The destructive-action protocol mirrors
DeleteBucketCommand: a discard
requires confirmed=True, refuses the active bucket (the operator switches
away first — the existing BucketMaintenanceService.delete contract), and
additionally refuses to discard any bucket whose label is not sandbox-tagged
unless the caller explicitly acknowledges bypassing that guard — so an
operator (or an LLM agent driving the CLI) cannot accidentally erase a real
profile through the sandbox verb.
See also
bucket_maintenancePublic facade that exports the sandbox lifecycle commands and results.
BucketMaintenanceServiceExisting bucket-maintenance service composed for discard, archive, restore, browse, and disk-usage operations.
register_active_profile()Profile-create primitive used when a sandbox bucket is provisioned.
select_profile_with_lifecycle_span()Profile-switch primitive used when a sandbox becomes active.
_sandboxCLI command surface that translates operator input into these command models.
TransactionCatalogueLedger catalogue promoted by sandbox merges when
ledgerscope is selected.
- SANDBOX_LABEL_PREFIX¶
Reserved operator-visible label prefix identifying a sandbox bucket.
A profile whose label starts with this prefix is a sandbox: created through
create_sandbox(), safe to discard without the non-sandbox confirmation escalation.create_sandbox()is the only path this module exposes for minting one, anddiscard_sandbox()(viais_sandbox_label()) decides whether a bucket may be discarded without the extra confirmation. The token itself is declared in the lightaeat.core.external_constantslayer so the state-free CLI surface can read it without importing this heavy module; it is re-exported here as the canonical application-facing name.
- exception SandboxAlreadyExistsError(label)[source]¶
Bases:
ExceptionRaised when
create_sandboxtargets a name that already has a manifest.- Parameters:
label (str)
- Return type:
None
- exception SandboxSourceNotFoundError(name)[source]¶
Bases:
ExceptionRaised when
create_sandbox’s seed source names an unknown or non-live profile.- Parameters:
name (str)
- Return type:
None
- exception SandboxNotFoundError(bucket_id)[source]¶
Bases:
ExceptionRaised when a sandbox lookup targets an unknown bucket.
- Parameters:
bucket_id (str)
- Return type:
None
- exception SandboxDiscardRefusedError[source]¶
Bases:
ExceptionRaised when discarding a bucket is refused by the sandbox destructive-action gate.
- exception SandboxNotArchivedError(bucket_id)[source]¶
Bases:
ExceptionRaised when
restore_sandboxtargets a sandbox that is not currently archived.- Parameters:
bucket_id (str)
- Return type:
None
- exception SandboxMergeRefusedError[source]¶
Bases:
ExceptionRaised when
merge_sandboxis refused (missing confirmation, unknown scope target).
- class SandboxMergeScope(*values)[source]¶
Bases:
StrEnumClosed axis of promotable data categories a sandbox merge may select.
LEDGERpromotes ledger transactions only (the manual/importedTransactioncatalogue, which already carries classification decisions on each row).MODELOpromotes the modelo work-unit, calculation-revision, and filing-record catalogues.ALLpromotes every one of the above in one call.Ledger promotion reads the sandbox
TransactionCataloguethrough the concreteTransactionCatalogueRepositoryand upserts the rows into the target bucket.- LEDGER¶
- MODELO¶
- ALL¶
- class PreviewDiscardSandboxCommand(**data)[source]¶
Bases:
BaseModelOperator request to preview what a sandbox discard would remove.
Read-only counterpart to
DiscardSandboxCommand: it never opens the target bucket for write and never callsBucketMaintenanceService().delete.allow_non_sandboxmirrors the discard command’s escape hatch so a preview against a non-sandbox bucket reports the same “not a sandbox” refusal a real discard would, rather than silently previewing an erase the real verb would never allow.- Parameters:
- bucket_id: BucketId¶
- allow_non_sandbox: bool¶
- class PreviewDiscardSandboxResult(**data)[source]¶
Bases:
BaseModelRead-only preview of what discarding a sandbox would remove.
namespacesis the same per-namespace row-count inventoryBucketMaintenanceService().browse returns for the active bucket, computed here for a bucket that need not be active.is_activeflags whether the target is the current session — a real discard of it would be refused until the operator switches away.- Parameters:
data (Any)
- bucket_id: BucketId¶
- label: str¶
- is_active: bool¶
- namespaces: tuple[SandboxNamespaceInventoryRow, ...]¶
- class SandboxNamespaceInventoryRow(**data)[source]¶
Bases:
BaseModelOne namespace row in a sandbox discard preview.
- namespace: str¶
- row_count: int¶
- class CreateSandboxCommand(**data)[source]¶
Bases:
BaseModelOperator request to fork an isolated, discardable sandbox bucket.
nameis the operator-chosen sandbox name (rendered assandbox:<name>);from_profileoptionally names a live profile whose facts seed the sandbox (an experiment that needs realistic profile data without touching the source). A sandbox is a real profile bucket underneath, so it is bound by the same profile-schema required-field validationconfig profile createenforces: creating one with nofrom_profileand no facts fails that validation exactly as an emptyconfig profile createwould. Seed from an existing profile to get a realistic, immediately-usable sandbox.- name: str¶
- from_profile: str | None¶
- class CreateSandboxResult(**data)[source]¶
Bases:
BaseModelOutcome of a successful sandbox creation. The sandbox becomes the active bucket.
- Parameters:
- bucket_id: BucketId¶
- label: str¶
- seeded_from: str | None¶
- class DiscardSandboxCommand(**data)[source]¶
Bases:
BaseModelOperator request to permanently erase a sandbox bucket.
confirmed=Trueis required, mirroringDeleteBucketCommand.allow_non_sandboxmust be explicitly set to erase a bucket whose label does NOT carry the reserved sandbox prefix — the default refuses, so a mistaken discard of a real profile is not silently possible.- Parameters:
- bucket_id: BucketId¶
- confirmed: bool¶
- allow_non_sandbox: bool¶
- class DiscardSandboxResult(**data)[source]¶
Bases:
BaseModelOutcome of a successful sandbox discard.
- Parameters:
- bucket_id: BucketId¶
- previous_label: str¶
- occurred_at: datetime¶
- class ArchiveSandboxCommand(**data)[source]¶
Bases:
BaseModelOperator request to move a sandbox into reversible dormancy.
Unlike
DiscardSandboxCommand,archivenever removes the sandbox bucket: it composesBucketMaintenanceService().archive (soft tombstone only), so the bucket directory, manifest, and encrypted record all survive intact andrestore_sandbox()can bring the same sandbox back.confirmed=TruemirrorsDiscardSandboxCommand’s boundary contract.- Parameters:
- bucket_id: BucketId¶
- confirmed: bool¶
- allow_non_sandbox: bool¶
- class ArchiveSandboxResult(**data)[source]¶
Bases:
BaseModelOutcome of a successful sandbox archive.
- Parameters:
- bucket_id: BucketId¶
- label: str¶
- occurred_at: datetime¶
- class RestoreSandboxCommand(**data)[source]¶
Bases:
BaseModelOperator request to bring an archived sandbox back to active status.
Symmetric inverse of
ArchiveSandboxCommand. Refuses when the target is not currently archived (tombstoned).- Parameters:
- bucket_id: BucketId¶
- allow_non_sandbox: bool¶
- class RestoreSandboxResult(**data)[source]¶
Bases:
BaseModelOutcome of a successful sandbox restore.
- Parameters:
- bucket_id: BucketId¶
- label: str¶
- occurred_at: datetime¶
- class MergeSandboxCommand(**data)[source]¶
Bases:
BaseModelOperator request to promote one data scope from a sandbox into a target profile.
source_bucket_idmust carry the reserved sandbox label (theallow_non_sandboxescape hatch mirrors every other sandbox verb’s non-sandbox guard).target_bucket_idnames the profile bucket the scope is merged INTO — ordinarily the operator’s main profile, resolved by the CLI layer before this command is built, never the sandbox itself.confirmed=Trueis required because the merge mutates the target profile’s real records.- Parameters:
source_bucket_id (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=1, max_length=128, pattern=None, ascii_only=None)])
target_bucket_id (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=1, max_length=128, pattern=None, ascii_only=None)])
scope (SandboxMergeScope)
confirmed (bool)
allow_non_sandbox (bool)
- source_bucket_id: BucketId¶
- target_bucket_id: BucketId¶
- scope: SandboxMergeScope¶
- confirmed: bool¶
- allow_non_sandbox: bool¶
- class MergeSandboxResult(**data)[source]¶
Bases:
BaseModelOutcome of a successful sandbox merge.
merged_countsreports, per merged typed category, how many rows the merge upserted into the target bucket (a re-run against unchanged sandbox content upserts the same content-addressed ids again — an idempotent no-op write, never a duplicate row).- Parameters:
source_bucket_id (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=1, max_length=128, pattern=None, ascii_only=None)])
target_bucket_id (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=None, to_lower=None, strict=None, min_length=1, max_length=128, pattern=None, ascii_only=None)])
scope (SandboxMergeScope)
occurred_at (datetime)
- source_bucket_id: BucketId¶
- target_bucket_id: BucketId¶
- scope: SandboxMergeScope¶
- merged_counts: dict[str, int]¶
- occurred_at: datetime¶
- create_sandbox(command)[source]¶
Fork a fresh, isolated sandbox bucket and make it the active profile.
Composes the canonical atomic-create span (
profile_create_storage_span()+register_active_profile()) — the exact primitiveconfig profile createandconfig profile duplicatealready use — so the sandbox bucket, its manifest, its encrypted record, and the active-profile pointer land in one all-or-nothing unit of work. Whencommand.from_profileis supplied, the source profile’s live facts are read through a read-only lifecycle session (the source bucket is never opened for write), so seeding cannot mutate main state.- Return type:
- Returns:
CreateSandboxResultdescribing the new sandbox bucket, which is now the active profile.- Parameters:
command (CreateSandboxCommand)
- preview_discard_sandbox(command)[source]¶
Report what discarding
command.bucket_idwould remove, without removing it.Reads the target bucket’s namespace inventory through a read-only lifecycle session (
profile_storage_session()) — the identical read-only patterncreate_sandbox()already uses to seed from a non-active source profile — so a sandbox need not be the active bucket to be previewed. No write primitive is invoked; the target bucket, its manifest, and its records are untouched.Applies the same non-sandbox refusal
discard_sandbox()applies, so a preview can never suggest an erase the real verb would refuse.- Return type:
- Returns:
PreviewDiscardSandboxResultdescribing the bucket’s current contents.- Parameters:
command (PreviewDiscardSandboxCommand)
- list_sandboxes()[source]¶
Return every
(bucket_id, label)pair currently carrying the sandbox prefix.Read-only enumeration shared by
sandbox listandsandbox prune.
- discard_sandbox(command)[source]¶
Permanently erase the sandbox bucket identified by
command.bucket_id.Composes
BucketMaintenanceService.delete — the same soft-tombstone-then-hard-directory-removal primitiveconfig profile deleteuses — after confirming the target either carries the reserved sandbox label or the caller explicitly setallow_non_sandbox=True. Refuses (via the composed service) to discard the currently active bucket; the operator must switch away first. For a reversible alternative that never erases the bucket, seearchive_sandbox().- Return type:
- Returns:
DiscardSandboxResultdescribing the erased sandbox.- Parameters:
command (DiscardSandboxCommand)
- archive_sandbox(command)[source]¶
Move the sandbox bucket identified by
command.bucket_idinto reversible dormancy.Composes
BucketMaintenanceService.archive — the same soft-tombstone-only primitive that leaves the bucket directory, manifest, and encrypted record intact — after confirming the target either carries the reserved sandbox label or the caller explicitly setallow_non_sandbox=True. Refuses (via the composed service) to archive the currently active bucket; the operator must switch away first. Unlikediscard_sandbox(), the archived sandbox can be brought back withrestore_sandbox().- Return type:
- Returns:
ArchiveSandboxResultdescribing the archived sandbox.- Parameters:
command (ArchiveSandboxCommand)
- restore_sandbox(command)[source]¶
Bring the archived sandbox bucket identified by
command.bucket_idback to active.Composes
BucketMaintenanceService.restore — the symmetric inverse ofarchive_sandbox()— after confirming the target either carries the reserved sandbox label or the caller explicitly setallow_non_sandbox=True. Refuses when the target is not currently archived.- Return type:
- Returns:
RestoreSandboxResultdescribing the restored sandbox.- Parameters:
command (RestoreSandboxCommand)
- merge_sandbox(command)[source]¶
Promote
command.scopefrom a sandbox bucket intocommand.target_bucket_id.Composes the SAME typed-catalogue repositories and domain upsert primitives the portable-bundle import path (
deserialize_profile_bundle()) uses for ledger/work-unit/calculation-revision/filing-record restore (composition-service-no-parallel-write-path): this function does not reimplement a write path, it selects which of those existing upserts to run for the requested scope. Every upsert keys on the row’s own content-addressed or natural id, so re-running a merge against unchanged sandbox content is an idempotent no-op write (the target ends up with the identical row it already had, never a duplicate).Refuses unless
command.confirmedisTrue. Refuses whencommand.source_bucket_idis not sandbox-labelled unless the caller explicitly setscommand.allow_non_sandbox, mirroring every other sandbox verb’s non-sandbox guard. Refuses when the source and target buckets are identical (nothing to promote).- Return type:
- Returns:
MergeSandboxResultreporting the per-category row counts merged into the target bucket.- Parameters:
command (MergeSandboxCommand)