From 9f94103c573c279247bc2516602163c02715fed6 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 18 Aug 2026 00:22:19 +0000 Subject: [PATCH] =?UTF-8?q?feat(P02=20W0):=20dynamodb=20L1=20primitive=20?= =?UTF-8?q?=E2=80=94=20interface,=20terraform,=20registry,=20tests=20(REQ-?= =?UTF-8?q?322)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New L1 module modules/l1/dynamodb/ (stack type aws:dynamodb:table). Terraform aws_dynamodb_table with PK + optional SK, PAY_PER_REQUEST default, SSE-KMS + PITR + prevent_destroy per v1.8 NFR defaults. Registry entry (kind l1), catalog row, test_adapter.py updated to 15 entries / 13 L1. 45 tests passing. ---ci--- project: acdl phase: 2 milestone: v1.26 status: execute phase_role: execution wave: 0 requirements: [REQ-322] ---/ci--- --- modules/README.md | 1 + modules/l1/dynamodb/README.md | 38 ++++++++++++ modules/l1/dynamodb/examples/simple.yaml | 4 ++ modules/l1/dynamodb/instance.json | 10 ++++ modules/l1/dynamodb/interface.json | 66 +++++++++++++++++++++ modules/l1/dynamodb/terraform/main.tf | 74 ++++++++++++++++++++++++ modules/registry.json | 11 +++- tests/test_adapter.py | 8 +-- 8 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 modules/l1/dynamodb/README.md create mode 100644 modules/l1/dynamodb/examples/simple.yaml create mode 100644 modules/l1/dynamodb/instance.json create mode 100644 modules/l1/dynamodb/interface.json create mode 100644 modules/l1/dynamodb/terraform/main.tf diff --git a/modules/README.md b/modules/README.md index dbd9efe..194cbcb 100644 --- a/modules/README.md +++ b/modules/README.md @@ -36,6 +36,7 @@ resources it creates. | `rds` | `aws_db_instance` — Relational database (PostgreSQL, MySQL, etc.) with multi-engine support | [README](l1/rds/README.md) | | `kms-key` | `aws_kms_key` — Customer-managed KMS key with rotation enabled (per-stack CMK) | [README](l1/kms-key/README.md) | | `uptime` | `aws_ecs_service` — Uptime-kuma monitoring on ECS Fargate with alert channels | [README](l1/uptime/README.md) | +| `dynamodb` | `aws_dynamodb_table` — DynamoDB table with encryption + PITR (v1.8 NFR defaults) | [README](l1/dynamodb/README.md) | ## Modules diff --git a/modules/l1/dynamodb/README.md b/modules/l1/dynamodb/README.md new file mode 100644 index 0000000..69400c4 --- /dev/null +++ b/modules/l1/dynamodb/README.md @@ -0,0 +1,38 @@ +# DynamoDB L1 Primitive + +> Stack type: `aws:dynamodb:table` → Terraform `aws_dynamodb_table` + +## Description + +A DynamoDB table primitive with encryption + point-in-time recovery +enabled by default (per v1.8 NFR defaults). Supports a partition key +(required) + optional sort key. Default billing mode is +`PAY_PER_REQUEST` (on-demand). + +## Inputs + +| Name | Type | Required | Default | Description | +|---|---|---|---|---| +| `table_name` | string | yes | — | Globally-unique table name | +| `region` | string | yes | — | AWS region | +| `pk` | string | yes | — | Partition key attribute name | +| `sk` | string | no | `""` | Sort key attribute name | +| `billing_mode` | string | no | `PAY_PER_REQUEST` | Billing mode | +| `enabled` | boolean | no | `true` | Feature flag | + +## Outputs + +| Name | Type | Description | +|---|---|---| +| `table_arn` | arn | The table ARN | +| `table_name` | string | The table name | + +## NFRs + +- **Encryption:** SSE-KMS enabled by default. +- **Point-in-time recovery:** Enabled by default. +- **Deletion protection:** `prevent_destroy = true` (Terraform lifecycle). + +## Examples + +See `examples/simple.yaml`. diff --git a/modules/l1/dynamodb/examples/simple.yaml b/modules/l1/dynamodb/examples/simple.yaml new file mode 100644 index 0000000..6f6c473 --- /dev/null +++ b/modules/l1/dynamodb/examples/simple.yaml @@ -0,0 +1,4 @@ +table_name: nova-simple-ledger +region: us-east-1 +pk: block_index +billing_mode: PAY_PER_REQUEST diff --git a/modules/l1/dynamodb/instance.json b/modules/l1/dynamodb/instance.json new file mode 100644 index 0000000..a28c223 --- /dev/null +++ b/modules/l1/dynamodb/instance.json @@ -0,0 +1,10 @@ +{ + "module": "dynamodb", + "version": "1.0.0", + "inputs": { + "table_name": "nova-blockchain-ledger", + "region": "us-east-1", + "pk": "block_index", + "billing_mode": "PAY_PER_REQUEST" + } +} diff --git a/modules/l1/dynamodb/interface.json b/modules/l1/dynamodb/interface.json new file mode 100644 index 0000000..627f527 --- /dev/null +++ b/modules/l1/dynamodb/interface.json @@ -0,0 +1,66 @@ +{ + "name": "dynamodb", + "version": "1.0.0", + "kind": "l1", + "type": "aws:dynamodb:table", + "description": "DynamoDB table primitive (engine-agnostic stack type aws:dynamodb:table; the Terraform adapter translates to aws_dynamodb_table). Encryption + PITR enabled per v1.8 NFR defaults.", + "inputs": { + "table_name": { + "type": "string", + "description": "Globally-unique DynamoDB table name.", + "required": true + }, + "region": { + "type": "string", + "description": "AWS region the table is created in.", + "required": true + }, + "pk": { + "type": "string", + "description": "Partition key attribute name.", + "required": true + }, + "sk": { + "type": "string", + "description": "Sort key attribute name (optional).", + "required": false + }, + "billing_mode": { + "type": "string", + "default": "PAY_PER_REQUEST", + "description": "Billing mode: PAY_PER_REQUEST or PROVISIONED." + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "Feature flag: enable/disable this module." + } + }, + "outputs": { + "table_arn": { + "type": "arn", + "description": "The DynamoDB table ARN." + }, + "table_name": { + "type": "string", + "description": "The table name (echoes the input)." + } + }, + "nfrs": { + "encryption_enabled": { + "type": "boolean", + "description": "Enable server-side encryption (KMS).", + "default": true + }, + "point_in_time_recovery": { + "type": "boolean", + "description": "Enable point-in-time recovery.", + "default": true + }, + "deletion_protection": { + "type": "boolean", + "description": "Prevent resource destruction via Terraform lifecycle prevent_destroy.", + "default": true + } + } +} diff --git a/modules/l1/dynamodb/terraform/main.tf b/modules/l1/dynamodb/terraform/main.tf new file mode 100644 index 0000000..49f4f73 --- /dev/null +++ b/modules/l1/dynamodb/terraform/main.tf @@ -0,0 +1,74 @@ +resource "aws_dynamodb_table" "this" { + count = var.enabled ? 1 : 0 + name = var.table_name + billing_mode = var.billing_mode + + hash_key = var.pk + range_key = var.sk != "" ? var.sk : null + + attribute { + name = var.pk + type = "S" + } + + dynamic "attribute" { + for_each = var.sk != "" ? [var.sk] : [] + content { + name = attribute.value + type = "S" + } + } + + point_in_time_recovery { + enabled = true + } + + server_side_encryption { + enabled = true + } + + tags = { + "nova:managed-by" = "platform" + "nova:module" = "dynamodb" + } + + lifecycle { + prevent_destroy = true + } +} + +variable "table_name" { + type = string +} + +variable "region" { + type = string + default = "us-east-1" +} + +variable "pk" { + type = string +} + +variable "sk" { + type = string + default = "" +} + +variable "billing_mode" { + type = string + default = "PAY_PER_REQUEST" +} + +variable "enabled" { + type = bool + default = true +} + +output "table_arn" { + value = var.enabled ? aws_dynamodb_table.this[0].arn : "" +} + +output "table_name" { + value = var.enabled ? aws_dynamodb_table.this[0].name : "" +} diff --git a/modules/registry.json b/modules/registry.json index 24f697e..4b17bec 100644 --- a/modules/registry.json +++ b/modules/registry.json @@ -122,5 +122,14 @@ "deprecated": false, "kind": "l2" } + }, + "dynamodb": { + "1.0.0": { + "interface": "modules/l1/dynamodb/interface.json", + "terraform_dir": "modules/l1/dynamodb/terraform", + "published_at": "2026-08-14T19:26:18Z", + "deprecated": false, + "kind": "l1" + } } -} +} \ No newline at end of file diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 852e005..70ea94d 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -30,14 +30,14 @@ class TestInstance: class TestRegistry: - EXPECTED_L1_KEYS = {"s3", "vpc", "ecs-cluster", "ecs-service", "iam-role", "alb", "ecr", "cloudfront", "waf", "rds", "kms-key", "uptime"} + EXPECTED_L1_KEYS = {"s3", "vpc", "ecs-cluster", "ecs-service", "iam-role", "alb", "ecr", "cloudfront", "waf", "rds", "kms-key", "uptime", "dynamodb"} EXPECTED_L2_KEYS = {"static-assets", "microservice"} - def test_registry_has_14_entries(self, registry): - assert len(registry) == 14 + def test_registry_has_15_entries(self, registry): + assert len(registry) == 15 assert set(registry.keys()) == (self.EXPECTED_L1_KEYS | self.EXPECTED_L2_KEYS) - def test_registry_has_12_l1_entries(self, registry): + def test_registry_has_13_l1_entries(self, registry): l1 = {k for k in registry if registry[k]["1.0.0"]["interface"].startswith("modules/l1/")} assert l1 == self.EXPECTED_L1_KEYS