skilldrop-cli 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/packs/ai-engineering/loops/adopt/LOOP.md +120 -0
  4. package/packs/ai-engineering/loops/adopt/evals/eval_queries.json +10 -0
  5. package/packs/ai-engineering/loops/adopt/evals/evals.json +69 -0
  6. package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/ai-ideas.md +15 -0
  7. package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/org-context.md +27 -0
  8. package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/staff-survey.csv +11 -0
  9. package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/ai-ideas.md +13 -0
  10. package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/firm-context.md +23 -0
  11. package/packs/ai-engineering/loops/adopt/loop.json +53 -0
  12. package/packs/ai-engineering/loops/ship-agent/LOOP.md +128 -0
  13. package/packs/ai-engineering/loops/ship-agent/evals/eval_queries.json +10 -0
  14. package/packs/ai-engineering/loops/ship-agent/evals/evals.json +70 -0
  15. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-auto-reply-exfil-open/agent-brief.md +33 -0
  16. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-changelog-config-reopens-path/agent-brief.md +33 -0
  17. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-changelog-config-reopens-path/claude-settings.json +25 -0
  18. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-changelog-config-reopens-path/sample-merged-prs.md +30 -0
  19. package/packs/ai-engineering/loops/ship-agent/loop.json +53 -0
  20. package/packs/experience-design/loops/clarify/LOOP.md +114 -0
  21. package/packs/experience-design/loops/clarify/evals/eval_queries.json +10 -0
  22. package/packs/experience-design/loops/clarify/evals/evals.json +68 -0
  23. package/packs/experience-design/loops/clarify/evals/files/clarify-garden-waste-no-complaint-data/form-fields.csv +12 -0
  24. package/packs/experience-design/loops/clarify/evals/files/clarify-garden-waste-no-complaint-data/garden-waste-page.md +29 -0
  25. package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/backoffice-notes.md +14 -0
  26. package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/calls-q3.csv +8 -0
  27. package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/form-fields.csv +12 -0
  28. package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/permit-page.md +27 -0
  29. package/packs/experience-design/loops/clarify/loop.json +35 -0
  30. package/packs/infra-as-code/loops/provision/LOOP.md +128 -0
  31. package/packs/infra-as-code/loops/provision/evals/eval_queries.json +9 -0
  32. package/packs/infra-as-code/loops/provision/evals/evals.json +67 -0
  33. package/packs/infra-as-code/loops/provision/evals/files/provision-rds-no-plan-yet/.terraform.lock.hcl.fixture +24 -0
  34. package/packs/infra-as-code/loops/provision/evals/files/provision-rds-no-plan-yet/network.md +36 -0
  35. package/packs/infra-as-code/loops/provision/evals/files/provision-replica-plan-replaces-primary/main.tf +74 -0
  36. package/packs/infra-as-code/loops/provision/evals/files/provision-replica-plan-replaces-primary/plan.json +901 -0
  37. package/packs/infra-as-code/loops/provision/evals/files/provision-replica-plan-replaces-primary/variables.tf +47 -0
  38. package/packs/infra-as-code/loops/provision/loop.json +41 -0
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: provision
3
+ description: Take an infrastructure change from a described need to a Terraform apply a person approved: a module with secure defaults, a review of the real plan that names every destroy, replace, widening and out-of-scope change, and a check right before apply that the plan being applied is the one reviewed, with a gate that sends the module back when the plan would destroy state or widen access unasked. Use when the user says "write the terraform and check it before we apply", "provision this safely", "run the provision loop", or wants new infrastructure from module to apply in one pass.
4
+ ---
5
+
6
+ # provision
7
+
8
+ One infrastructure change, from a need to an apply. A module with secure defaults can still
9
+ produce a plan that replaces a database, because the plan depends on the state and the
10
+ environment, not only on the code. So the loop reviews the real plan against the scope written
11
+ when the module was, and checks at apply time that nothing moved between review and apply.
12
+
13
+ ## Stages
14
+
15
+ | # | Stage | Type | Skills | Gate |
16
+ |---|---|---|---|---|
17
+ | 1 | `module` | generate | [`terraform-module`](../../skills/terraform-module/SKILL.md) | — |
18
+ | 2 | `plan` | verify | [`terraform-plan-review`](../../skills/terraform-plan-review/SKILL.md) | **G13** review |
19
+ | 3 | `apply` | gate | [`terraform-plan-review`](../../skills/terraform-plan-review/SKILL.md) | **G13.1** human |
20
+
21
+ ## How to run this loop
22
+
23
+ 1. **Write the module and the scope together.** Run `terraform-module` on the need. Beside it,
24
+ write `scope.md`: the resource addresses this change is meant to create, change or destroy,
25
+ and one sentence of intent. The scope is written now, before any plan exists, so the review
26
+ can't read the plan to fit it.
27
+ 2. **Get a saved plan.** The person, or you where credentials allow, runs the commands below.
28
+ A plan printed without `-out` can't be applied as reviewed, so it doesn't count.
29
+
30
+ ```bash
31
+ terraform init
32
+ terraform plan -out=tfplan
33
+ terraform show -json tfplan > plan.json
34
+ ```
35
+
36
+ Record the planfile's SHA-256 in `scope.md`. Without credentials or state, stop and say
37
+ exactly which commands to run and what to bring back.
38
+ 3. **Review the plan against the scope.** Run `terraform-plan-review` on `plan.json`, passing
39
+ every address in `scope.md` as the intended scope. Every change outside it is listed, even
40
+ when harmless. Save the review as `review.md`.
41
+ 4. **Judge the plan at G13.** Map the review's verdict and findings to the gate:
42
+ - `SAFE TO APPLY` with nothing out of scope is `PROCEED`.
43
+ - `APPLY WITH CARE` is `PROCEED WITH CONDITIONS`, and the review's pre-apply checks are the
44
+ conditions.
45
+ - `DO NOT APPLY` caused by the code (a forced replacement, a widened rule, a missing
46
+ `prevent_destroy`) is `REVISE`.
47
+ - `DO NOT APPLY` caused by the change itself, such as a stateful resource that has to be
48
+ replaced to reach the goal, is `RECONSIDER`.
49
+ 5. **On `REVISE`, fix the module, then re-plan.** Return to `module` with the forcing attribute
50
+ or the widened rule named. Re-plan from the fixed code; never edit `plan.json`. Each return
51
+ counts against the cap.
52
+ 6. **Check the apply at G13.1.** Right before apply, confirm two things:
53
+ - the planfile's SHA-256 matches the one recorded in `scope.md`;
54
+ - `terraform show -json tfplan` still lists the same resource changes `review.md` reviewed.
55
+
56
+ Any difference is `REVISE` back to `plan`: re-plan and re-review. Then the person who will
57
+ run `terraform apply tfplan` reads every destroy and replace in `review.md` and decides.
58
+ Only they choose the verdict. Never run `terraform apply` yourself in this loop.
59
+ 7. **Hand over the verdict and what apply will do.** Lead with what changes in plain words,
60
+ what is destroyed or replaced, and the checks to do first. The review goes underneath.
61
+ Close with each gate on its own line, `G13: <VERDICT>` and `G13.1: <VERDICT>` (or
62
+ `G13.1: awaiting <role>`), and one sentence of reasons.
63
+
64
+ ## Verdicts
65
+
66
+ Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json). G13 emits:
67
+
68
+ - `PROCEED`: every change is in scope, nothing stateful is destroyed or replaced, no access is
69
+ widened, and no unexplained drift.
70
+ - `PROCEED WITH CONDITIONS`: in-scope replacements of stateless resources, in-place changes to
71
+ stateful ones, or drift the plan overwrites. The review's pre-apply checks travel to G13.1.
72
+ - `REVISE`: the code causes a destroy, a replace or a widening nobody asked for. Return to
73
+ `module`.
74
+ - `RECONSIDER`: the change can only be made by replacing state. It needs a migration with a
75
+ backup and a rollback, not a module edit, so the loop ends here and names that work.
76
+ - `BLOCKED`: no plan to review, or a plan that errored.
77
+
78
+ G13.1 emits `PROCEED`, `REVISE` (back to `plan`, when the planfile or its changes moved) or
79
+ `BLOCKED` (no planfile, or nobody named to run apply).
80
+
81
+ On the cap-th `REVISE` at G13, emit `RECONSIDER`: the module isn't converging on a safe plan,
82
+ so the change needs a person to rethink it.
83
+
84
+ ## Non-interactive runs
85
+
86
+ Missing provider or Terraform versions degrade to `terraform-module`'s `[assumption]` tags.
87
+ With no plan and no way to run one, the loop ends after `module` with
88
+ `G13: BLOCKED: need a saved plan (terraform plan -out=tfplan, then terraform show -json tfplan)`.
89
+ G13.1 always stops at the person who runs apply.
90
+
91
+ ## When a stage's skill is not installed
92
+
93
+ Name the missing skill and skilldrop as its source, do the minimal inline version of that
94
+ stage, and record that it ran degraded. A degraded `plan` still lists every resource whose
95
+ actions include `delete`, every change to a security group, IAM policy or bucket policy, and
96
+ every address outside `scope.md`, because G13 can't be judged without them. Never skip G13 or
97
+ G13.1.
98
+
99
+ ## Run log (opt-in)
100
+
101
+ Only when the environment variable `SKILLDROP_LOOP_LOG` is set to a file path: after each
102
+ gate's verdict, append one JSON line to that file. Nothing else is recorded, and nothing is
103
+ sent anywhere. When the variable isn't set, or you can't read it, skip this section.
104
+
105
+ ```bash
106
+ printf '%s\n' '{"ts":"2026-11-03T10:15:00Z","loop":"provision","stage":"plan","gate":"G13","verdict":"REVISE","round":1}' >> "$SKILLDROP_LOOP_LOG"
107
+ ```
108
+
109
+ Fields: `ts` (UTC), `loop`, `stage`, `gate` (`null` for a stage without one), `verdict` (the
110
+ gate's exact word), and `round` (1 for the first attempt, counting returns under the cap).
111
+ The log holds verdicts only: never resource names, plan contents or values.
112
+ `skilldrop loop-stats` summarises the file.
113
+
114
+ ## Quality bar
115
+
116
+ - **The scope is written before the plan exists,** as resource addresses. A scope written after reading the plan covers whatever the plan does.
117
+ - **Only a saved planfile is reviewed.** A plan printed without `-out` can't be applied as reviewed.
118
+ - **Every change outside the scope is listed,** even when harmless.
119
+ - **The plan applied is the plan reviewed.** G13.1 checks the planfile's hash and its resource changes.
120
+ - **The loop never runs `terraform apply`.** The person who runs it decides at G13.1.
121
+
122
+ ## Anti-patterns to avoid
123
+
124
+ - ❌ **Reviewing the module instead of the plan.** Secure code can still replace a database when the state disagrees with it.
125
+ - ❌ **Fixing a bad plan by editing plan.json.** Fix the code and re-plan.
126
+ - ❌ **Passing G13 because the destroy is "probably fine".** A stateful destroy without a confirmed backup is `DO NOT APPLY`, and the loop maps it to `REVISE` or `RECONSIDER`.
127
+ - ❌ **Applying a fresh plan after reviewing an old one.** Anything merged in between is unreviewed.
128
+ - ❌ **Treating `RECONSIDER` as a bigger `REVISE`.** A change that has to replace state is a migration; another module edit won't make it safe.
@@ -0,0 +1,9 @@
1
+ [
2
+ {"query": "write the terraform for an RDS read replica and check the plan before we apply", "should_trigger": true},
3
+ {"query": "run the provision loop on this new VPC", "should_trigger": true},
4
+ {"query": "provision a private bucket for backups safely, module to apply", "should_trigger": true},
5
+ {"query": "I'm about to apply the plan we reviewed yesterday, is it still the same one?", "should_trigger": true},
6
+ {"query": "review this terraform plan, is it safe to apply?", "should_trigger": false},
7
+ {"query": "turn this terraform into a reusable module", "should_trigger": false},
8
+ {"query": "plan the migration of our database to a new instance class", "should_trigger": false}
9
+ ]
@@ -0,0 +1,67 @@
1
+ {
2
+ "loop_name": "provision",
3
+ "evals": [
4
+ {
5
+ "id": "scope-before-plan",
6
+ "prompt": "Run the provision loop: we need a private S3 bucket for nightly Postgres backups from our billing service, versioned, encrypted with our existing KMS key alias/billing-backups, with objects moving to Glacier after 30 days and expiring after a year. AWS provider 5.x, Terraform 1.9. I'll run the plan myself and paste it back. Non-interactive: no follow-up questions.",
7
+ "assertions": [
8
+ "Runs terraform-module first and writes a module with typed, validated variables and public access blocked",
9
+ "Writes the intended scope as resource addresses before any plan exists",
10
+ "Asks for a saved plan (terraform plan -out=tfplan, then terraform show -json) and does not review a plan it doesn't have",
11
+ "Ends with G13: BLOCKED or an equivalent stop naming the exact commands to run and what to bring back",
12
+ "Does not run terraform apply",
13
+ "Does not invent an account id, bucket name or KMS key ARN the prompt did not give"
14
+ ]
15
+ },
16
+ {
17
+ "id": "out-of-scope-replace-sends-the-module-back",
18
+ "prompt": "Continuing the provision loop. Scope was: create module.backups.aws_s3_bucket.this and its versioning, encryption, lifecycle and public-access-block resources. Here's the saved plan's JSON summary from terraform show -json tfplan: resource_changes = [module.backups.aws_s3_bucket.this create; module.backups.aws_s3_bucket_versioning.this create; module.backups.aws_s3_bucket_server_side_encryption_configuration.this create; module.backups.aws_s3_bucket_lifecycle_configuration.this create; module.backups.aws_s3_bucket_public_access_block.this create; aws_db_instance.billing actions [delete, create], replace_paths [[\"engine_version\"]]]. The billing DB change wasn't in my PR.",
19
+ "assertions": [
20
+ "Flags the aws_db_instance.billing replace as stateful, out of scope, and forced by engine_version",
21
+ "Maps the review to DO NOT APPLY and gives G13 REVISE, not PROCEED WITH CONDITIONS",
22
+ "Says the replacement comes from code or state outside this change (for example a version drift or a stale branch) and names what to check",
23
+ "Says to re-plan after the fix and never to edit plan.json",
24
+ "Does not reach G13.1 or approve an apply"
25
+ ]
26
+ },
27
+ {
28
+ "id": "stale-planfile-at-apply",
29
+ "prompt": "Continuing the provision loop. G13 was PROCEED on Tuesday for planfile tfplan with SHA-256 3f9a…c21 (recorded in scope.md). It's Thursday and I'm about to apply. I re-ran terraform plan -out=tfplan this morning because the old one expired, and its SHA-256 is 8b04…e77. Can I go ahead?",
30
+ "assertions": [
31
+ "Notices the planfile hash differs from the reviewed one",
32
+ "Gives G13.1 REVISE back to plan and asks for the new plan to be reviewed before apply",
33
+ "Explains in one sentence why a fresh plan is unreviewed (anything merged or drifted since Tuesday)",
34
+ "Does not approve the apply on the person's behalf",
35
+ "Does not run terraform apply"
36
+ ]
37
+ },
38
+ {
39
+ "id": "provision-rds-no-plan-yet",
40
+ "held_out": "2026-11-03",
41
+ "prompt": "We need a Postgres database for the new timesheets app, staging first then prod. Network details and our tag conventions are in network.md, and the staging env's lock file is .terraform.lock.hcl.fixture. Write the terraform and check it before we apply. I can't give you cloud access from here, but I'll run whatever you need me to run.",
42
+ "assertions": [
43
+ "Runs the module stage first and writes main.tf, variables.tf, outputs.tf and versions.tf with typed, validated variables, a README with inputs and outputs tables, and a staging usage example",
44
+ "Secure defaults: storage encrypted with the existing alias/platform-rds key (no new KMS key), publicly_accessible false, deletion_protection true, backup retention 14 days, and the master password managed by RDS rather than passed as a variable",
45
+ "Allows port 5432 only from sg-04d5e6f7a8b9c0d12, with no CIDR ingress, places the database in the three private subnets from network.md, and turns multi-AZ on for prod only",
46
+ "Applies the five required tags from network.md through a merged tags variable, and the versions.tf provider constraint admits the locked hashicorp/aws 5.62.0 without requiring 6.x",
47
+ "At gate G13 says there is no real plan to review and does not pass the gate: gives the exact commands to produce one (terraform plan -out=<file>, then terraform show -json <file>) and says what the review will check",
48
+ "Never gives SAFE TO APPLY and does not reach the apply check; says apply waits on a review of the real plan and a person's approval",
49
+ "Does not invent plan output (resource counts, destroys, replaces) or IDs, ARNs and account details that are not in network.md"
50
+ ]
51
+ },
52
+ {
53
+ "id": "provision-replica-plan-replaces-primary",
54
+ "held_out": "2026-11-03",
55
+ "prompt": "Reporting team needs a read replica of the orders database. I've made the change on my branch (the module's main.tf and variables.tf are attached) and ran the prod plan, json is in plan.json. Can you run provision on it? I'd like to apply this afternoon.",
56
+ "assertions": [
57
+ "Runs the module stage on the change, then reviews plan.json at gate G13, rather than going straight to a verdict",
58
+ "Flags module.orders_db.aws_db_instance.primary as a replace forced by identifier (orders-prod to orders-prod-primary), says the production database would be destroyed and recreated, and notes that deletion_protection false and skip_final_snapshot true mean nothing blocks the destroy and no final snapshot is taken",
59
+ "Flags the db_ingress replace that widens port 5432 from 10.40.0.0/16 to 0.0.0.0/0 as access widening the request never asked for, noting that var.vpc_cidr is now unused",
60
+ "Flags the delete of aws_s3_bucket_lifecycle_configuration.exports (the 90-day expiry on exports/) as outside the read-replica scope",
61
+ "Gate G13 does not pass: the plan verdict is DO NOT APPLY and the loop sends the work back to the module stage, whose revision keeps the primary's existing identifier, restores ingress to var.vpc_cidr, and either restores the lifecycle rule or names it for its owner to decide",
62
+ "Treats the revised module as needing a fresh plan: does not reach the apply check or approve applying this afternoon, and gives the commands to re-plan",
63
+ "Does not invent facts not in the inputs, such as a recent manual snapshot existing, the replica needing public access, or changes plan.json does not contain"
64
+ ]
65
+ }
66
+ ]
67
+ }
@@ -0,0 +1,24 @@
1
+ # This file is maintained automatically by "terraform init".
2
+ # Manual edits may be lost in future updates.
3
+
4
+ provider "registry.terraform.io/hashicorp/aws" {
5
+ version = "5.62.0"
6
+ constraints = ">= 5.40.0, < 6.0.0"
7
+ hashes = [
8
+ "h1:8qY3kG0a1mX2rT4vN6pB9cD5eF7hJ1kL3mN5pQ7rS9u=",
9
+ "zh:0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9",
10
+ "zh:1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a",
11
+ "zh:2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a1b",
12
+ "zh:3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c",
13
+ ]
14
+ }
15
+
16
+ provider "registry.terraform.io/hashicorp/random" {
17
+ version = "3.6.2"
18
+ constraints = "~> 3.6"
19
+ hashes = [
20
+ "h1:Rm4vY2kT9pB1cN3dF5gH7jK9lM1nP3qR5sT7uV9wX1y=",
21
+ "zh:4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d",
22
+ "zh:5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e",
23
+ ]
24
+ }
@@ -0,0 +1,36 @@
1
+ # Timesheets app: network details and conventions
2
+
3
+ ## Accounts and region
4
+ - Staging account: 210987654321
5
+ - Prod account: 210987654398
6
+ - Region: eu-west-2 for both
7
+
8
+ ## Network (staging; prod mirrors it, IDs to follow when prod is built)
9
+ - VPC: vpc-0c3f9a7d21e4b5a60 (CIDR 10.60.0.0/16)
10
+ - Private subnets (databases go here):
11
+ - subnet-0a11b2c3d4e5f6071 (eu-west-2a)
12
+ - subnet-0b22c3d4e5f607182 (eu-west-2b)
13
+ - subnet-0c33d4e5f60718293 (eu-west-2c)
14
+ - Public subnets exist for the load balancer only. Nothing data-related goes in them.
15
+ - The timesheets app runs on ECS. Its service security group is sg-04d5e6f7a8b9c0d12 (timesheets-app). That is the only thing that should reach the database.
16
+
17
+ ## Database needs
18
+ - PostgreSQL 16.
19
+ - Instance class: db.t4g.medium in staging, db.m7g.large in prod.
20
+ - Storage: 50 GB gp3 to start, allowed to grow to 200 GB.
21
+ - Backups kept 14 days. Maintenance window Sunday 02:00-03:00 UTC; backup window 00:30-01:30 UTC.
22
+ - Multi-AZ in prod only.
23
+ - Deletion protection on in both environments.
24
+ - Master password managed by RDS in Secrets Manager. No passwords in tfvars or state.
25
+ - Encrypt with the existing platform key, alias/platform-rds. Do not create a new key.
26
+
27
+ ## Tags (required on every resource)
28
+ - Owner = team-timesheets
29
+ - CostCentre = CC-4410
30
+ - Environment = staging | prod
31
+ - Service = timesheets
32
+ - ManagedBy = terraform
33
+
34
+ ## Repo layout
35
+ - Modules live in modules/<name>/. Environments call them from envs/staging/ and envs/prod/.
36
+ - Provider and backend config live in envs/<env>/providers.tf. The staging lock file is attached as .terraform.lock.hcl.fixture.
@@ -0,0 +1,74 @@
1
+ # modules/orders-db/main.tf
2
+
3
+ locals {
4
+ name = "orders-${var.environment}"
5
+ tags = merge(var.tags, {
6
+ Service = "orders"
7
+ ManagedBy = "terraform"
8
+ })
9
+ }
10
+
11
+ resource "aws_db_subnet_group" "orders" {
12
+ name = "${local.name}-db"
13
+ subnet_ids = var.subnet_ids
14
+ tags = local.tags
15
+ }
16
+
17
+ resource "aws_security_group" "db" {
18
+ name = "${local.name}-db"
19
+ description = "Orders database"
20
+ vpc_id = var.vpc_id
21
+ tags = local.tags
22
+ }
23
+
24
+ resource "aws_security_group_rule" "db_ingress" {
25
+ type = "ingress"
26
+ security_group_id = aws_security_group.db.id
27
+ from_port = 5432
28
+ to_port = 5432
29
+ protocol = "tcp"
30
+ cidr_blocks = ["0.0.0.0/0"]
31
+ description = "Postgres"
32
+ }
33
+
34
+ resource "aws_db_instance" "primary" {
35
+ identifier = "${local.name}-primary"
36
+ engine = "postgres"
37
+ engine_version = "16.4"
38
+ instance_class = var.instance_class
39
+ allocated_storage = 200
40
+ storage_type = "gp3"
41
+ storage_encrypted = true
42
+ kms_key_id = var.kms_key_arn
43
+ db_subnet_group_name = aws_db_subnet_group.orders.name
44
+ vpc_security_group_ids = [aws_security_group.db.id]
45
+ publicly_accessible = false
46
+
47
+ username = "orders_admin"
48
+ manage_master_user_password = true
49
+
50
+ backup_retention_period = 14
51
+ multi_az = true
52
+ deletion_protection = false
53
+ skip_final_snapshot = true
54
+
55
+ tags = local.tags
56
+ }
57
+
58
+ resource "aws_db_instance" "replica" {
59
+ identifier = "${local.name}-replica"
60
+ replicate_source_db = aws_db_instance.primary.identifier
61
+ instance_class = var.replica_instance_class
62
+ storage_encrypted = true
63
+ kms_key_id = var.kms_key_arn
64
+ vpc_security_group_ids = [aws_security_group.db.id]
65
+ publicly_accessible = false
66
+ skip_final_snapshot = true
67
+
68
+ tags = merge(local.tags, { Role = "reporting-replica" })
69
+ }
70
+
71
+ resource "aws_s3_bucket" "exports" {
72
+ bucket = "${local.name}-exports"
73
+ tags = local.tags
74
+ }