---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).
22 KiB
RESEARCH — Nova Platform v1.0
Phase 0 RESEARCH artifact. Derived from structural analysis of the Nova reference at
/home/opencode/acdl/. Each section documents the reference's exact shape so execution phases can mirror it, then notes the nova-platform adaptation referencing the locked decisions (D-001..D-035 in.ciagent/CLARIFY.md+.ciagent/PROJECT.md).Scope rule: security/audit/identity/CI-workflow machinery is OUT OF SCOPE. Files related to those subsystems were NOT read. This document covers only the in-scope infrastructure-delivery core.
1. schemas/contract.schema.json — contract envelope
Reference shape
- JSON Schema draft 2020-12.
$id: https://nova.cloudinit.dev/schemas/contract.schema.json. TitleNova Consumer Contract. - Top-level
type: object,required: ["id", "name", "environment", "infrastructure"],additionalProperties: false. id:type: string,pattern: ^[a-z][a-z0-9-]{2,5}$(3-6 char operational acronym).name:type: string,minLength: 3(human-readable).environment:type: string,enum: [dev, qa, prod, dr].infrastructure: OBJECT (map),minProperties: 1,additionalProperties: false.patternPropertieskeyed by^[a-z][a-z0-9-]*$(module name). Each entry is an objectrequired: ["inputs"]withversion(optional, semver^\d+\.\d+\.\d+$) andinputs(object,additionalPropertiesallowing string/number/boolean/object/array),additionalProperties: falseon the entry.- The contract is the ONLY consumer surface. Engine-agnostic: no
aws_*terms in the schema keywords.
Nova-platform adaptation (REQ-01 + D-015)
- Same draft 2020-12, same
$idhost, same title pattern. - Same
idpattern, samenameminLength, sameenvironmentenum. - KEY DEVIATION:
infrastructureis an ARRAY (per REQ-01 + D-015), not the reference's object map.type: array,minItems: 1. Each item is an objectrequired: ["module", "inputs"](NOT keyed by module name — the module name is a field). Fields per item:module(string, required),version(string, optional, semver pattern — defaults to latest non-deprecated per D-015),inputs(object, required,additionalProperties: falseallowing primitives/objects/arrays). - No
aws_*or engine terms in the schema. Validated bytests/test_contract_schema.py(REQ-27).
2. schemas/stack.schema.json — resolved stack shape
Reference shape
- Draft 2020-12. Title
Nova Target Stack.required: ["version", "stack", "resources"]. version: semver string.stack: objectrequired: ["name", "kind", "depth"].resources: array of$defs/resource— eachrequired: ["id", "type", "module", "inputs"].idpattern^[a-z][a-z0-9-]*$.typeis stack-typed (aws:s3:bucket, NOTaws_s3_bucket).moduleisname@semver. Optionalparent,outputs,nfrs.- Optional
relationshipsarray. Schema body is engine-agnostic.
Nova-platform adaptation (REQ-04 + D-012)
- FLAT shape per D-012.
required: ["contract_id", "contract_name", "environment", "resources"]. contract_id,contract_name(strings),environment(string enum).resources: array of{module, version, source, inputs}wheresourceis a Terraform module path (engine terms appear HERE only — the stack is the resolved form passed to the adapter, NOT the contract).- NO
stackwrapper, NOrelationships, NOnfrs, NOdata_sources, NOoutputsmap. L2 is opaque per D-012.
3. schemas/environment.schema.json — environment record
Reference shape
- Draft 2020-12.
required: ["name", "account_id", "region", "state_backend", "network", "runner_role_arn", "autonomy", "confidence_threshold"]. account_id: 12-digit pattern (placeholder000000000000allowed).state_backend:{bucket, lock_table}.network:{vpc_cidr, azs}.runner_role_arn,autonomyenum,confidence_threshold0-1.additionalProperties: falseon top level.
Nova-platform adaptation (D-017 + D-018)
- KEEP the schema (D-017) but simplify the required field set per D-018.
required: ["name", "account_id", "region", "state_backend", "network"].- DROP
runner_role_arn(ABAC OOS),autonomy(HITL OOS),confidence_threshold(OOS). - Keep
descriptionoptional,account_id12-digit pattern + placeholder warning,region,state_backend {bucket, lock_table},network {vpc_cidr, azs}.additionalProperties: false.
4. core/contract_resolver.py — resolve() function
Reference shape
resolve(contract_path, repo_root=None, environment_override=None) -> dict. Takes a file path to contract YAML. Loads YAML, loads env viaenvironment_check.load(), builds interpolation context{"env": env, "contract": contract}. Expands${env.*}/${contract.*}AFTER schema validation, BEFORE IR resolution._TOKEN_RE = re.compile(r"\$\{([a-zA-Z_][a-zA-Z0-9_.]*)\}")._lookup_dotted(context, dotted)._expand_vars(value, context)recurses; unknown token raisesValueError._latest_version(registry, module_name)._resolve_l1(...)builds resource from interface.json._resolve_l2(...)loads composition.json, expands children/wires/data_sources.- Exceptions: generic
ValueErrorstrings (no custom classes). CLI delegates tocontract_resolver_cli.main.
Nova-platform adaptation (REQ-03 + D-011 + D-016)
- Signature per D-011:
resolve(contract: dict, registry: dict, modules_dir: Path) -> dict. Takes a validated contract dict (not a path), a registry dict, and a modules_dir Path. "Pure" = no network/side-effects; local file reads for module metadata ARE permitted. - Interpolation KEPT (D-016):
_TOKEN_RE,_lookup_dotted,_expand_varspreserved verbatim (engine-agnostic). Unknown token raisesValueError. - Named exceptions per REQ-03:
ModuleNotFoundError(unknown module),VersionNotFoundError(unknown version) — REPLACE the reference's genericValueErrorstrings. - L2 = opaque per D-012: NO
_resolve_l2composition expansion. L2 is a single stack resource{module, version, source, inputs}. NO children/wires/data_sources. - NO policy evaluation, NO CLI module. Run as script. Returns the flat stack dict per REQ-04.
5. core/environment_check.py — check()/load()
Reference shape
load(env_name, root=None) -> dict— raisesFileNotFoundError.check(contract_path, env_name, root) -> (ok, message)tuple._onboarding_message(env_name)friendly prompt.main(argv)CLI.
Nova-platform adaptation (REQ-05 + D-019)
- Signature per D-019:
check(env_name: str, environments_dir: Path) -> dict. Returns the env dict directly. RaisesEnvironmentNotFoundErroron missing env. - DROP
_onboarding_message,contract_pathparam,main()CLI.load()folded intocheck()or kept as internal helper.
6. adapters/terraform/ — the Terraform adapter
Reference shape
adapters/terraform/adapter.py+policy/(OOS). NO__init__.py.adapt(stack_instance, out_dir)— emits THREE files:main.tf(module blocks + data blocks + root outputs),terraform.tf(required_version + required_providers + s3 backend env-scoped key),providers.tf(provider "aws")._tf_value,_ref_expr,_module_name,_emit_module_block,_emit_root_output. Multi-resource L1 dedup (_child_id).- Statelessness guards: no TYPE_MAP/INPUT_MAP/OUTPUT_MAP, < 250 lines.
Nova-platform adaptation (REQ-07 + REQ-08 + D-013)
- File layout:
adapters/terraform/adapter.py+adapters/terraform/__init__.py(re-exportsadapt). NOpolicy/. - Signature per REQ-07:
adapt(stack: dict, modules_dir: Path) -> str. Returns HCL string (caller writes main.tf). Stateless assembler — no terraform invocation, no state, no plan. - Emits
module "x" { source = ...; <inputs> }per resource. L2source=modules/l2/<name>/terraform(D-013).ref:translation + multi-resource dedup SIMPLIFIED (L2 opaque, flat stack has no refs). regionskip + provider-level region pattern preserved. terraform.tf + providers.tf emitted by small helper orrun_platform.sh.- Engine boundary: ONLY place
aws_*/terraform/module "/provider "/resource "appear (REQ-09).
7. modules/registry.json — module index
Reference shape
- Top-level object keyed by module name → version string → entry. L1:
{interface, terraform_dir, published_at, deprecated, kind:"l1"}. L2:{interface, published_at, deprecated, kind:"l2"}— NOterraform_dir. - 13 L1 + 2 L2 = 15 entries.
Nova-platform adaptation (REQ-10 + D-013)
- Matches reference shape exactly per REQ-10.
- DEVIATION per D-013: L2 entries DO include
terraform_dir: "modules/l2/<name>/terraform"(required by flat stack'ssourcefield). - L2
interfacepoints atmodules/l2/<name>/interface.json(NOTcomposition.json). Same 15 entries, same names.
8. modules/l1/s3/ — representative L1 primitive
Reference shape
interface.json:{name, version, kind:"l1", type, description, inputs, outputs, nfrs, resources?, intra_refs?}.typestack-typed (aws:s3:bucket).inputs/outputskeyed by name →{type, description, required?, default?}.terraform/:main.tf,variables.tf,outputs.tf,versions.tf,locals.tf.count = var.enabled ? 1 : 0.required_version = ">= 1.9, < 1.10".aws = { source = "hashicorp/aws"; version = "~> 5.0" }.README.md(follows README-TEMPLATE.md),instance.json,examples/.
Nova-platform adaptation (REQ-11 + D-014)
interface.jsonper D-014: KEEPname, version, kind, type, description, inputs, outputs, resources(multi-resource array for vpc/ecs-service/alb). DROPnfrs(confidence signal OOS) and DROPintra_refs(wire engine eliminated by D-012).terraform/shape preserved (5 files, same HCL conventions). All 13 L1 primitives authored.- Per D-035: L1
s3stays reference interface (bucket_name/region/kms_key_arn/enabled) — does NOT gainindex_document.
9. modules/l2/microservice/ — L2 pattern
Reference shape
composition.json:{name, version, kind:"l2", depth, children[], data_sources[], wires[], outputs[]}. ~24 wires. NOterraform/directory (resolver expands; adapter emits per-L1 blocks).
Nova-platform adaptation (REQ-12 + D-012 + D-013)
interface.jsonreplacescomposition.jsonper D-013. Content:{name, version, kind:"l2", description, inputs, outputs}— L2-level only, NO children/wires.terraform/main.tfADDED per D-012: composes L1 internally viamoduleblocks. Resolver treats L2 as single resource; adapter emits onemodule "microservice" { source = "modules/l2/microservice/terraform" }block. L2'smain.tfinstantiatesmodule "cluster" { source = "../../l1/ecs-cluster/terraform" }etc.- 2 L2 patterns: microservice (vpc + ecs-cluster + ecs-service + iam-role + ecr + alb), static-assets (s3 + cloudfront + kms-key).
10. scripts/run_platform.sh — platform pipeline orchestrator
Reference shape
set -euo pipefail. Flag parsing:--check-only,--plan-only,--apply,--destroy,--quiet,--deploy-uptime,--decommission,--local,--environment,--help.- Stages: env check → validate contract → resolve → adapter → [check-only: validate output → exit 0] → load AWS creds (NOVA_AWS_* → AWS_*) → terraform init/validate/plan → [plan-only: exit 0] → apply → E2E OK.
- Banners:
=== PLATFORM CHECK OK ===,=== PLATFORM PLAN OK ===,=== PLATFORM APPLY OK ===,=== PLATFORM E2E OK ===.
Nova-platform adaptation (REQ-20 + D-020 + D-031 + D-032)
- Flags per D-031: ONLY
--check-only,--plan-only,--quiet,--help(+-h). Default = apply. DROP all others. - Stages per D-020: check-only (offline): env_check → validate → resolve → adapter → validate output →
=== PLATFORM CHECK OK ===. plan-only: + creds → init/validate/plan →=== PLATFORM PLAN OK ===. default: + apply →=== PLATFORM APPLY OK ===. - AWS creds per D-032:
NOVA_AWS_*prefix →AWS_*copy, then unsetNOVA_AWS_*. - DROP: env_transition, Checkov, kyverno-json, confidence, HITL, outbox, output publisher, uptime, decommission, local emulators.
11. scripts/run_ci.sh — local CI pipeline mirror
Reference shape
- 3 stages: lint (py_compile, hardcoded file list) → test (pytest) → check-only.
=== CI PIPELINE OK ===.
Nova-platform adaptation (REQ-21 + D-021)
- Same 3-stage flow. Stage 1 per D-021: glob
python3 -m py_compile $(find core/ adapters/ scripts/ -name '*.py')(no hardcoded list — no OOS Python files exist). - Banners preserved.
--quietsuppresses banners.
12. terraform/bootstrap/ — state backend + IAM user scripts
Reference shape
create_state_backend.py: S3nova-tfstate-<account>-us-east-1+ DynamoDBnova-outbox. Idempotent.NOVA_BOOTSTRAP_AWS_*(fallbackNOVA_AWS_*).create_iam_user.py: IAM usernova-spike-runner+ inline policy + key. PrintsNOVA_AWS_*.spike_runner_policy.json: hardcoded account581513795199.
Nova-platform adaptation (REQ-14 + REQ-15 + D-022 + D-026)
create_state_backend.pyper D-022: S3nova-tfstate-<account>-<region>+ DynamoDBnova-tfstate-locks(NOTnova-outbox). Idempotent.create_iam_user.pyper D-026: usernova-spike-runner+ policy + key. Account ID parameterized (NOT hardcoded).spike_runner_policy.json: account parameterized. Grants S3/DynamoDB lock/ECS/ECR/ELB/IAM/EC2/CloudFront/WAF/KMS. DROP Lambda, Secrets, SNS, CostExplorer, OIDC.
13. terraform/platform/main.tf — platform infrastructure
Reference shape
- 367 lines: KMS, DynamoDB contracts, Secrets, Lambda, SNS, shared VPC, outputs
vpc_id/subnet_ids/ecs_security_group_id.
Nova-platform adaptation (REQ-17 + D-023)
- ONLY shared platform VPC per D-023:
aws_vpc.nova_shared,aws_subnet.nova_shared(count=2), IGW, route table, ECS SG. Outputsvpc_id,subnet_ids,ecs_security_group_id. - DROP Lambda, DynamoDB, KMS, Secrets, SNS, consumer_invoke_policy — ALL OOS.
14. terraform/ci-vpc/main.tf — short-lived test VPC
Reference shape
- Short-lived VPC for module lifecycle testing. VPC + 2 subnets + IGW + route table + ECS SG + ECS cluster. Outputs
vpc_id/subnet_ids/ecs_security_group_id/cluster_arn. State keyspike/ci-vpc/terraform.tfstate.
Nova-platform adaptation (REQ-16 + D-024)
- Preserved per D-024. Short-lived test VPC. Shared platform VPC lives in
terraform/platform/main.tf. Same shape.
15. terraform/onboarding/main.tf — consumer onboarding stack
Reference shape
- IAM role
nova-<consumer_repo>-deploywith OIDC trust. Inline policylambda:InvokeFunctionUrl(ABAC). Outputsconsumer_deploy_role_arn/consumer_deploy_role_name.
Nova-platform adaptation (REQ-19 + D-025)
- IAM ROLE (not user) per D-025 (human override). Trust policy allows platform's runner user to assume it (cross-account assume role,
sts:AssumeRole). NO OIDC. consumer_repo/owner_idvars for tagging. Inline policy: Terraform-deployable permissions scoped via tags. DROPlambda:InvokeFunctionUrl.- Outputs
consumer_deploy_role_arn/consumer_deploy_role_name. README documents dev-only static-key.
16. terraform/microservice/main.tf — sample consumer Terraform root
Reference shape
- 147 lines. Hand-authored root: VPC, subnets, ECS cluster, ECR, IAM role, ALB, listener, task def, ECS service. Companion
terraform.tf+providers.tf.
Nova-platform adaptation (REQ-18)
- Preserved as sample consumer-facing root. Simplified to opaque L2 model (D-012): L2's own
modules/l2/microservice/terraform/main.tfcomposes L1 viamoduleblocks.terraform/microservice/main.tfinstantiates the L2 module + wires to platform VPC viadata.terraform_remote_state. - State key
spike/microservice/<env>/terraform.tfstate(env-scoped).
17. tests/ — test file naming + structure
Reference shape
conftest.py:ROOT+sys.path.insert. Fixtures:repo_root,stack_instance,stack_schema,registry,policy_check_result_schema.test_contract_resolver.py,test_environment_check.py,test_adapter.py(classes for instance, registry, module assembly, ref expr, tf value, statelessness, valid terraform, dedup).
Nova-platform adaptation (REQ-23..REQ-30)
conftest.py:repo_root,stack_schema,registry. DROPstack_instance+policy_check_result_schema.test_contract_resolver.py(REQ-23):resolve(contract, registry, modules_dir)— happy path,ModuleNotFoundError,VersionNotFoundError, empty infrastructure.test_environment_check.py(REQ-24):check(env_name, environments_dir)— existing env,EnvironmentNotFoundError, malformed.test_terraform_adapter.py(REQ-25):adapt(stack, modules_dir)— single/multi resource, input passthrough, HCL validity.test_engine_boundary.py(REQ-26 + D-034): grep.pyfiles only (core/schemas/contracts/tests/scripts/root), excludeadapters/terraform//modules/.tf/.md/.json.test_contract_schema.py(REQ-27),test_stack_schema.py(REQ-28),test_run_platform_check_only.py(REQ-29),test_run_ci.sh(REQ-30).
18. pyproject.toml — pytest config + project metadata
Reference shape
[project] name = "nova",[project.scripts] nova = "nova.cli:main". test deps include pytest-cov, pytest-json-report, hypothesis.addoptswrites tometrics/.
Nova-platform adaptation (REQ-31 + D-027)
- No CLI package per D-027.
[project] name = "nova-platform". NO[project.scripts]. NOnova/dir. - test deps:
pytest>=8.0, moto[dynamodb]>=5.0, jsonschema>=4.20, pyyaml>=6.0, boto3>=1.34. DROP pytest-cov, pytest-json-report, hypothesis. addopts = "-v --tb=short"(no metrics/).markers = [offline, slow].[tool.setuptools.packages.find]includescore, core.*, adapters.*(NOnova).
19. requirements-test.txt
pytest>=8.0
moto[dynamodb]>=5.0
jsonschema>=4.20
pyyaml>=6.0
boto3>=1.34
(DROP pytest-cov — no coverage reporting.)
20. .gitignore
Already seeded in nova-platform (matches reference minus OOS metrics lines). Contains __pycache__/, .env*, terraform state, credentials, .ciagent/logs/, nova_platform.egg-info/.
21. modules/README.md + modules/README-TEMPLATE.md
Reference shape
README.md(63 lines): Primitives vs Modules, tables, registry, template link.README-TEMPLATE.md(62 lines): Overview/Resources/Inputs/Outputs/NFRs/Usage/Compliance/Versioning.STANDARDS.md(673 lines — security/compliance).
Nova-platform adaptation (REQ-13 + D-029)
modules/README.md: L1/L2 distinction, registry format, how to add a module. Trimmed of security/attestation. 13-row primitives table, 2-row modules table.modules/README-TEMPLATE.md: KEEP. Sections: Overview/Resources/Inputs/Outputs/Usage/Versioning. DROP NFRs + Compliance sections.- DROP
modules/STANDARDS.mdper D-029.
22. docs/ — documentation shapes
Reference shape
docs/modules/index.md(catalog → links tomodules/*/README.md).docs/contracts/index.md(fields table, samples).docs/environments/index.md(env model, autonomy table).docs/consumer-guide.md(513 lines, 9 steps).docs/architecture.md(241 lines, 4 layers + cross-cutting).
Nova-platform adaptation (REQ-32..REQ-37)
docs/modules/index.md(REQ-34 + D-028): catalog table linking tomodules/*/README.md(per-module docs live inmodules/, notdocs/modules/).docs/contracts/index.md(REQ-35): array-basedinfrastructureschema + samples + per-env variants (D-033).docs/environments/index.md(REQ-36): env model (account/network/state backend — NO IAM role/ABAC). Autonomy table simplified (dev autonomous; qa/prod/dr manual operator — NO HITL gates).docs/consumer-guide.md(REQ-37): infra-only. Steps: create repo, write contract, run check-only, run against AWS. Keep interpolation table. DROP OIDC/reusable-workflow/decommission/compliance.docs/architecture.md(REQ-33): mirrors.ciagent/ARCHITECTURE.md. Four layers + engine boundary + OOS list + module catalog. NO cross-cutting concerns sections.
Cross-cutting observations
Engine-agnostic invariant
schemas/contract.schema.json + schemas/stack.schema.json + core/contract_resolver.py + core/environment_check.py contain NO aws_* / Terraform terms. ONLY adapters/terraform/ is engine-specific. Verified by tests/test_engine_boundary.py (D-034 — grep .py only).
Structural conventions preserved without deviation
- Directory names:
schemas/,core/(+core/environments/),adapters/terraform/,modules/(l1/,l2/,registry.json),contracts/,scripts/,terraform/(bootstrap/,ci-vpc/,microservice/,onboarding/,platform/),tests/,docs/. - File roles:
interface.json,terraform/main.tf(+ variables/outputs/versions/locals),registry.jsonentry shape, schemas, shell scripts, bootstrap scripts. - Module interface shape:
{name, version, kind, type, description, inputs, outputs}+resources[]for multi-resource L1s. Stack-typedtype(aws:s3:bucket). - Registry entry shape:
{interface, terraform_dir, published_at, deprecated, kind}. - Banner strings:
=== PLATFORM CHECK OK ===,=== PLATFORM PLAN OK ===,=== PLATFORM APPLY OK ===,=== CI PIPELINE OK ===. - State key convention:
spike/<stack_name>/<environment>/terraform.tfstate(env-scoped). - Tag convention:
nova:owner,nova:contract,nova:environment,nova:cost-center.
Key deviations (locked in CLARIFY.md)
- D-012: L2 is opaque (single stack resource, no children/wires expansion). Flat stack.
- D-013: L2 uses
interface.json(notcomposition.json) +terraform_dirin registry. - D-015:
infrastructureis an ARRAY (not object map);versionoptional. - D-017/D-018: environment schema simplified (drops runner_role_arn/autonomy/confidence_threshold).
- D-019:
check()returns dict, raisesEnvironmentNotFoundError(not tuple). - D-022: lock table
nova-tfstate-locks(notnova-outbox). - D-023:
terraform/platform/main.tf= ONLY shared VPC. - D-025: onboarding = IAM role cross-account assume (not OIDC, not user).
- D-027: no CLI package (scripts only).
- D-029: drop
STANDARDS.md.