# Design ## Goals 1. Converge an S3 bucket to a desired spec for the supported features: - tags - versioning - public access block 2. Stay declarative: Desired → Current → Plan → Apply → Result 3. Keep the module primitive and composable: no “SDK wrapper” surface Future scope (not implemented yet): encryption, lifecycle rules, bucket policy templates (e.g., TLS-only), CloudFront-origin/OAC policy convergence. ## Architecture The purpose of this module is to provide a convergent management interface to Amazon S3 buckets. We do **not** expose the full boto3 surface area. Instead, we converge a pragmatic subset of bucket features that are commonly needed by dxaws primitives. This keeps the public contract small, testable, and stable. ## Declarative Convergence This module follows a **declarative convergence** model. Instead of issuing imperative AWS calls directly, callers describe the *desired state*. The module then: 1. **Plans** what needs to change to reach that state 2. **Applies** the plan using provider-backed AWS operations This separation makes behavior predictable, testable, and idempotent, while keeping AWS-specific logic isolated in providers and making plans easy to unit test. ## Key Concepts ### Providers Providers encapsulate AWS SDK (boto3) calls. The rest of the module treats the provider as an interface so that planning/execution can be unit tested without AWS. ### Manager (public facade) `manager.py` is the public entrypoint. It: 1. Validates the desired state 2. Reads the current state from the provider 3. Calls the planner to compute a plan 4. Applies the plan via the executor 5. Returns a result object ### Planner / Executor - **planner.py** computes a deterministic plan (a list of actions) by comparing *desired* vs *current* state. - **executor.py** applies the plan using provider-backed operations. ### planner.py Planner rules are deterministic and include purpose-driven invariants: 1. If bucket doesn’t exist → `CREATE_BUCKET` 2. Tags drift → `PUT_TAGS` 3. Public access block drift → `PUT_PUBLIC_ACCESS_BLOCK` 4. Versioning drift → `PUT_VERSIONING` These four rules define the current contract that higher-level modules can rely on. #### Future planner rules (not implemented yet) - Encryption drift → `PUT_ENCRYPTION` - Bucket policy convergence: - TLS-only deny statement - CloudFront OAC allow-read statement - Lifecycle rules / intelligent-tiering / replication templates ### executor.py The executor applies actions in a safe order: 1. Create bucket (if needed) 2. Public access block 3. Versioning 4. Tags Returns `S3BucketResult` from a provider read-back. ### providers/base.py (interface) boto3 methods that will be encapsulated: - `get_current(name) -> S3BucketCurrent` - `create_bucket(desired)` - `put_tags(name, tags)` - `put_public_access_block(name, config)` - `put_versioning(name, enabled: bool)` - `destroy_bucket(name)` - `get_outputs(name) -> S3BucketResult` AWS implementation in `providers/aws.py` uses boto3 S3. ### constants.py / exceptions.py Defaults: - `DEFAULT_ENCRYPTION = "SSE-S3"` - `DEFAULT_BLOCK_PUBLIC_ACCESS = True` - `DEFAULT_ENFORCE_TLS = True` Exceptions: - `InvalidDesiredStateError` - `BucketNameError` - `MissingCloudFrontInfoError` (only if strict mode is enabled) ## CLI Surface The CLI orchestration for S3 lives in **dxaws-cli**. This module remains **Python-first** and focuses on providing a stable manager/provider contract that higher-level tooling can call. ## Tradeoffs ### Eventual Consistency Some S3 operations (especially create/delete and feature reads immediately after) can be eventually consistent. Acceptance tests intentionally include: - a visibility wait after create - drift + converge checks - a delete waiter with small grace delays This keeps the contract stable while acknowledging AWS propagation behavior. ### CloudFront / Policy Convergence CloudFront-origin and policy convergence (TLS-only, OAC statements, etc.) are intentionally deferred. The current module stays focused on the primitive features we already rely on.