aeat.application.bucket_maintenance._service module¶
BucketMaintenanceService composition implementation.
The service delegates every cross-store mutation to its existing
single-writer primitive (see the ADR
2026-06-03-cli-workflow-redesign-adr). It contributes the
bucket-maintenance audit-event emission that the inner primitives do
not own; the inner primitives keep emitting their lifecycle events
(PROFILE_RENAMED etc.) so each operator action surfaces both
perspectives in the bucket-event history.
This module uses BucketEventHistoryRepository for event
emission, UserProfilePortableExport
for sealed export/import payloads, and
ExportArchiveHeader
for archive frontmatter. The archive file is an explicit operator
handoff artifact; bucket state remains owned by the profile and secure
repository primitives the service composes.
- ensure_archive_schema_readable(archive_schema_version)[source]¶
Refuse a sealed-archive version this application cannot restore.
The gate is a ceiling with a durability floor, not an equality: a version above
_ARCHIVE_SCHEMA_VERSIONwas exported by a newer application and is refused as such, and a version below_ARCHIVE_DURABILITY_FLOORpredates the durability guarantee.Unlike the secure-object and bundle tiers, the archive tier carries NO upgrade dispatch: this is a range gate only, and nothing here transforms an older archive layout on restore. The lineage gate therefore pins
_ARCHIVE_DURABILITY_FLOOR == _ARCHIVE_SCHEMA_VERSION: raising the current version forces an explicit decision in the same change — raise the floor too (dropping older archives, the pre-release posture) or land a version-aware reader/restore transform and widen the gate then. A floor held below current without that machinery would pass this gate green while restore misreads the old layout.- Raises:
BucketImportError – When the version is above the ceiling or below the durability floor.
- Return type:
- Parameters:
archive_schema_version (int)
- class BucketMaintenanceService(*, event_repository=None)[source]¶
Bases:
objectCompose existing primitives behind the bucket-maintenance surface.
The service holds no state of its own. An optional event-history repository override is accepted for tests that want to assert against an in-memory or alternate-backend repository; production instantiates the default which is bound to the active bucket via
BucketEventHistoryRepository.- Parameters:
event_repository (BucketEventHistoryRepositoryProtocol | None)
- rename(command)[source]¶
Relabel the bucket identified by
command.bucket_id.Reads the current operator-visible label, delegates the cross-store relabel to
rename_profile(), then emitsBUCKET_RENAMEDcarrying the previous label in the payload so the audit consumer can render the before / after pair without re-reading the manifest.The inner
rename_profile()call emitsPROFILE_RENAMEDfrom the lifecycle service; the two events are co-emitted by design — the lifecycle event records the data change, the maintenance event records the operator-surface invocation.Both events land in the renamed bucket’s OWN event history: the lifecycle service already binds its event repository to the target bucket’s database, and the maintenance emission mirrors that binding so the audit trail can never split from the records it describes when the renamed bucket is not the active one.
- Returns:
The result of the rename operation.
- Return type:
- Parameters:
command (RenameBucketCommand)
- delete(command)[source]¶
Destructively erase the bucket identified by
command.bucket_id.Composes the existing two-step erase pattern: soft tombstone via
delete_profile_with_lifecycle_span()(clears the active-profile pointer, writes the manifest lifecycle status, tombstones the encrypted record, emitsPROFILE_TOMBSTONED) followed by hard directory removal viaremove_profile_bucket_directory(). TheBUCKET_DELETEDevent is emitted into the bucket’s own history between the soft and hard steps so the operator’s verb invocation is recorded before the storage is gone.Refuses unless
command.confirmedisTrue; refuses if the target bucket is the active profile (the operator must switch profiles first, per the 2026-05-15 amendment to the bucket ADR). Both refusals are service-boundary contracts, not CLI ergonomics — a programmatic caller observes the same guarantees.- Returns:
The result of the delete operation.
- Return type:
- Parameters:
command (DeleteBucketCommand)
- archive(command)[source]¶
Move the bucket identified by
command.bucket_idinto reversible dormancy.Composes
reactivate_profile_with_lifecycle_span()’s counterpart,delete_profile_with_lifecycle_span()— the SAME soft-tombstone primitivedelete()composes — but deliberately stops there: the hard directory removal (remove_profile_bucket_directory()) thatdelete()performs afterward never runs, so the bucket directory, manifest, and encrypted record all survive intact andrestore()can bring the same bucket back.Refuses unless
command.confirmedisTrue; refuses if the target bucket is the active profile (the operator must switch profiles first, mirroringdelete()’s own contract). TheBUCKET_ARCHIVEDevent lands in the archived bucket’s OWN event history (mirroringrename()’s binding) since the bucket still exists after this call — unlikedelete()’s event, which must outlive the erased bucket.- Returns:
The result of the archive operation.
- Return type:
- Parameters:
command (ArchiveBucketCommand)
- restore(command)[source]¶
Bring the archived bucket identified by
command.bucket_idback to active.Composes
reactivate_profile_with_lifecycle_span()— the symmetric inverse of the soft tombstonearchive()composes. Refuses when the target is not currently tombstoned (i.e. was never archived, or is already active), surfaced byProfileNotFoundErrorfrom the underlying lifecycle service.The
BUCKET_RESTOREDevent lands in the restored bucket’s OWN event history, mirroringarchive()’s binding.- Returns:
The result of the restore operation.
- Return type:
- Parameters:
command (RestoreBucketCommand)
- browse(command)[source]¶
Enumerate the bucket namespace inventory and return a
BrowseBucketResult.Composes
SecureObjectRepository.list_namespaces()with a per-namespace row count vialist_keys()(whose return is the HMAC-digest list — the count is meaningful even though the digests themselves are opaque). The result excludes any namespace whose name does not containnamespace_filteras a substring when one is supplied. Read-only; emits no bucket event.Key-level browse (returning operator-readable keys + classification per row) requires decryption and a
SensitivityClassredaction policy; deferred to a follow-up Step per the composition-pattern ADR.- Return type:
- Parameters:
command (BrowseBucketCommand)
- disk_usage(command)[source]¶
Measure
command.bucket_id’s on-disk footprint and return aDiskUsageBucketResult.Walks the bucket’s fixed directory layout (
bucket_paths()) and sums regular-file byte sizes viaos.stat— plain filesystem metadata, never decrypted content. This is the same non-active-safe posturebrowse()andpreview_discard_sandbox()already rely on: no master key or active-bucket session is opened, so a non-active (even archived) bucket can be measured. Read-only; emits no bucket event.- Return type:
- Returns:
DiskUsageBucketResultreporting the total byte count and a per-subdirectory (db,blobs,audit) breakdown, plus the bucket’s own manifest file folded into thedbrow (the manifest sits directly under the bucket directory, not in a fixed subdirectory of its own).- Parameters:
command (DiskUsageBucketCommand)
- export(command)[source]¶
Write a sealed bucket archive for
command.bucket_id.The method composes the existing profile portable-bundle serializer,
compute_manifest_digest(), sealed-archive writer, active bucket DEK, and bucket-event history. It does not reimplement profile export logic. When a recovery passphrase is supplied, the payload is sealed under a passphrase-derived key and the archive carries a small recovery-wrap salt member; otherwise the currently active bucket DEK seals the payload for same-host backup.- Return type:
- Returns:
An
ExportBucketResultdescribing the written sealed archive.- Parameters:
command (ExportBucketCommand)
- import_(command)[source]¶
Import a sealed bucket archive through the profile bundle service.
Archives with a recovery-wrap member require the matching passphrase. Archives without one are same-host backups and require the active bucket DEK to match the archive payload. New buckets are provisioned through the canonical profile create span before the
UserProfilePortableExportpayload is restored. The archive header’s manifest digest is authenticated through AEAD associated data during decryption; it is not recomputed against the imported host manifest.- Return type:
- Returns:
An
ImportBucketResultdescribing the restored bucket.- Parameters:
command (ImportBucketCommand)
- inspect(command)[source]¶
Read a sealed bucket archive’s header without decrypting or restoring it.
Composes
read_sealed_archive()(the same readerimport_uses for layout and header validation) with the on-disk file size. No session is opened, no key is required, and no bucket state is written or read — this is a pure inspection of the archive file itself, so an operator can confirm a backup’s identity, age, and recovery-wrap presence before deciding whether and how to restore it.- Return type:
- Returns:
An
InspectBucketArchiveResultdescribing the archive header.- Parameters:
command (InspectBucketArchiveCommand)
- recovery_wrap_passphrase_present(command)[source]¶
Return whether
commandrequests a recovery-passphrase archive.- Return type:
- Parameters:
command (ExportBucketCommand)