Files
nova-platform/.ciagent/RESEARCH.md
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

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. Title Nova 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. patternProperties keyed by ^[a-z][a-z0-9-]*$ (module name). Each entry is an object required: ["inputs"] with version (optional, semver ^\d+\.\d+\.\d+$) and inputs (object, additionalProperties allowing string/number/boolean/object/array), additionalProperties: false on 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 $id host, same title pattern.
  • Same id pattern, same name minLength, same environment enum.
  • KEY DEVIATION: infrastructure is an ARRAY (per REQ-01 + D-015), not the reference's object map. type: array, minItems: 1. Each item is an object required: ["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: false allowing primitives/objects/arrays).
  • No aws_* or engine terms in the schema. Validated by tests/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: object required: ["name", "kind", "depth"].
  • resources: array of $defs/resource — each required: ["id", "type", "module", "inputs"]. id pattern ^[a-z][a-z0-9-]*$. type is stack-typed (aws:s3:bucket, NOT aws_s3_bucket). module is name@semver. Optional parent, outputs, nfrs.
  • Optional relationships array. 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} where source is a Terraform module path (engine terms appear HERE only — the stack is the resolved form passed to the adapter, NOT the contract).
  • NO stack wrapper, NO relationships, NO nfrs, NO data_sources, NO outputs map. 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 (placeholder 000000000000 allowed). state_backend: {bucket, lock_table}. network: {vpc_cidr, azs}. runner_role_arn, autonomy enum, confidence_threshold 0-1.
  • additionalProperties: false on 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 description optional, account_id 12-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 via environment_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 raises ValueError.
  • _latest_version(registry, module_name). _resolve_l1(...) builds resource from interface.json. _resolve_l2(...) loads composition.json, expands children/wires/data_sources.
  • Exceptions: generic ValueError strings (no custom classes). CLI delegates to contract_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_vars preserved verbatim (engine-agnostic). Unknown token raises ValueError.
  • Named exceptions per REQ-03: ModuleNotFoundError (unknown module), VersionNotFoundError (unknown version) — REPLACE the reference's generic ValueError strings.
  • L2 = opaque per D-012: NO _resolve_l2 composition 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 — raises FileNotFoundError. 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. Raises EnvironmentNotFoundError on missing env.
  • DROP _onboarding_message, contract_path param, main() CLI. load() folded into check() 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-exports adapt). NO policy/.
  • 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. L2 source = modules/l2/<name>/terraform (D-013). ref: translation + multi-resource dedup SIMPLIFIED (L2 opaque, flat stack has no refs).
  • region skip + provider-level region pattern preserved. terraform.tf + providers.tf emitted by small helper or run_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"}NO terraform_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's source field).
  • L2 interface points at modules/l2/<name>/interface.json (NOT composition.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?}. type stack-typed (aws:s3:bucket). inputs/outputs keyed 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.json per D-014: KEEP name, version, kind, type, description, inputs, outputs, resources (multi-resource array for vpc/ecs-service/alb). DROP nfrs (confidence signal OOS) and DROP intra_refs (wire engine eliminated by D-012).
  • terraform/ shape preserved (5 files, same HCL conventions). All 13 L1 primitives authored.
  • Per D-035: L1 s3 stays reference interface (bucket_name/region/kms_key_arn/enabled) — does NOT gain index_document.

9. modules/l2/microservice/ — L2 pattern

Reference shape

  • composition.json: {name, version, kind:"l2", depth, children[], data_sources[], wires[], outputs[]}. ~24 wires. NO terraform/ directory (resolver expands; adapter emits per-L1 blocks).

Nova-platform adaptation (REQ-12 + D-012 + D-013)

  • interface.json replaces composition.json per D-013. Content: {name, version, kind:"l2", description, inputs, outputs} — L2-level only, NO children/wires.
  • terraform/main.tf ADDED per D-012: composes L1 internally via module blocks. Resolver treats L2 as single resource; adapter emits one module "microservice" { source = "modules/l2/microservice/terraform" } block. L2's main.tf instantiates module "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 unset NOVA_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. --quiet suppresses banners.

12. terraform/bootstrap/ — state backend + IAM user scripts

Reference shape

  • create_state_backend.py: S3 nova-tfstate-<account>-us-east-1 + DynamoDB nova-outbox. Idempotent. NOVA_BOOTSTRAP_AWS_* (fallback NOVA_AWS_*).
  • create_iam_user.py: IAM user nova-spike-runner + inline policy + key. Prints NOVA_AWS_*.
  • spike_runner_policy.json: hardcoded account 581513795199.

Nova-platform adaptation (REQ-14 + REQ-15 + D-022 + D-026)

  • create_state_backend.py per D-022: S3 nova-tfstate-<account>-<region> + DynamoDB nova-tfstate-locks (NOT nova-outbox). Idempotent.
  • create_iam_user.py per D-026: user nova-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. Outputs vpc_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 key spike/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>-deploy with OIDC trust. Inline policy lambda:InvokeFunctionUrl (ABAC). Outputs consumer_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_id vars for tagging. Inline policy: Terraform-deployable permissions scoped via tags. DROP lambda: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.tf composes L1 via module blocks. terraform/microservice/main.tf instantiates the L2 module + wires to platform VPC via data.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. DROP stack_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 .py files only (core/schemas/contracts/tests/scripts/root), exclude adapters/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. addopts writes to metrics/.

Nova-platform adaptation (REQ-31 + D-027)

  • No CLI package per D-027. [project] name = "nova-platform". NO [project.scripts]. NO nova/ 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] includes core, core.*, adapters.* (NO nova).

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.md per D-029.

22. docs/ — documentation shapes

Reference shape

  • docs/modules/index.md (catalog → links to modules/*/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 to modules/*/README.md (per-module docs live in modules/, not docs/modules/).
  • docs/contracts/index.md (REQ-35): array-based infrastructure schema + 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.json entry shape, schemas, shell scripts, bootstrap scripts.
  • Module interface shape: {name, version, kind, type, description, inputs, outputs} + resources[] for multi-resource L1s. Stack-typed type (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 (not composition.json) + terraform_dir in registry.
  • D-015: infrastructure is an ARRAY (not object map); version optional.
  • D-017/D-018: environment schema simplified (drops runner_role_arn/autonomy/confidence_threshold).
  • D-019: check() returns dict, raises EnvironmentNotFoundError (not tuple).
  • D-022: lock table nova-tfstate-locks (not nova-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.