3 Commits

Author SHA1 Message Date
CIAgent f8cf46b94c docs(P00): PDLC state intake — STATE.md created
---ci---
project: nova-platform
phase: 0
milestone: v1.1
status: specify
---/ci---

PDLC Phase 0 state intake. STATE.md populated with absolute ground
truth: architecture state (13 L1 + 2 L2 modules, 4-layer architecture),
technical stack (Python 3.11, Terraform 1.9, bash scripts), 38 locked
decisions (D-001..D-038), active constraints, recent history (v1.0
complete, v0.1.6), known tensions (forge token blank, only dev.json
env, no coverage), agent assumptions.
2026-08-28 19:19:03 +00:00
CIAgent fe9ab7e444 docs(ship): milestone v1.0 complete (release v0.1.6, local-only)
---ci---
project: nova-platform
milestone: v1.0
status: complete
---/ci---

v1.0 MILESTONE COMPLETE. 38 REQ-IDs all complete. 38 decisions
(D-001..D-038). 76 tests passing. Merged to main 2443909. Tag
v0.1.6 (milestone release on v0.1 patch line). All milestone
branches deleted (local-only; no remote configured). NOVA_FORGE_TOKEN
blank → Gitea release pending (local-only ship). Checkpoint cleared —
next run starts a new milestone.
2026-08-25 02:19:21 +00:00
CIAgent 2443909362 docs(milestone): complete v1.0-nova-platform (release v0.1.6)
---ci---
project: nova-platform
milestone: v1.0
status: complete
requirements:
  covered: [REQ-01,REQ-02,REQ-03,REQ-04,REQ-05,REQ-06,REQ-07,REQ-08,REQ-09,REQ-10,REQ-11,REQ-12,REQ-13,REQ-14,REQ-15,REQ-16,REQ-17,REQ-18,REQ-19,REQ-20,REQ-21,REQ-22,REQ-23,REQ-24,REQ-25,REQ-26,REQ-27,REQ-28,REQ-29,REQ-30,REQ-31,REQ-32,REQ-33,REQ-34,REQ-35,REQ-36,REQ-37,REQ-38]
  partial: []
---/ci---

v1.0 milestone complete: simplified infrastructure-delivery platform
derived from Nova (acdl). 6 phases (P0-P5 + P6 final). 38 REQ-IDs.
38 decisions (D-001..D-038). 76 tests pass. Engine boundary holds.
Happy paths green (check-only + CI). 13 L1 + 2 L2 modules. 5 terraform
roots. Shell reproducibility. Zero OOS files.

Tags: v0.1.0 (P0) → v0.1.1..v0.1.5 (P1..P5) → v0.1.6 (P6 = milestone
release on v0.1 patch line).
2026-08-25 02:18:35 +00:00
157 changed files with 5833 additions and 15 deletions
+7 -12
View File
@@ -1,19 +1,14 @@
{
"phase": 0,
"stage": "grill",
"milestone": "v1.0",
"stage": "specify",
"milestone": "v1.1",
"phase_role": "pre_execution",
"attempts": 0,
"updated_at": "2026-08-20T18:30:00Z",
"updated_at": "2026-08-28T19:09:26Z",
"project": "nova-platform",
"milestone_branch": "milestone/v1.0-nova-platform",
"milestone_branch": "milestone/v1.1-pdlc-state",
"phase_branch": "phase/00-pre-execution",
"specify_validated": true,
"requirements_count": 38,
"clarify_decisions": "D-011..D-038",
"clarify_escalated": ["D-017", "D-025", "D-030"],
"grill_verdict": "PROCEED (conditions resolved)",
"grill_confidence": 0.82,
"grill_conditions": ["C-1 D-037", "C-2 D-038", "C-3 docs-grep", "C-4 reorder", "C-5 concurrency-note", "C-6 deviation-claim-fix"],
"mvp_ux_check": "PASS"
"milestone_complete": false,
"pdlc_intake": true,
"state_md_created": true
}
+6 -3
View File
@@ -58,9 +58,12 @@ documented. Ships as `v0.1.5`.
### Phase 6 — Final Review + Ship (milestone release)
REVIEW + AUDIT + milestone SHIP. Merges `phase/06`
`milestone/v1.0-nova-platform``main`. Tags `v1.0.0` (major — the
initial release). Creates the Gitea release. Deletes all milestone
branches.
`milestone/v1.0-nova-platform``main`. Tags `v0.1.6` (the milestone
release on the v0.1 patch line — v1.0 is the milestone *label*, tags
run on the previous minor's patch line per branch-strategy.md; since
there is no prior minor, v0.1.x is the patch line and v0.1.6 IS the
v1.0 milestone release). Creates the Gitea release. Deletes all
milestone branches.
## Coverage (init estimate — refined by PLAN)
+101
View File
@@ -0,0 +1,101 @@
# STATE — Nova Platform
> PDLC Phase 0 intake. Absolute ground truth as of 2026-08-28T19:09Z.
> Single-pass discovery for the Product Owner/Manager.
---
## 1. Header
Project: nova-platform
Initiative: PDLC Phase 0 — State Intake for next milestone planning
Initiator: CIAgent (automated harness)
Date (UTC): 2026-08-28T19:09:26Z
Current Version: v1.0 milestone complete; release v0.1.6 on main; no active milestone in progress
System Health: GREEN — 76/76 tests pass, engine boundary holds, zero OOS files, both happy paths green
Raw Idea (≤ 3 sentences):
Nova Platform v1.0 shipped a simplified infrastructure-delivery platform (contract → resolve → terraform adapter → apply) derived from the Nova/acdl reference, with security/audit/identity/CI-workflow machinery deliberately excluded.
This intake was triggered by the PDLC system instruction to produce a STATE.md for the Product Owner to review before planning the next milestone.
The desired outcome is a single-pass ground-truth snapshot enabling the PO to ask pointed Phase 1 questions without back-and-forth.
---
## 2. Architecture State
Active Layers (which exist and are stable):
[x] Core Primitives — 13 L1 modules: s3, vpc, ecs-cluster, ecs-service, iam-role, alb, ecr, cloudfront, waf, rds, kms-key, dynamodb, uptime
[x] Domain Modules — 2 L2 patterns: microservice (vpc+ecs-cluster+ecs-service+iam-role+ecr+alb per D-038), static-assets (s3+cloudfront+kms-key per D-038)
[x] API/Dev Surface — contract schema (array infrastructure D-015), stack schema (flat, no source D-037), environment schema (simplified D-017/D-018); resolver (core/contract_resolver.py), environment_check (core/environment_check.py), terraform adapter (adapters/terraform/adapter.py)
[ ] UI/Agent Surface — N/A (no frontend, no agent UI; frontend-engineer persona deactivated per D-006)
Compute Topology (per environment):
local: abstract (offline --check-only: contract → resolve → adapter → validate; no AWS, no terraform binary)
dev: serverless (ECS Fargate + ALB + S3 + CloudFront via terraform apply; platform-managed VPC in terraform/platform/)
staging: N/A — UNKNOWN — needs investigation (no qa.json environment file created; only dev.json exists)
prod: N/A — UNKNOWN — needs investigation (no prod.json environment file created)
dr: N/A — UNKNOWN — needs investigation (no dr.json environment file created)
Identity Stack in Force:
auth: N/A — out of scope (Nova-idp, PAT lifecycle, ABAC evaluator explicitly OOS per D-007)
token-vend: N/A — out of scope
signing: N/A — out of scope (KMS signing, JWS attestation explicitly OOS)
session: N/A — out of scope
Audit Stream:
source of truth: N/A — out of scope (outbox writer, audit ledger, attestation matrix explicitly OOS per D-007)
in-repo fallback: no
retention policy: N/A
---
## 3. Technical Stack (concrete, not aspirational)
Language(s) and runtime(s): Python 3.11 (>=3.11 required per pyproject.toml); Bash (shell scripts); HCL (Terraform 1.9.* pinned)
Build / packaging: pyproject.toml (setuptools backend); no CLI package (D-027 — scripts invoked via shell, no [project.scripts]); requirements-test.txt pins 5 deps
CI / CD: Local shell only (scripts/run_ci.sh: lint → test → check-only); NO .github/workflows (OOS per D-007); NO pipelines/ central contract (OOS)
Infrastructure: AWS (S3 state backend, DynamoDB lock table, ECS Fargate, ALB, CloudFront, WAF, RDS, KMS, ECR, IAM); terraform/bootstrap/ scripts create S3 bucket nova-tfstate-<account>-<region> + DynamoDB nova-tfstate-locks (D-022); only dev.json environment exists (account_id placeholder 000000000000 for offline)
Data stores: S3 (state backend, static assets), DynamoDB (state locking — nova-tfstate-locks; also L1 dynamodb primitive for consumer tables), RDS (L1 rds primitive)
Secrets / KMS: Static AWS key only (NOVA_AWS_* in .ciagent/.env.secrets, chmod 600, gitignored); NO OIDC (OOS per D-025); KMS via L1 kms-key primitive (consumer-side, not platform-side); NOVA_FORGE_TOKEN for gitea release (currently blank in .env.secrets — release pending)
External integrations in scope: gitea @ https://git.cloudinit.dev/continuous-intelligence/nova-platform (release forge; token NOVA_FORGE_TOKEN); AWS (boto3 for bootstrap scripts + terraform apply)
---
## 4. Active Constraints (the load-bearing ones)
Locked Decisions: D-001 (milestone type major, first release), D-002 (all 13 L1 + 2 L2 in v1.0), D-003 (branching_strategy phase), D-004 (auto_commit/auto_push true), D-005 (test_first false), D-006 (personas: lead+data+backend active, frontend deactivated), D-007 (config.policy removed, ideation drops security), D-008 (secrets.scopes keeps forge + model-backend), D-009 (ship.confirm_before_ship true), D-010 (telemetry.persist true), D-011 (resolver modules_dir param, file reads permitted), D-012 (L2 opaque, flat stack, no children/wires expansion), D-013 (L2 interface.json + terraform_dir in registry), D-014 (interface.json: no nfrs, no intra_refs), D-015 (infrastructure is ARRAY, version optional), D-016 (interpolation ${env.*}/${contract.*} kept), D-017 (environment.schema.json kept simplified), D-018 (env fields: name/account_id/region/state_backend/network; no runner_role_arn/autonomy/confidence_threshold), D-019 (check() returns dict, raises EnvironmentNotFoundError), D-020 (run_platform.sh stages: check-only/plan-only/apply), D-021 (run_ci.sh glob py_compile), D-022 (lock table nova-tfstate-locks, NOT nova-outbox), D-023 (platform/main.tf = ONLY shared VPC), D-024 (ci-vpc = short-lived test VPC), D-025 (onboarding = IAM role cross-account assume, NOT OIDC, NOT user), D-026 (runner policy account parameterized, NOT hardcoded), D-027 (no CLI package, scripts only), D-028 (docs/modules/index links to modules/*/README.md), D-029 (drop STANDARDS.md, keep README-TEMPLATE.md trimmed), D-030 (moto pinned, minimal use), D-031 (run_platform.sh flags: --check-only/--plan-only/--quiet/--help only), D-032 (NOVA_* env prefix, NOVA_AWS_* → AWS_* copy then unset), D-033 (per-env contract variants differ ONLY in environment field), D-034 (engine-boundary test scans .py stripped of docstrings/comments), D-035 (index_document is L2 static-assets input, NOT L1 s3), D-036 (L2 children: microservice=vpc+ecs-cluster+ecs-service+iam-role+ecr+alb, static-assets=s3+cloudfront+kms-key), D-037 (adapter loads registry, no source in stack — grill C-1 fix), D-038 (L2 compositions locked, grill C-2 fix)
Active Invariants: No INV-* IDs formally registered (v1.0 used D-* decisions, not INV-* invariants). Load-bearing invariants: (1) engine-agnostic core — no aws_*/terraform/module "/provider "/resource " in .py outside adapters/terraform/ (verified by tests/test_engine_boundary.py); (2) structural conventions preserved with 9 locked deviations (D-012, D-013, D-015, D-017, D-018, D-019, D-022, D-023, D-025, D-027); (3) no OOS-creep — zero files from the PROJECT.md/REQUIREMENTS.md OOS list exist in the repo.
Standing Capability Gate: N/A — no GATE-* IDs registered
Anti-Goals Touched: PROJECT.md §"Out of scope" + REQUIREMENTS.md §"Out of scope (locked — do NOT implement in v1.0)": security/policy (kyverno, Wiz, Checkov, PolicyEngine), confidence signal + evidence outbox, identity/ABAC (Nova-idp, PAT, JWS, KMS signing, SoD, HITL, attestation, submission_readiness), CI/CD pipeline (.github/workflows, pipelines/, pipeline schemas), metrics/telemetry, leadership decks, decommission/env_transition/mode_resolver/onboarding flow beyond bootstrap, multi-project mode, OIDC federation.
Out-of-Scope (hard): kyverno-json adapter, Wiz adapter, Checkov custom rules, core/policy_engine.py, core/confidence_signal.py, core/outbox_writer.py, schemas/policy_check_result.schema.json, schemas/metrics_*.schema.json, audit ledger, attestation matrix, core/abac_evaluator.py, core/auth_store.py, core/jws_attestation.py, core/kms_signing.py, core/pat_lifecycle.py, core/separation_of_duties.py, core/hitl_gates.py, core/attestation_matrix.py, core/submission_readiness.py, .github/workflows/ci.yml, .github/workflows/deploy.yml, pipelines/, schemas/pipeline.schema.json, schemas/deploy-pipeline.schema.json, metrics/, core/metrics/, core/regression_verify*.py, modules/STANDARDS.md, PPTX/marp slides, core/env_transition.py, core/decommission_transform.py, core/mode_resolver.py, core/onboarding.py, OIDC federation.
---
## 5. Recent History & Quality Gates (last 1-2 milestones)
Last Shipped: v1.0 milestone — 2026-08-25 (tag v0.1.6 on main); delivered: simplified infrastructure-delivery platform with 13 L1 + 2 L2 modules, contract surface, resolver, terraform adapter, engine boundary, 5 terraform roots, bootstrap scripts, shell reproducibility, 76 tests, docs; 38 REQ-IDs all complete; 38 decisions (D-001..D-038)
In Progress: No active milestone in progress (v1.0 complete; checkpoint cleared; next /ci-run starts a new milestone)
Coverage Floor: UNKNOWN — needs investigation (pytest-cov not installed; no coverage measurement configured in pyproject.toml addopts; D-030 dropped pytest-cov as OOS)
Recent Incidents: none
Known Tensions: (1) NOVA_FORGE_TOKEN blank in .ciagent/.env.secrets — v0.1.6 shipped local-only (tag + merge complete, gitea release pending); the forge repo was created during this intake but the token must be set before releases can be created. (2) Only dev.json environment exists — qa/prod/dr environment files were not created in v1.0 (contracts reference them via enum but environment_check will raise EnvironmentNotFoundError for qa/prod/dr). (3) Coverage measurement is absent — pytest-cov was dropped as OOS but this means no coverage floor is enforceable.
---
## 6. Agent Context & Assumptions (Agent Initiators Only)
Missing Context: (1) qa/prod/dr environment JSON files — not created in v1.0 (only core/environments/dev.json exists); the contract schema allows these environment values but no environment record exists for them. (2) Coverage percentage — pytest-cov is not installed; no coverage data available. (3) NORTH_STAR.md — not created (the reference acdl has one; nova-platform deferred it to a future /ci-run specify stage). (4) Live AWS verification — bootstrap scripts + terraform roots were authored but never applied against real AWS (offline-only verification via --check-only).
Agent Assumptions: (1) Assumed the gitea forge token from the acdl reference repo (/home/opencode/acdl/.env.secrets) is valid for the nova-platform org — verified via API (200 auth). (2) Assumed the next milestone is v1.1 (incrementing from v1.0) — no ROADMAP.md entry exists for a next milestone yet. (3) Assumed PDLC Phase 0 STATE.md work belongs on a new milestone/phase-0 branch hierarchy (milestone/v1.1-pdlc-state → phase/00-pre-execution) per the branch gate convention. (4) Assumed "merge to the forge upstream but DO NOT perform a release" means: push main + branches to origin, do NOT create a gitea release (no tag push, no release API call).
---
## 7. Canonical State References (Version/Hash)
Vision/Strategy doc: UNKNOWN — needs investigation (NORTH_STAR.md not created; PROJECT.md §"Vision / Core Value" serves as the de facto vision but is not versioned as a strategy doc)
Architecture document: v0.1.6 (tag on main commit fe9ab7e); .ciagent/ARCHITECTURE.md + docs/architecture.md are the architecture source of truth
Last approved SPEC: N/A — no SPEC-ID system in use; REQUIREMENTS.md (38 REQ-IDs, REQ-01..REQ-38) is the requirements source of truth at commit fe9ab7e
Decision log: fe9ab7e (last synced commit on main); decisions D-001..D-035 in .ciagent/CLARIFY.md; D-036..D-038 (grill fixes) in .ciagent/CLARIFY.md §"Grill fixes"; D-001..D-010 also in .ciagent/PROJECT.md
Invariants catalog: N/A — no INV-* IDs formally registered; load-bearing invariants documented in STATE.md §4 above
+127
View File
@@ -0,0 +1,127 @@
# Nova Platform
> Nova Platform — infrastructure delivery, simplified. A consumer declares
> intent in a YAML contract; the platform resolves it to a stack, compiles
> it through the Terraform adapter, and applies it. Every deployment is
> reproducible from the shell, not just in CI.
Nova Platform is the **infrastructure-delivery core** of the Nova model.
The DevSecOps, identity, audit-ledger, and central CI-pipeline-contract
machinery of the reference are intentionally removed. What remains: a
consumer writes a small YAML contract that names one or more modules by
name + version, selects an environment, and supplies module-specific
inputs. The platform resolves the contract to a stack instance, compiles
it through the Terraform adapter, and applies it.
- **Consumer guide:** [`docs/consumer-guide.md`](docs/consumer-guide.md)
- **Contracts:** [`docs/contracts/`](docs/contracts/index.md)
- **Environments:** [`docs/environments/`](docs/environments/index.md)
- **Architecture:** [`docs/architecture.md`](docs/architecture.md)
- **Modules:** [`docs/modules/`](docs/modules/index.md)
## How to run
### Quick start (offline, no AWS required)
The fastest way to verify the platform works — no AWS credentials, no
bootstrap, no cost.
```bash
# Install test dependencies
pip install -r requirements-test.txt
# 1. Run the test suite (all offline)
python3 -m pytest tests/ -q
# 2. Run the platform in check-only mode (offline — contract -> resolve ->
# adapter -> structure validation). Uses the default sample contract.
bash scripts/run_platform.sh --check-only contracts/static-assets.yml
# Expected: "=== PLATFORM CHECK OK ==="
# 3. Reproduce the full CI pipeline locally (lint -> test -> check-only)
bash scripts/run_ci.sh
# Expected: "=== CI PIPELINE OK ==="
# Show all run_platform.sh flags:
bash scripts/run_platform.sh --help
```
### Run against live AWS (requires credentials + bootstrap)
> Prerequisites: a platform-managed environment (see
> [docs/environments/](docs/environments/index.md); `core/environments/dev.json`
> is the sample), AWS credentials for dev (in `.ciagent/.env.secrets`,
> gitignored; see [Credentials](#credentials)), `terraform` (pin `>= 1.9, < 1.10`),
> `python3` + `boto3` + `jsonschema` + `pyyaml`.
```bash
# 1. Bootstrap the AWS state backend + runner IAM user (one-time, idempotent)
# See terraform/bootstrap/README.md for the full runbook.
export NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID="<root key>"
export NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="<root secret>"
export AWS_DEFAULT_REGION="us-east-1"
python3 terraform/bootstrap/create_state_backend.py
python3 terraform/bootstrap/create_iam_user.py # prints the initial key
bash scripts/rotate_spike_key.sh # writes .ciagent/.env.secrets
# 2. Run the full platform pipeline (contract -> environment check -> stack ->
# adapter -> terraform init/validate/plan -> apply).
bash scripts/run_platform.sh contracts/microservice.yml
# Expected: "=== PLATFORM APPLY OK ==="
# Or plan-only (contract -> stack -> adapter -> terraform plan; no apply):
bash scripts/run_platform.sh --plan-only contracts/static-assets.yml
# Add --quiet to suppress streaming (output to log files only):
bash scripts/run_platform.sh --quiet contracts/static-assets.yml
```
### run_platform.sh flags (D-031)
| Flag | Mode | AWS required | Description |
|------|------|--------------|-------------|
| `--check-only` | offline | no | contract → resolve → adapter → structure validation |
| `--plan-only` | AWS | yes | above + `terraform init`/`validate`/`plan` (no apply) |
| `--quiet` | any | — | suppress streaming output |
| `-h`, `--help` | — | — | show usage |
| *(none)* | apply | yes | full path: above + `terraform apply -auto-approve` |
## Repository layout
| Path | Purpose | Status |
|------|---------|--------|
| `core/` | Platform code: contract resolver, environment check, environments | active |
| `schemas/` | JSON Schemas (draft 2020-12): contract, stack, environment | active |
| `adapters/terraform/` | The Terraform adapter — the only engine-specific code | active |
| `terraform/` | State backend (S3 + DynamoDB) + bootstrap scripts + platform/onboarding/ci-vpc | active |
| `modules/` | L1 primitives (13) + L2 patterns (2) + `registry.json`. Each module has `interface.json` + `terraform/` | active |
| `contracts/` | Sample consumer contracts (`static-assets.yml`, `microservice.yml`) + per-env variants | active |
| `scripts/` | `run_platform.sh` (pipeline runner), `run_ci.sh` (local CI mirror), `rotate_spike_key.sh` | active |
| `tests/` | Pytest suite (all offline — resolver, adapter, schemas, engine boundary, environment check) | active |
| `docs/` | Documentation: consumer guide, contracts, environments, architecture, modules | active |
| `.ciagent/` | CIAgent config + locked decisions (`ARCHITECTURE.md`, `PROJECT.md`, `CLARIFY.md`) | active |
## Credentials
Nova Platform uses a **static AWS key** for dev/local operation. There is no
zero-trust federation layer in v1.0 — that is out of scope.
- The runner key is stored in **`.ciagent/.env.secrets`** (gitignored,
`chmod 600`) using the `NOVA_AWS_*` prefix (D-032):
`NOVA_AWS_ACCESS_KEY_ID`, `NOVA_AWS_SECRET_ACCESS_KEY`.
- `scripts/run_platform.sh` copies `NOVA_AWS_*` to the standard `AWS_*`
env vars before invoking Terraform, then unsets the `NOVA_*` copies.
- Bootstrap uses a one-shot root key via `NOVA_BOOTSTRAP_AWS_*` env vars
(never committed, never echoed). See
[`terraform/bootstrap/README.md`](terraform/bootstrap/README.md) for the
full bootstrap runbook (state backend + runner IAM user + key rotation).
- Onboarding creates a per-consumer IAM **role** (cross-account assume-role
pattern, D-025) — not a user, not federation. See
`terraform/onboarding/main.tf`.
## Consumer guide
A step-by-step guide for a consumer to create a repo, write a contract,
validate it offline, and run it against AWS is at
[`docs/consumer-guide.md`](docs/consumer-guide.md). The guide is generic
across all modules; `static-assets` is the worked example.
View File
+3
View File
@@ -0,0 +1,3 @@
from adapters.terraform.adapter import adapt
__all__ = ["adapt"]
+102
View File
@@ -0,0 +1,102 @@
"""Nova Platform — Terraform Adapter.
The ONLY engine-specific code in the platform (per REQ-09, verified by
tests/test_engine_boundary.py). Loads modules/registry.json internally
to map module -> terraform_dir (per D-037/C-1 grill fix — the resolver
does NOT put a `source` field in the stack; the adapter resolves it
here, inside the engine boundary).
Stateless assembler: no `terraform` CLI invocation, no state files, no
plan files. Emits Terraform HCL: one `module "x" { source = ...; <inputs> }`
block per stack resource.
"""
import json
import os
from pathlib import Path
def _load_registry(repo_root):
"""Load modules/registry.json -> {module_name: terraform_dir}."""
registry_path = os.path.join(str(repo_root), "modules", "registry.json")
with open(registry_path) as fh:
registry = json.load(fh)
return {name: list(versions.values())[0].get("terraform_dir")
for name, versions in registry.items()
if list(versions.values())[0].get("terraform_dir")}
def _tf_value(value):
"""Render a Python value as an HCL expression."""
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, (int, float)):
return str(value)
if isinstance(value, list):
return "[" + ", ".join(_tf_value(v) for v in value) + "]"
if isinstance(value, dict):
return "{ " + ", ".join(f"{k} = {_tf_value(v)}" for k, v in value.items()) + " }"
return json.dumps(str(value))
def _emit_module_block(resource, terraform_dirs, repo_root):
"""Emit one `module "x" { source = ...; <inputs> }` block."""
module_name = resource["module"]
rid = module_name.replace("-", "_")
tf_dir = terraform_dirs.get(module_name)
if tf_dir is None:
raise ValueError(f"module '{module_name}' has no terraform_dir in registry")
source = os.path.join(str(repo_root), tf_dir)
lines = [f'module "{rid}" {{', f' source = "{source}"']
for key, val in resource.get("inputs", {}).items():
if key == "region":
continue
lines.append(f" {key} = {_tf_value(val)}")
lines.append("}")
return "\n".join(lines)
def adapt(stack, repo_root):
"""Compile a flat stack dict to Terraform HCL.
Args:
stack: a flat stack dict conforming to schemas/stack.schema.json
(NO `source` field per D-037 — the adapter resolves
module -> terraform_dir via the registry).
repo_root: Path to the repo root (the adapter loads
modules/registry.json from here).
Returns:
A string of Terraform HCL with one `module "x" {}` block per
stack resource.
"""
terraform_dirs = _load_registry(repo_root)
blocks = []
for resource in stack.get("resources", []):
blocks.append(_emit_module_block(resource, terraform_dirs, repo_root))
return "\n\n".join(blocks) + "\n"
def main(argv=None):
import sys
argv = argv or sys.argv[1:]
if len(argv) < 1:
print("usage: adapter.py <stack.json> [out.tf]", file=sys.stderr)
return 2
stack_path = argv[0]
out_path = argv[1] if len(argv) > 1 else None
repo_root = Path(__file__).resolve().parent.parent.parent
with open(stack_path) as fh:
stack = json.load(fh)
hcl = adapt(stack, repo_root)
if out_path:
with open(out_path, "w") as fh:
fh.write(hcl)
else:
print(hcl)
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
+9
View File
@@ -0,0 +1,9 @@
id: msvc
name: Microservice
environment: dev
infrastructure:
- module: microservice
version: "1.0.0"
inputs:
service_name: "${env.environment}-${contract.id}-svc"
desired_count: 2
+9
View File
@@ -0,0 +1,9 @@
id: msvc
name: Microservice
environment: dr
infrastructure:
- module: microservice
version: "1.0.0"
inputs:
service_name: "${env.environment}-${contract.id}-svc"
desired_count: 2
+9
View File
@@ -0,0 +1,9 @@
id: msvc
name: Microservice
environment: prod
infrastructure:
- module: microservice
version: "1.0.0"
inputs:
service_name: "${env.environment}-${contract.id}-svc"
desired_count: 2
+9
View File
@@ -0,0 +1,9 @@
id: msvc
name: Microservice
environment: qa
infrastructure:
- module: microservice
version: "1.0.0"
inputs:
service_name: "${env.environment}-${contract.id}-svc"
desired_count: 2
+9
View File
@@ -0,0 +1,9 @@
id: msvc
name: Microservice
environment: dev
infrastructure:
- module: microservice
version: "1.0.0"
inputs:
service_name: "${env.environment}-${contract.id}-svc"
desired_count: 2
+9
View File
@@ -0,0 +1,9 @@
id: stsi
name: Static Assets Site
environment: dev
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: "${env.environment}-${contract.id}-assets"
index_document: index.html
+9
View File
@@ -0,0 +1,9 @@
id: stsi
name: Static Assets Site
environment: dr
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: "${env.environment}-${contract.id}-assets"
index_document: index.html
+9
View File
@@ -0,0 +1,9 @@
id: stsi
name: Static Assets Site
environment: prod
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: "${env.environment}-${contract.id}-assets"
index_document: index.html
+9
View File
@@ -0,0 +1,9 @@
id: stsi
name: Static Assets Site
environment: qa
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: "${env.environment}-${contract.id}-assets"
index_document: index.html
+9
View File
@@ -0,0 +1,9 @@
id: stsi
name: Static Assets Site
environment: dev
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: "${env.environment}-${contract.id}-assets"
index_document: index.html
View File
+181
View File
@@ -0,0 +1,181 @@
"""Nova Platform — Contract Resolver.
Resolves a validated consumer contract to a Stack instance (a flat dict
conforming to schemas/stack.schema.json).
Flow:
1. Validate the contract dict against schemas/contract.schema.json.
2. Load the environment via core.environment_check.check().
3. Build an interpolation context {'env': env, 'contract': contract}.
4. For each infrastructure entry: look up the module + version in the
registry, interpolate ${env.*} / ${contract.*} tokens in inputs,
and emit a flat stack resource {module, version, inputs}.
5. Return the stack dict.
Engine-agnostic: no aws_*, no Terraform terms, no module paths. The stack
carries NO 'source' field (D-037/C-1 grill fix) — the adapter loads the
registry to map module -> terraform_dir. L2 is opaque (D-012): a single
stack resource, no children/wires expansion.
"""
import json
import re
from pathlib import Path
import jsonschema
import yaml
_TOKEN_RE = re.compile(r"\$\{([a-zA-Z_][a-zA-Z0-9_.]*)\}")
class ModuleNotFoundError(KeyError):
"""Raised when a contract references a module not in the registry."""
class VersionNotFoundError(KeyError):
"""Raised when a contract references a version not in the registry."""
def _lookup_dotted(context, dotted):
parts = dotted.split(".")
cur = context
for part in parts:
if isinstance(cur, dict) and part in cur:
cur = cur[part]
else:
raise KeyError(dotted)
return cur
def _expand_vars(value, context):
if isinstance(value, str):
def _replace(match):
token = match.group(1)
try:
resolved = _lookup_dotted(context, token)
except KeyError:
raise ValueError(f"unresolved interpolation token: ${{{token}}}")
if isinstance(resolved, (dict, list)):
return json.dumps(resolved)
return str(resolved)
return _TOKEN_RE.sub(_replace, value)
if isinstance(value, dict):
return {k: _expand_vars(v, context) for k, v in value.items()}
if isinstance(value, list):
return [_expand_vars(v, context) for v in value]
return value
def _latest_version(registry, module_name):
versions = registry[module_name]
non_deprecated = [(v, e) for v, e in versions.items()
if not e.get("deprecated", False)]
if not non_deprecated:
non_deprecated = list(versions.items())
non_deprecated.sort(key=lambda x: [int(p) for p in x[0].split(".")],
reverse=True)
return non_deprecated[0][0]
def _load_schema(path):
with open(path) as fh:
return json.load(fh)
def resolve(contract, registry, modules_dir, environments_dir=None,
repo_root=None):
"""Resolve a validated contract dict to a flat Stack dict.
Args:
contract: validated contract dict (must conform to
schemas/contract.schema.json).
registry: modules/registry.json loaded as a dict.
modules_dir: Path to the modules/ directory (unused for L2-opaque
resolution but kept per D-011 for future interface.json reads).
environments_dir: Path to core/environments/. If None, derived from
repo_root / 'core' / 'environments'.
repo_root: Path to the repo root. If None, derived from modules_dir
parent's parent (modules_dir is <root>/modules).
Returns:
A flat stack dict conforming to schemas/stack.schema.json:
{contract_id, contract_name, environment, resources: [{module,
version, inputs}]}.
Raises:
ModuleNotFoundError: contract references an unknown module.
VersionNotFoundError: contract references an unknown version.
jsonschema.ValidationError: contract does not conform to schema.
ValueError: unresolved interpolation token.
"""
if repo_root is None:
repo_root = Path(modules_dir).parent.parent
if environments_dir is None:
environments_dir = Path(repo_root) / "core" / "environments"
contract_schema_path = Path(repo_root) / "schemas" / "contract.schema.json"
contract_schema = _load_schema(contract_schema_path)
jsonschema.validate(contract, contract_schema)
from core import environment_check
env = environment_check.check(contract["environment"], environments_dir)
# Expose 'environment' as an alias for the env's 'name' field so
# ${env.environment} resolves (the env JSON uses 'name', but contracts
# reference the environment by ${env.environment}).
env["environment"] = env.get("name", contract["environment"])
context = {"env": env, "contract": contract}
resources = []
for item in contract["infrastructure"]:
module_name = item["module"]
if module_name not in registry:
raise ModuleNotFoundError(module_name)
version = item.get("version")
if version is None:
version = _latest_version(registry, module_name)
elif version not in registry[module_name]:
raise VersionNotFoundError(f"{module_name}@{version}")
inputs = _expand_vars(item.get("inputs", {}), context)
resources.append({
"module": module_name,
"version": version,
"inputs": inputs,
})
return {
"contract_id": contract["id"],
"contract_name": contract["name"],
"environment": contract["environment"],
"resources": resources,
}
def main(argv=None):
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
argv = argv or sys.argv[1:]
if len(argv) < 2:
print("usage: contract_resolver.py <contract.yaml> [out.json]",
file=sys.stderr)
return 2
contract_path = argv[0]
out_path = argv[1] if len(argv) > 1 else None
repo_root = Path(__file__).resolve().parent.parent
with open(contract_path) as fh:
contract = yaml.safe_load(fh)
with open(repo_root / "modules" / "registry.json") as fh:
registry = json.load(fh)
stack = resolve(contract, registry, repo_root / "modules",
repo_root=repo_root)
if out_path:
with open(out_path, "w") as fh:
json.dump(stack, fh, indent=2)
else:
print(json.dumps(stack, indent=2))
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
+37
View File
@@ -0,0 +1,37 @@
"""Nova Platform — Environment Check.
Loads and validates a platform-managed environment JSON file.
Simplified per D-019: check(env_name, environments_dir) -> dict, raises
EnvironmentNotFoundError on missing env. Drops the reference's
(ok, message) tuple, _onboarding_message, and main() CLI.
"""
import json
from pathlib import Path
class EnvironmentNotFoundError(FileNotFoundError):
"""Raised when a named environment has no JSON file."""
def check(env_name, environments_dir):
"""Load and return the environment dict for env_name.
Args:
env_name: environment name (dev, qa, prod, dr).
environments_dir: Path to the core/environments/ directory.
Returns:
The parsed environment dict.
Raises:
EnvironmentNotFoundError: no <env_name>.json in environments_dir.
"""
env_path = Path(environments_dir) / f"{env_name}.json"
if not env_path.exists():
raise EnvironmentNotFoundError(
f"environment '{env_name}' not found at {env_path}")
with open(env_path) as fh:
env = json.load(fh)
return env
+14
View File
@@ -0,0 +1,14 @@
{
"name": "dev",
"description": "Sample dev environment for offline/local testing. account_id placeholder (000000000000) for offline mode.",
"account_id": "000000000000",
"region": "us-east-1",
"state_backend": {
"bucket": "nova-tfstate-dev-us-east-1",
"lock_table": "nova-tfstate-locks"
},
"network": {
"vpc_cidr": "10.0.0.0/16",
"azs": ["us-east-1a", "us-east-1b"]
}
}
+130
View File
@@ -0,0 +1,130 @@
# Architecture
> **Status:** v1.0 (current). This document mirrors `.ciagent/ARCHITECTURE.md`.
> Where the two conflict, `.ciagent/ARCHITECTURE.md` wins.
## 0. Purpose
Nova Platform is the **infrastructure-delivery core**. A consumer declares
intent in a YAML contract; the platform resolves it to a stack, compiles it
through the Terraform adapter, and applies it. The DevSecOps, identity,
audit-ledger, and central CI-pipeline-contract machinery of the Nova
reference are intentionally removed — see the OOS list below.
## 1. Layers (4)
```
┌──────────────────────────────────────────────────────┐
│ 1. Contract Surface schemas/contract.schema.json
│ contracts/*.yml (samples)
├──────────────────────────────────────────────────────┤
│ 2. Resolution core/contract_resolver.py
│ core/environment_check.py
│ schemas/stack.schema.json
├──────────────────────────────────────────────────────┤
│ 3. Engine Adapter adapters/terraform/ (the only
│ (only engine-specific) engine-specific code)
├──────────────────────────────────────────────────────┤
│ 4. Apply terraform/ (bootstrap, modules)
│ scripts/run_platform.sh
└──────────────────────────────────────────────────────┘
```
### Layer 1 — Contract Surface
A consumer writes a small YAML contract. The `infrastructure` field is an
**array** (D-015), not a map:
```yaml
id: stsi
name: My Static Site
environment: dev
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: my-static-site-assets
index_document: index.html
```
Validated against `schemas/contract.schema.json`. The contract is the only
consumer-facing surface. It is engine-agnostic — no `aws_*` terms.
### Layer 2 — Resolution
`core/contract_resolver.py` resolves a validated contract to a Stack
instance (a typed structure conforming to `schemas/stack.schema.json`).
Resolution is pure: contract in, stack out. No I/O beyond local file reads
for module metadata (D-011). No engine terms.
`core/environment_check.py` validates that the named environment exists in
`core/environments/*.json` and returns its definition. Environments are
platform-managed (consumers provide no AWS account, VPC, or state bucket).
Interpolation (D-016): the resolver expands `${env.<field>}` and
`${contract.<field>}` tokens after the environment is loaded. Unknown tokens
raise `ValueError`.
### Layer 3 — Engine Adapter
`adapters/terraform/` is the only engine-specific code. It takes a Stack
and emits Terraform (`module "x" { source = "../../modules/..." }` blocks).
The adapter is a stateless assembler — lifecycle ownership belongs to
Terraform via the shell orchestrator. This is the only place `aws_*` /
Terraform terms appear.
### Layer 4 — Apply
`scripts/run_platform.sh` orchestrates: contract → resolve → adapter →
`terraform init``terraform plan``terraform apply`. Modes (D-031):
`--check-only` (offline, structure validation), `--plan-only` (no apply),
full (apply). `--quiet` suppresses streaming.
## 2. Engine Boundary (Enforced)
The engine boundary is strict. Code outside `adapters/terraform/` MUST NOT
contain engine-specific terms (`aws_s3_bucket`, `aws_*`, Terraform HCL).
This invariant is verified by tests (`tests/test_engine_boundary.py`,
scope per D-034: `.py` files in `core/`, `schemas/`, `contracts/`,
`tests/`, `scripts/`, root — excluding `adapters/terraform/`, `modules/`,
`.tf`/`.md`/`.json` data files).
## 3. What is NOT here (intentionally removed vs the reference)
Nova Platform is a simplified, infrastructure-only platform. The following
reference features are **out of scope** for v1.0:
- No confidence signal — no score gating apply.
- No audit outbox — no hash-chained evidence events.
- No policy engine / policy adapter — no policy checks.
- No identity layer, no attribute-based authorization, no human-in-the-loop
approval gates.
- No reusable CI workflow — local shell only (`scripts/run_ci.sh`).
- No central pipeline contract — no `pipelines/` directory.
- No platform telemetry / metrics.
- No decommission transform, env-transition transform, or onboarding flow
beyond bootstrap.
- No leadership decks or slide rendering.
## 4. Module Catalog
L1 primitives (single resources) + L2 patterns (composites of primitives).
Each module has an `interface.json` (inputs/outputs, no engine terms) and a
`terraform/` directory. `modules/registry.json` indexes every module +
version.
**L1 (primitives — 13):** `s3`, `vpc`, `ecs-cluster`, `ecs-service`,
`iam-role`, `alb`, `ecr`, `cloudfront`, `waf`, `rds`, `kms-key`,
`dynamodb`, `uptime`.
**L2 (patterns — 2):**
| Module | Composes (D-038) | Description |
|--------|------------------|-------------|
| `microservice` | vpc + ecs-cluster + ecs-service + iam-role + ecr + alb (6 L1s) | Container microservice with a public ALB |
| `static-assets` | s3 + cloudfront + kms-key (3 L1s) | Static site fronted by CloudFront |
L2 modules are opaque stack entries (D-012): the resolver does not expand
their children. The L2's `terraform/main.tf` composes L1 modules internally
via `module` blocks. See [`docs/modules/index.md`](modules/index.md) for the
full catalog and each module's README.
+208
View File
@@ -0,0 +1,208 @@
# Consumer Guide — Declare intent, deploy to AWS
This guide walks a consumer through creating a repo, writing a contract,
validating it offline, and running it against AWS. It is **generic** across
all modules in the registry; `static-assets` is the worked example, but
every step applies to `microservice` and any future module.
## The model
You write a contract YAML file and the platform does the rest. Your
repository contains only your application code and your contracts. You do
not write infrastructure modules or adapter code.
```mermaid
flowchart LR
A["your repo<br/>(app code + contract)"] -->|run_platform.sh| B
B["platform<br/>(resolver + adapter + modules)"] -->|contract -> stack -> terraform -> apply| C
C["your resources in AWS"]
```
## Prerequisites
- **A consumer repository** for your application code + contract.
- **A platform-managed environment** bound to your repo. The platform team
provisions the AWS account, network, and state backend. See
[Environments](environments/index.md).
- **AWS credentials** for the target environment, in `.ciagent/.env.secrets`
(gitignored) using the `NOVA_AWS_*` prefix. See the
[platform README](../README.md#credentials) and
[`terraform/bootstrap/README.md`](../terraform/bootstrap/README.md).
## Step 1 — Create a consumer repo
Create a repository for your application. The top level holds your app
code; your contract lives at the repo root (or wherever you point
`run_platform.sh`). Example for a static site:
```
my-static-site/
index.html
assets/
style.css
logo.png
contract.yml
```
Example for a microservice:
```
my-microservice/
app.py
Dockerfile
contract.yml
```
## Step 2 — Define the contract
Write `contract.yml`. The `static-assets` example:
```yaml
id: stsi
name: Static Assets Site
environment: dev
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: "${env.environment}-${contract.id}-assets"
index_document: index.html
```
A `microservice` example:
```yaml
id: msvc
name: Microservice
environment: dev
infrastructure:
- module: microservice
version: "1.0.0"
inputs:
service_name: "${env.environment}-${contract.id}-svc"
desired_count: 2
```
### Contract fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | yes | Short operational acronym (`^[a-z][a-z0-9-]{2,5}$`, 3-6 chars). Becomes the stack name used for the Terraform state key and resource naming prefix. Stable across deploys and environment promotions. |
| `name` | string | yes | Full human-readable stack name (min 3 chars). |
| `environment` | string | yes | The platform-managed environment to deploy to (`dev`/`qa`/`prod`/`dr`). See [Environments](environments/index.md). |
| `infrastructure` | array | yes | **Array** of modules to deploy (D-015). Each entry carries a `module` name (matching a registry key), an optional `version` (defaults to latest non-deprecated), and required `inputs`. One entry = single-module deploy; N entries = multi-module manifest. |
### Infrastructure item fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `module` | string | yes | Module name from `modules/registry.json` (`^[a-z][a-z0-9-]*$`). |
| `version` | string | no | Semver pin `X.Y.Z`. Omitted = latest non-deprecated version. |
| `inputs` | object | yes | Module-specific inputs (see the module's README). No `aws_*` keys — the contract is engine-agnostic. |
Each module declares its inputs in its `interface.json`. Consult the
[module catalog](modules/index.md) for the full list, or read the module's
own README under `modules/l1/<name>/` or `modules/l2/<name>/`.
The contract is validated against `schemas/contract.schema.json`. An invalid
contract (missing field, unknown module, wrong type) fails at the
validate-contract stage with a clear error.
### Interpolation reference (D-016)
The resolver expands `${env.*}` and `${contract.*}` tokens after the
environment is loaded. Unknown tokens raise `ValueError` (fail loud).
Expansion is recursive (nested map/list values expand too).
| Token | Resolves to | Example |
|-------|-------------|---------|
| `${env.name}` | the environment name | `dev` |
| `${env.region}` | the environment's AWS region | `us-east-1` |
| `${env.account_id}` | the environment's AWS account id | `000000000000` |
| `${env.state_backend.bucket}` | the environment's state bucket | `nova-tfstate-dev-us-east-1` |
| `${env.state_backend.lock_table}` | the environment's lock table | `nova-tfstate-locks` |
| `${env.network.vpc_cidr}` | the environment's VPC CIDR | `10.0.0.0/16` |
| `${contract.id}` | the contract's operational acronym | `stsi` |
| `${contract.name}` | the contract's name field | `Static Assets Site` |
| `${contract.environment}` | the contract's environment field | `dev` |
## Step 3 — Validate offline (no AWS required)
Before touching AWS, validate the contract end-to-end offline. Clone the
Nova Platform repo and run `--check-only` against your contract:
```bash
bash scripts/run_platform.sh --check-only path/to/your/contract.yml
# Expected: "=== PLATFORM CHECK OK ==="
```
This runs: environment check → contract schema validation → resolve to
stack → adapter compiles to HCL → output structure validation. No AWS
credentials are needed.
## Step 4 — Run against live AWS
Once the contract validates offline, run the full pipeline against AWS.
Ensure your credentials are in `.ciagent/.env.secrets` (see
[Credentials](../README.md#credentials)):
```bash
bash scripts/run_platform.sh contracts/static-assets.yml
# Expected: "=== PLATFORM APPLY OK ==="
```
The full path: environment check → validate contract → resolve to stack →
adapter compiles to HCL → load AWS credentials → `terraform init`
`terraform validate``terraform plan``terraform apply -auto-approve`.
To stop before apply (review the plan only):
```bash
bash scripts/run_platform.sh --plan-only contracts/static-assets.yml
# Expected: "=== PLATFORM PLAN OK ==="
```
## Step 5 — What gets created
After a successful `dev` run, the resources declared by your module's
pattern exist in your AWS account.
For the `static-assets` example (s3 + cloudfront + kms-key, D-038):
- An **S3 bucket** (named via your `bucket_name` input, interpolation
expanded) with versioning enabled.
- A **CloudFront distribution** with the S3 bucket as the origin.
- A **KMS key** for SSE.
For other modules, consult the module's README
(`modules/l1/<name>/README.md` or `modules/l2/<name>/README.md`) for the
exact resources created.
## Step 6 — Upload your content (static-assets example)
The platform provisions the infrastructure; you upload your content. For the
`static-assets` module:
```bash
aws s3 sync ./assets s3://<your-bucket-name>/
```
For a `microservice`, the platform provisions the ECS service and ALB; you
push your container image to the ECR repo the platform created.
## Reference
| Resource | Path | Description |
|----------|------|-------------|
| Contract schema | `schemas/contract.schema.json` | JSON Schema for consumer contracts. |
| Stack schema | `schemas/stack.schema.json` | JSON Schema for the resolved stack instance. |
| Module catalog | [modules/](modules/index.md) | All primitives and modules. |
| Sample contract | `contracts/static-assets.yml` | The reference example contract. |
| Sample contract | `contracts/microservice.yml` | The microservice example contract. |
| Module examples | `modules/<name>/examples/` | Validated per-module example contracts. |
| Contract resolver | `core/contract_resolver.py` | Resolves contracts to stack instances. |
| Terraform adapter | `adapters/terraform/adapter.py` | Compiles stack instances to Terraform. |
| Pipeline runner | `scripts/run_platform.sh` | The pipeline runner. |
| Environments | [environments/](environments/index.md) | Platform-managed environments. |
| Platform README | `README.md` | How the platform works + how to run it. |
| Credentials | `README.md#credentials` | The static-key model + bootstrap runbook. |
+139
View File
@@ -0,0 +1,139 @@
# Contracts
A consumer declares intent in a **contract** — a small YAML file that names
infrastructure (one or more modules), selects an environment, and supplies
module-specific inputs. The platform validates, resolves, and deploys it.
## The contract file
A minimal example (the `static-assets` module):
```yaml
id: stsi
name: Static Assets Site
environment: dev
infrastructure:
- module: static-assets
version: "1.0.0"
inputs:
bucket_name: "${env.environment}-${contract.id}-assets"
index_document: index.html
```
A `microservice` example:
```yaml
id: msvc
name: Microservice
environment: dev
infrastructure:
- module: microservice
version: "1.0.0"
inputs:
service_name: "${env.environment}-${contract.id}-svc"
desired_count: 2
```
## Array-based infrastructure (D-015)
The `infrastructure` field is an **array** of module entries, not a map
keyed by module name. This is a locked deviation from the Nova reference
(D-015). Each entry is an object with `module`, `version` (optional), and
`inputs`.
One entry = single-module deploy. N entries = multi-module manifest deployed
in one pipeline run:
```yaml
id: app
name: Pricing Service API
environment: dev
infrastructure:
- module: microservice
version: "1.0.0"
inputs: { ... }
- module: static-assets
version: "1.0.0"
inputs: { ... }
```
All modules deploy to the same `environment` in one pipeline run.
## Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | yes | Short operational acronym (`^[a-z][a-z0-9-]{2,5}$`, 3-6 chars). Becomes the stack name used for the Terraform state key and resource naming prefix. Stable across deploys and environment promotions. |
| `name` | string | yes | Full human-readable stack name (min 3 chars). |
| `environment` | string | yes | The platform-managed environment to deploy to (`dev`/`qa`/`prod`/`dr`). See [Environments](../environments/index.md). |
| `infrastructure` | array | yes | Array of modules to deploy (D-015). `minItems: 1`. |
### Infrastructure item fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `module` | string | yes | Module name from `modules/registry.json` (`^[a-z][a-z0-9-]*$`). |
| `version` | string | no | Semver pin (`^\d+\.\d+\.\d+$`). Omitted = latest non-deprecated version from the registry. |
| `inputs` | object | yes | Module-specific inputs (see the module's README / `interface.json`). No `aws_*` keys — the contract is engine-agnostic. |
## Validation
The contract is validated against
[`schemas/contract.schema.json`](../../schemas/contract.schema.json). An
invalid contract (missing field, unknown module, wrong type) fails at the
validate-contract stage with a clear error.
## Interpolation tokens (D-016)
The resolver expands `${env.*}` and `${contract.*}` tokens after the
environment is loaded. Unknown tokens raise `ValueError` (fail loud).
Expansion is recursive (nested map/list values expand too).
| Token | Resolves to | Example |
|-------|-------------|---------|
| `${env.name}` | the environment name | `dev` |
| `${env.region}` | the environment's AWS region | `us-east-1` |
| `${env.account_id}` | the environment's AWS account id | `000000000000` |
| `${env.state_backend.bucket}` | the environment's state bucket | `nova-tfstate-dev-us-east-1` |
| `${env.state_backend.lock_table}` | the environment's lock table | `nova-tfstate-locks` |
| `${env.network.vpc_cidr}` | the environment's VPC CIDR | `10.0.0.0/16` |
| `${env.network.azs}` | the environment's availability zones | `["us-east-1a","us-east-1b"]` |
| `${contract.id}` | the contract's operational acronym | `stsi` |
| `${contract.name}` | the contract's name field | `Static Assets Site` |
| `${contract.environment}` | the contract's environment field | `dev` |
## Sample contracts
Two reference examples exist in `contracts/`:
- [`contracts/static-assets.yml`](../../contracts/static-assets.yml) — the
`static-assets` module (s3 + cloudfront + kms-key, D-038).
- [`contracts/microservice.yml`](../../contracts/microservice.yml) — the
`microservice` module (vpc + ecs-cluster + ecs-service + iam-role + ecr +
alb, D-038).
Additionally, every module has a `modules/<name>/examples/` directory with
validated example contracts. See the [module catalog](../modules/index.md)
for the full list.
## Per-environment variants (D-033)
Each sample contract has per-environment variants that differ **only** in
the `environment` field — all other fields are identical. Interpolation
resolves environment-specific values at resolver time.
| File | Environment |
|------|-------------|
| `contracts/static-assets.yml` | dev (default) |
| `contracts/static-assets.dev.yml` | dev |
| `contracts/static-assets.qa.yml` | qa |
| `contracts/static-assets.prod.yml` | prod |
| `contracts/static-assets.dr.yml` | dr |
| `contracts/microservice.yml` | dev (default) |
| `contracts/microservice.dev.yml` | dev |
| `contracts/microservice.qa.yml` | qa |
| `contracts/microservice.prod.yml` | prod |
| `contracts/microservice.dr.yml` | dr |
Promotion = running the pipeline against the matching variant. See the
[Consumer Guide](../consumer-guide.md) for the end-to-end flow.
+104
View File
@@ -0,0 +1,104 @@
# Environments
A consumer does **not** provide an AWS account, a VPC, a subnet, or an S3
state bucket. The platform manages environments.
## What an environment is
A named environment is a **platform-owned** bundle of:
- An **AWS account** (or a scoped partition of one).
- A **network** (VPC + subnets / AZs).
- A **state backend** (an S3 bucket + DynamoDB lock table for Terraform
state).
A consumer selects an environment **by name** in their contract:
```yaml
environment: dev
```
The platform resolves the name to the underlying account/network/state
backend at run time. The consumer never sees the raw credentials.
## Environment definition shape (D-018)
Each environment is a JSON file in `core/environments/`. The field set is
reduced from the reference — the IAM role, autonomy, and confidence
threshold fields are out of scope for v1.0.
`core/environments/dev.json` (the sample):
```json
{
"name": "dev",
"description": "Sample dev environment for offline/local testing. account_id placeholder (000000000000) for offline mode.",
"account_id": "000000000000",
"region": "us-east-1",
"state_backend": {
"bucket": "nova-tfstate-dev-us-east-1",
"lock_table": "nova-tfstate-locks"
},
"network": {
"vpc_cidr": "10.0.0.0/16",
"azs": ["us-east-1a", "us-east-1b"]
}
}
```
### Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | yes | The environment name (`dev`/`qa`/`prod`/`dr`). |
| `description` | string | no | Human-readable description. |
| `account_id` | string | yes | The AWS account id (placeholder `000000000000` for offline dev). |
| `region` | string | yes | The AWS region. |
| `state_backend.bucket` | string | yes | The S3 state bucket name. |
| `state_backend.lock_table` | string | yes | The DynamoDB lock table name (`nova-tfstate-locks` per D-022). |
| `network.vpc_cidr` | string | yes | The VPC CIDR block. |
| `network.azs` | array | yes | The availability zones. |
### Dropped from the reference (D-018)
- `runner_role_arn` — identity/authorization is out of scope.
- `autonomy` — human-in-the-loop gates are out of scope.
- `confidence_threshold` — the confidence signal is out of scope.
## State backend (D-022)
The Terraform state backend uses a dedicated DynamoDB lock table named
`nova-tfstate-locks` (NOT `nova-outbox` — the audit outbox is out of scope
for v1.0). The S3 state bucket is named `nova-tfstate-<account>-<region>`
with versioning enabled. See
[`terraform/bootstrap/README.md`](../../terraform/bootstrap/README.md) for
the bootstrap runbook that creates both.
## Autonomy by environment
| Environment | Autonomy | Operator action |
|-------------|----------|-----------------|
| dev | Fully autonomous | None — `terraform apply -auto-approve` runs automatically. |
| qa | Manual | An operator runs `run_platform.sh` against the `qa` contract. |
| prod | Manual | An operator runs `run_platform.sh` against the `prod` contract. |
| dr | Manual | An operator runs `run_platform.sh` against the `dr` contract. |
`dev` is the only autonomous environment. Higher environments require a
human operator to invoke the pipeline against the environment's contract
variant. There are no automated gates or attestation steps — those are out
of scope for v1.0. Staging does not exist.
## Onboarding scaffold (current state)
The platform repo ships a minimal onboarding scaffold:
- [`core/environments/`](../../core/environments/) — environment definitions
(a sample `dev.json`).
- `core/environment_check.py` — checks whether an environment is defined
for a given contract's environment name; raises `EnvironmentNotFoundError`
when none is defined (D-019).
- `scripts/run_platform.sh` calls the check before contract validation.
The scaffold is minimal: provisioning a new environment is a platform-team
action today (bootstrap the state backend + network). Self-service
environment provisioning is a future milestone.
+36
View File
@@ -0,0 +1,36 @@
# Module Catalog
Every module's full documentation lives next to its code under
`modules/l1/<name>/README.md` or `modules/l2/<name>/README.md` (per
D-028). This page is the index: it lists the available modules and
links to their per-module docs.
## L1 primitives (13)
| Module | Stack type | Multi-resource? | Docs |
|-----------------|-------------------------------|-----------------|-----------------------------------------------|
| `s3` | `aws:s3:bucket` | no | [modules/l1/s3/README.md](../../modules/l1/s3/README.md) |
| `vpc` | `aws:ec2:vpc` | yes | [modules/l1/vpc/README.md](../../modules/l1/vpc/README.md) |
| `ecs-cluster` | `aws:ecs:cluster` | no | [modules/l1/ecs-cluster/README.md](../../modules/l1/ecs-cluster/README.md) |
| `ecs-service` | `aws:ecs:service` | yes | [modules/l1/ecs-service/README.md](../../modules/l1/ecs-service/README.md) |
| `iam-role` | `aws:iam:role` | no | [modules/l1/iam-role/README.md](../../modules/l1/iam-role/README.md) |
| `alb` | `aws:alb` | yes | [modules/l1/alb/README.md](../../modules/l1/alb/README.md) |
| `ecr` | `aws:ecr:repository` | no | [modules/l1/ecr/README.md](../../modules/l1/ecr/README.md) |
| `cloudfront` | `aws:cloudfront:distribution` | no | [modules/l1/cloudfront/README.md](../../modules/l1/cloudfront/README.md) |
| `waf` | `aws:waf:web_acl` | no | [modules/l1/waf/README.md](../../modules/l1/waf/README.md) |
| `rds` | `aws:rds:instance` | no | [modules/l1/rds/README.md](../../modules/l1/rds/README.md) |
| `kms-key` | `aws:kms:key` | no | [modules/l1/kms-key/README.md](../../modules/l1/kms-key/README.md) |
| `dynamodb` | `aws:dynamodb:table` | no | [modules/l1/dynamodb/README.md](../../modules/l1/dynamodb/README.md) |
| `uptime` | `aws:uptime:monitor` | no | [modules/l1/uptime/README.md](../../modules/l1/uptime/README.md) |
## L2 compositions (2)
| Module | Composes | Docs |
|------------------|---------------------------------------------|---------------------------------------------------|
| `microservice` | vpc + ecs-cluster + ecs-service + alb + ecr | [modules/l2/microservice/README.md](../../modules/l2/microservice/README.md) |
| `static-assets` | s3 + cloudfront | [modules/l2/static-assets/README.md](../../modules/l2/static-assets/README.md) |
## See also
- [modules/README.md](../../modules/README.md) — L1/L2 distinction, registry format, how to add a module.
- [modules/README-TEMPLATE.md](../../modules/README-TEMPLATE.md) — per-module doc template.
+96
View File
@@ -0,0 +1,96 @@
# Module: `<name>`
> Copy this template into `modules/l1/<name>/README.md` or
> `modules/l2/<name>/README.md` and fill in the placeholders. Sections
> marked **DROP** are intentionally omitted from nova modules
> (D-029): do **not** add `NFRs` or `Compliance` sections.
## Overview
One-paragraph description of what this module provisions, the stack
type(s) it exposes, and when to reach for it. Mention whether it is L1
(single primitive) or L2 (composition of L1s), and whether it is
multi-resource.
- **Stack type:** `aws:<service>:<resource>`
- **Kind:** `l1` (or `l2`)
- **Version:** `1.0.0`
## Resources
List the concrete cloud resources the Terraform adapter creates. For L1
single-resource modules this is one row; for multi-resource L1s mirror
the `resources[]` array in `interface.json`.
| Stack type | Terraform resource | Notes |
|-------------------------|------------------------------------|----------------------------------|
| `aws:s3:bucket` | `aws_s3_bucket` | The bucket itself |
| `aws:s3:bucket` | `aws_s3_bucket_versioning` | Versioning sibling |
| `aws:s3:bucket` | `aws_s3_bucket_server_side_encryption_configuration` | SSE config sibling |
For L2 modules, list the L1 modules composed via `module` blocks in
`terraform/main.tf` instead.
## Inputs
Mirror `interface.json``inputs`. Mark required inputs with **yes**.
| Name | Type | Required | Default | Description |
|----------------|---------|----------|---------------|-----------------------------------|
| `bucket_name` | string | yes | — | Globally-unique S3 bucket name |
| `region` | string | yes | — | AWS region |
| `kms_key_arn` | string | no | `null` | CMK ARN for SSE-KMS |
| `enabled` | boolean | no | `true` | Feature flag |
| `tags` | map | no | `{}` | Tags merged with module defaults |
## Outputs
Mirror `interface.json``outputs`.
| Name | Type | Description |
|---------------------------------|--------|----------------------------------------------|
| `bucket_arn` | arn | The S3 bucket ARN |
| `bucket_name` | string | The bucket name |
| `bucket_regional_domain_name` | string | The bucket regional domain name |
## Usage
```hcl
module "bucket" {
source = "modules/l1/s3/terraform"
bucket_name = "nova-prod-assets"
region = "us-east-1"
tags = {
"nova:owner" = "team-platform"
"nova:environment" = "prod"
}
}
```
Or as a flat-stack contract entry:
```json
{
"module": "s3",
"version": "1.0.0",
"inputs": {
"bucket_name": "nova-prod-assets",
"region": "us-east-1"
}
}
```
## Versioning
This module follows the registry semver contract: bump the patch/minor
version in `interface.json` and `modules/registry.json` for any
input/output/behavior change. Breaking changes (renamed inputs,
removed outputs, changed defaults) require a major bump and a new
registry entry; the previous version is marked `deprecated: true` and
remains selectable by pinned contracts. See `modules/README.md` for
the registry format and the resolver's version-selection rules.
<!-- DROP: NFRs — out of scope for nova v1 (D-029) -->
<!-- DROP: Compliance / attestation — out of scope for nova v1 (D-029) -->
+127
View File
@@ -0,0 +1,127 @@
# Nova Modules
Nova ships a two-tier module library. Modules are **engine-agnostic**:
their contract is declared in `interface.json` (stack types like
`aws:s3:bucket`), and an adapter translates the contract to a concrete
IaC engine (Terraform today; Pulumi/CDK possible later). All L1 modules
in this repo ship a Terraform adapter under `terraform/`.
## L1 vs L2
| Tier | What it is | Composes | Examples |
|------|----------------------------------------------------------------------------|---------------------|-----------------------------------|
| L1 | A single primitive resource (or tightly-coupled resource group) on a cloud | One stack resource | `s3`, `vpc`, `ecs-cluster`, `alb` |
| L2 | A composition of L1s expressing an architectural pattern | Multiple L1 modules | `microservice`, `static-assets` |
- **L1** = one entry in the flat stack. Even multi-resource L1s (e.g.
`vpc`, `ecs-service`, `alb`) emit a single stack entry; their
`interface.json` lists the child resources in a `resources[]` array
for documentation, but the resolver does **not** expand them
(D-012).
- **L2** = also one opaque entry in the flat stack. The L2's
`terraform/main.tf` composes L1 modules internally via `module` blocks
(D-012). The L2 exposes its own L2-level `inputs`/`outputs`; children
and wiring live in terraform, not in the interface.
## Registry format
`modules/registry.json` maps `module_name -> version -> entry`:
```json
{
"s3": {
"1.0.0": {
"interface": "modules/l1/s3/interface.json",
"terraform_dir": "modules/l1/s3/terraform",
"published_at": "2026-08-20T00:00:00Z",
"deprecated": false,
"kind": "l1"
}
}
}
```
- `interface` — path to the `interface.json` declaring the contract.
- `terraform_dir` — path to the adapter's Terraform module directory
(the flat stack's `source` field).
- `kind``"l1"` or `"l2"`.
- `deprecated` — when `true`, the resolver warns and selects the latest
non-deprecated version unless the caller pins a version.
## interface.json shape (D-014)
```json
{
"name": "s3",
"version": "1.0.0",
"kind": "l1",
"type": "aws:s3:bucket",
"description": "...",
"inputs": { "<name>": { "type": "...", "required": true, "description": "..." } },
"outputs": { "<name>": { "type": "...", "description": "..." } },
"resources": [ { "type": "aws:ec2:vpc", "inputs": [...], "outputs": [...] } ]
}
```
- `type` is **stack-typed**`aws:<service>:<resource>` (e.g.
`aws:s3:bucket`), **not** the Terraform resource name
(`aws_s3_bucket`). The adapter performs the translation.
- `resources[]` is present only on multi-resource L1s (`vpc`,
`ecs-service`, `alb`); it documents the child stack types but does not
drive resolution.
- **Dropped** per D-014: `nfrs` (confidence signal, out of scope) and
`intra_refs` (wire engine, eliminated by D-012). Do not re-add them.
## Conventions shared by all L1 Terraform adapters
- `terraform/versions.tf` pins `required_version = ">= 1.9, < 1.10"` and
`aws ~> 5.0`.
- Every resource is guarded by `count = var.enabled ? 1 : 0`; the
`enabled` input defaults to `true`.
- `locals.tf` merges module-default tags with caller-supplied `var.tags`:
```hcl
tags = merge({ "nova:owner" = "nova", "nova:environment" = "dev" }, var.tags)
```
- Every `interface.json` input has a matching `variable` block; every
output has a matching `output` block. Outputs return `null` (or `[]`)
when `enabled = false`.
## How to add a module
1. Pick the tier. New primitive → L1. New pattern composing existing
L1s → L2.
2. Create `modules/l1/<name>/` (or `modules/l2/<name>/`).
3. Author `interface.json` (L1) or `interface.json` + L2 terraform that
composes L1s via `module` blocks. Use `modules/README-TEMPLATE.md`
as the per-module doc template.
4. Author `terraform/{main,variables,outputs,versions,locals}.tf`
following the conventions above.
5. Add an entry to `modules/registry.json` and a row to the catalog at
`docs/modules/index.md`.
6. Verify: `python3 -c "import json; json.load(open('modules/l1/<name>/interface.json'))"`
and `terraform validate` inside `terraform/`.
## L1 primitives (13)
| Module | Stack type | Multi-resource? | Description |
|-----------------|-------------------------------|-----------------|----------------------------------------------------------|
| `s3` | `aws:s3:bucket` | no | S3 bucket with versioning + SSE-KMS |
| `vpc` | `aws:ec2:vpc` | yes | VPC + subnets + route table + IGW |
| `ecs-cluster` | `aws:ecs:cluster` | no | ECS cluster |
| `ecs-service` | `aws:ecs:service` | yes | ECS task definition + service |
| `iam-role` | `aws:iam:role` | no | IAM role with assume-role policy |
| `alb` | `aws:alb` | yes | ALB + target group + listener |
| `ecr` | `aws:ecr:repository` | no | ECR repository with scan-on-push |
| `cloudfront` | `aws:cloudfront:distribution` | no | CloudFront distribution with a single origin |
| `waf` | `aws:waf:web_acl` | no | WAFv2 web ACL (regional, default allow) |
| `rds` | `aws:rds:instance` | no | RDS Postgres DB instance |
| `kms-key` | `aws:kms:key` | no | KMS CMK with alias |
| `dynamodb` | `aws:dynamodb:table` | no | DynamoDB table (PAY_PER_REQUEST default) |
| `uptime` | `aws:uptime:monitor` | no | Uptime monitor (CloudWatch alarm stand-in) |
## L2 compositions (2)
| Module | Composes | Description |
|------------------|-------------------------------------------|----------------------------------------------|
| `microservice` | vpc + ecs-cluster + ecs-service + iam-role + ecr + alb | Container microservice with public ALB (D-038) |
| `static-assets` | s3 + cloudfront + kms-key | Static site fronted by CloudFront (D-038) |
+3
View File
@@ -0,0 +1,3 @@
# L1: alb
Application Load Balancer primitive (multi-resource: LB + target group + listener; stack type `aws:alb`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+68
View File
@@ -0,0 +1,68 @@
{
"name": "alb",
"version": "1.0.0",
"kind": "l1",
"type": "aws:alb",
"description": "Application Load Balancer primitive (multi-resource: LB + target group + listener). Engine-agnostic stack types aws:alb + aws:alb:targetgroup + aws:alb:listener; the Terraform adapter translates to aws_lb/aws_lb_target_group/aws_lb_listener.",
"inputs": {
"lb_name": {
"type": "string",
"description": "Name of the load balancer.",
"required": true
},
"subnet_ids": {
"type": "list",
"description": "List of subnet ids the LB is deployed into.",
"required": true
},
"target_group_port": {
"type": "integer",
"default": 80,
"description": "Port the target group forwards to."
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"lb_arn": {
"type": "arn",
"description": "The load balancer ARN."
},
"dns_name": {
"type": "string",
"description": "The load balancer DNS name."
},
"target_group_arn": {
"type": "arn",
"description": "The target group ARN."
}
},
"resources": [
{
"type": "aws:alb",
"description": "The Application Load Balancer.",
"inputs": ["lb_name", "subnet_ids"],
"outputs": ["lb_arn", "dns_name"]
},
{
"type": "aws:alb:targetgroup",
"description": "Target group on the LB port.",
"inputs": ["lb_name", "target_group_port"],
"outputs": ["target_group_arn"]
},
{
"type": "aws:alb:listener",
"description": "Listener forwarding to the target group.",
"inputs": ["target_group_port", "target_group_arn"],
"outputs": []
}
]
}
+16
View File
@@ -0,0 +1,16 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
# Target group requires a vpc_id. The L1 interface does not expose it as
# an input by design (kept minimal per D-014); the caller is expected to
# supply subnets in a single VPC. When a vpc_id input is added later, this
# local can be removed. For now, null forces the caller to set it via a
# provider-level default or an extension.
vpc_id = null
}
+36
View File
@@ -0,0 +1,36 @@
resource "aws_lb" "this" {
count = var.enabled ? 1 : 0
name = var.lb_name
load_balancer_type = "application"
subnets = var.subnet_ids
tags = local.tags
}
resource "aws_lb_target_group" "this" {
count = var.enabled ? 1 : 0
name_prefix = "${var.lb_name}-"
port = var.target_group_port
protocol = "HTTP"
target_type = "ip"
vpc_id = local.vpc_id
lifecycle {
create_before_destroy = true
}
tags = local.tags
}
resource "aws_lb_listener" "this" {
count = var.enabled ? 1 : 0
load_balancer_arn = aws_lb.this[0].id
port = var.target_group_port
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.this[0].arn
}
depends_on = [aws_lb_target_group.this]
}
+14
View File
@@ -0,0 +1,14 @@
output "lb_arn" {
value = var.enabled ? aws_lb.this[0].arn : null
description = "The load balancer ARN."
}
output "dns_name" {
value = var.enabled ? aws_lb.this[0].dns_name : null
description = "The load balancer DNS name."
}
output "target_group_arn" {
value = var.enabled ? aws_lb_target_group.this[0].arn : null
description = "The target group ARN."
}
+27
View File
@@ -0,0 +1,27 @@
variable "lb_name" {
type = string
description = "Name of the load balancer."
}
variable "subnet_ids" {
type = list(string)
description = "List of subnet ids the LB is deployed into."
}
variable "target_group_port" {
type = number
description = "Port the target group forwards to."
default = 80
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: cloudfront
CloudFront distribution primitive (stack type `aws:cloudfront:distribution`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+39
View File
@@ -0,0 +1,39 @@
{
"name": "cloudfront",
"version": "1.0.0",
"kind": "l1",
"type": "aws:cloudfront:distribution",
"description": "CloudFront distribution primitive (engine-agnostic stack type aws:cloudfront:distribution; the Terraform adapter translates to aws_cloudfront_distribution).",
"inputs": {
"distribution_name": {
"type": "string",
"description": "Name (comment) of the CloudFront distribution.",
"required": true
},
"origin_domain": {
"type": "string",
"description": "Domain name of the origin (e.g. an S3 bucket regional domain or ALB DNS).",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"distribution_arn": {
"type": "arn",
"description": "The CloudFront distribution ARN."
},
"domain_name": {
"type": "string",
"description": "The CloudFront distribution domain name."
}
}
}
+11
View File
@@ -0,0 +1,11 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
origin_id = "${var.distribution_name}-origin"
}
+41
View File
@@ -0,0 +1,41 @@
resource "aws_cloudfront_distribution" "this" {
count = var.enabled ? 1 : 0
comment = var.distribution_name
enabled = true
price_class = "PriceClass_100"
tags = local.tags
origin {
domain_name = var.origin_domain
origin_id = local.origin_id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.origin_id
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
@@ -0,0 +1,9 @@
output "distribution_arn" {
value = var.enabled ? aws_cloudfront_distribution.this[0].arn : null
description = "The CloudFront distribution ARN."
}
output "domain_name" {
value = var.enabled ? aws_cloudfront_distribution.this[0].domain_name : null
description = "The CloudFront distribution domain name."
}
@@ -0,0 +1,21 @@
variable "distribution_name" {
type = string
description = "Name (comment) of the CloudFront distribution."
}
variable "origin_domain" {
type = string
description = "Domain name of the origin (e.g. an S3 bucket regional domain or ALB DNS)."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: dynamodb
DynamoDB table primitive (stack type `aws:dynamodb:table`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+44
View File
@@ -0,0 +1,44 @@
{
"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).",
"inputs": {
"table_name": {
"type": "string",
"description": "Name of the DynamoDB table.",
"required": true
},
"hash_key": {
"type": "string",
"description": "Name of the partition (hash) key.",
"required": true
},
"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. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"table_arn": {
"type": "arn",
"description": "The DynamoDB table ARN."
},
"table_name": {
"type": "string",
"description": "The DynamoDB table name (echoes the input)."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+12
View File
@@ -0,0 +1,12 @@
resource "aws_dynamodb_table" "this" {
count = var.enabled ? 1 : 0
name = var.table_name
billing_mode = var.billing_mode
hash_key = var.hash_key
tags = local.tags
attribute {
name = var.hash_key
type = "S"
}
}
+9
View File
@@ -0,0 +1,9 @@
output "table_arn" {
value = var.enabled ? aws_dynamodb_table.this[0].arn : null
description = "The DynamoDB table ARN."
}
output "table_name" {
value = var.enabled ? aws_dynamodb_table.this[0].name : null
description = "The DynamoDB table name (echoes the input)."
}
@@ -0,0 +1,27 @@
variable "table_name" {
type = string
description = "Name of the DynamoDB table."
}
variable "hash_key" {
type = string
description = "Name of the partition (hash) key."
}
variable "billing_mode" {
type = string
description = "Billing mode: PAY_PER_REQUEST or PROVISIONED."
default = "PAY_PER_REQUEST"
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: ecr
ECR repository primitive (stack type `aws:ecr:repository`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "ecr",
"version": "1.0.0",
"kind": "l1",
"type": "aws:ecr:repository",
"description": "ECR repository primitive (engine-agnostic stack type aws:ecr:repository; the Terraform adapter translates to aws_ecr_repository).",
"inputs": {
"repository_name": {
"type": "string",
"description": "Name of the ECR repository.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"repository_url": {
"type": "string",
"description": "The ECR repository URL."
},
"repository_arn": {
"type": "arn",
"description": "The ECR repository ARN."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+10
View File
@@ -0,0 +1,10 @@
resource "aws_ecr_repository" "this" {
count = var.enabled ? 1 : 0
name = var.repository_name
image_tag_mutability = "MUTABLE"
tags = local.tags
image_scanning_configuration {
scan_on_push = true
}
}
+9
View File
@@ -0,0 +1,9 @@
output "repository_url" {
value = var.enabled ? aws_ecr_repository.this[0].repository_url : null
description = "The ECR repository URL."
}
output "repository_arn" {
value = var.enabled ? aws_ecr_repository.this[0].arn : null
description = "The ECR repository ARN."
}
+16
View File
@@ -0,0 +1,16 @@
variable "repository_name" {
type = string
description = "Name of the ECR repository."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: ecs-cluster
ECS cluster primitive (stack type `aws:ecs:cluster`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "ecs-cluster",
"version": "1.0.0",
"kind": "l1",
"type": "aws:ecs:cluster",
"description": "ECS cluster primitive (engine-agnostic stack type aws:ecs:cluster; the Terraform adapter translates to aws_ecs_cluster).",
"inputs": {
"cluster_name": {
"type": "string",
"description": "Name of the ECS cluster.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"cluster_arn": {
"type": "arn",
"description": "The ECS cluster ARN."
},
"cluster_name": {
"type": "string",
"description": "The ECS cluster name (echoes the input)."
}
}
}
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+5
View File
@@ -0,0 +1,5 @@
resource "aws_ecs_cluster" "this" {
count = var.enabled ? 1 : 0
name = var.cluster_name
tags = local.tags
}
@@ -0,0 +1,9 @@
output "cluster_arn" {
value = var.enabled ? aws_ecs_cluster.this[0].arn : null
description = "The ECS cluster ARN."
}
output "cluster_name" {
value = var.enabled ? aws_ecs_cluster.this[0].name : null
description = "The ECS cluster name (echoes the input)."
}
@@ -0,0 +1,16 @@
variable "cluster_name" {
type = string
description = "Name of the ECS cluster."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: ecs-service
ECS service primitive (multi-resource: task definition + service; stack type `aws:ecs:service`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+63
View File
@@ -0,0 +1,63 @@
{
"name": "ecs-service",
"version": "1.0.0",
"kind": "l1",
"type": "aws:ecs:service",
"description": "ECS service primitive (multi-resource: task definition + service). Engine-agnostic stack types aws:ecs:taskdef + aws:ecs:service; the Terraform adapter translates to aws_ecs_task_definition/aws_ecs_service.",
"inputs": {
"service_name": {
"type": "string",
"description": "Name of the ECS service (also used as the task definition family).",
"required": true
},
"cluster_arn": {
"type": "arn",
"description": "ARN of the ECS cluster the service runs in.",
"required": true
},
"task_definition": {
"type": "string",
"description": "Task definition ARN or family:revision to run. If supplied as a path/string JSON, the module creates an aws_ecs_task_definition.",
"required": true
},
"desired_count": {
"type": "integer",
"default": 1,
"description": "Number of tasks to run."
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"service_arn": {
"type": "arn",
"description": "The ECS service ARN."
},
"service_name": {
"type": "string",
"description": "The ECS service name (echoes the input)."
}
},
"resources": [
{
"type": "aws:ecs:taskdef",
"description": "The ECS task definition (registered from task_definition input).",
"inputs": ["service_name", "task_definition"],
"outputs": []
},
{
"type": "aws:ecs:service",
"description": "The ECS service running the task definition on the cluster.",
"inputs": ["service_name", "cluster_arn", "desired_count"],
"outputs": ["service_arn", "service_name"]
}
]
}
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+15
View File
@@ -0,0 +1,15 @@
resource "aws_ecs_task_definition" "this" {
count = var.enabled ? 1 : 0
family = var.service_name
container_definitions = var.task_definition
tags = local.tags
}
resource "aws_ecs_service" "this" {
count = var.enabled ? 1 : 0
name = var.service_name
cluster = var.cluster_arn
task_definition = aws_ecs_task_definition.this[0].arn
desired_count = var.desired_count
tags = local.tags
}
@@ -0,0 +1,9 @@
output "service_arn" {
value = var.enabled ? aws_ecs_service.this[0].id : null
description = "The ECS service ARN."
}
output "service_name" {
value = var.enabled ? aws_ecs_service.this[0].name : null
description = "The ECS service name (echoes the input)."
}
@@ -0,0 +1,32 @@
variable "service_name" {
type = string
description = "Name of the ECS service (also used as the task definition family)."
}
variable "cluster_arn" {
type = string
description = "ARN of the ECS cluster the service runs in."
}
variable "task_definition" {
type = string
description = "Task definition JSON string (container definitions). The module registers an aws_ecs_task_definition with family = service_name."
}
variable "desired_count" {
type = number
description = "Number of tasks to run."
default = 1
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: iam-role
IAM role primitive (stack type `aws:iam:role`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+39
View File
@@ -0,0 +1,39 @@
{
"name": "iam-role",
"version": "1.0.0",
"kind": "l1",
"type": "aws:iam:role",
"description": "IAM role primitive (engine-agnostic stack type aws:iam:role; the Terraform adapter translates to aws_iam_role).",
"inputs": {
"role_name": {
"type": "string",
"description": "Name of the IAM role.",
"required": true
},
"policy_document": {
"type": "string",
"description": "Assume-role policy document JSON string.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"role_arn": {
"type": "arn",
"description": "The IAM role ARN."
},
"role_name": {
"type": "string",
"description": "The IAM role name (echoes the input)."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+6
View File
@@ -0,0 +1,6 @@
resource "aws_iam_role" "this" {
count = var.enabled ? 1 : 0
name = var.role_name
assume_role_policy = var.policy_document
tags = local.tags
}
+9
View File
@@ -0,0 +1,9 @@
output "role_arn" {
value = var.enabled ? aws_iam_role.this[0].arn : null
description = "The IAM role ARN."
}
output "role_name" {
value = var.enabled ? aws_iam_role.this[0].name : null
description = "The IAM role name (echoes the input)."
}
@@ -0,0 +1,21 @@
variable "role_name" {
type = string
description = "Name of the IAM role."
}
variable "policy_document" {
type = string
description = "Assume-role policy document JSON string."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: kms-key
KMS customer master key primitive (stack type `aws:kms:key`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "kms-key",
"version": "1.0.0",
"kind": "l1",
"type": "aws:kms:key",
"description": "KMS customer master key primitive (engine-agnostic stack type aws:kms:key; the Terraform adapter translates to aws_kms_key).",
"inputs": {
"key_name": {
"type": "string",
"description": "Name (alias) of the KMS key.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"key_arn": {
"type": "arn",
"description": "The KMS key ARN."
},
"key_id": {
"type": "string",
"description": "The KMS key id."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+12
View File
@@ -0,0 +1,12 @@
resource "aws_kms_key" "this" {
count = var.enabled ? 1 : 0
description = "KMS key managed by nova L1 kms-key primitive."
deletion_window_in_days = 30
tags = local.tags
}
resource "aws_kms_alias" "this" {
count = var.enabled ? 1 : 0
name = "alias/${var.key_name}"
target_key_id = aws_kms_key.this[0].key_id
}
+9
View File
@@ -0,0 +1,9 @@
output "key_arn" {
value = var.enabled ? aws_kms_key.this[0].arn : null
description = "The KMS key ARN."
}
output "key_id" {
value = var.enabled ? aws_kms_key.this[0].key_id : null
description = "The KMS key id."
}
+16
View File
@@ -0,0 +1,16 @@
variable "key_name" {
type = string
description = "Name (alias) of the KMS key."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: rds
RDS DB instance primitive (stack type `aws:rds:instance`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+44
View File
@@ -0,0 +1,44 @@
{
"name": "rds",
"version": "1.0.0",
"kind": "l1",
"type": "aws:rds:instance",
"description": "RDS DB instance primitive (engine-agnostic stack type aws:rds:instance; the Terraform adapter translates to aws_db_instance).",
"inputs": {
"instance_name": {
"type": "string",
"description": "Name (identifier) of the RDS DB instance.",
"required": true
},
"instance_class": {
"type": "string",
"default": "db.t3.micro",
"description": "DB instance class."
},
"allocated_storage": {
"type": "integer",
"default": 20,
"description": "Allocated storage in GiB."
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"instance_endpoint": {
"type": "string",
"description": "The RDS DB instance endpoint (host:port)."
},
"instance_arn": {
"type": "arn",
"description": "The RDS DB instance ARN."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+12
View File
@@ -0,0 +1,12 @@
resource "aws_db_instance" "this" {
count = var.enabled ? 1 : 0
identifier = var.instance_name
instance_class = var.instance_class
allocated_storage = var.allocated_storage
engine = "postgres"
engine_version = "14"
username = "nova"
password = "changeme-rotate-me"
skip_final_snapshot = true
tags = local.tags
}
+9
View File
@@ -0,0 +1,9 @@
output "instance_endpoint" {
value = var.enabled ? aws_db_instance.this[0].endpoint : null
description = "The RDS DB instance endpoint (host:port)."
}
output "instance_arn" {
value = var.enabled ? aws_db_instance.this[0].arn : null
description = "The RDS DB instance ARN."
}
+28
View File
@@ -0,0 +1,28 @@
variable "instance_name" {
type = string
description = "Name (identifier) of the RDS DB instance."
}
variable "instance_class" {
type = string
description = "DB instance class."
default = "db.t3.micro"
}
variable "allocated_storage" {
type = number
description = "Allocated storage in GiB."
default = 20
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: s3
S3 bucket primitive (stack type `aws:s3:bucket`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+48
View File
@@ -0,0 +1,48 @@
{
"name": "s3",
"version": "1.0.0",
"kind": "l1",
"type": "aws:s3:bucket",
"description": "S3 bucket primitive (engine-agnostic stack type aws:s3:bucket; the Terraform adapter translates to aws_s3_bucket).",
"inputs": {
"bucket_name": {
"type": "string",
"description": "Globally-unique S3 bucket name.",
"required": true
},
"region": {
"type": "string",
"description": "AWS region the bucket is created in (provider-level; not a resource arg).",
"required": true
},
"kms_key_arn": {
"type": "string",
"description": "ARN of the CMK for SSE-KMS; if absent, uses managed key (SSE-S3).",
"required": false
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"bucket_arn": {
"type": "arn",
"description": "The S3 bucket ARN."
},
"bucket_name": {
"type": "string",
"description": "The bucket name (echoes the input)."
},
"bucket_regional_domain_name": {
"type": "string",
"description": "The bucket regional domain name (e.g. nova-bucket.s3.us-east-1.amazonaws.com)."
}
}
}
+13
View File
@@ -0,0 +1,13 @@
locals {
# SSE algorithm: KMS when a CMK ARN is supplied, else AES256 (SSE-S3).
sse_algorithm = var.kms_key_arn != null ? "aws:kms" : "AES256"
# Tags: merge caller-supplied tags with the module defaults.
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+26
View File
@@ -0,0 +1,26 @@
resource "aws_s3_bucket" "this" {
count = var.enabled ? 1 : 0
bucket = var.bucket_name
tags = local.tags
}
resource "aws_s3_bucket_versioning" "this" {
count = var.enabled ? 1 : 0
bucket = aws_s3_bucket.this[0].id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
count = var.enabled ? 1 : 0
bucket = aws_s3_bucket.this[0].id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = local.sse_algorithm
kms_master_key_id = var.kms_key_arn
}
}
}
+14
View File
@@ -0,0 +1,14 @@
output "bucket_arn" {
value = var.enabled ? aws_s3_bucket.this[0].arn : null
description = "The S3 bucket ARN."
}
output "bucket_name" {
value = var.enabled ? aws_s3_bucket.this[0].id : null
description = "The bucket name (echoes the input)."
}
output "bucket_regional_domain_name" {
value = var.enabled ? aws_s3_bucket.this[0].bucket_regional_domain_name : null
description = "The bucket regional domain name (e.g. nova-bucket.s3.us-east-1.amazonaws.com)."
}
+28
View File
@@ -0,0 +1,28 @@
variable "bucket_name" {
type = string
description = "Globally-unique S3 bucket name."
}
variable "region" {
type = string
description = "AWS region the bucket is created in (provider-level; not a resource arg)."
default = null
}
variable "kms_key_arn" {
type = string
description = "ARN of the CMK for SSE-KMS; if absent, uses managed key (SSE-S3)."
default = null
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: uptime
Uptime monitor primitive (simplified stand-in: a CloudWatch alarm; stack type `aws:uptime:monitor`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+35
View File
@@ -0,0 +1,35 @@
{
"name": "uptime",
"version": "1.0.0",
"kind": "l1",
"type": "aws:uptime:monitor",
"description": "Uptime monitor primitive (simplified stand-in: a CloudWatch alarm watching the target resource). Engine-agnostic stack type aws:uptime:monitor; the Terraform adapter translates to aws_cloudwatch_metric_alarm.",
"inputs": {
"monitor_name": {
"type": "string",
"description": "Name of the uptime monitor (CloudWatch alarm).",
"required": true
},
"target_arn": {
"type": "arn",
"description": "ARN of the target resource being monitored.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"monitor_arn": {
"type": "arn",
"description": "The CloudWatch alarm ARN (stand-in for the monitor ARN)."
}
}
}

Some files were not shown because too many files have changed in this diff Show More