Caltech Library logo
skip to main content

AWS Tools — awsops — Implementation Plan (Go)

Source Design

See DESIGN.md and DECISIONS.md for the complete design and decision rationale, including why this plan retargets from Bash to Go (DECISIONS.md, 2026-07-01 “Retarget implementation from Bash to Go”).

Status (2026-07-01)

Starting from scratch. ec2_ami_manager.bash remains in the repo as the working reference for expected behavior and stays in place, untouched, until this plan’s Phase 14 (main menu) is complete and TEST_PLAN_REAL_AWS.txt passes against the Go binary. Nothing below has been implemented yet.

Unlike the Bash plan, the AMI-from-instance capabilities that were folded in later as “Phase 5b” (volume-size time estimate, fstrim/SSM step, unbounded polling, Invenio RDM crash-consistency guidance) are included in Phase 5 from the start — we already know they’re required.

Phases

TDD throughout: write the *_test.go file (with fakes for the AWS SDK interfaces) before the implementation it covers.


Phase 0 — Project Setup

Effort: ~2 hours Priority: High

Tasks


Phase 1 — AWS Client Layer

Effort: ~3 hours Priority: High Files: internal/awsclient/

Work Items

Tests: credential-check failure path; region client construction; retry/backoff behavior on throttling errors (table-driven, using a fake that returns a throttling error N times then succeeds)

Dependency: Phase 0


Phase 2 — Resource Listing

Effort: ~4 hours Priority: High Files: internal/inventory/

Work Items

Tests: aggregation across regions; empty-region handling; terminated- instance filtering; Name/Project/Environment-tag extraction (present/ absent); group/filter by Project and by Environment

Dependency: Phase 1


Phase 3 — Terminal UI (done)

Effort: ~4 hours Priority: High Files: internal/ui/

Built on github.com/rsdoiel/termlib rather than a stdlib-only implementation — see DECISIONS.md, “Use github.com/rsdoiel/termlib for the Terminal UI”.

Work Items

Tests: pick list with valid/invalid/cancel input, and prompt with default/validator, driven through termlib.LineEditor via an os.Pipe() (not a TTY) rather than a real terminal – LineEditor.Prompt detects the non-TTY input and falls back to plain line reading, which is termlib’s own documented/intended way to drive it in tests. This is the Go equivalent of the Bash version’s skipped “interactive testing is hard” tests, and is not skipped here.

Dependency: Phase 2


Phase 4 — Create EC2 Instance from AMI (done)

Effort: ~6 hours Priority: High Files: internal/workflow/{launch_instance,launch_execute,cloud_init_check,confirm,ssm,userdata}.go

Implemented as several smaller files rather than one, each independently tested: confirm.go (the reusable yes/no gate — see below), ssm.go (WaitForSSMOnline/RunShellCommand, generic building blocks reused by Phases 10/12/13), userdata.go (@file vs. inline text), cloud_init_check.go (the launch-time completion check), launch_instance.go (params struct + interactive collection), launch_execute.go (Launch/WaitUntilRunning), and create_instance_from_ami.go (the orchestrator PLAN.md originally called out as this phase’s one file).

Confirm(t, le, question) (bool, error) is the first instance of the “reusable confirmation/dry-run gate” DECISIONS.md’s “Structure workflows for future record/replay” calls for — a simple yes/no tier for reversible actions. Phase 8/11/13’s heavier dry-run + type-to-confirm tier is a separate function, added when those phases need it.

Work Items

Tests: parameter collection with a fake reader; user-data-from-file loading (success and file-not-found); launch success/failure; poll-until-running with a fake that transitions state after N calls; poll timeout path; cloud-init completion check (done/error/SSM-unavailable)

Dependency: Phase 1 (for the SSM client), Phase 3


Phase 5 — Create EC2 Instance from Cloud-Init YAML (done)

Effort: ~2 hours Priority: Medium Files: internal/workflow/launch_from_cloud_init.go, create_instance_from_cloud_init.go; extracted the shared execution logic from Phase 4’s create_instance_from_ami.go into a new runLaunch function in launch_execute.go, confirmed behavior-preserving against Phase 4’s existing tests before adding this phase’s own.

Work Items

Tests: prompt-ordering test confirming cloud-init is collected before the AMI pick list; otherwise covered by Phase 4’s execution-path tests (no new execution logic to test independently)

Dependency: Phase 4 (shares its execution path directly)


Phase 6 — Start EC2 Instance (done)

Effort: ~2 hours Priority: Medium Files: internal/workflow/power_state.go

Reuses Phase 4’s WaitUntilRunning (poll-until-running) and the displayConnectionInfo helper extracted from launch_execute.go when Phase 5 shared its execution path — no new poll/display logic needed here, so the timeout path is covered by Phase 4’s existing tests rather than re-tested at this orchestrator level (same reuse pattern as Phase 5’s own Tests note).

Work Items

Tests: start success/failure; poll-until-running with a fake state transition; poll timeout path; confirmation decline (no API call made)

Dependency: Phase 3


Phase 7 — Stop EC2 Instance (done)

Effort: ~2 hours Priority: Medium Files: internal/workflow/power_state.go

WaitUntilRunning (Phase 4) was refactored into a shared waitUntilState helper in launch_execute.go (confirmed behavior-preserving against Phase 4/6’s existing tests first), so WaitUntilStopped here is a one-line wrapper rather than duplicated polling logic.

Work Items

Tests: stop success/failure; poll-until-stopped with a fake state transition; poll timeout path; confirmation decline (no API call made)

Dependency: Phase 3


Phase 8 — Terminate EC2 Instance (done)

Effort: ~6 hours Priority: High Files: internal/workflow/terminate_instance.go; added ConfirmDestructive to confirm.go – the heavier type-to-confirm gate DECISIONS.md’s “Structure workflows for future record/replay” anticipated. Single-shot (no retry loop on mismatch), matching ec2_ami_manager.bash’s type_to_confirm; accepts either the instance ID or Name (DESIGN.md says “ID or name,” though the Bash version’s actual remove_ami call site only ever passed the ID – Go version honors the doc’s stated intent).

Work Items

Tests: dry-run display, including the DeleteOnTermination warning present/absent; production-tag warning shown/not-shown; type-to-confirm match/mismatch; termination success/failure

Dependency: Phase 3


Phase 9 — Manage Tags (done)

Effort: ~4 hours Priority: Medium Files: internal/workflow/manage_tags.go

Current tags are fetched fresh via ec2:DescribeInstances/DescribeImages (both already return the full Tags list) rather than a separate ec2:DescribeTags call. TagChangeParams stays minimal (resource ID, action, key, value), matching Phase 8’s TerminateInstanceParams precedent for the params-struct/confirm-gate seam.

Work Items

Tests: add/update/remove success paths, for both an instance and an AMI target; confirmation decline (no API call made); CreateTags/ DeleteTags failure handling

Dependency: Phase 3


Phase 10 — Create AMI from EC2 Instance (done)

Effort: ~8 hours Priority: High Files: internal/workflow/{volume_info,fstrim,create_ami_execute,create_ami_from_instance}.go

ec2:DescribeVolumes was missing from both the EC2API interface and DESIGN.md’s Assumptions list – added to both once this phase’s volume- info gathering needed it. isSSMOnline is a single-shot check (not Phase 4’s poll-based WaitForSSMOnline), matching ec2_ami_manager.bash’s check_ssm_availability for an instance that’s presumably been running a while already. WaitForAMIAvailable has no internal timeout at all (unlike every other poll in this codebase) – only the caller’s ctx can end it – since the Bash version’s fixed 600-second timeout for this exact operation was itself a correctness bug (DECISIONS.md, 2026-06-30).

Work Items

Tests: volume-info gathering; SSM-unavailable path; fstrim success/decline; AMI-name default generation and validation; create success/failure; unbounded-poll transitions (available/failed) with a fake clock or call-count-based fake

Dependency: Phase 3


Phase 11 — Remove AMI (done)

Effort: ~6 hours Priority: High Files: internal/workflow/remove_ami.go

The dependency check (instancesUsingAMI) filters the already-fetched inventory listing by ImageID rather than making a fresh AWS call – Phase 2’s ListInstances already carries each instance’s ImageID. Reuses ConfirmDestructive from Phase 8 unchanged.

Work Items

Tests: dry-run display; dependency detection (AMI in use / not in use); production-tag warning shown/not-shown; type-to-confirm match/mismatch; removal success/failure

Dependency: Phase 3


Phase 12 — Show/Export Cloud-Init (done)

Effort: ~8 hours Priority: High Files: internal/workflow/{cloud_init_instance,cloud_init_ami,cloud_init_export,show_cloud_init}.go

The always-terminate guarantee is a defer against a cleanup-scoped context.WithTimeout(context.Background(), 30s), deliberately decoupled from the caller’s ctx so cleanup isn’t skipped by an early return or by ctx itself being cancelled. Verified with dedicated tests asserting TerminateInstances is called exactly once across every failure path (SSM never online, command fails, instance never reaches running) and exactly zero times when launch itself fails (nothing to clean up).

Work Items

Tests: instance-path success; instance-path no-user-data-set; AMI-path full happy path with a fake SSM client; AMI-path cleanup-on-SSM-timeout (assert TerminateInstances is still called); AMI-path cleanup-on-command-failure; export-to-file success and path-error handling

Dependency: Phase 1, Phase 2, Phase 3


Phase 13 — Backup Archive & Trim (done, unit tests only)

Effort: ~10 hours Priority: High Files: internal/workflow/{backup_list,backup_upload,backup_verify,backup_delete,backup_archive}.go

Age filtering happens in Go (FilterByAge), not in the remote find command – the SSM command only lists every file’s size/mtime; all threshold logic is local and independently testable, avoiding fragile shell-arithmetic date math. Building the remote upload/delete scripts reintroduces the shell-quoting risk category this whole rewrite exists to eliminate for local command construction – SSM’s API only accepts a command string, so it’s unavoidable here. Caught a real quoting bug in review: the upload script’s S3 destination URI and echoed key were unquoted (only the source path was), which would have broken on any filename containing a single quote – fixed by routing every dynamic value through shellQuote and passing it as a separate printf argument rather than interpolating into a double-quoted string.

Real-AWS verification remains blocked on the S3 bucket and target instance IAM policy the user still needs to set up outside this repo (per DESIGN.md Assumptions #3-4) – flagged rather than guessed at.

Work Items

Tests: dry-run empty result; dry-run with matches; type-to-confirm mismatch; full happy path (all files verified, deleted, fstrim runs) with fake EC2API/SSMAPI/S3API; partial-verification-failure path (only verified files deleted, failures reported, fstrim still runs on whatever was freed); SSM-unavailable path

Dependency: Phase 1 (including the S3API), Phase 2, Phase 3. Real-AWS verification (Phase 16) additionally depends on Phase 4, since the live test target is a throwaway instance launched from an existing AMI that already has these backups baked in — never tested directly against a production instance.


Phase 14 — Main Menu and Integration (done)

Effort: ~4 hours Priority: High Files: cmd/awsops/main.go, internal/workflow/menu.go

Closed an architectural gap Phases 4-13 deliberately deferred here: each of the ten orchestrators originally took a single-region EC2API/ SSMAPI, but Phase 2 aggregates instances/AMIs across all four regions, so the right client isn’t known until after a resource is picked inside the orchestrator. Fixed by changing each orchestrator to take per-region client maps and resolve (internal/workflow/region_clients.go) by the picked resource’s Region field immediately after the pick – touched all ten orchestrator files and their tests, verified build/vet/race clean after each one before moving to the next.

menu.go’s MenuActions struct of func(ctx) error closures (bound by main.go to live clients and a mutable instance/AMI snapshot) lets menu dispatch itself be tested with fakes, without driving any workflow’s full interactive prompt sequence. Ctrl+C between prompts cancels ctx via signal.NotifyContext (every poll loop already selects on ctx.Done()); Ctrl+C/Ctrl+D during an active prompt surfaces as termlib.ErrInterrupted/ io.EOF instead, handled the same way in the menu loop.

Work Items

Tests: menu navigation and dispatch (fake workflows); refresh-after- operation; clean exit; signal handling

Dependency: Phase 4, 5, 6, 7, 8, 9, 10, 11, 12, 13


Phase 15 — Polish and Error Handling (done)

Effort: ~4 hours Priority: Medium

Work Items

Dependency: Phase 14


Phase 15.1 — Debug Logging (-debug) (done)

Effort: ~2 hours Priority: Medium

Work Items

Dependency: Phase 1 (AWS Client Layer)


Phase 15.2 — Create Key Pair inline (done)

Effort: ~1.5 hours Priority: Medium

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI)


Phase 15.3 — IAM Instance Profile pick-or-create (done)

Effort: ~2 hours Priority: Medium

Real-AWS testing hit InvalidParameterValue: ... Invalid IAM Instance Profile name at launch – see DECISIONS.md, “Support picking or creating an IAM instance profile from within awsops”.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI)


Phase 15.4 — Derive key pair name from a private key filename/path (done)

Effort: ~1 hour Priority: Medium

Real-AWS testing hit InvalidKeyPair.NotFound from typing a private key file path at “Key pair name” – see DECISIONS.md, “Derive the AWS key pair name from a private key filename/path”.

Work Items

Dependency: Phase 15.2 (Create Key Pair inline)


Phase 15.5 — Pre-flight check: instance type vs. subnet Availability Zone (done)

Effort: ~2 hours Priority: Medium

Real-AWS testing (via the -debug log) hit Unsupported: ... instance type ... is not supported in ... Availability Zone – see DECISIONS.md, “Pre-flight check: instance type vs. subnet Availability Zone”.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI)


Phase 15.6 — Instance type: curated pick list (done)

Effort: ~1 hour Priority: Medium

See DECISIONS.md, “Instance type pick list: curated shortlist, not the full AWS catalog”.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI)


Phase 15.7 — Pre-flight check: instance type vs. AMI ENA support (done)

Effort: ~2 hours Priority: Medium

Real-AWS testing hit InvalidParameterCombination: Enhanced networking ... is required for the '<type>' instance type – see DECISIONS.md, “Pre-flight check: instance type vs. AMI ENA support”. Closes the ENA item queued in TODO.md since an earlier session.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI), Phase 15.6 (reuses promptInstanceType)


Phase 15.8 — Tolerate post-launch/post-create eventual-consistency windows (done)

Effort: ~1 hour Priority: High

A real, confirmed launch failed immediately after RunInstances succeeded – see DECISIONS.md, “Tolerate DescribeInstances’ post- RunInstances eventual-consistency window”. Not an edge case: a near-certain race on every launch that happened to hit AWS’s brief propagation window.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI), Phase 5 (Create AMI from EC2 Instance)


Phase 15.9 — Filter the subnet picker by instance-type Availability Zone support (done)

Effort: ~1.5 hours Priority: Medium

Real-AWS testing surfaced repeated instance-type/subnet back-and-forth – see DECISIONS.md, “Filter the subnet picker by instance-type Availability Zone support”.

Work Items

Dependency: Phase 15.5 (Pre-flight check: instance type vs. subnet Availability Zone)


Phase 15.10 — Move “Show resource lists” to the top of the Compute menu (done)

Effort: ~30 minutes Priority: Low

See DECISIONS.md, “Move ‘Show resource lists’ to the top of the Compute menu; rename from ‘Refresh’”.

Work Items

Dependency: Phase 14 (Main Menu and Integration)


Phase 15.11 — Auto-detect a bare existing-file path in User data / Cloud-init YAML input (done)

Effort: ~1 hour Priority: Medium

Real-world use: typing newt-machine.yaml (no @ prefix) at the Cloud-init YAML prompt silently became the instance’s literal user-data instead of loading the file – see DECISIONS.md, “Auto-detect a bare existing-file path in User data / Cloud-init YAML input”.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI), Phase 5 (Create EC2 Instance from Cloud-Init YAML)


Phase 15.12 — Create EC2 Instance from Cloud-Init YAML always reads from a file (done)

Effort: ~1.5 hours Priority: Medium

Follow-up to Phase 15.11: for this specific prompt, “inline text or @file path” was itself the wrong shape – see DECISIONS.md, “Create EC2 Instance from Cloud-Init YAML always reads from a file”.

Work Items

Dependency: Phase 15.11 (Auto-detect a bare existing-file path in User data / Cloud-init YAML input)


Phase 15.13 — Offer official Ubuntu LTS AMIs alongside owned AMIs (done)

Effort: ~2 hours Priority: Medium

See DECISIONS.md, “Offer official Ubuntu LTS AMIs alongside owned AMIs when picking a base AMI”.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI), Phase 15.6 (curated instance-type list), Phase 15.7 (ENA pre-flight check)


Phase 15.14 — Narrow configured regions to us-west-1/us-west-2 (done)

Effort: ~1 hour Priority: High

See DECISIONS.md, “Narrow configured regions to us-west-1/us-west-2” – follow-up to the official-Ubuntu-AMI feature surfacing a region (us-west-1) with no provisioned key pairs.

Work Items

Dependency: Phase 1 (region configuration)


Phase 15.15 — Validate key pair name against the AMI’s region (done)

Effort: ~3 hours Priority: High

A real launch failed with InvalidKeyPair.NotFound after every prompt was already answered and confirmed – see DECISIONS.md, “Validate key pair name against the AMI’s region”.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI), Phase 15.2 (Create Key Pair inline), Phase 15.4 (key filename/path derivation)


Phase 15.16 — Tolerate GetCommandInvocation’s post-SendCommand eventual-consistency window (done)

Effort: ~1 hour Priority: High

Third instance of the same eventual-consistency bug pattern found this session – see DECISIONS.md, “Tolerate GetCommandInvocation’s post-SendCommand eventual-consistency window”.

Work Items

Dependency: Phase 4 (Create EC2 Instance from AMI – introduced RunShellCommand/the cloud-init completion check)


Phase 15.17 — ~/.awsops YAML config file (done)

Effort: ~2.5 hours Priority: Medium

See DECISIONS.md, “Add a ~/.awsops YAML config file for awsops’ own operational settings”.

Work Items

Dependency: Phase 15.14 (Narrow configured regions to us-west-1/us-west-2)


Phase 15.18 — Highlight PickList’s prompt header (done)

Effort: ~1 hour Priority: Low

See DECISIONS.md, “Highlight PickList’s prompt header when color is enabled”.

Work Items

Dependency: Phase 15 (Color output for state – ui.ColorEnabled(), NO_COLOR/non-TTY fallback)


Phase 15.19 — Configure per-instance backup directories by Name pattern (done)

Effort: ~1.5 hours Priority: Medium

See DECISIONS.md, “Configure per-instance backup directories by Name pattern”.

Work Items

Dependency: Phase 15.17 (~/.awsops YAML config file)


Phase 15.20 — Per-file upload progress for Backup Archive & Trim (done)

Effort: ~1.5 hours Priority: Medium

See DECISIONS.md, “Per-file upload progress for Backup Archive & Trim”.

Work Items

Dependency: Phase 11 (Backup Archive & Trim)


Phase 15.21 — Preflight check: S3 bucket access before Backup Archive & Trim’s dry-run list (done)

Effort: ~1 hour Priority: Medium

See DECISIONS.md, “Preflight check: S3 bucket access before Backup Archive & Trim’s dry-run list”.

Work Items

Dependency: Phase 11 (Backup Archive & Trim)


Phase 15.22 — Resolve a bucket’s actual region before Backup Archive & Trim’s access check (done)

Effort: ~1.5 hours Priority: High

Real-AWS regression found immediately after Phase 15.21 shipped – see DECISIONS.md, “Resolve a bucket’s actual region before Backup Archive & Trim’s access check”.

Work Items

Dependency: Phase 15.21 (Preflight check: S3 bucket access)


Phase 15.23 — Namespace backup uploads by instance (done)

Effort: ~1 hour Priority: Medium

See DECISIONS.md, “Namespace backup uploads by instance”.

Work Items

Dependency: Phase 11 (Backup Archive & Trim)


Phase 15.24 — Show instance IP addresses in the main listing (done)

Effort: ~1 hour Priority: Medium

See DECISIONS.md, “Show instance IP addresses in the main listing”.

Work Items

Dependency: Phase 1 (Unified Resource Listing)


Phase 15.25 — Suppress aws s3 cp’s progress output to avoid truncating the OK/FAIL signal (done)

Effort: ~1 hour Priority: High

Real-AWS regression found immediately after the IAM permission fix above – see DECISIONS.md, “Suppress aws s3 cp’s progress output to avoid truncating the OK/FAIL signal”.

Work Items

Dependency: Phase 15.20 (Per-file upload progress)


Phase 15.26 — Preflight check: AWS CLI availability before Backup Archive & Trim (done)

Effort: ~1 hour Priority: High

Hit twice in a row in real-AWS testing (newauthors, then data-new) – see DECISIONS.md, “Preflight check: AWS CLI availability before Backup Archive & Trim”.

Work Items

Dependency: Phase 11 (Backup Archive & Trim)


Phase 16 — Testing

Effort: ~6 hours Priority: High

Work Items

Dependency: Phase 14


Phase 17 — Documentation and Bash Retirement (done)

Effort: ~2 hours Priority: Medium

Work Items

Dependency: Phase 16


Phase 18 — Domain Picker Refactor

Effort: ~4 hours Priority: High Files: internal/ui/domainmenu.go, cmd/awsops/main.go, internal/workflow/menu.go

Per DECISIONS.md, 2026-07-02 “Redesign navigation as a domain picker; add Key Management, S3, and CloudFront domains”. Pure navigation refactor — no new AWS calls, and Compute’s existing workflows and tests are untouched. Runs alongside Phase 16/17, not blocking or blocked by them.

Work Items

Tests: domain picker dispatches to the right domain loop; Compute’s existing workflow tests continue to pass unmodified; “Back”/“Exit” behavior from within a domain menu

Dependency: Phase 14 (Main Menu and Integration)


Phase 19 — Key Management Domain (done)

Effort: ~6 hours Priority: Medium Files: internal/inventory/keypairs.go, internal/workflow/{keypair_create,keypair_import,keypair_delete,keymgmt_common,keymgmt_menu}.go

Implements DESIGN.md Features 13-16.

Work Items

Tests: internal/inventory/keypairs_test.go, internal/workflow/{keypair_create,keypair_import,keypair_delete,keymgmt_menu}_test.go – fakes for DescribeKeyPairs/CreateKeyPair/ImportKeyPair/DeleteKeyPair covering success, name-collision re-prompt (Create and Import), malformed public key file, dependent-instance detection, and the menu loop’s dispatch/refresh/error/exit-signal behavior. go build ./..., go vet ./..., go test ./... -race, gofmt -l . all clean.

Tests: fakes for DescribeKeyPairs/CreateKeyPair/ImportKeyPair/ DeleteKeyPair covering success, name-collision re-prompt, malformed public key file, dependent-instance detection

Dependency: Phase 18; reuses Phase 15.2’s CreateKeyPair wrapper


Phase 20 — S3 Domain (Buckets & Static Websites) (done)

Effort: ~16 hours (raised from ~10h after adding Feature 21.1, 2026-07-08 — see DECISIONS.md) Priority: Medium Files: internal/awsclient/s3.go (broadened), internal/inventory/buckets.go, internal/workflow/{bucket_create,bucket_website,bucket_sync,bucket_browse,bucket_lifecycle,s3_menu}.go

Implements DESIGN.md Features 17-21, 21.1, and the 2026-07-02 “CloudFront + OAC by default for static websites” decision, with scope decisions made 2026-07-08 before implementation started (see DECISIONS.md): public-read bucket policy opt-out deferred, key-prefix filter added to Browse/Manage Objects, and Feature 21.1 (Manage Bucket Lifecycle Policies) added as new scope with a Purpose-tag-driven guided/generic split.

Work Items

Tests: internal/inventory/buckets_test.go, internal/workflow/{bucket_create,bucket_website,bucket_sync,bucket_browse,bucket_lifecycle,s3_menu}_test.go – fakes for each new S3 call covering success/error paths (bucket-name-taken, website-not-configured treated as non-error, sync diff correctness, upload/delete confirmations never bundled, prefix filter narrows the object listing, lifecycle guided-vs-generic branch selection by Purpose tag, rule add/edit/remove round-trips through the fetch-modify-PutBack cycle correctly) — TDD throughout. go build ./..., go vet ./..., go test ./... -race, gofmt -l . all clean.

Real-AWS verification (2026-07-08): created one throwaway bucket per purpose, configured website hosting, ran Sync twice (upload pass, then a locally-deleted-file pass exercising the separate delete confirm), browsed with and without a prefix filter (view metadata, delete an object), set and round-tripped a guided backup lifecycle policy, and added/edited/removed a named rule via the generic editor – all against account 074441911104. Found and fixed one real bug along the way (empty- Rules PutBucketLifecycleConfiguration on last-rule removal; see DECISIONS.md) and left one open, documented gap (no local validation that transition-days < expiration-days; see TODO.md). All throwaway buckets and objects cleaned up afterward – no production bucket was touched.

Dependency: Phase 18


Phase 21 — CloudFront Domain

Status: postponed to a later version (2026-07-09, see TODO.md and DECISIONS.md) – not part of 0.0.1. No code written; the CloudFront domain-picker entry was removed rather than left wired to NotYetImplemented, so the 0.0.1 UI doesn’t expose a menu item that goes nowhere. The design below stays valid reference for whenever this is picked back up.

Effort: ~8 hours Priority: Medium Files: internal/awsclient/cloudfront.go, internal/inventory/distributions.go, internal/workflow/{distribution_create,distribution_invalidate}.go

Implements DESIGN.md Features 22-25.

Work Items

Tests: fakes for each new CloudFront call; OAC-then-bucket-policy sequencing; poll-until-Deployed/Completed with bounded test timeouts

Dependency: Phase 18, Phase 20 (Create Distribution hands off to Create Bucket)


Phase 22 — Real-AWS Testing: Key Management, S3, CloudFront

Effort: ~6 hours Priority: High Files: TEST_PLAN_REAL_AWS.txt (extended with new sections)

Mirrors Phase 16’s manual-verification approach, extended to the three new domains. Independent of Phase 16/17 (Compute’s own verification and Bash retirement) — see DECISIONS.md, 2026-07-02.

Work Items

Dependency: Phase 19, 20, 21


Deferred to a Later Version (Phase 23+, not scheduled)

Not part of v1/v2 — see DECISIONS.md, “V1 scope: ship the four primitives first, defer composite workflows”, “Add Show/Export Cloud-Init as a v1 primitive”, “Add Backup Archive & Trim as a v1 primitive”, “Add Rename Instance as a v1 primitive; AMI Name is immutable”, “Add Create EC2 Instance from Cloud-Init YAML as a v1 primitive”, “Add Start/Stop/ Terminate EC2 Instance as v1 primitives”, “Structure workflows for future record/replay”, “Redesign navigation as a domain picker…”, “CloudFront + OAC by default for static websites”, and DESIGN.md, “Deferred to a Later Version”. Recorded here so they’re scheduled deliberately once Phase 16/22 pass, not lost:


Priority Order for Implementation

Phase Priority Effort Dependencies
Phase 0 High 2h None
Phase 1 High 3h Phase 0
Phase 2 High 4h Phase 1
Phase 3 High 4h Phase 2
Phase 4 High 6h Phase 1, 3
Phase 5 Medium 2h Phase 4
Phase 6 Medium 2h Phase 3
Phase 7 Medium 2h Phase 3
Phase 8 High 6h Phase 3
Phase 9 Medium 4h Phase 3
Phase 10 High 8h Phase 3
Phase 11 High 6h Phase 3
Phase 12 High 8h Phase 1, 2, 3
Phase 13 High 10h Phase 1, 2, 3
Phase 14 High 4h Phase 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
Phase 15 Medium 4h Phase 14
Phase 16 High 6h Phase 14
Phase 17 Medium 2h Phase 16
Phase 18 High 4h Phase 14 (runs alongside 16/17)
Phase 19 Medium 6h Phase 18
Phase 20 Medium 16h Phase 18
Phase 21 Medium 8h Phase 18, Phase 20
Phase 22 High 6h Phase 19, 20, 21
Phase 23+ Deferred Phase 16, 22 (see above)