Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93ae9e4a39 | |||
| d3179fff37 |
+14
-24
@@ -50,22 +50,13 @@ from core import env
|
|||||||
def _load_env(env_name, repo_root):
|
def _load_env(env_name, repo_root):
|
||||||
"""Load the environment onboarding JSON for env_name.
|
"""Load the environment onboarding JSON for env_name.
|
||||||
|
|
||||||
Mirrors core.environment_check.load() but is self-contained so the
|
P7 (REQ-171): delegates to core.environment_check.load() (dedup —
|
||||||
resolver works both as a package import (`from core.contract_resolver
|
the two were verbatim duplicates). The environment_check module is
|
||||||
import resolve`) and as a script (`python3 core/contract_resolver.py`).
|
in the same core/ package, so the import works both as a package
|
||||||
Emits a stderr warning when account_id is the placeholder and env != dev.
|
import and as a script (`python3 core/contract_resolver.py`).
|
||||||
"""
|
"""
|
||||||
env_file = os.path.join(repo_root, "core", "environments", f"{env_name}.json")
|
from core import environment_check
|
||||||
if not os.path.isfile(env_file):
|
return environment_check.load(env_name, root=repo_root)
|
||||||
raise FileNotFoundError(f"no environment file for '{env_name}' at {env_file}")
|
|
||||||
env = _load_json(env_file)
|
|
||||||
if env.get("account_id") == "000000000000" and env_name != "dev":
|
|
||||||
sys.stderr.write(
|
|
||||||
f"WARNING: environment '{env_name}' has the placeholder account_id "
|
|
||||||
f"000000000000 — replace it with the real {env_name} account id "
|
|
||||||
f"before deploying (onboarding scaffold).\n"
|
|
||||||
)
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
def _load_json(path):
|
def _load_json(path):
|
||||||
@@ -534,10 +525,14 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
f"module '{module_name}' version '{version}' not found in registry")
|
f"module '{module_name}' version '{version}' not found in registry")
|
||||||
module_inputs = module_entry.get("inputs", {})
|
module_inputs = module_entry.get("inputs", {})
|
||||||
|
|
||||||
# Determine if L1 or L2
|
# Determine if L1 or L2 — prefer the registry `kind` field (P7,
|
||||||
|
# REQ-171); fall back to the path heuristic for entries that
|
||||||
|
# predate the kind field.
|
||||||
entry = registry[module_name][version]
|
entry = registry[module_name][version]
|
||||||
interface_path = entry["interface"]
|
interface_path = entry["interface"]
|
||||||
is_l2 = "l2" in interface_path or "composition" in interface_path
|
is_l2 = entry.get("kind") == "l2" or (
|
||||||
|
"kind" not in entry and ("l2" in interface_path or "composition" in interface_path)
|
||||||
|
)
|
||||||
|
|
||||||
if is_l2:
|
if is_l2:
|
||||||
fragment = _resolve_l2(module_name, version, module_inputs,
|
fragment = _resolve_l2(module_name, version, module_inputs,
|
||||||
@@ -580,13 +575,8 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
merged_outputs.update(fragment.get("outputs", {}))
|
merged_outputs.update(fragment.get("outputs", {}))
|
||||||
all_resources.extend(fragment["resources"])
|
all_resources.extend(fragment["resources"])
|
||||||
|
|
||||||
# Determine stack kind: L2 if any module is L2 or if multi-module
|
# Determine stack kind: L2 if any module is L2 or if multi-module (P7)
|
||||||
if multi_module:
|
kind = "l2" if (multi_module or any_l2) else "l1"
|
||||||
kind = "l2"
|
|
||||||
elif any_l2:
|
|
||||||
kind = "l2"
|
|
||||||
else:
|
|
||||||
kind = "l1"
|
|
||||||
|
|
||||||
stack_instance = {
|
stack_instance = {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
+28
-14
@@ -4,7 +4,8 @@
|
|||||||
"interface": "modules/l1/s3/interface.json",
|
"interface": "modules/l1/s3/interface.json",
|
||||||
"terraform_dir": "modules/l1/s3/terraform",
|
"terraform_dir": "modules/l1/s3/terraform",
|
||||||
"published_at": "2026-07-21T19:00:00Z",
|
"published_at": "2026-07-21T19:00:00Z",
|
||||||
"deprecated": false
|
"deprecated": false,
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vpc": {
|
"vpc": {
|
||||||
@@ -12,7 +13,8 @@
|
|||||||
"interface": "modules/l1/vpc/interface.json",
|
"interface": "modules/l1/vpc/interface.json",
|
||||||
"published_at": "2026-07-21T21:30:00Z",
|
"published_at": "2026-07-21T21:30:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/vpc/terraform"
|
"terraform_dir": "modules/l1/vpc/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ecs-cluster": {
|
"ecs-cluster": {
|
||||||
@@ -20,7 +22,8 @@
|
|||||||
"interface": "modules/l1/ecs-cluster/interface.json",
|
"interface": "modules/l1/ecs-cluster/interface.json",
|
||||||
"published_at": "2026-07-21T21:30:00Z",
|
"published_at": "2026-07-21T21:30:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/ecs-cluster/terraform"
|
"terraform_dir": "modules/l1/ecs-cluster/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ecs-service": {
|
"ecs-service": {
|
||||||
@@ -28,7 +31,8 @@
|
|||||||
"interface": "modules/l1/ecs-service/interface.json",
|
"interface": "modules/l1/ecs-service/interface.json",
|
||||||
"published_at": "2026-07-21T21:30:00Z",
|
"published_at": "2026-07-21T21:30:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/ecs-service/terraform"
|
"terraform_dir": "modules/l1/ecs-service/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"iam-role": {
|
"iam-role": {
|
||||||
@@ -36,7 +40,8 @@
|
|||||||
"interface": "modules/l1/iam-role/interface.json",
|
"interface": "modules/l1/iam-role/interface.json",
|
||||||
"published_at": "2026-07-21T21:30:00Z",
|
"published_at": "2026-07-21T21:30:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/iam-role/terraform"
|
"terraform_dir": "modules/l1/iam-role/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alb": {
|
"alb": {
|
||||||
@@ -44,7 +49,8 @@
|
|||||||
"interface": "modules/l1/alb/interface.json",
|
"interface": "modules/l1/alb/interface.json",
|
||||||
"published_at": "2026-07-21T21:30:00Z",
|
"published_at": "2026-07-21T21:30:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/alb/terraform"
|
"terraform_dir": "modules/l1/alb/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ecr": {
|
"ecr": {
|
||||||
@@ -52,7 +58,8 @@
|
|||||||
"interface": "modules/l1/ecr/interface.json",
|
"interface": "modules/l1/ecr/interface.json",
|
||||||
"published_at": "2026-07-21T21:30:00Z",
|
"published_at": "2026-07-21T21:30:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/ecr/terraform"
|
"terraform_dir": "modules/l1/ecr/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cloudfront": {
|
"cloudfront": {
|
||||||
@@ -60,7 +67,8 @@
|
|||||||
"interface": "modules/l1/cloudfront/interface.json",
|
"interface": "modules/l1/cloudfront/interface.json",
|
||||||
"published_at": "2026-07-22T19:00:00Z",
|
"published_at": "2026-07-22T19:00:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/cloudfront/terraform"
|
"terraform_dir": "modules/l1/cloudfront/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"waf": {
|
"waf": {
|
||||||
@@ -68,7 +76,8 @@
|
|||||||
"interface": "modules/l1/waf/interface.json",
|
"interface": "modules/l1/waf/interface.json",
|
||||||
"published_at": "2026-07-22T19:00:00Z",
|
"published_at": "2026-07-22T19:00:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/waf/terraform"
|
"terraform_dir": "modules/l1/waf/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rds": {
|
"rds": {
|
||||||
@@ -76,7 +85,8 @@
|
|||||||
"interface": "modules/l1/rds/interface.json",
|
"interface": "modules/l1/rds/interface.json",
|
||||||
"published_at": "2026-07-22T20:00:00Z",
|
"published_at": "2026-07-22T20:00:00Z",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/rds/terraform"
|
"terraform_dir": "modules/l1/rds/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"kms-key": {
|
"kms-key": {
|
||||||
@@ -84,7 +94,8 @@
|
|||||||
"interface": "modules/l1/kms-key/interface.json",
|
"interface": "modules/l1/kms-key/interface.json",
|
||||||
"published_at": "2026-07-22T20:00",
|
"published_at": "2026-07-22T20:00",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/kms-key/terraform"
|
"terraform_dir": "modules/l1/kms-key/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uptime": {
|
"uptime": {
|
||||||
@@ -92,21 +103,24 @@
|
|||||||
"interface": "modules/l1/uptime/interface.json",
|
"interface": "modules/l1/uptime/interface.json",
|
||||||
"published_at": "2026-07-22T21:00",
|
"published_at": "2026-07-22T21:00",
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"terraform_dir": "modules/l1/uptime/terraform"
|
"terraform_dir": "modules/l1/uptime/terraform",
|
||||||
|
"kind": "l1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"static-assets": {
|
"static-assets": {
|
||||||
"1.0.0": {
|
"1.0.0": {
|
||||||
"interface": "modules/l2/static-assets/composition.json",
|
"interface": "modules/l2/static-assets/composition.json",
|
||||||
"published_at": "2026-07-22T15:00:00Z",
|
"published_at": "2026-07-22T15:00:00Z",
|
||||||
"deprecated": false
|
"deprecated": false,
|
||||||
|
"kind": "l2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"microservice": {
|
"microservice": {
|
||||||
"1.0.0": {
|
"1.0.0": {
|
||||||
"interface": "modules/l2/microservice/composition.json",
|
"interface": "modules/l2/microservice/composition.json",
|
||||||
"published_at": "2026-07-22T15:00:00Z",
|
"published_at": "2026-07-22T15:00:00Z",
|
||||||
"deprecated": false
|
"deprecated": false,
|
||||||
|
"kind": "l2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-52
@@ -113,6 +113,38 @@ fi
|
|||||||
|
|
||||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# run_hitl_gate <contract_id> <resolved_env> <context>
|
||||||
|
# REQ-108: for qa/prod/dr, call hitl_gates.attest before apply. Dev skips.
|
||||||
|
# Extracted from the two duplicated inline blocks (P6, REQ-170).
|
||||||
|
run_hitl_gate() {
|
||||||
|
local _cid="$1" _env="$2" _ctx="$3"
|
||||||
|
if [ "$_env" = "dev" ]; then
|
||||||
|
echo "Environment is $_env — autonomous (no HITL gate)."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Environment is $_env — HITL attestation gate required$_ctx."
|
||||||
|
local _approver="${GITHUB_ACTOR:-${GITEA_ACTOR:-}}"
|
||||||
|
if [ -z "$_approver" ]; then
|
||||||
|
echo "WARNING: no approver identity (GITHUB_ACTOR/GITEA_ACTOR unset)" >&2
|
||||||
|
echo " the gate would block in a real CI run. Passing for local." >&2
|
||||||
|
fi
|
||||||
|
python3 -c "
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, '.')
|
||||||
|
from core.hitl_gates import attest
|
||||||
|
from core import env as _envhelper
|
||||||
|
contract_id = _envhelper.get_env('HITL_CONTRACT_ID') or os.environ['NOVA_HITL_CONTRACT_ID']
|
||||||
|
env = _envhelper.get_env('HITL_ENV') or os.environ['NOVA_HITL_ENV']
|
||||||
|
approver = _envhelper.get_env('HITL_APPROVER', '') or 'local-test'
|
||||||
|
ok, reason = attest(contract_id, env, approver)
|
||||||
|
if ok:
|
||||||
|
print(f'HITL PASS: {reason}')
|
||||||
|
else:
|
||||||
|
print(f'HITL BLOCK: {reason}', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
" NOVA_HITL_CONTRACT_ID="$_cid" NOVA_HITL_ENV="$_env" NOVA_HITL_APPROVER="$_approver"
|
||||||
|
}
|
||||||
|
|
||||||
# --local: run the headline E2E against the local emulating tier (D-092).
|
# --local: run the headline E2E against the local emulating tier (D-092).
|
||||||
# No AWS credentials, no Checkov, no DynamoDB. Emulates ECS, outbox, S3
|
# No AWS credentials, no Checkov, no DynamoDB. Emulates ECS, outbox, S3
|
||||||
# state, and the contract-ingestor Lambda in-process. Exits 0 on success.
|
# state, and the contract-ingestor Lambda in-process. Exits 0 on success.
|
||||||
@@ -142,8 +174,8 @@ stream() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
CONTRACT_ID="11111111-1111-1111-1111-111111111111" # spike fixed UUID
|
CONTRACT_ID="${NOVA_CONTRACT_ID:-11111111-1111-1111-1111-111111111111}" # spike UUID (override via NOVA_CONTRACT_ID)
|
||||||
WORK="/tmp/acdl_platform_run_v18"
|
WORK="${NOVA_WORK_DIR:-/tmp/nova_platform_run}"
|
||||||
TF_DIR="$WORK/tf"
|
TF_DIR="$WORK/tf"
|
||||||
rm -rf "$WORK"; mkdir -p "$TF_DIR"
|
rm -rf "$WORK"; mkdir -p "$TF_DIR"
|
||||||
|
|
||||||
@@ -325,31 +357,7 @@ if [ "$APPLY_ONLY" = "1" ]; then
|
|||||||
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
||||||
RESOLVED_ENV="$ENVIRONMENT_OVERRIDE"
|
RESOLVED_ENV="$ENVIRONMENT_OVERRIDE"
|
||||||
fi
|
fi
|
||||||
if [ "$RESOLVED_ENV" != "dev" ]; then
|
run_hitl_gate "$CONTRACT_ID" "$RESOLVED_ENV" " before apply" || { echo "FAIL: HITL attestation gate blocked the apply" >&2; exit 1; }
|
||||||
echo "Environment is $RESOLVED_ENV — HITL attestation gate required before apply."
|
|
||||||
APPROVER="${GITHUB_ACTOR:-${GITEA_ACTOR:-}}"
|
|
||||||
if [ -z "$APPROVER" ]; then
|
|
||||||
echo "WARNING: no approver identity (GITHUB_ACTOR/GITEA_ACTOR unset)" >&2
|
|
||||||
echo " the gate would block in a real CI run. Passing for local." >&2
|
|
||||||
fi
|
|
||||||
python3 -c "
|
|
||||||
import os, sys
|
|
||||||
sys.path.insert(0, '.')
|
|
||||||
from core.hitl_gates import attest
|
|
||||||
from core import env as _envhelper
|
|
||||||
contract_id = _envhelper.get_env('HITL_CONTRACT_ID') or os.environ['NOVA_HITL_CONTRACT_ID']
|
|
||||||
env = _envhelper.get_env('HITL_ENV') or os.environ['NOVA_HITL_ENV']
|
|
||||||
approver = _envhelper.get_env('HITL_APPROVER', '') or 'local-test'
|
|
||||||
ok, reason = attest(contract_id, env, approver)
|
|
||||||
if ok:
|
|
||||||
print(f'HITL PASS: {reason}')
|
|
||||||
else:
|
|
||||||
print(f'HITL BLOCK: {reason}', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
" NOVA_HITL_CONTRACT_ID="$CONTRACT_ID" NOVA_HITL_ENV="$RESOLVED_ENV" NOVA_HITL_APPROVER="$APPROVER" || { echo "FAIL: HITL attestation gate blocked the apply" >&2; exit 1; }
|
|
||||||
else
|
|
||||||
echo "Environment is dev — autonomous (no HITL gate)."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 5: terraform apply -auto-approve ==="
|
echo "=== Step 5: terraform apply -auto-approve ==="
|
||||||
@@ -441,31 +449,7 @@ RESOLVED_ENV=$(python3 -c "import yaml; print(yaml.safe_load(open('$CONTRACT')).
|
|||||||
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
||||||
RESOLVED_ENV="$ENVIRONMENT_OVERRIDE"
|
RESOLVED_ENV="$ENVIRONMENT_OVERRIDE"
|
||||||
fi
|
fi
|
||||||
if [ "$RESOLVED_ENV" != "dev" ]; then
|
run_hitl_gate "$CONTRACT_ID" "$RESOLVED_ENV" "" || { echo "FAIL: HITL attestation gate blocked the promotion" >&2; exit 1; }
|
||||||
echo "Environment is $RESOLVED_ENV — HITL attestation gate required."
|
|
||||||
APPROVER="${GITHUB_ACTOR:-${GITEA_ACTOR:-}}"
|
|
||||||
if [ -z "$APPROVER" ]; then
|
|
||||||
echo "WARNING: no approver identity (GITHUB_ACTOR/GITEA_ACTOR unset); " >&2
|
|
||||||
echo " the gate would block in a real CI run. Passing for local." >&2
|
|
||||||
fi
|
|
||||||
python3 -c "
|
|
||||||
import os, sys
|
|
||||||
sys.path.insert(0, '.')
|
|
||||||
from core.hitl_gates import attest
|
|
||||||
from core import env as _envhelper
|
|
||||||
contract_id = _envhelper.get_env('HITL_CONTRACT_ID') or os.environ['NOVA_HITL_CONTRACT_ID']
|
|
||||||
env = _envhelper.get_env('HITL_ENV') or os.environ['NOVA_HITL_ENV']
|
|
||||||
approver = _envhelper.get_env('HITL_APPROVER', '') or 'local-test'
|
|
||||||
ok, reason = attest(contract_id, env, approver)
|
|
||||||
if ok:
|
|
||||||
print(f'HITL PASS: {reason}')
|
|
||||||
else:
|
|
||||||
print(f'HITL BLOCK: {reason}', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
" NOVA_HITL_CONTRACT_ID="$CONTRACT_ID" NOVA_HITL_ENV="$RESOLVED_ENV" NOVA_HITL_APPROVER="$APPROVER" || { echo "FAIL: HITL attestation gate blocked the promotion" >&2; exit 1; }
|
|
||||||
else
|
|
||||||
echo "Environment is dev — autonomous (no HITL gate)."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 8: write evidence event to DynamoDB outbox ==="
|
echo "=== Step 8: write evidence event to DynamoDB outbox ==="
|
||||||
|
|||||||
Reference in New Issue
Block a user