---ci--- project: acdl phase: 0 milestone: v1.29 status: complete ---/ci---
28 KiB
Nova — v1.28 Research Findings
Phase: research (pre-execution). Milestone: v1.28 (CLI Canonicalization
- Identity Layer). Status: research. Researcher: ci-researcher. Autonomy: full.
Research delegated to the ci-researcher subagent (full domain/ecosystem research with web citations). This file is the curated summary; the full 868-line research document is preserved in git history (the subagent's task output). Key findings + recommendations are below.
§1 — Codebase Inventory (grounding)
1.1 core/ modules (the REQ-324 subcommand surface)
19 Python files under core/ (plus core/lambda/, core/metrics/).
Two already have _cli.py companions (contract_resolver_cli.py 40
lines, regression_verify_cli.py 32 lines) — the thin-delegate
precedent for nova/<module>.py. No nova/ dir, no bin/, no
[project.scripts] entry exists today. The CLI is greenfield.
1.2 Existing Lambda pattern (core/lambda/contract_ingestor.py)
521 lines. Function URL + IAM auth (D-051). DynamoDB via lazy
module-global boto3.resource. Secrets Manager for tokens. Schema
validation in-Lambda. __main__ block already does CLI dispatch
(--check-readiness → core.submission_readiness.cli_main) — this is
the dual-use precedent for REQ-329. Local testing via
core/local_emulators.py:LocalLambdaStub.
1.3 core/env.py — getter, not synthesizer
31 lines. get_env(name, default) reads NOVA_<name> from os.environ.
REQ-330 needs a NEW synthesize_local_env() function added here.
The closest existing pattern is core/onboarding.py:generate_env_file().
1.4 PolicyEngine Protocol + KyvernoJsonEngine (the ABAC substrate)
core/policy_engine.py: PolicyEngine Protocol with evaluate(payload, policy_dir, contract_id) -> list[dict]. KyvernoJsonEngine shells to
kj scan --policy <dir> --payload <file> --output json. Policy shape =
ValidatingPolicy (apiVersion: json.kyverno.io/v1alpha1) with
spec.rules[].assert.all[].check using JMESPath. Severity from
metadata.annotations["nova.cloudinit.dev/severity"]. The payload
can be ANY JSON — not just contracts (the v1.25 design point). This
is what makes kyverno-json usable for ABAC token vending (D-227).
1.5 pyproject.toml state
name nova, version 1.14.0, requires-python >=3.10 (spec wants
3.12 — bump needed for REQ-326). setuptools build backend. No
[project.scripts], no [tool.setuptools.packages.find] — both needed.
Deps: boto3, jsonschema, pyyaml. No argon2-cffi, cryptography,
pyjwt, click/typer — argparse-only is the repo convention.
1.6 Forge conventions
.github/workflows/ + .gitea/workflows/ kept byte-identical. Python
3.12 already pinned via actions/setup-python@v5. No composite action
exists yet — nova cli-action (REQ-326) is greenfield.
1.7 IAM baseline (load-bearing for REQ-340)
.ciagent/IAM_POLICY.md + terraform/bootstrap/spike_runner_policy.json.
The nova-spike-runner principal already has KMS (incl. CreateKey,
Sign, GetPublicKey), Lambda (incl. PublishLayerVersion), DynamoDB
grants. New grants needed: cloudformation:* (for nova idp setup --apply) + codeartifact:* (for the wheel publish pipeline). Flagged
for P1/P2.
§2 — CodeArtifact + Lambda Layer Pipeline (REQ-323)
Recommendation: single CI job on merge to main affecting
core/**/adapters/**/nova/**/pyproject.toml. Build wheel
(python -m build --wheel) → twine upload to CodeArtifact → build
layer (pip install --target layer/python/ dist/nova-*.whl argon2-cffi cryptography pyjwt) → aws lambda publish-layer-version → record
version mapping in SSM /nova/layer/nova-cli/version (CAP-035). If
either publish fails, the job fails (merge blocked, REQ-323 AC).
Atomicity: wheel publish is idempotent (pin version to
<semver>+<sha7>); layer publish retries on failure. CAP-035 reads the
SSM parameter to verify layer-version ↔ wheel-version match.
Risks: CodeArtifact not yet provisioned in 581513795199 (CLARIFY
assumption #1); codeartifact:* grant missing. Fallback: Gitea-hosted
wheel index. Layer --compatible-architectures: build x86_64 only for
v1.28 (aarch64 only if Graviton Lambda needed).
§3 — CLI Subcommand Architecture (REQ-324)
Recommendation: three-layer. nova/__init__.py (marker) →
nova/cli.py (~80 lines, auto-discovers nova/<module>.py via
pkgutil.iter_modules, dispatches, emits cli.invocation audit event)
→ nova/<module>.py (≤50 lines each, exports add_parser(subparsers)
run(args) -> int, delegates tocore/). Entry point:[project.scripts] nova = "nova.cli:main". argparse-only (no click/typer — repo convention).
CAP-034 AST scan: ≤50 lines; ≤3 function defs; every ast.Call
resolves to a core. import; no conditionals beyond if __name__.
Subcommand groups: nova auth, nova idp, nova metrics =
nested subparsers (same pattern, one level deeper).
setuptools: add [tool.setuptools.packages.find] including nova,
nova.*, core, core.*, adapters.*.
§4 — Argon2id in Lambda Python 3.12 (REQ-334, D-228)
Findings: argon2-cffi-bindings v25.1.0 ships cp39-abi3
manylinux x86_64 + aarch64 wheels — ABI-stable, compatible with
Python 3.9..3.13. Lambda Python 3.12 runs Amazon Linux 2023 (glibc
2.34 ≥ 2.28 required). The abi3 manylinux wheel loads cleanly.
Confidence: 0.92.
D-228 AMENDMENT: the "pure-Python fallback" clause is weaker than stated — there is no maintained pure-Python Argon2 implementation. A pure-Python crypto fallback is a liability (weaker hashing, violates INV-16's spirit). Revised recommendation:
- Primary: bundled manylinux abi3 wheel in the
nova-cliLambda layer. Works. Confidence 0.92. - Fallback: detect
ImportErrorat Lambda cold-start → fail closed (503, refuse sign-ups). The Lambda health check reports C-extension status. Do NOT ship a pure-Python fallback. - Escape hatch: Fargate (~1 week, per CLARIFY Q1).
Lambda memory ≥ 512 MB (Argon2id memory_cost ~20 MB + overhead).
§5 — KMS Asymmetric Signing for OIDC Tokens (REQ-337)
Recommendation: key spec = ECC_NIST_P256, alg = ECDSA_SHA_256
(JWS ES256). RSA-2048 is larger + slower; P-256 is RFC 7518's
recommended JWT alg. Signature size 64 bytes (vs RSA 256). JWKS
compactness matters (fetched often).
The #1 gotcha: KMS returns DER-encoded ECDSA signatures; JWS
requires raw r‖s concatenation (RFC 7515 §3.1.3). The token-vend
Lambda converts via cryptography.hazmat.primitives.asymmetric.utils. decode_dss_signature → r.to_bytes(32) + s.to_bytes(32). ~5 lines.
Flagged for the threat model (REQ-347) + KMS round-trip test (REQ-350).
Flow: validate PAT → ABAC eval → build JWT header/payload →
kms.sign(Message=signing_input, MessageType="RAW", SigningAlgorithm= "ECDSA_SHA_256") → DER→raw → JWT. kid = KMS key alias.
Verification: use pyjwt (jwt.decode handles JWK→key natively);
cryptography only for SPKI→JWK in the JWKS Lambda.
Rotation: manual, 90 days (matches D-069 CMK cadence). New key +
re-point alias + JWKS serves both kids during overlap.
§6 — JWKS Endpoint (REQ-338, D-230)
D-230 confirmed. Lambda function URL (AuthType: NONE — JWKS is
public-key only) + reserved concurrency 10 (max 100 RPS, JWKS is
cached client-side). Cache-Control: max-age=3600. Separate tiny
nova-idp-jwks Lambda (separation of concerns).
Custom domain + WAF = OPTIONAL via --public-jwks-domain <domain>
flag on nova idp setup. Without it, raw function URL (acceptable for
v1.28 pilot). With it: CloudFront + ACM + WAF rate-based rule (>100
req/5min per IP) + Route53 ALIAS. Adds ~8 CloudFormation resources.
Defer API Gateway (D-230) — $3.50/M + complexity for no benefit at v1.28 volume.
§7 — kyverno-json ABAC Policy (REQ-339, D-227)
D-227 confirmed. Policy at platform/abac/token-vend.policy =
ValidatingPolicy with JMESPath checks against a payload of
{subject, requested_claims, target_resource, environment, pat_jti, policy_version}. Decision logic: any fail PCR with severity
critical → deny (403 + audit); all pass → allow → KMS sign.
policy_version (D-231): git SHA of the policy file, baked into
the Lambda layer, recorded in every token.vend.allowed/denied audit
event.
BIGGEST PACKAGING RISK: the token-vend Lambda needs the kj Go
binary (~40 MB) on PATH. Bundle it in the nova-cli Lambda layer
(wget the Linux amd64 release into layer/bin/kj). KyvernoJsonEngine .is_configured() checks which kj → /opt/bin/kj (layer mount). P2
spike confirms it runs in AL2023 Lambda. Fallback: Fargate. Confidence
0.75 — needs the spike.
§8 — PAT Lifecycle (REQ-342, REQ-343, REQ-344)
PAT = signed JWT (KMS-signed, typ: "developer_pat" distinguishes
from nova_oidc_token per INV-14). Claims: iss, sub, typ, jti, iat, exp, roles, owner.
nova-pats DynamoDB table (4th table): PK=jti, GSI1=sub (list
PATs for user), GSI2=pat_hash (lookup by hash). Only the hash stored
(not raw PAT). Revoked PATs retained for audit.
Revocation (D-229 CLARIFIED): GSIs don't support strongly-consistent
reads. The token-vend Lambda extracts jti from the PAT JWT (decode
without verifying — signature verified separately) →
GetItem(PK=jti, ConsistentRead=True) on the main table. Satisfies the
60s SLO. Confidence 0.90.
CLI: nova auth login (session→OIDC token, store locally),
nova auth revoke --pat <jti>, nova auth status (active credential,
mode, selection_reason). Local file ~/.nova/credentials.json (0600,
never to stdout, in .gitignore). "Most recent wins" (D-226 Q5) =
active_credential_jti field.
§9 — nova idp setup CloudFormation (REQ-340, REQ-341)
Template (raw dict → JSON, no troposphere dep): 2-3 Lambdas, 4
DynamoDB tables (nova-users, nova-sessions, nova-password-resets,
nova-pats), KMS key alias/nova-oidc-signing (ECC_NIST_P256),
function URLs, IAM roles, optional CloudFront/WAF/ACM.
--check: validates prerequisites (AWS creds, CFN perms, KMS perms,
layer exists via CAP-035). Prints required IAM policy delta.
--apply: generate → print to temp file + resource summary →
$PAGER → Apply? [y/N] → cloudformation deploy --capabilities CAPABILITY_IAM. NFR-10 satisfied by the explicit prompt.
--dry-run: resource list only, no write.
--verify: runs the KMS round-trip test (REQ-350).
New IAM grants needed: cloudformation:*, iam:CreateRole/PassRole,
lambda:CreateFunction/CreateFunctionUrlConfig,
dynamodb:CreateTable, kms:CreateKey/CreateAlias, ssm:PutParameter.
§10 — GitHub + Gitea Marketplace Composite Action (REQ-326)
Single action.yml at .github/actions/nova-cli/action.yml,
referenced by both GitHub + Gitea via uses: continuous-intelligence/ acdl/.github/actions/nova-cli@v1.28. Composite action: setup-python@v5
(python 3.12) → CodeArtifact login + pip install nova → nova ${{ inputs.command }}. NOVA_CLIENT_MODE env from input.
Byte-identical test (REQ-326 AC2): CI matrix runs the action on
GitHub ubuntu-latest + Gitea act_runner with same inputs; assert
same stdout/exit code.
Risk: Gitea actions/checkout/setup-python may need Gitea
mirrors (https://gitea.com/actions/...). P1 test on the actual Gitea
instance. Confidence 0.70.
§11 — mode_resolver Priority (REQ-327, D-226)
TTY detection: check sys.stdin.isatty() (NOT stdout). Edge 3
(nova apply | tee log.txt): stdout piped, stdin is TTY → user is
present → interactive (correct). sys.stdout.isatty() would
misresolve to agent. stdin answers "is a human at a terminal?"
Credential type detection: read ~/.nova/credentials.json →
active_credential_jti's type (developer_pat/nova_oidc_token).
Both + TTY → interactive; + no TTY → agent (INV-14).
Property tests (REQ-349): hypothesis with strategies for
flag/env/cred/tty. Properties: deterministic (INV-13), flag-wins,
invalid-env-ignored, no-silent-fallback (every resolution has a
non-empty selection_reason).
mode_resolver.py lives in core/ (not nova/) so Lambdas could
import it, but it's CLI-only — the token-vend Lambda doesn't resolve
modes.
§12 — Persona Assessment
See .ciagent/PERSONAS.md for the full YAML roster. Summary:
- Deactivate frontend-engineer (no UI) + data-engineer (no data pipelines in v1.28).
- Activate backend-engineer (Lambda/DynamoDB/KMS/CodeArtifact) + lead-developer (plan/review/ship).
- Add security-engineer (Argon2id/KMS/ABAC/threat model) + cli-engineer (subcommand surface/mode_resolver/argparse/CAP-034).
§13 — Architecture Sketch (ARCHITECTURE.md §12.10)
See .ciagent/ARCHITECTURE.md §12.10 (appended this stage). New
greenfield files: nova/ CLI package, platform/abac/token-vend.policy,
core/mode_resolver.py, core/env.py:+synthesize_local_env(),
core/lambda/nova_idp_{auth,token_vend,jwks}.py, tests/test_*,
docs/{operator-guide-idp,developer-guide-auth,threat-model}.md.
Decisions re-validated / amended
| Decision | Status | Change |
|---|---|---|
| D-226 | re-validated + refined | sys.stdin.isatty() is the TTY check (not stdout) |
| D-227 | re-validated | kj Go binary bundled in Lambda layer — packaging risk flagged |
| D-228 | amended | Pure-Python fallback → fail-closed + Fargate (pure-Python crypto is a liability) |
| D-229 | re-validated + clarified | Strong read on main table PK (jti), not GSI (GSIs don't support strong reads) |
| D-230 | re-validated | CloudFront/WAF/ACM made optional via --public-jwks-domain flag |
| D-231 | re-validated | policy_version (git SHA) in the ABAC payload |
New recommendations for PLAN/GRILL to formalize (no D-ID yet):
- KMS key spec =
ECC_NIST_P256, algES256; DER→raw ECDSA conversion required. nova-cliLambda layer bundles thekjGo binary (~40 MB).nova-pats= 4th DynamoDB table; PK=jti, GSI1=sub, GSI2=pat_hash.sys.stdin.isatty()is the TTY heuristic.[project.scripts] nova = "nova.cli:main"; argparse-only.cloudformation:*+codeartifact:*= new IAM baseline grants (P1/P2).
RESEARCH complete
All 11 research questions answered with cited findings + concrete
recommendations + risks. D-228 amended (fail-closed, not pure-Python
fallback). The kj binary packaging is the highest-risk item (P2
spike). Next: PLAN.
Nova — v1.29 Research Findings
Phase: research (pre-execution). Milestone: v1.29 (Reposplit + Identity Layer Bring-Live). Status: research. Researcher: ci-researcher. Autonomy: full.
Research delegated to the ci-researcher subagent (10 topics — Terraform import idempotency,
data.aws_ecr_imagedigest resolution,lifecycle.precondition, CloudFront OAC for Lambda Function URL, WAF on CloudFront, ACM DNS validation + Route53 alias,kjGo binary static build, ECR tag format, codebase inspection, Gitea Actions HITL). This file is the curated summary. Key findings + recommendations below.
§1 — Terraform import idempotency (REQ-361)
terraform import <addr> <id>reads an existing cloud resource into state without modifying it; the resource must have a matchingresourceblock in config.- Re-importing an address already in state fails with
Error: Resource already managed by Terraform(non-zero exit). The CI import step must treat this specific error as idempotent success (grep the message, not just exit code) — this is the IMPORT-IDEMPOTENT contract. importable-resources.tfis a convention (not built-in): a dedicated file listing resource addresses imported from the live account (S3 state bucket, DynamoDB tables, IAM OIDC role, KMS keys) so the import surface is enumerable + reviewable.- Drift detection:
terraform plan -detailed-exitcode(exit 2 = drift) fails the apply; the state bucket is bootstrapped manually then imported (never created by Terraform — avoids bootstrapping the bootstrapper, Q1/§7.1, D-235).
Recommendation: nova-platform-ops maintains an
importable-resources.tf map; CI import treats "already managed" as
idempotent success; plan -detailed-exitcode asserts zero drift.
§2 — data.aws_ecr_image digest resolution (REQ-355, REQ-371)
data "aws_ecr_image" "kj_image" { repository_name = …; image_tag = … }resolves the tag to an immutablesha256:digest viaimage_digest.- ECR tags are mutable by default (a re-push moves a tag → different
digest). KJ-LOCKSTEP pins on
image_digest, never the tag. image_uri=${data.aws_ecr_repository.kj.repository_url}@${data.aws_ecr_image.kj_image.image_digest}— pinning by@digest, not:tag. Both Lambda and Fargate reference the same data source → same digest by construction.data.aws_ecr_imagereads at plan time; if the tag doesn't exist (engineering hasn't published), the data source fails the plan (Q7 fail-closed).
Recommendation: Both image-bearing resources reference a single
data.aws_ecr_image.kj_image; image_uri = repo@digest; LOCKSTEP is
true by construction + the precondition (§3) is a verification.
§3 — lifecycle.precondition — the KJ-LOCKSTEP mechanism (REQ-371)
- Version correction (D-240): preconditions introduced in
Terraform v1.2.0 (May 2022), NOT v1.4+ as the spec implies. The
ops repo
required_version = ">= 1.2.0"suffices. - Syntax:
preconditionblock insidelifecycle { … }for resources. Evaluated before the resource action (during planning); a failing precondition aborts the plan with the customerror_message. error_messageis a string expression — can interpolate values:error_message = "KJ-LOCKSTEP: Fargate='${aws_ecs_task_definition.kj.image}' canonical='${data.aws_ecr_image.kj_image.image_digest}'".- Asserting two attributes resolve to the same value:
lifecycle { precondition { condition = self.image_uri == "${data.aws_ecr_repository.kj.repository_url}@${data.aws_ecr_image.kj_image.image_digest}" error_message = "KJ-LOCKSTEP: Lambda image does not match the resolved ECR digest" } }
Pitfalls: precondition blocks cannot reference count/for_each
unexpanded resources; both resources must depend on the same data source
(explicit depends_on if image_uri is computed indirectly).
Recommendation: Add lifecycle { precondition { … } } to both
the Lambda and Fargate task; set required_version = ">= 1.2.0".
§4 — CloudFront OAC pinning to Lambda Function URL (D-233, REQ-364)
- Critical: CloudFront OAC for a Lambda Function URL origin requires
AuthType: AWS_IAMon the Function URL (NOTAuthType: NONE). WithAWS_IAM, direct access returns 403 unless SigV4-signed; CloudFront + OAC signs requests on the viewer's behalf → CloudFront 200, direct 403 (INV-18 JWKS-EDGE-ONLY). - OAC resource:
OriginAccessControlOriginType = "lambda",SigningBehavior = "always",SigningProtocol = "sigv4". Attach viaOriginAccessControlIdon the origin block; HTTPS only. - Resource-based permission:
aws lambda add-permission --action lambda:InvokeFunctionUrl --principal cloudfront.amazonaws.com --source-arn <distribution ARN>— binds the Function URL to the specific distribution. - OAC replaces the deprecated S3-origin OAI; for Lambda origins, OAC is the only signing mechanism.
Pitfall: if AuthType: NONE is left on the Function URL, OAC signing
is ignored and the URL stays public — the 403 guarantee evaporates.
Recommendation: JWKS Function URL authorization_type = "AWS_IAM",
lambda-type OAC (SigningBehavior: always), lambda:InvokeFunctionUrl
permission scoped to the distribution ARN.
§5 — WAF WebACL rate-limit + AWS Managed Rules on CloudFront (REQ-365)
- Rate-based rule:
RateBasedStatementwithLimit: 3000,AggregateKeyType: "IP",EvaluationWindowSec: 300(5-min window; accepted values 60/120/300/600). WAF checks ~every 10s. - AWS Managed Rules Common Rule Set = managed rule group
AWSManagedRulesCommonRuleSet(vendorAWS), attached as a separate priority from the rate rule. - CloudFront WebACLs must be created in
us-east-1withScope = "CLOUDFRONT"(regional WebACLs cannot associate with CloudFront). - CloudWatch metrics: per-rule
VisibilityConfig.CloudWatchMetricsEnabled = true; S3 access logs viaaws_cloudfront_distribution.logging_config.
Recommendation: WebACL in us-east-1 Scope=CLOUDFRONT; rate rule
(3000/5min/IP) + Common Rule Set; associate to JWKS distribution;
CloudWatch metrics + S3 access logs.
§6 — ACM cert DNS validation + Route53 alias (REQ-366)
- ACM DNS validation:
aws_acm_certificatewithvalidation_method = "DNS"; createaws_route53_recordfor eachdomain_validation_optionsCNAME;aws_acm_certificate_validationwaits onISSUED. For CloudFront, the cert must be inus-east-1. - Route53 alias:
type = "A",alias { name = aws_cloudfront_distribution.jwks.domain_name; zone_id = aws_cloudfront_distribution.jwks.hosted_zone_id; evaluate_target_health = false }. route53_record_not_resolvablefailure mode: the alias doesn't resolve until CloudFrontstatus = DeployedAND ACM certISSUED. If the validation CNAME is mis-created or Route53 is not authoritative, the CNAME never validates → cert staysPENDING_VALIDATION→ alias NXDOMAIN.
Recommendation: ACM cert in us-east-1 DNS validation; validation
CNAMEs in the authoritative Route53 zone; aws_acm_certificate_validation
gates on ISSUED; Route53 A-alias to the distribution. Operator guide
documents the route53_record_not_resolvable → check-cert-status
debugging path.
§7 — kj Go binary static build for AL2023 Lambda (KJ-STATIC, REQ-354, REQ-363)
- Build:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o kj ./….CGO_ENABLED=0is load-bearing — no cgo, no dynamic libc link. file(1)must reportELF 64-bit LSB executable, x86-64, statically linked+ absence ofshared library/interpreter. Secondary:readelf -d kjshows noNEEDEDentries.- Base image
public.ecr.aws/lambda/python:3.12-al2023; copy binary to/opt/kj/kjchmod 0555owned bysbx_user:1051(Lambda sandbox user, uid/gid 1051 in AL2023).0555+ immutable-owned prevents runtime tampering. - Lambda handler invokes
subprocess.run(['/opt/kj/kj', 'apply', …], capture_output=True, check=True)—kjis a substrate binary, not a library; the Python handler is a thin shim. kyverno-json (INV-4) is separate + unaffected.
Pitfall: CGO_ENABLED=1 (default on systems with gcc) produces a
dynamically-linked binary; AL2023 glibc mismatch → runtime
GLIBC_X not found. CGO_ENABLED=0 eliminates this.
Recommendation: publish.yml P1 builds with CGO_ENABLED=0 GOOS=linux GOARCH=amd64, asserts file reports statically linked +
no shared library (fail build otherwise), copies to /opt/kj/kj
chmod 0555, handler calls subprocess.run(['/opt/kj/kj', 'apply', …]).
§8 — ECR image tag format (REQ-354 AC 3) — SPEC CORRECTION (D-239)
- ECR image tags do NOT allow
+. The ECR tag regex is^[a-zA-Z0-9]+(?:[._-][a-zA-Z0-9]+)*$— permitted chars[a-zA-Z0-9._-]only;+is rejected byPutImage/BatchGetImagewithInvalidParameterException. - The spec's tag format
v1.29.x+kj-<sha>is invalid as written. Correct format:v1.29.x-kj-<sha>(replace+with-). - The digest is the immutable trust surface regardless of the tag string — a re-tag is detectable only via digest mismatch. The tag is a human hint, not a security boundary.
Decision D-239 (spec correction): REQ-354 AC 3 tag format corrected
to v1.29.x-kj-<sha>. Confidence 0.95. Applied to REQUIREMENTS.md
§v1.29 REQ-354 AC (3).
§9 — Codebase inspection (actual file paths)
| Target | Path | Summary |
|---|---|---|
publish.yml |
.github/workflows/publish.yml (165 lines) + .gitea/workflows/publish.yml mirror |
Currently publishes wheel + Lambda layer on push: branches: [main] (NOT tag-triggered). P1 must change trigger to on: push: tags: ['v1.29.*'] + attach Lambda zip + ECR image to GitHub Releases. |
nova/idp/setup.py CFN |
nova/idp/setup.py (40 lines, thin CLI dispatcher) + core/lambda/nova_idp_setup.py (actual CFN logic, importlib-loaded because lambda is reserved) |
REQ-369 archives to docs/archive/nova-idp-cfn-v1.28.md; --apply delegates to terraform apply. |
platform/abac/kj-version.txt |
platform/abac/kj-version.txt (2 lines: v0.0.3 + SHA 4ebb9a19...) |
Already pins kj v0.0.3 + source SHA from v1.28 P4. P1 reads this SHA to embed in the ECR tag + verify the build. |
.gitea/ scrub targets |
.gitea/workflows/ (7 files) + scripts/sync_workflows.py (line 26: GITEA_DIR), scripts/sync_to_nova.sh, scripts/rotate_spike_key.sh, terraform/bootstrap/, ~100 .ciagent/ doc matches |
REQ-367 P2 removes .gitea/, scrubs gitea from .github/ docs/ pyproject.toml README.md .ciagent/, asserts forge_parity_disabled in CI (D-232). sync_workflows.py is the central removal target. |
Consumer deploy.yml |
NOT in acdl/.github/workflows/deploy.yml (that's the platform reusable workflow). Consumer's deploy.yml is in the nova-blockchain-exchange project — documented at .ciagent/nova-blockchain-exchange/REQUIREMENTS.md (REQ-314) + .ciagent/nova-blockchain-exchange/README.md. |
P5 bumps consumer's uses: ref @v1.25 → @v1.29 in both .github/workflows/deploy.yml + .gitea/workflows/deploy.yml (consumer's .gitea/ is out of scope for REQ-367 — that scrub is acdl/acdl only) + smoke test. |
§10 — Gitea Actions HITL approval (REQ-357, TFM-HITL)
- Gitea Actions has no Environments API with required reviewers. The
approval signal is
gitea.actor(triggering user) +gitea.triggering_actor(may differ on re-run — the re-dispatcher). - PR author:
${{ gitea.event.pull_request.user.login }}. INV-3 check:${{ gitea.triggering_actor }} != ${{ gitea.event.pull_request.user.login }}(usetriggering_actorfor re-run safety). - Gitea scoped-workflows (v1.27+) supports required workflows that gate PR merges via status checks — but this gates merge, not apply.
- The
workflow_dispatchapprove-input pattern (D-042) is the mechanism: plan runs automatically on PR; apply is a separateworkflow_dispatchwithapprove_applyinput; the apply job asserts INV-3 + fails closed. - Codebase precedent:
core/hitl_gates.py+core/separation_of_duties.py(D-042) —hitl_gates.attest(env, approver)readsGITHUB_ACTOR/FORGE_ACTOR, writes to DynamoDB outbox;separation_of_duties.checkcompares approvers. This is the production pattern to extend fornova-platform-opsterraform apply.
Pitfalls: scoped-workflow required-check enforcement needs branch
protection on main; a re-run changes gitea.actor to the re-dispatcher
— use gitea.triggering_actor for the effective approver.
Recommendation: nova-platform-ops uses workflow_dispatch
approve-input pattern (extending hitl_gates.py/separation_of_duties.py);
plan auto-runs on PR, apply is workflow_dispatch with approve_apply;
apply job asserts ${{ gitea.triggering_actor }} != ${{ gitea.event.pull_request.user.login }};
branch protection on main + required scoped-workflow status check.
New decisions for the decision ledger (research-derived)
| D-ID | Title | Confidence | Source |
|---|---|---|---|
| D-239 | ECR tag format v1.29.x+kj-<sha> invalid (+ not in ECR tag regex) → corrected to v1.29.x-kj-<sha> |
0.95 | §8 ECR API PutImage character class |
| D-240 | lifecycle.precondition introduced in Terraform v1.2.0 (not v1.4+); ops repo required_version = ">= 1.2.0" suffices |
0.98 | §3 Terraform v1.2.0 CHANGELOG |
Both are spec-vs-reality corrections logged at full autonomy (confidence
≥ 0.60 threshold). D-239 is applied to REQUIREMENTS.md §v1.29 REQ-354
AC (3). D-240 is documented in the operator guide (P4) for the
nova-platform-ops required_version floor.
RESEARCH complete
All 10 research questions answered with cited findings + concrete
recommendations + risks. Two spec corrections (D-239 ECR tag, D-240
Terraform precondition floor). The highest-risk item is the M1.5
verification gate (Q7 carry-forward — kj static build + 3 consecutive
rebuilds in nova-platform-ops CI). Next: PLAN.