feat(P01): nova subcommands — thin delegates to core/* (CAP-033/034, cli-engineer)

One nova/<name>.py per user-facing core/ module. Each ≤50 lines, ≤3
FunctionDef (add_parser + run [+1 helper]), every user-function call
resolves to a core.* import, no `if` statements except `if __name__`.

Subcommands:
- nova resolve       → core.contract_resolver.resolve
- nova decommission  → core.decommission_transform.decommission_transform
- nova env-transition detect|record → core.env_transition
- nova env-check     → core.environment_check.check
- nova hitl          → core.hitl_gates.attest (+ approver_from_env)
- nova onboard       → core.onboarding.generate_env_file
- nova outbox        → core.outbox_writer.write_event
- nova publish-outputs → core.output_publisher.publish_to_ssm + format_comment
- nova policy        → core.policy_engine.get_engine + get_policy_root (status)
- nova regression    → core.regression_verify.run_regression + write_report
- nova sod           → core.separation_of_duties.check
- nova readiness     → core.submission_readiness.cli_main
- nova attestation-matrix → core.attestation_matrix.cli_main (new thin wrapper)
- nova confidence    → core.confidence_signal.cli_main (new thin wrapper)

core wrappers added (minimal): attestation_matrix.cli_main,
confidence_signal.cli_main — extracted from their __main__ blocks so
the nova subcommands stay thin.

---ci---
project: acdl
phase: 1
milestone: v1.28
status: execute
persona: cli-engineer
---/ci---
This commit is contained in:
Jon Chery
2026-08-19 22:25:00 +00:00
parent 83883076ff
commit 2ed2b3ae0f
16 changed files with 411 additions and 20 deletions
+14 -13
View File
@@ -169,20 +169,21 @@ def check(env: str, evidence: dict) -> Tuple[bool, str]:
return (True, f"{env}: all {len(concerns)} concern(s) pass")
if __name__ == "__main__":
def cli_main(argv) -> int:
"""Thin CLI entry (P1): nova attestation-matrix <env> [evidence.json]."""
import json
if len(sys.argv) < 2:
print("usage: attestation_matrix.py <env> [evidence.json]", file=sys.stderr)
sys.exit(2)
_env = sys.argv[1]
if len(argv) < 2:
print("usage: attestation_matrix <env> [evidence.json]", file=sys.stderr)
return 2
_env = argv[1]
_evidence = {}
if len(sys.argv) >= 3 and os.path.isfile(sys.argv[2]):
with open(sys.argv[2]) as f:
if len(argv) >= 3 and os.path.isfile(argv[2]):
with open(argv[2]) as f:
_evidence = json.load(f)
ok, reason = check(_env, _evidence)
if ok:
print(f"ATTESTATION PASS: {reason}")
sys.exit(0)
else:
print(f"ATTESTATION BLOCK: {reason}", file=sys.stderr)
sys.exit(1)
print(f"ATTESTATION PASS: {reason}") if ok else print(f"ATTESTATION BLOCK: {reason}", file=sys.stderr)
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(cli_main(sys.argv))
+13 -7
View File
@@ -218,12 +218,18 @@ def compute(contract_id: str, environment: str,
return signal
if __name__ == "__main__":
if len(sys.argv) < 3:
print("usage: confidence_signal.py <inputs.json> <environment>", file=sys.stderr)
sys.exit(2)
env = sys.argv[2]
with open(sys.argv[1], "r", encoding="utf-8") as fh:
def cli_main(argv) -> int:
"""Thin CLI entry (P1): nova confidence <inputs.json> <environment>."""
if len(argv) < 3:
print("usage: confidence <inputs.json> <environment>", file=sys.stderr)
return 2
env = argv[2]
with open(argv[1], "r", encoding="utf-8") as fh:
inputs = json.load(fh)
sig = compute("cli", env, inputs)
print(json.dumps(asdict(sig), indent=2))
print(json.dumps(asdict(sig), indent=2))
return 0
if __name__ == "__main__":
sys.exit(cli_main(sys.argv))
+22
View File
@@ -0,0 +1,22 @@
"""nova attestation-matrix — run the 8-concern attestation matrix (REQ-109)."""
from __future__ import annotations
from core.attestation_matrix import cli_main
def add_parser(subparsers):
p = subparsers.add_parser("attestation-matrix", help="run the 8-concern attestation matrix")
p.add_argument("env", help="target environment (dev/qa/prod/dr)")
p.add_argument("evidence", nargs="?", default=None, help="evidence JSON path")
p.set_defaults(_run=run)
def run(args) -> int:
argv = ["nova-attestation-matrix", args.env] + ([args.evidence] if args.evidence else [])
return cli_main(argv)
if __name__ == "__main__":
import sys
print("use: nova attestation-matrix <env> [evidence.json]", file=sys.stderr)
+21
View File
@@ -0,0 +1,21 @@
"""nova confidence — compute the confidence signal (REQ-19)."""
from __future__ import annotations
from core.confidence_signal import cli_main
def add_parser(subparsers):
p = subparsers.add_parser("confidence", help="compute the confidence signal")
p.add_argument("inputs_json", help="path to an inputs JSON file")
p.add_argument("environment", help="target environment")
p.set_defaults(_run=run)
def run(args) -> int:
return cli_main(["nova-confidence", args.inputs_json, args.environment])
if __name__ == "__main__":
import sys
print("use: nova confidence <inputs.json> <environment>", file=sys.stderr)
+28
View File
@@ -0,0 +1,28 @@
"""nova decommission — transform a resolved stack for decommission (REQ-92)."""
from __future__ import annotations
import json
from core.decommission_transform import decommission_transform
def add_parser(subparsers):
p = subparsers.add_parser("decommission", help="transform a stack JSON for decommission")
p.add_argument("stack_json", help="path to a resolved stack JSON")
p.add_argument("--out", default=None, help="output path (default: stdout)")
p.set_defaults(_run=run)
def run(args) -> int:
with open(args.stack_json) as fh:
stack = json.load(fh)
out = decommission_transform(stack)
blob = json.dumps(out, indent=2)
print(blob)
return 0
if __name__ == "__main__":
import sys
print("use: nova decommission <stack.json>", file=sys.stderr)
+25
View File
@@ -0,0 +1,25 @@
"""nova env-check — check that an environment is bound (REQ-181)."""
from __future__ import annotations
import sys
from core.environment_check import check
def add_parser(subparsers):
p = subparsers.add_parser("env-check", help="check that an environment is bound")
p.add_argument("contract", nargs="?", default=None, help="contract path")
p.add_argument("--env", default=None, help="environment name override")
p.set_defaults(_run=run)
def run(args) -> int:
ok, message = check(contract_path=args.contract, env_name=args.env)
print(message) if ok else sys.stderr.write(message + "\n")
return 0 if ok else 1
if __name__ == "__main__":
import sys
print("use: nova env-check <contract.yml> [--env name]", file=sys.stderr)
+37
View File
@@ -0,0 +1,37 @@
"""nova env-transition — detect/record the applied environment (REQ-183)."""
from __future__ import annotations
import json
from core.env_transition import detect_prior_env, record_applied_env
def add_parser(subparsers):
p = subparsers.add_parser("env-transition", help="detect/record the env for a contract")
sub = p.add_subparsers(dest="env_transition_command", required=True)
pd = sub.add_parser("detect")
pd.add_argument("--contract-id", required=True)
pd.add_argument("--consumer-repo", required=True)
pd.add_argument("--new-env", required=True)
pr = sub.add_parser("record")
pr.add_argument("--contract-id", required=True)
pr.add_argument("--consumer-repo", required=True)
pr.add_argument("--env", required=True)
p.set_defaults(_run=run)
def run(args) -> int:
cmd = args.env_transition_command
payload = _dispatch(cmd, args)
print(json.dumps(payload))
return 0 if cmd == "detect" else (0 if payload["recorded"] else 1)
def _dispatch(cmd, args) -> dict:
return {"prior_env": detect_prior_env(args.contract_id, args.consumer_repo, args.new_env)} if cmd == "detect" else {"recorded": record_applied_env(args.contract_id, args.consumer_repo, args.env)}
if __name__ == "__main__":
import sys
print("use: nova env-transition detect|record ...", file=sys.stderr)
+33
View File
@@ -0,0 +1,33 @@
"""nova hitl — attest a promotion gate (REQ-108)."""
from __future__ import annotations
import json
import sys
from core.hitl_gates import attest, approver_from_env
def add_parser(subparsers):
p = subparsers.add_parser("hitl", help="attest a promotion gate")
p.add_argument("--contract-id", required=True)
p.add_argument("--env", required=True, help="dev/qa/prod/dr")
p.add_argument("--evidence", default=None, help="evidence JSON path")
p.set_defaults(_run=run)
def run(args) -> int:
evidence = _load_evidence(args.evidence)
approver = approver_from_env() or ""
ok, reason = attest(args.contract_id, args.env, approver, evidence)
print(f"HITL PASS: {reason}") if ok else sys.stderr.write(f"HITL BLOCK: {reason}\n")
return 0 if ok else 1
def _load_evidence(path):
return {} if path is None else json.loads(open(path).read())
if __name__ == "__main__":
import sys
print("use: nova hitl --contract-id <id> --env <env> [--evidence f.json]", file=sys.stderr)
+33
View File
@@ -0,0 +1,33 @@
"""nova onboard — generate an env binding from an onboarding request (REQ-181)."""
from __future__ import annotations
import json
from core.onboarding import generate_env_file
def add_parser(subparsers):
p = subparsers.add_parser("onboard", help="generate an env binding from a request")
p.add_argument("--request", default=None, help="inline request JSON")
p.add_argument("request_file", nargs="?", default=None, help="request JSON path")
p.add_argument("--out", default=None, help="output path (default: stdout)")
p.add_argument("--template-env", default="dev")
p.set_defaults(_run=run)
def run(args) -> int:
request = _load_request(args)
env = generate_env_file(request, template_env=args.template_env)
blob = json.dumps(env, indent=2) + "\n"
print(blob) if args.out is None else open(args.out, "w").write(blob)
return 0
def _load_request(args):
return json.loads(args.request) if args.request else json.loads(open(args.request_file).read())
if __name__ == "__main__":
import sys
print("use: nova onboard <request.json> [--out env.json]", file=sys.stderr)
+26
View File
@@ -0,0 +1,26 @@
"""nova outbox — write an evidence event to the DynamoDB outbox (D-P10-3)."""
from __future__ import annotations
import json
from core.outbox_writer import write_event
def add_parser(subparsers):
p = subparsers.add_parser("outbox", help="write an evidence event to the outbox")
p.add_argument("event_json", help="path to an event JSON file")
p.set_defaults(_run=run)
def run(args) -> int:
with open(args.event_json) as fh:
event = json.load(fh)
item = write_event(event)
print(json.dumps({k: list(v.values())[0] for k, v in item.items()}, indent=2))
return 0
if __name__ == "__main__":
import sys
print("use: nova outbox <event.json>", file=sys.stderr)
+27
View File
@@ -0,0 +1,27 @@
"""nova policy — print the active policy engine status (REQ-122)."""
from __future__ import annotations
import json
from core.policy_engine import get_engine, get_policy_root
def add_parser(subparsers):
p = subparsers.add_parser("policy", help="print the active policy engine status")
p.set_defaults(_run=run)
def run(args) -> int:
eng = get_engine()
print(json.dumps({
"engine": eng.name,
"is_configured": eng.is_configured(),
"policy_root": str(get_policy_root()),
}, indent=2))
return 0
if __name__ == "__main__":
import sys
print("use: nova policy", file=sys.stderr)
+28
View File
@@ -0,0 +1,28 @@
"""nova publish-outputs — publish stack outputs to SSM + format a PR comment (REQ-168)."""
from __future__ import annotations
import json
from core.output_publisher import publish_to_ssm, format_comment
def add_parser(subparsers):
p = subparsers.add_parser("publish-outputs", help="publish outputs to SSM + format comment")
p.add_argument("outputs_json", help="path to an outputs JSON file")
p.add_argument("environment")
p.add_argument("contract_id")
p.set_defaults(_run=run)
def run(args) -> int:
with open(args.outputs_json) as fh:
outputs = json.load(fh)
ssm_results = publish_to_ssm(outputs, args.environment, args.contract_id)
print(format_comment(outputs, args.environment, args.contract_id, ssm_results))
return 0
if __name__ == "__main__":
import sys
print("use: nova publish-outputs <outputs.json> <env> <contract-id>", file=sys.stderr)
+20
View File
@@ -0,0 +1,20 @@
"""nova readiness — submission readiness check (REQ-178)."""
from __future__ import annotations
from core.submission_readiness import cli_main
def add_parser(subparsers):
p = subparsers.add_parser("readiness", help="submission readiness check")
p.add_argument("contract_json", help="path to a contract/submission JSON")
p.set_defaults(_run=run)
def run(args) -> int:
return cli_main(["nova-readiness", args.contract_json])
if __name__ == "__main__":
import sys
print("use: nova readiness <contract.json>", file=sys.stderr)
+29
View File
@@ -0,0 +1,29 @@
"""nova regression — run the regression gate and write the report (REQ-177)."""
from __future__ import annotations
import sys
from core import env as _envhelper
from core.regression_verify import run_regression, write_report
def add_parser(subparsers):
p = subparsers.add_parser("regression", help="run the regression gate + write report")
p.add_argument("--milestone", default=None)
p.add_argument("--phase", type=int, default=None)
p.set_defaults(_run=run)
def run(args) -> int:
milestone = args.milestone or _envhelper.get_env("REGRESSION_MILESTONE", "v1.10") or "v1.10"
phase = args.phase if args.phase is not None else int(_envhelper.get_env("REGRESSION_PHASE", "52") or "52")
report = run_regression(milestone=milestone, phase=phase)
md, js = write_report(report)
print(f"regression: {report.summary} -> {md}")
return 0 if report.passed else 1
if __name__ == "__main__":
import sys
print("use: nova regression [--milestone v1.x] [--phase N]", file=sys.stderr)
+30
View File
@@ -0,0 +1,30 @@
"""nova resolve — resolve a contract YAML to a Target Stack JSON."""
from __future__ import annotations
import json
from core.contract_resolver import resolve
from core import env
def add_parser(subparsers):
p = subparsers.add_parser("resolve", help="resolve a contract.yml to stack JSON")
p.add_argument("contract")
p.add_argument("out")
p.add_argument("--environment", default=None)
p.set_defaults(_run=run)
def run(args) -> int:
env_override = args.environment or env.get_env("ENVIRONMENT_OVERRIDE")
result = resolve(args.contract, environment_override=env_override)
with open(args.out, "w") as fh:
json.dump(result, fh, indent=2)
print(f"resolve: wrote {args.out}")
return 0
if __name__ == "__main__":
import sys
print("use: nova resolve <contract.yml> <out.json>", file=sys.stderr)
+25
View File
@@ -0,0 +1,25 @@
"""nova sod — separation-of-duties check for a prod promotion (REQ-107)."""
from __future__ import annotations
import sys
from core.separation_of_duties import check
def add_parser(subparsers):
p = subparsers.add_parser("sod", help="separation-of-duties check for prod promotion")
p.add_argument("--contract-id", required=True)
p.add_argument("--approver", required=True, help="current prod approver identity")
p.set_defaults(_run=run)
def run(args) -> int:
ok, reason = check(None, args.contract_id, args.approver)
print(f"SOD PASS: {reason}") if ok else sys.stderr.write(f"SOD BLOCK: {reason}\n")
return 0 if ok else 1
if __name__ == "__main__":
import sys
print("use: nova sod --contract-id <id> --approver <user>", file=sys.stderr)