diff --git a/.ciagent/PERSONAS.md b/.ciagent/PERSONAS.md index 3793226..652e384 100644 --- a/.ciagent/PERSONAS.md +++ b/.ciagent/PERSONAS.md @@ -1,33 +1,31 @@ --- project: acdl -milestone: v1.17 -generated_at: 2026-08-04 +milestone: v1.18 +generated_at: 2026-08-06 generator: lead-developer verification_toolchain: - typecheck: "terraform validate && python3 -m py_compile core/**/*.py && python3 -m jsonschema schemas/*.schema.json" - test: "bash scripts/run_regression.sh # 22-capability gate (D-091/D-118) + CAP-023/024 (v1.17)" - build: "bash scripts/run_ci.sh # full local CI reproduction (lint+test+check-only)" + typecheck: "python3 -m py_compile core/submission_readiness.py mcp/atelier/server.py && python3 -m jsonschema schemas/submission-readiness.schema.json" + test: "pytest tests/test_submission_readiness.py tests/test_atelier_mcp.py # REQ-220 + REQ-225" + build: "bash scripts/render_deck.sh docs/presentations/nova-no-humans-platform-marp.md # HTML + PPTX (D-142)" note: | - v1.17 adds a telemetry/observability layer (metrics emitters, SQLite - cold store, PowerBI export, Decision Ledger) + a unified narrative - deck + a durable NORTH_STAR.md. Three active personas: lead-developer - (coordination + deck narrative co-author), backend-engineer (event - emitters, outbox_writer extension, Infracost adapter), data-engineer - (SQLite store, schemas, PowerBI views, metrics collector). frontend- - engineer stays deactivated (no Nova web UI — dashboards are PowerBI, - not a Nova-built frontend; decks are markdown = lead-developer - territory). No new custom personas needed — the metrics domain maps - cleanly to data-engineer (schema/store/export) + backend-engineer - (emitters/instrumentation). + v1.18 adds the Citizen Developer & Production-Grade Guidance surface: + submission-readiness gate, Atelier-derived skills, the Atelier MCP server + (plugin-registry, stdio), and PPTX-as-first-class-artifact deck automation. + Three active personas: lead-developer (coordination + decks + RACI/scope + docs), backend-engineer (MCP server + submission-readiness validator + + render/attach scripts), data-engineer (submission-readiness schema if it + touches contract storage / DynamoDB shape). frontend-engineer stays + deactivated (v1.18 has no frontend; decks are markdown = lead-developer + territory). The MCP plugin-registry is a backend pattern, so a separate + mcp-engineer persona is NOT added — it folds into backend-engineer. --- -# ACDL — Persona Roster (project-level, v1.11 RESTART) +# ACDL — Persona Roster (v1.18 Citizen Developer & Production-Grade Guidance) -> v1.11 is a restart (D-097). The v1.9 roster is superseded. Three -> structural corrections: (1) stateless adapter (D-098), (2) terraform -> owns lifecycle (D-101), (3) pipeline-driven testing (D-102). The roster -> is simplified to the three active domains: data (terraform foundation), -> backend (adapter/resolver), general (pipelines/workflows). +> v1.18 roster. Three active personas + one deactivated. The MCP server +> plugin-registry (D-140) is a backend pattern, not a new persona — it +> folds into backend-engineer. v1.17 precedent (frontend-engineer +> deactivated, decks are markdown = lead-developer territory) is upheld. ## Active personas @@ -35,351 +33,104 @@ verification_toolchain: - **Domain:** coordination - **Active:** true - **Phase-specific:** false -- **Reason:** Owns CIAgent metadata, cross-phase verification scripts, the v1.11 phase orchestration (D-107: P56a + P56b split), and arbitrates persona conflicts. Resolves the milestone decomposition and the STANDARDS.md §8 rewrite (the adapter extension pattern is replaced by the per-module terraform subdir pattern). +- **Frameworks:** [] (no framework — owns process + narrative, not code) +- **Constraints:** ["pragmatic", "battle-tested defaults", "no fabrication (NORTH_STAR honesty model)"] +- **Territory:** + - `docs/presentations/**` (Step 1/2/4 markdown + the deck automation trigger) + - `.ciagent/**` (PROJECT, ROADMAP, REQUIREMENTS, RESEARCH, PLAN, GRILL, PERSONAS, REVIEW, CHECKPOINT) + - `PROJECT.md` (RACI matrix + PDLC-scope statement, REQ-215/216) + - `ROADMAP.md` + - `REQUIREMENTS.md` + - `docs/raci.md` (REQ-215) + - `docs/scope.md` (REQ-216) + - `docs/skills.md` (REQ-222 — the index page, not the skill files themselves) + - `docs/submission-readiness.md` (REQ-219 — citizen-developer-facing copy; co-owned with backend-engineer for the reason-code catalog) +- **Reason:** Owns CIAgent metadata, the milestone narrative, the RACI + + PDLC-scope statements (REQ-215/216), the deck (21 slides, S&P theme + regression check vs P1, CAP-024), the skills index page (REQ-222), and + the citizen-developer-facing submission-readiness doc (REQ-219). Is + the only persona that touches `.ciagent/**` and the deck markdown. +- **Phase-specific flag:** none (active for all of P0–P7). ### backend-engineer - **Domain:** backend - **Active:** true - **Phase-specific:** false -- **Reason:** Owns the adapter rewrite (D-098: stateless assembler — deletes TYPE_MAP/INPUT_MAP/OUTPUT_MAP + 39 type-specific branches, becomes a ~80-line assembler that emits `module "x" { source = "..." ... }` blocks) and the contract resolver env-aware state keys (D-106: `spike/{id}/{env}/terraform.tfstate`). The adapter holds no module content; the engine binding lives in the per-module `terraform/` subdir. Co-authoring expected on the adapter + `run_platform.sh` boundary (general adds `--apply`/`--destroy` modes that invoke the adapter). -- **Territory:** `adapters/terraform/adapter.py` (rewrite to stateless assembler), `core/contract_resolver.py` (env-aware state keys, deterministic composition), `schemas/stack.schema.json` (if the stack instance shape changes), `tests/test_adapter*.py` (regression baseline — the s3 instance.json round-trip must still pass). +- **Frameworks:** ["mcp (Python SDK v2)", "pydantic", "jsonschema", "urllib"] +- **Constraints:** ["api-first", "strict-typing", "plugin-registry extensible (D-140)", "stdio now / HTTP-ready (D-135)", "no stack traces to citizen developers (REQ-218)"] +- **Territory:** + - `mcp/atelier/server.py` (REQ-223) + - `mcp/atelier/plugins/**/*.py` (REQ-223 — principles.py, validation.py) + - `mcp/atelier/vendor/**` (REQ-224 — vendored Atelier snapshot) + - `mcp/atelier/VERSION.md` + `mcp/atelier/README.md` (REQ-224) + - `scripts/update_atelier_vendor.sh` (REQ-224) + - `core/submission_readiness.py` (REQ-218 — the validator, invoked as `contract_ingestor.py --check-readiness`) + - `scripts/render_deck.sh` (REQ-228 — HTML + PPTX render) + - `scripts/attach_release_asset.py` (REQ-228 — Gitea release asset upload) + - `tests/test_atelier_mcp.py` (REQ-225) + - `tests/test_submission_readiness.py` (REQ-220) + - `docs/submission-readiness.md` (REQ-219 — reason-code catalog section; co-owned with lead-developer for the narrative) +- **Reason:** Owns the MCP server (plugin-registry, stdio, vendored + Atelier), the submission-readiness validator (extends + `contract_ingestor.py --check-readiness`, D-133), the render/attach + scripts (D-142 trigger), and the two new test files. The MCP + plugin-registry (D-140) is a backend pattern — no separate + mcp-engineer persona is created; backend-engineer owns it. +- **Phase-specific flag:** none (active for P1 deck-render, P3 validator, + P5 MCP server, P6 scripts). ### data-engineer - **Domain:** data - **Active:** true - **Phase-specific:** false -- **Reason:** Reactivated for v1.11. Owns the heaviest territory: the per-module `terraform/` subdirs (D-098/D-099/D-100 — the engine binding) for all 12 L1 modules, plus the single platform VPC (D-105: `terraform/platform` owns ONE VPC; the microservice composition drops its `vpc` child and references the platform VPC via data source). Each L1 module ships a real terraform module dir (versions/variables/locals/main/outputs.tf) owning its resource shape, nested blocks, and defaults. `locals.tf` is used heavily to centralize default interpolation (D-099). Multi-resource modules get the full 5-file split; trivial single-resource modules may inline locals in main.tf. This is the binding constraint — the stateless adapter cannot be written until the reference s3 module exists (D-107: P56a proves the design with s3 first). -- **Territory:** `terraform/` (platform VPC, D-105), `modules/l1/*/terraform/` (per-module terraform subdirs — the engine binding), `modules/l1/*/interface.json` (defaults move from adapter to interface inputs), `modules/registry.json` (terraform_dir field), `modules/l2/microservice/composition.json` (drop the vpc child, D-105), `modules/STANDARDS.md` §8 (rewrite the adapter extension pattern → per-module terraform subdir pattern). - -### general (lead-developer + backend-engineer pipeline work) -- **Domain:** coordination + pipelines -- **Active:** true -- **Phase-specific:** false -- **Reason:** Owns the pipeline-driven testing (D-102/D-103/D-104) and the terraform lifecycle modes (D-101). The modules-lifecycle pipeline (Gitea + GitHub, byte-identical) matrix-runs each L1 module's `examples/{simple,complex}.yml` contracts through apply→modify→destroy against live AWS. `run_platform.sh` gains `--apply` and `--destroy` modes; Python never runs terraform. `verify_deploy_microservice.py` is deleted (D-101). Co-authoring expected on the `run_platform.sh` boundary (backend-engineer rewrites the adapter that `run_platform.sh` invokes). -- **Territory:** `pipelines/modules-lifecycle.yml`, `.gitea/workflows/modules-lifecycle.yml` + `.github/workflows/modules-lifecycle.yml` (byte-identical, D-102), `scripts/run_platform.sh` (`--apply`/`--destroy` modes, D-101), `scripts/run_primitive_plan.sh` (if extended for lifecycle), `scripts/run_pattern_plan.sh` (if extended), `pipelines/README.md` (document the new pipeline), `schemas/deploy-pipeline.schema.json` (if the lifecycle stages are added to the contract). - -## Deactivated personas - -### lambda-engineer (custom, v1.9 — deactivated for v1.11) -- **Domain:** serverless -- **Active:** false -- **Phase-specific:** false -- **Reason:** No per-module Python this milestone (D-102: testing is pipeline-driven, not pytest). The v1.9 Lambda (`core/lambda/contract_ingestor.py`) and the `terraform/platform/main.tf` Lambda/DynamoDB/KMS/Secrets definitions persist from v1.9 but are not touched in v1.11. The `acdl-sod-halt` SNS topic and the attestation matrix are out of scope. Removed from the roster for v1.11; reactivates if a future milestone touches the Lambda. - -### platform-engineer (custom, v1.9 — folded into data-engineer for v1.11) -- **Domain:** infra -- **Active:** false -- **Phase-specific:** false -- **Reason:** The v1.11 scope (D-097..D-107) is terraform module authoring + adapter rewrite + pipelines — not the v1.9-era L1/L2 IR-typed module authoring or the AWS OIDC bootstrap. The platform-engineer's v1.9 territory (`adapters/terraform/**`, `modules/**`, `terraform/**`) is split: the adapter goes to backend-engineer (rewrite), the per-module terraform subdirs + platform VPC go to data-engineer (the heaviest v1.11 work). Folded into data-engineer for v1.11; reactivates if a future milestone does IR-shaped module authoring or OIDC bootstrap work. - -### security-engineer (custom, v1.9 — deactivated for v1.11) -- **Domain:** security -- **Active:** false -- **Phase-specific:** false -- **Reason:** The v1.11 scope does not touch Wiz/Kyverno/Checkov adapters, the HITL matrix, separation-of-duties, or the audit ledger. The security-engineer's v1.9 territory persists but is not touched. Removed from the roster for v1.11; reactivates if a future milestone touches security adapters or HITL gates. - -### frontend-engineer -- **Domain:** frontend -- **Active:** false -- **Phase-specific:** false -- **Reason:** The evidence timeline UI (`evidence-ui/**`) is unchanged from v1.0 and not touched in v1.11. Removed from the active roster; reactivates if a future milestone touches the timeline UI. - -### data-engineer (v1.9 — was deactivated, reactivated for v1.11) -- **Domain:** data -- **Active:** true (reactivated) -- **Phase-specific:** false -- **Reason:** See the active `data-engineer` entry above. The v1.9 deactivation rationale ("No ORM/persistence framework") no longer applies — v1.11's data-engineer owns terraform module authoring, not a data persistence layer. - -### infra-stub-engineer (custom, v1.0 only) -- **Domain:** backend -- **Active:** false -- **Reason:** Owned L1 stub modules in the v1.0 demo. The demo is archived to `demo/`; real L1 modules are owned by data-engineer (v1.11). Not reactivated. - -## Phase-specific overrides - -| Phase | Personas active | Notes | -|-------|------------------|-------| -| 56a adapter-rewrite-and-s3-reference-module | data-engineer (lead: s3 reference terraform module — proves the design), backend-engineer (lead: stateless adapter rewrite — emits module blocks for s3), general (run_platform.sh --apply/--destroy skeleton) | security/lambda/frontend idle | -| 56b remaining-11-l1-module-terraform-subdirs | data-engineer (lead: author 11 L1 module terraform subdirs — vpc, ecs-cluster, ecs-service, iam-role, alb, ecr, cloudfront, waf, rds, kms-key, uptime), backend-engineer (adapter: confirm each module round-trips through the assembler), general (modules-lifecycle pipeline wiring) | security/lambda/frontend idle | -| (modules-lifecycle pipeline) | general (lead: byte-identical Gitea+GitHub workflow + matrix apply→modify→destroy), data-engineer (examples/{simple,complex}.yml contracts as the modify variants), backend-engineer (adapter confirms the lifecycle cells resolve) | security/lambda/frontend idle | -| (platform VPC + composition drop) | data-engineer (lead: terraform/platform VPC + microservice composition drops vpc child, D-105), backend-engineer (resolver: env-aware state keys, D-106) | general/security/lambda/frontend idle | -| verify | lead-developer (lead: 4-layer verification), all active personas (review their territory) | — | -| review-audit-complete | lead-developer (lead: review + audit + milestone completion), all active personas (review participation) | — | - -## Domain priority (used by TaskDecomposer) - -`data → backend → general` - -Rationale: in v1.11, the terraform foundation (per-module `terraform/` -subdirs + platform VPC) is the binding constraint — the stateless adapter -cannot be written until the reference s3 module exists (D-107: P56a -proves the design with s3 first). Backend (adapter/resolver) follows once -the module shape is proven. General (pipelines/workflows) wires the -lifecycle modes last, once the adapter + modules produce valid terraform. - -## Conflict resolutions (lead-developer arbitration) - -- `backend-engineer` vs `data-engineer` over `modules/l1/*/interface.json`: - data-engineer owns the interface defaults (defaults move from the - adapter to the interface inputs, D-100); backend-engineer owns the - adapter that reads them. Co-authoring is expected; conflict goes to - lead-developer. -- `backend-engineer` vs `general` over `scripts/run_platform.sh`: - backend-engineer rewrites the adapter that `run_platform.sh` invokes; - general adds the `--apply`/`--destroy` modes. The interface (the CLI - flags + the adapter invocation) is co-authored; conflicts go to - lead-developer. -- `data-engineer` vs `general` over `modules/l1/*/examples/`: - data-engineer owns the example contracts (the modify variants, - D-103); general owns the pipeline that matrix-runs them. Co-authoring - is expected; conflicts go to lead-developer. -- `lead-developer` vs any: lead-developer owns `.ciagent/**` + `docs/**` - meta + verification scripts + `modules/STANDARDS.md` §8 rewrite; persona - engineers do not edit CIAgent metadata or the vision/architecture - source docs. - -## Territory enforcement mode - -`warn` — config.json has no `personas.territory_enforcement` field, so the -default per execute.md is `warn`. Cross-territory edits are logged in the -commit message but do not fail the task. v1.11's scope means co-authoring -across territories is likely (e.g. backend + general on the adapter + -`run_platform.sh` boundary; data + general on the examples + pipeline -boundary); `warn` keeps it frictionless. ---- - -## v1.15 Persona Addendum — Nova Rebrand (2026-07-30) - -**Milestone:** v1.15-Nova. The roster carries forward from v1.11/v1.14 -unchanged — the rebrand touches existing territories, no new domains. -**frontend-engineer** remains deactivated (no UI; decks are markdown = -lead-developer territory). No **security-engineer** persona is activated -— the ABAC session-policy + tag-key migration (REQ-162) is data-engineer -territory (terraform IAM) with lead-developer review. - -### v1.15 territory assignments - -| Phase | Lead | Contributors | Territory | -|-------|------|---------------|-----------| -| P1 docs-decks-prose | lead-developer | — | `README.md`, `docs/**`, `.ciagent/*.md`, deck `.md`/`-marp.md`/`-talking-points.md`/`.html`, `docs/presentations/assets/mmd/*.mmd` (+ PNG re-export), `pyproject.toml`, `schemas/*.schema.json` `$id` (D-110), `docs/NOVA_MIGRATION.md`, `.github/workflows/release.yml` title, `modules/STANDARDS.md` | -| P2 code-envvars-consumer-path | backend-engineer | lead-developer (docs/runbook) | `core/env.py` (NEW dual-read helper, D-108), `core/*.py` (call-site migration), `scripts/*.py` + `*.sh`, `adapters/**`, `tests/**`, `.gitea/workflows/**` + `.github/workflows/**`, `.env` + `.env.secrets` (key rename), `schemas/tagging-standard.json`, `adapters/terraform/policy/custom_rules/acdl_tagging.py` → `nova_tagging.py` (D-109: warn mode) | -| P3 ssm-tagkeys | data-engineer | backend-engineer (readers) | `core/output_publisher.py` (SSM path `/nova/`), `core/contract_resolver.py` (SSM reads), `scripts/migrate_ssm_paths.py` (NEW), `terraform/**` (tag keys `nova:*`), `adapters/terraform/policy/custom_rules/nova_tagging.py` (D-109: hard mode), ABAC session-policy terraform | -| P4 aws-resource-migration | data-engineer | lead-developer (runbook) | `terraform/platform/main.tf`, `terraform/microservice/main.tf`, `terraform/ci-vpc/main.tf`, `terraform/bootstrap/**`, `modules/l1/alb/instance.json`, `scripts/migrate_dynamodb_data.py` (NEW), `docs/NOVA_AWS_MIGRATION.md` (NEW runbook), `core/lambda/contract_ingestor.py` (default table names → `nova-*`, D-111) | -| P5 final-review-ship | lead-developer | all active (review) | `.ciagent/**` (REQUIREMENTS/ROADMAP/PROJECT complete), `core/env.py` (remove dual-read fallback), `nova_tagging.py` (hard-fail `acdl:*`), review + audit | - -### v1.15 domain priority - -`lead → backend → data` (inverted from v1.11) - -Rationale: the rebrand is docs/prose-first (P1 establishes the -vocabulary, no runtime impact), then code/env-vars/consumer-path (P2), -then SSM/tag-keys (P3), then the heavy terraform/AWS migration (P4). -Lead-developer owns the docs + runbooks + verification + final ship; -backend-engineer owns the dual-read helper + call-site migration + -contract resolver; data-engineer owns the terraform resource/tag/SSM -migration (the heaviest terraform territory). Co-authoring expected at: -`core/env.py` + `core/*.py` boundary (backend + lead on the helper -design), `nova_tagging.py` + `schemas/tagging-standard.json` boundary -(backend authors the rule, data-engineer owns the tag-key schema), -`core/output_publisher.py` SSM path + `terraform` outputs boundary -(backend writes the reader, data-engineer owns the terraform that -produces the outputs). - -### v1.15 verification toolchain (unchanged from v1.14) - -``` -typecheck: terraform validate && python3 -m py_compile core/**/*.py adapters/**/*.py -test: bash scripts/run_regression.sh # 16-capability gate -build: bash scripts/run_ci.sh # full local CI reproduction -``` - -The regression gate (CAP-001..CAP-016) must stay **16/16 Verified** -throughout the rebrand — the rebrand must not regress any capability. -P2/P3/P4 update test fixtures that reference `ACDL`/`acdl` so the gate -stays green. - -## v1.16 Persona Addendum — Nova Simplification (2026-07-30) - -**Milestone:** v1.16-Nova-Simplification (NFR). Roster carries forward -unchanged — NFR work touches existing territories, no new domains. The -onboarding request-path (P18–P20) is backend-engineer (Lambda action + -onboarding.py) + data-engineer (cross-account Terraform) territory. -**frontend-engineer** remains deactivated. No **security-engineer** -persona — the ingestor defense-in-depth (P10) is backend-engineer with -lead-developer review; IAM/ABAC (P20) is data-engineer territory. - -### v1.16 territory assignments - -| Phase | Lead | Contributors | Territory | -|-------|------|---------------|-----------| -| P1 state-bucket+kyverno fix | backend-engineer | data-engineer (kyverno policy) | `adapters/terraform/adapter.py:117`, `adapters/kyverno/policies/require-resource-labels.yml` | -| P2 user-facing brand sweep | lead-developer | backend-engineer | `core/environment_check.py`, `core/lambda/contract_ingestor.py`, `scripts/post_stage_comment.sh`, `scripts/run_ci.sh`, module docstrings, `adapters/README.md` | -| P3 dead-code+stale-prefix | lead-developer | — | `scripts/run_platform.sh`, `core/local_emulators.py`, `core/regression_verify.py`, lifecycle scripts | -| P4 migrate-ssm except | backend-engineer | — | `scripts/migrate_ssm_paths.py` | -| P5 regression-verify dedup | backend-engineer | — | `core/regression_verify.py` | -| P6 run-platform deadcode+hitl-fn | lead-developer | — | `scripts/run_platform.sh` | -| P7 contract-resolver envloader+kind | backend-engineer | — | `core/contract_resolver.py`, `modules/registry.json` | -| P8 workflow generator | lead-developer | backend-engineer (test) | `scripts/sync_workflows.py` (NEW), `tests/test_pipeline_contract.py`, `.gitea/workflows/**`, `.github/workflows/**` | -| P9 run-platform split | lead-developer | — | `scripts/run_platform.sh`, `scripts/run_decommission.sh` (NEW), `scripts/run_uptime.sh` (NEW) | -| P10 ingestor defense-in-depth | backend-engineer | lead-developer (review) | `core/lambda/contract_ingestor.py`, `core/environments/` | -| P11 ingestor payload validation | backend-engineer | — | `core/lambda/contract_ingestor.py` | -| P12 split contract-resolver | backend-engineer | — | `core/contract_resolver.py` → `core/contract_resolve.py` + `core/decommission_transform.py` + `core/contract_resolver_cli.py` | -| P13 split regression-verify | backend-engineer | — | `core/regression_verify.py` → split modules | -| P14 schema-driven outputs+cache | backend-engineer | data-engineer (interface.json) | `core/output_publisher.py`, `core/contract_resolver.py`, `modules/l1/*/interface.json` | -| P15 run-platform --help+flags | lead-developer | — | `scripts/run_platform.sh`, `README.md` | -| P16 workflows README catalog | lead-developer | — | `.github/workflows/README.md` (NEW) | -| P17 getting-started consolidation | lead-developer | — | `README.md` | -| P18 onboarding schema+lambda | backend-engineer | lead-developer (schema) | `schemas/onboarding.schema.json` (NEW), `core/lambda/contract_ingestor.py` | -| P19 onboarding envfile autogen | backend-engineer | lead-developer (docs) | `core/onboarding.py` (NEW), `core/environment_check.py`, `core/environments/README.md` | -| P20 cross-account role offline | data-engineer | backend-engineer (ABAC) | `terraform/onboarding/` (NEW), `terraform/platform/main.tf` | -| P21 final-review-ship | lead-developer | all active (review) | `.ciagent/**`, review + audit + ship | - -### v1.16 domain priority - -`backend → lead → data` (the simplification + security + ingestor work -is backend-heavy; lead-developer owns docs/DX/splits; data-engineer owns -the P20 cross-account Terraform only). - -### v1.16 verification toolchain - -``` -typecheck: terraform validate && python3 -m py_compile core/**/*.py adapters/**/*.py -test: bash scripts/run_regression.sh # 22-capability gate (D-118: P9 + P21) -build: bash scripts/run_ci.sh # full local CI reproduction -``` - -The regression gate (22 capabilities) must stay **22/22 Verified** -throughout v1.16 — simplification must not regress any capability -(D-118). P9 (end of Wave 2) and P21 (milestone complete) run the gate; -P14 (end of Wave 3) is an offline mid-milestone checkpoint. - ---- - -# v1.17 Persona Roster — Strategic Direction, Leadership Metrics & Unified Story - -> v1.17 adds a telemetry/observability layer (P1–P3), a metrics catalog -> + NORTH_STAR integration (P4), a unified narrative deck (P5), a -> regression capability (P6), and a final review/ship (P7). Three -> active personas; frontend-engineer stays deactivated (no Nova web UI -> — dashboards are PowerBI, not a Nova-built frontend). - -## Active personas - -### lead-developer -- **Domain:** coordination + deck narrative -- **Active:** true -- **Phase-specific:** false -- **Reason:** Owns CIAgent metadata, the NORTH_STAR.md authoring - process (P0), the milestone decomposition, the unified narrative deck - co-authoring (P5 — the deck is markdown, which is lead-developer - territory per the established convention), and the final review/ship - (P7). Arbitrates persona conflicts (e.g., backend vs data on the - emitter/store boundary). -- **Territory:** `.ciagent/NORTH_STAR.md`, `.ciagent/PROJECT.md`, - `.ciagent/REQUIREMENTS.md`, `.ciagent/PLAN.md`, `.ciagent/RESEARCH.md`, - `.ciagent/ARCHITECTURE.md`, `docs/presentations/nova-no-humans-platform.md` - (NEW — unified deck source of truth), `docs/presentations/nova-no-humans-platform-marp.md`, - `docs/presentations/nova-no-humans-platform-talking-points.md`, - `docs/METRICS.md`, `docs/metrics/*.md` (per-KPI definition docs). - -### backend-engineer -- **Domain:** backend (event emitters + instrumentation) -- **Active:** true -- **Phase-specific:** false -- **Reason:** Owns the event emitters (P1): the CloudEvents envelope, - the per-run manifest writer, the `outbox_writer.py` extension to the - SQLite Decision Ledger, the Infracost post-processor, the - `hitl_gates.py` attestation event emission, the `confidence_signal.py` - decision event emission, the `checkov_adapter.py` policy event - emission, and the pytest `--junitxml` addopts change. Also owns the - `regression_verify.py` CAP-023/024 additions (P6). The emitter work - is the bridge between existing Nova components and the new metrics - layer — it touches the code paths that already exist. -- **Territory:** `core/metrics/event_envelope.py` (NEW), - `core/metrics/run_manifest.py` (NEW), - `core/metrics/infracost_adapter.py` (NEW), - `core/metrics/decision_ledger.py` (NEW — extends outbox_writer), - `core/outbox_writer.py` (extend to SQLite), - `core/hitl_gates.py` (emit attestation.recorded), - `core/confidence_signal.py` (emit ai.decision.made), - `adapters/terraform/policy/checkov_adapter.py` (emit policy.evaluated), - `scripts/run_platform.sh` (invoke manifest writer + Infracost), - `core/regression_verify.py` (CAP-023/024), - `pyproject.toml` (addopts --junitxml), - `tests/test_metrics_emitters.py` (NEW), - `tests/test_decision_ledger.py` (NEW). - -### data-engineer -- **Domain:** data (schema, SQLite store, PowerBI export) -- **Active:** true -- **Phase-specific:** false -- **Reason:** Reactivated with a new territory for v1.17: the metrics - collector (P2) and the PowerBI export (P3). Owns the schema design - (metrics_*.schema.json), the SQLite cold store (nova_metrics.db), the - fact/dimension table design, the 8 deferred placeholder views, and - the CSV/JSON export. The data-engineer's schema-first constraint - applies: all event types and fact/dim tables have JSON Schema - definitions before any code is written. The collector reads files + - events → SQLite; the export reads SQLite → CSV/JSON. This is the - heaviest data-territory work since v1.11's terraform modules. -- **Territory:** `core/metrics/collector.py` (NEW), - `core/metrics/powerbi_export.py` (NEW), - `schemas/metrics_*.schema.json` (NEW — event + fact/dim schemas), - `metrics/nova_metrics.db` (NEW — SQLite cold store), - `metrics/powerbi/` (NEW — CSV/JSON export dir), - `docs/METRICS_VIEWS.md` (NEW — schema doc for PowerBI views), - `tests/test_metrics_collector.py` (NEW), - `tests/test_powerbi_export.py` (NEW). +- **Frameworks:** ["jsonschema", "dynamodb (item shape)"] +- **Constraints:** ["schema-first", "superset-gate NOT duplicate (PROJECT.md hard constraint)", "W3.E per-env mandatory table is the source of truth"] +- **Territory:** + - `schemas/**` (REQ-217 — `submission-readiness.schema.json` is the new schema; existing schemas untouched) + - `core/lambda/contract_ingestor.py` (the `--check-readiness` subcommand wiring, D-133 — the validator is in `core/submission_readiness.py` but the ingestor dispatches to it; co-owned with backend-engineer) +- **Reason:** Owns the submission-readiness JSON Schema (REQ-217) — it + is a schema artifact, data-engineer territory. The schema is a + *superset gate above* `contract.schema.json`, not a duplicate (it + references contract fields, does not redefine them). The + per-env-mandatory table comes from W3.E (the locked decision). The + ingestor wiring is co-owned with backend-engineer (the dispatch point + is backend; the schema it validates against is data). +- **Phase-specific flag:** none (active for P3 schema + ingestor wiring). ## Deactivated personas ### frontend-engineer -- **Domain:** frontend - **Active:** false -- **Phase-specific:** false -- **Reason:** v1.17 has no Nova web UI. The leadership dashboards are - PowerBI (an external tool that ingests CSV/JSON files), not a - Nova-built frontend. The decks are markdown (lead-developer - territory). frontend-engineer stays deactivated, consistent with - v1.11–v1.16. Reactivates if a future milestone builds a Nova web UI. +- **Domain:** frontend +- **Frameworks:** ["react", "next.js"] (inert — no territory) +- **Constraints:** ["component-first", "server-components", "minimal-client-js"] (inert) +- **Territory:** [] (no territory in v1.18) +- **Reason:** v1.18 has no frontend; decks are markdown (lead-developer + territory); deactivated per PERSONAS.md v1.17 precedent. v1.18's + observability stays PowerBI / external (Out of Scope: "A Nova-built + frontend / dashboard"). The MCP server exposes tools to an AI agent, + not a web UI. No reactivation trigger in this milestone. -### lambda-engineer, platform-engineer, security-engineer -- **Active:** false (carried forward from v1.11) -- **Reason:** v1.17 does not touch the Lambda (beyond emitting events - from the existing hitl_gates/attestation_matrix), does not do IR- - shaped module authoring, and does not touch security adapters beyond - emitting policy.evaluated events. The existing components are - instrumented, not rewritten. +## Roster decisions -## v1.17 phase assignment +### D-143 (0.90): Fold mcp-engineer into backend-engineer +The MCP plugin-registry (D-140: `plugins/.py register(mcp)`) is a +backend code pattern — Python modules, type hints, stdio transport, +urllib for the Gitea asset API. It shares nothing with the data domain +(schemas/DynamoDB) and is not a new engineering discipline. Creating a +separate `mcp-engineer` persona would fragment ownership of the server + +its tests + the render/attach scripts (all backend). **Decision:** fold +into backend-engineer. backend-engineer's `frameworks` list gains +`mcp (Python SDK v2)`. Confidence 0.90 — the only counter-argument is +that MCP is a distinct protocol skill, but the SDK v2 API surface +(`@mcp.tool()` + type hints) is small and well within backend-engineer's +range (it's the same Pydantic/FastAPI-style pattern the persona already +knows). -| Phase | Primary persona | Supporting | Territory | -|-------|----------------|------------|-----------| -| P0 pre-execution | lead-developer | — | `.ciagent/NORTH_STAR.md`, `PROJECT.md`, `REQUIREMENTS.md`, `RESEARCH.md`, `ARCHITECTURE.md`, `PERSONAS.md`, `PLAN.md` | -| P1 event-emitters | backend-engineer | data-engineer (schemas) | `core/metrics/event_envelope.py`, `run_manifest.py`, `decision_ledger.py`, `infracost_adapter.py`, `outbox_writer.py`, `hitl_gates.py`, `confidence_signal.py`, `checkov_adapter.py`, `run_platform.sh`, `pyproject.toml` | -| P2 metrics-collector | data-engineer | backend-engineer (event formats) | `core/metrics/collector.py`, `schemas/metrics_*.schema.json`, `metrics/nova_metrics.db` | -| P3 powerbi-export | data-engineer | — | `core/metrics/powerbi_export.py`, `metrics/powerbi/`, `docs/METRICS_VIEWS.md` | -| P4 metrics-catalog + north-star-integration | lead-developer | data-engineer (metric definitions) | `docs/METRICS.md`, `docs/metrics/*.md`, `PROJECT.md`, `ARCHITECTURE.md`, `config.json` | -| P5 deck-rebuild | lead-developer | — | `docs/presentations/nova-no-humans-platform*.md`, retire old decks | -| P6 regression-capability | backend-engineer | data-engineer (CAP-023 schema) | `core/regression_verify.py` (CAP-023, CAP-024) | -| P7 final-review-ship | lead-developer | all active (review) | `.ciagent/**`, review + audit + ship | +### Territory-overlap resolution (co-ownership) -## v1.17 domain priority - -`backend → data → lead` (the emitter work in P1 is the foundation; -data-engineer's collector + export in P2–P3 depends on P1's event -formats; lead-developer's catalog + deck in P4–P5 depends on the -metrics being grounded). - -## v1.17 verification toolchain - -``` -typecheck: terraform validate && python3 -m py_compile core/**/*.py adapters/**/*.py -test: bash scripts/run_regression.sh # 22-capability gate + CAP-023/024 (v1.17) -build: bash scripts/run_ci.sh # full local CI reproduction -``` - -The regression gate (22 capabilities + CAP-023 metrics collector + -CAP-024 deck structure) must pass at P6 and P7. CAP-009 (offline pytest -suite) must remain Verified after the `--junitxml` addopts change -(assumption A5). +| Path | Primary | Co-owner | Why | +|------|---------|----------|-----| +| `docs/submission-readiness.md` | lead-developer (narrative + examples) | backend-engineer (reason-code catalog, REQ-218 codes) | The doc is citizen-developer-facing copy (lead) but the reason-code catalog (MISSING_TAGS, ENV_MISSING_MANDATORY, AGENTIC_MISSING_INTENT, MISSING_APP_SOURCE, POLICY_PRECONDITION_MISSING) is backend (it mirrors the validator's return codes). | +| `core/lambda/contract_ingestor.py` | backend-engineer (dispatch wiring) | data-engineer (the schema it validates against) | D-133 places the `--check-readiness` subcommand on the ingestor (backend dispatch), but the readiness schema it loads is data-engineer territory. | +| `schemas/submission-readiness.schema.json` | data-engineer (schema artifact) | backend-engineer (the validator must match it) | The schema is data-engineer's; the validator (REQ-218) is backend-engineer's and must stay in sync with it. | \ No newline at end of file diff --git a/.ciagent/RESEARCH.md b/.ciagent/RESEARCH.md index 755aed5..8dba006 100644 --- a/.ciagent/RESEARCH.md +++ b/.ciagent/RESEARCH.md @@ -1493,3 +1493,856 @@ Total: ~12–16 slides. Opening = arc preview; closing = recap + ask. cost estimate. No live AWS access required. If Infracost is not available, the `cost.estimated` event is omitted (degraded mode, not a failure). + +--- + +## v1.18 Research — Citizen Developer & Production-Grade Guidance + +> Phase 0 RESEARCH. Autonomy = full. Findings are evidence-grounded +> (fetched from live sources, not assumed). The Atelier repo, the MCP +> Python SDK v2 docs, the existing Nova schemas/ingestor, and the Marp +> CLI README were all fetched directly. Decisions are logged with +> confidence scores; low-confidence items are flagged. + +### 1. Atelier Integration Reference + +#### 1.1 The 8 core principles (C1–C8) + +Source: `core/first-principles.md` (fetched 2026-08-06 from +`https://git.cloudinit.dev/coreci/atelier/raw/branch/main/core/first-principles.md`). +Precedence is a **total order** — a lower-numbered principle is never +sacrificed for a higher-numbered one (C1 never sacrificed; C2 only for +C1; C3 only for C1/C2; C4–C8 tradeable among themselves but always below +C1–C3). + +| ID | Principle | One-line description | +|----|-----------|----------------------| +| **C1** | Correctness | The system does what it is supposed to do, and nothing else. Highest principle; never overridden. Security is a subset (exploitable code is incorrect). Includes temporal correctness (a late answer is wrong when the deadline mattered). | +| **C2** | Clarity | The intent of the code is obvious to its reader. Optimize for the reader; names reveal intent; comments explain *why* not *what*. Unclear code is where bugs hide. | +| **C3** | Simplicity | The solution is as simple as possible, and no simpler. Complexity is the enemy of correctness; every line is a liability. Not laziness — the result of removing everything unnecessary. | +| **C4** | Locality | Decisions and their consequences live near each other. State, logic, side effects that depend on each other live near each other. A change needing many distant files is a locality violation. | +| **C5** | Reversibility | Every decision can be undone, and the cost of undoing is known. Migrations/deploys/schema/API changes reversible by default. Versioning, feature flags, rollback paths are the mechanisms. | +| **C6** | Composability | Parts combine into wholes, and the parts are reusable in new wholes. A part that does one thing well composes; the boundary is its contract. Composable parts are understandable in isolation. | +| **C7** | Observability | The system's behavior is visible to the people who must understand it. Logs/metrics/traces are first-class, designed in. An observable system answers "what/why/what next" without reading source. | +| **C8** | Economy | The system uses no more resources than the task requires (time, memory, attention, money, complexity). Most tradeable principle; unbounded growth in any resource is a defect. | + +The precedence string (from `core/first-principles.md` §3): +`C1 Correctness > C2 Clarity > C3 Simplicity > C4 Locality > C5 Reversibility > C6 Composability > C7 Observability > C8 Economy`. + +Conflict resolution (`core/conflict-resolution.md`, fetched): a +deterministic 6-step procedure. The **hierarchy** is +`core/first-principles.md` > `domains//first-principles.md` > +`domains//.md` > `languages/.md` > `examples/.md`. +Same-level conflicts resolve by core derivation (via the matrix), then +by specificity, then by filing an issue (a tie is a defect). A domain's +"non-tradeable" declaration (e.g. Security: 8 of 10) promotes those +rules to **C1-equivalent** — a binding escalation recorded in the +matrix's derivation. + +#### 1.2 The 19 domains and Nova-citizen-dev relevance + +Source: `matrix/principles-matrix.md` (fetched) + the releases page +(v0.4 milestone = 19 domains, 190 P-rules, confirmed in the v0.3.6 +release notes and the matrix Coverage Summary). + +| # | Domain | Atelier path | P-rules | Nova-citizen-dev relevant? | Reason | +|---|--------|--------------|---------|------------------------------|--------| +| 1 | UI/UX | `domains/uiux/` | 10 | **NO** — excluded | v1.18 has no frontend (Out of Scope: "A Nova-built frontend / dashboard"). Decks are markdown, not a UI. | +| 2 | API Design | `domains/api/` | 10 | **YES** | A citizen developer building a web API / worker / scheduled job touches API contracts. Maps to `skills/api.md`. | +| 3 | Security | `domains/security/` | 10 | **YES** | Zero-trust, input validation, secret hygiene, fail-securely — universal for any production-grade service. Maps to `skills/security.md`. | +| 4 | Data | `domains/data/` | 10 | **YES** | Schema-as-truth, migration safety, referential integrity — applies to any stateful service. Maps to `skills/data.md`. | +| 5 | Testing | `domains/testing/` | 10 | **YES** | Tests-as-specification, determinism, edge-case coverage — required for a citizen developer's UAT. Maps to `skills/testing.md`. | +| 6 | Performance | `domains/performance/` | 10 | **YES (reference, not a skill)** | Measure-first, bounded operations, no N+1, timeouts. NOT one of the 9 REQ-221 skills; Performance principles are cited inside the 9 skills + the index. | +| 7 | Observability | `domains/observability/` | 10 | **YES** | Structured logs, correlation IDs, no secrets in logs — the "basic observability bootstrap" BA.A skill. Maps to `skills/observability.md`. | +| 8 | Errors | `domains/errors/` | 10 | **YES** | Errors are data, fail loudly + specifically, preserve context — production-grade error handling. Maps to `skills/errors.md`. | +| 9 | Documentation | `domains/documentation/` | 10 | **YES (reference, not a skill)** | Docs-as-code, audience awareness, examples mandatory. REQ-221 does NOT list `skills/documentation.md`; a self-referential "documentation skill" is redundant. Principles cited inside `docs/skills.md` index. | +| 10 | Concurrency | `domains/concurrency/` | 10 | **YES (reference, not a skill)** | Immutability, bounded queues, timeouts — advanced for a citizen developer's first 5 skills. REQ-221 does NOT list `skills/concurrency.md`. Top rules cross-referenced inside `skills/api.md` + `skills/errors.md`. | +| 11 | DevOps | `domains/devops/` | 10 | **YES** | Reproducibility, rollback-first, config-as-code — the citizen developer co-owns Release Management (RACI). Maps to `skills/devops.md`. | +| 12 | Infrastructure as Code | `domains/infrastructure-as-code/` | 10 | **YES** | Declarative intent, idempotence, plan-before-apply, no secrets in HCL — directly relevant to the Nova contract→Terraform path. Maps to `skills/infrastructure-as-code.md`. | +| 13 | Kubernetes | `domains/kubernetes/` | 10 | **NO** — excluded | Nova emits Terraform (ECS/Fargate per the architecture), not K8s manifests. Kyverno adapter is "ready but inactive" (D-053). Not citizen-dev-relevant. | +| 14 | GitOps + Operators | `domains/gitops-operators/` | 10 | **NO** — excluded | Nova uses a push pipeline (contract → resolve → plan → apply), not a pull-based reconciler. Not citizen-dev-relevant. | +| 15 | AI/ML | `domains/ai-ml/` | 10 | **YES (reference, not a skill)** | Reproducibility, data versioning, drift detection — relevant *to Nova itself* (Nova is an agentic platform), but a citizen developer on Nova is NOT building ML models; they consume Nova's agentic capability. REQ-221 does NOT list `skills/ai-ml.md`; the Atelier AI/ML domain is platform-team guidance, not citizen-dev guidance. | +| 16 | i18n | `domains/i18n/` | 10 | **NO** — excluded | Not relevant to a citizen developer's first production-grade service on Nova. | +| 17 | Compliance | `domains/compliance/` | 10 | **YES** | Audit logs append-only, policy-as-code, evidence-by-operation — directly relevant (Nova's compliance posture is a selling point). Maps to `skills/compliance.md`. | +| 18 | Edge | `domains/edge/` | 10 | **NO** — excluded | Nova does not deploy edge/CDN for the citizen developer's first 5 skills; Route53/ACM/CloudFront are consumer-supplied extension points (D-049). | +| 19 | Messaging | `domains/messaging/` | 10 | **NO** — excluded | The citizen developer's first 5 skills (web API / worker / scheduled job / static asset / observability bootstrap) do not require a broker; messaging is a future capability. | + +**Relevant count:** 13 of 19 are relevant to *some* Nova audience +(YES or YES-reference). Of those, **9 become skills** (per REQ-221, the +planned count). The other 4 relevant domains (Performance, Documentation, +Concurrency, AI/ML) are **reference-only** — their principles are cited +inside skills or the `docs/skills.md` index, but they do NOT get their +own skill file. This matches REQ-221's exact 9-skill list. + +**Excluded count:** 6 of 19 (UI/UX, Kubernetes, GitOps, i18n, Edge, +Messaging) are not relevant to a Nova citizen developer building a +production-grade application — confirmed. + +#### 1.3 Atelier domain → Nova skill mapping (final 9-skill list) + +REQ-221 names exactly 9 skills. The research **confirms the planned 9** +— no adjustment needed. The mapping (each skill cites its Atelier source +path + distills the citizen-developer-relevant subset + links to +agent-checklist triggers + maps to the BA.A 5-skill catalog): + +| Nova skill file | Atelier domain path | P-rules distilled | BA.A catalog skill it extends | +|-----------------|----------------------|-------------------|--------------------------------| +| `skills/api.md` | `domains/api/` | P1 Contract Fidelity, P2 Clarity, P5 Versioning, P6 Idempotency, P8 Security, P9 Error Transparency | web API | +| `skills/security.md` | `domains/security/` | P1 Zero Trust, P2 Least Privilege, P4 Input Validation, P6 Crypto Correctness, P8 Fail Securely, P9 Secret Hygiene | all 5 (cross-cutting) | +| `skills/data.md` | `domains/data/` | P1 Truth, P3 Invariants in Schema, P4 Migration Safety, P7 Type Fidelity, P9 Referential Integrity | web API, worker, scheduled job | +| `skills/testing.md` | `domains/testing/` | P1 Tests as Specification, P3 Determinism, P5 Coverage of Behavior, P9 Edge Case Coverage, P10 No Test Theater | all 5 (UAT is a citizen-developer RACI responsibility) | +| `skills/observability.md` | `domains/observability/` | P1 Structured by Default, P2 Correlation, P6 No Secrets in Obs, P7 Actionable Alerts | basic observability bootstrap | +| `skills/errors.md` | `domains/errors/` | P1 Errors are Data, P2 Fail Loudly, P3 Fail Specifically, P4 Preserve Context, P5 Recoverable When Possible | web API, worker, scheduled job | +| `skills/devops.md` | `domains/devops/` | P1 Reproducibility, P4 Rollback First, P5 Progressive Delivery, P6 Config as Code, P8 Security at Every Layer | scheduled job, worker (deploy/release is co-owned Release Mgmt) | +| `skills/infrastructure-as-code.md` | `domains/infrastructure-as-code/` | P1 Declarative Intent, P2 Idempotence, P4 Plan Before Apply, P5 Version Everything, P10 Secrets Never in Code | static asset (the contract→Terraform path) | +| `skills/compliance.md` | `domains/compliance/` | P1 Audit Logs Append-Only, P2 Every Significant Action Logged, P4 Policy is Code, P5 Policy is Evaluated as a Gate, P9 Secrets Redacted in Audit | all 5 (cross-cutting; Nova's compliance posture) | + +**Final recommendation: 9 skills, exactly as REQ-221 planned.** +Confidence 0.95 — the planned list maps cleanly to the relevant Atelier +domains and to the BA.A 5-skill catalog; the 4 "reference-only" domains +(Performance, Documentation, Concurrency, AI/ML) are correctly *not* +elevated to skills (a citizen developer's first production-grade service +does not need a standalone Concurrency or AI/ML skill; Performance and +Documentation principles are cited inside the 9 skills + the index). + +#### 1.4 Agent-checklist → MCP `atelier.validate_against_principles` checks + +Source: `review/agent-checklist.md` (fetched). The checklist has a +**Core (C1–C8)** section (8 subsections, ~30 boolean items) plus +**domain-specific trigger sections** (one per domain; Nova-relevant +ones: API, Security, Data, Testing, Performance, Observability, Errors, +Concurrency, DevOps, IaC, Compliance). + +The MCP `atelier.validate_against_principles` tool (REQ-223, in +`plugins/validation.py`) runs the relevant checklist items against a +code/diff snippet. The tool input model: + +```python +class ValidateInput(BaseModel): + snippet: str # the code/diff to validate + language: str # e.g. "python", "terraform", "yaml" + domains: list[str] # e.g. ["security", "api"] — which domain triggers to run + run_core: bool = True # always run C1–C8 unless explicitly skipped +``` + +The structured output model (Pydantic, returned as `structured_content`): + +```python +class Violation(BaseModel): + principle: str # e.g. "C1", "security/P9" + checklist_item: str # the verbatim checklist question + severity: str # "C1" (blocking) | "non-tradeable" | "tradeable" + evidence: str # the snippet substring + why it fails + fix_hint: str # the principle's remediation guidance + +class ValidateResult(BaseModel): + snippet_id: str # hash of the snippet for replay + passed: bool + violations: list[Violation] + domains_checked: list[str] + core_checked: bool +``` + +**Checklist → check mapping** (the validation plugin encodes each +checklist item as a boolean predicate over the snippet + language): + +| Checklist section | MCP check behavior | Nova-relevant? | +|-------------------|--------------------|-----------------| +| **C1 Correctness** (4 items) | Run all 4; any fail → `severity: "C1"` (blocking). | YES — always run (core) | +| **C2 Clarity** (4 items) | Heuristic checks: name smell (`data/temp/x/doStuff`), comment-why ratio. | YES — always run | +| **C3 Simplicity** (4 items) | Dead-code heuristic, function-length, premature-abstraction. | YES — always run | +| **C4 Locality** (3 items) | Cross-file-change heuristic (for diffs); within-file coupling. | YES — always run | +| **C5 Reversibility** (3 items) | Migration-has-down, deploy-has-rollback presence checks. | YES — always run | +| **C6 Composability** (3 items) | Single-responsibility heuristic, boundary-typed check. | YES — always run | +| **C7 Observability** (4 items) | Log-presence, error-context, metric, **no-secrets-in-logs** (hard check). | YES — always run | +| **C8 Economy** (3 items) | Unbounded-growth, no-timeout, resource-leak heuristics. | YES — always run | +| If API | 5 items: nouns-plural-lowercase, status codes, structured errors, schema validation, auth-required. | YES — when `domains` includes "api" | +| If Security | 5 items: no-secrets-in-code/logs/URLs, input-validation, output-encoding, vetted-crypto, authz-checked. **All 5 are non-tradeable** (Security domain §3). | YES — when "security" | +| If Data | 5 items: schema-reflects-domain, constraints-in-schema, migration-up-down, domain-types, no-SELECT-star. | YES — when "data" | +| If Testing | 4 items: independence, determinism, edge-cases, failure-specificity. | YES — when "testing" | +| If Performance | 4 items: no-unbounded, no-N+1, timeouts, cache-invalidation. | YES — when "performance" | +| If Observability | 4 items: structured-logs, correlation-id, no-high-cardinality, alerts-have-runbooks. | YES — when "observability" | +| If Errors | 4 items: not-swallowed, specific, context-preserved, recovery-attempted. | YES — when "errors" | +| If Concurrency | 5 items: shared-state-minimized, minimal-locks, bounded-queues, timeouts, cancellation. | YES — when "concurrency" | +| If DevOps | 4 items: pipeline-is-process, rollback-known, config-in-code, env-parity. | YES — when "devops" | +| If IaC | 8 items: declarative, pinned-providers, remote-locked-state, plan-before-apply, no-secrets-in-HCL, versioned-modules, drift-is-incident, least-priv-providers. | YES — when "infrastructure-as-code" | +| If Compliance | 10 items: append-only-audit, a-priori-action-set, retention-as-policy, policy-as-code, policy-as-gate, continuous-evidence, attributable-identity, subject-access, redacted-secrets, observable-posture. | YES — when "compliance" | + +The validation plugin reads the vendored `review/agent-checklist.md` +(frozen at the pinned tag — §1.6) so the checks are replayable against +the exact checklist version that produced a result. The plugin maps each +checklist line to a predicate function keyed by `(language, principle)` +so a "no secrets in code" check runs differently for Python (ast scan for +string-constant assignment) vs Terraform (HCL scan for hardcoded +provider keys) vs YAML (scan for `api_key:` literals). + +#### 1.5 Principle-lookup query model + +`atelier.lookup_principle(domain: str, principle_id: str)` (REQ-223, in +`plugins/principles.py`) resolves a principle reference to its full +text + core derivation + checklist items. Resolution model: + +**Input:** +```python +class LookupInput(BaseModel): + domain: str # "security" | "api" | "data" | ... | "core" + principle_id: str # "P4" | "C1" (core) | "P9" +``` + +**Resolution path (the lookup algorithm):** +1. If `domain == "core"`: load `vendor/core/first-principles.md`, parse + the `### C. ` section for `principle_id` (e.g. `C1` → + the "C1. Correctness" section). Return the full principle text. +2. Else: load `vendor/domains//first-principles.md`, parse the + `### P. ` section for `principle_id` (e.g. `security/P4` → + the "P4. Input Validation" section). +3. **Cross-reference the matrix:** load + `vendor/matrix/principles-matrix.md`, find the row for + ` P`, extract the `Core` column (e.g. Security P4 → `C1`). + This is the core derivation. +4. **Cross-reference the checklist:** load + `vendor/review/agent-checklist.md`, find the `If ` section, + extract the checklist items tagged with `P` (the IaC section + tags items with `(P1)`, `(P10)` etc.; the Security section items map + to P9, P4, P5, P6, P1/P10 by content). +5. **Check non-tradeable status:** load + `vendor/domains//first-principles.md` §3 (Conflict + Resolution); if the principle is listed as "never sacrificed", mark + `non_tradeable: true` (escalates it to C1-equivalent per + `core/conflict-resolution.md` §6). + +**Return (structured output):** +```python +class PrincipleLookup(BaseModel): + domain: str # "security" + principle_id: str # "P4" + name: str # "Input Validation" + text: str # full principle body + core_derivation: list[str] # ["C1"] (from the matrix) + non_tradeable: bool # True for security P1-P8, P9; False for P10 + checklist_items: list[str] # the verbatim checklist questions for this P-rule + source_path: str # "domains/security/first-principles.md" (relative to vendor/) +``` + +**Example resolution — `atelier.lookup_principle("security", "P4")`:** +- `name`: "Input Validation" +- `text`: "All input is untrusted until proven otherwise. Validation + happens at the boundary, against a schema, with explicit failure + modes." +- `core_derivation`: `["C1"]` (matrix row: Security P4 → C1) +- `non_tradeable`: `true` (Security §3 lists P4 as "never sacrificed") +- `checklist_items`: `["Input is validated at the boundary", "Output is + encoded for its context"]` (from `review/agent-checklist.md` If Security) +- `source_path`: `"domains/security/first-principles.md"` + +The two companion tools: +- `atelier.list_domains()` → returns the 19 domain names + their + P-rule counts + relevance flag (the plugin hardcodes the + Nova-relevance table from §1.2 so the citizen developer's agent can + filter to the 13 relevant / 9 skill-bearing domains). +- `atelier.matrix_lookup(domain: str)` → returns the full domain→core + mapping for one domain (all 10 P-rules → their core C-rule(s)), used + by `validate_against_principles` to set `severity` and by conflict + resolution when two findings collide. + +#### 1.6 Recommended Atelier pinned tag to vendor + +**Recommendation: vendor tag `v0.3.6`** (the v0.4 milestone release). + +Evidence (from `https://git.cloudinit.dev/coreci/atelier/releases`, +fetched 2026-08-06): +- The latest release is **v0.3.6**, dated 2026-08-05 16:22:58 +00:00, + tagged `v0.3.6` (commit `66b4767d25`), marked **Stable**, with the + title "v0.3.6 — v0.4 milestone: Edge + Messaging + Language-Derived + Docs". +- It is the **v0.4 milestone release** (the release notes state: + "v0.4 — Edge + Messaging + Language-Derived Docs (Milestone + Release). Tag: v0.3.6 (NFR milestone — final patch IS the deliverable; + no separate minor tag per branch-strategy.md)"). +- The matrix is at its complete state: **19 domains, 190 P-rules** + (the Coverage Summary in `matrix/principles-matrix.md` confirms this + exactly; the v0.3.6 release notes confirm "170 → 190 P-rules across + 19 domains"). All 190 P-rules trace to ≥1 core C-rule (no orphans — + verified in the release audit). +- `-11 commits to main since this release` — there is post-release + activity on `main`, which is exactly why pinning matters: vendoring + `main` HEAD would be a moving target. `v0.3.6` is the frozen, + audited, reproducible snapshot. This satisfies D-136 (vendor for audit + reproducibility) — an agentic validation result must be replayable + against the exact principles that produced it. + +**Vendoring mechanics (for REQ-224):** +- `mcp/atelier/vendor/` = a clean copy of the Atelier repo at tag + `v0.3.6` (the `core/`, `domains/`, `matrix/`, `review/` directories — + the docs the MCP tools read; `examples/` and `languages/` are optional + but cheap to include for completeness). +- `mcp/atelier/vendor/VERSION.md` records: tag `v0.3.6`, commit + `66b4767d25`, date 2026-08-05, milestone "v0.4 Edge + Messaging + + Language-Derived Docs", P-rule count 190, domain count 19. +- `scripts/update_atelier_vendor.sh` = a helper that takes a tag arg, + fetches the tarball from + `https://git.cloudinit.dev/coreci/atelier/archive/.tar.gz`, + extracts the doc directories into `mcp/atelier/vendor/`, and updates + `VERSION.md`. Intentional upgrades only (re-run + re-audit). + +Confidence: 0.95. The only risk is that a v0.5 milestone lands before +P5 ships — but the pinning model (VERSION.md + update script) makes a +future upgrade a deliberate, audited action, not a silent drift. + +--- + +### 2. MCP Python SDK v2 Reference + +Source: `https://py.sdk.modelcontextprotocol.io/` (the official Python +SDK docs, fetched 2026-08-06) + the Tools page +(`.../servers/tools/`) + the Structured Output page +(`.../servers/structured-output/`). The docs document **v2, the current +stable release line** (Python 3.10+). + +#### 2.1 Confirmed API patterns + +1. **Server creation + import path.** The v2 high-level server class is + `MCPServer` (NOT `FastMCP` — that was v1; v2 renamed/restructured): + ```python + from mcp.server import MCPServer + mcp = MCPServer("atelier") # one arg = server name + ``` + This is the exact pattern shown in the docs' landing-page example and + the Tools-page example. There is no `FastMCP` import in v2. + +2. **`@mcp.tool()` decorator — inputSchema from type hints.** Confirmed + verbatim from the docs: "No JSON Schema. `a: int, b: int` *is* the + schema." The SDK reads three things from the function: + - **name** = the function name (`search_books`) + - **description** = the docstring (the model sees this) + - **arguments** = the type hints (`query: str`, `limit: int`) + The SDK generates the JSON Schema and sends it during `tools/list`. + Type hints are **the contract** — if a client sends `"limit": "ten"`, + the SDK rejects it *before the function runs*. Optional args = + default values (`limit: int = 10` → leaves `required`, gains + `default: 10`). Richer constraints via + `Annotated[int, Field(ge=1, le=50, description="...")]`. Enums via + `Literal["a", "b"]`. Pydantic `BaseModel` parameter = structured + "body" (nested as `$defs`). + +3. **Multiple tools / dynamic registration (plugin-registry).** The + `@mcp.tool()` decorator is called on the `mcp` object. A plugin + receives `mcp` and calls `@mcp.tool()` on it — this is plain Python + decorator application, no registration magic. The plugin-registry + pattern (D-140): + ```python + # plugins/principles.py + from mcp.server import MCPServer + def register(mcp: MCPServer) -> None: + @mcp.tool() + def atelier_lookup_principle(domain: str, principle_id: str) -> PrincipleLookup: + """Look up an Atelier principle by domain + ID.""" + ... + ``` + `server.py` scans `plugins/`, imports each module, calls + `register(mcp)`. Each plugin's `@mcp.tool()` calls register the tool + on the shared `mcp` object. **This is the confirmed dynamic- + registration pattern** — no `add_tool()` API is needed; the decorator + does it. + +4. **stdio transport.** The landing-page example shows `uv run mcp dev + server.py` (Inspector). For stdio transport (D-135: stdio now), the + server runs over stdio via the SDK's run entry point. The v2 server + object supports stdio as the default transport. The exact run call is + `mcp.run()` (the SDK handles the transport based on how the process + is launched — stdio when invoked by an MCP host over stdio). The + README's "no protocol handling" promise means `mcp.run()` is the only + call needed. (HTTP transport is on the same server object — Out of + Scope for v1.18, future milestone; the server object is + transport-agnostic so adding HTTP later is a transport-only change, + confirming D-135.) + +5. **outputSchema / structured output.** Confirmed: **the return type + annotation IS the output schema.** From the Structured Output page: + "the return type annotation is the output schema. It's published in + `tools/list` as `output_schema`." A Pydantic `BaseModel` return type + produces an unwrapped object schema (no `result` wrapper); a + `TypedDict` or `dataclass` works identically. The result carries + both `content` (text, for the model) and `structured_content` (data, + for the application). **Validation is enforced**: whatever the + function returns is validated against the schema before it leaves the + server — a mismatch is a tool error (not a corrupt result). This is + exactly what `atelier.validate_against_principles` needs: a + `ValidateResult(BaseModel)` return type gives the host a structured + `violations` list while giving the model a JSON-text rendering of the + same object. `structured_output=False` opts out (text-only); we do + NOT opt out for the validation tool. + +6. **`listChanged` capability / dynamic tool registration.** The v2 + docs (Tools page + landing page) describe tool registration as + declarative (`@mcp.tool()` at import time). The docs do NOT document + a runtime `listChanged` notification API on the high-level + `MCPServer`. For Nova's use case (plugins loaded once at server + startup, not added/removed at runtime), this is fine — all 4 tools + are registered before `mcp.run()`. A future milestone that adds + tools at runtime would need the low-level Server + (`advanced/low-level-server/`) for explicit notification control. + **Conclusion: no `listChanged` needed for v1.18; the plugin-registry + loads at startup, before the stdio loop.** Confidence 0.85 (the docs + are silent on a high-level `listChanged`; the low-level server has + it, but we use the high-level server). + +#### 2.2 Skeleton for `mcp/atelier/server.py` (P5 basis) + +This is the 15-line pattern Nova's server should follow (the basis for +P5 implementation): + +```python +import importlib, pathlib +from mcp.server import MCPServer + +mcp = MCPServer("atelier") # server name; stdio transport is the default + +# Plugin-registry: scan plugins/, import each, call register(mcp). +for p in sorted(pathlib.Path(__file__).parent.glob("plugins/*.py")): + if p.stem != "__init__": importlib.import_module(f".plugins.{p.stem}", __package__).register(mcp) + +@mcp.tool() +def atelier_list_domains() -> list[dict]: + """List the 19 Atelier domains with P-rule counts + Nova-relevance.""" + return [{"domain": "security", "p_rules": 10, "nova_relevant": True}, ...] + +if __name__ == "__main__": + mcp.run() # stdio transport (D-135); HTTP-ready on the same object (future) +``` + +**Notes on the skeleton:** +- `MCPServer("atelier")` — one import, one constructor arg (the name). +- The plugin loop uses `importlib` + a `register(mcp)` convention (D-140). + Each plugin's `register` body contains `@mcp.tool()` calls that + register that plugin's tools on the shared `mcp` object. `sorted()` + makes plugin load order deterministic (audit reproducibility — a + plugin load order that changes between runs would break replay). +- The sample tool shows the pattern: `@mcp.tool()`, type hints ARE the + input schema, docstring IS the description, return type IS the + output schema. The real `atelier_list_domains` returns a + `list[DomainInfo]` (a `list[BaseModel]` → wrapped in `{"result": [...]}`, + per the Structured Output docs). +- `mcp.run()` — the single entry point; stdio is the default. No + transport boilerplate. Adding HTTP later = a transport argument or a + different run call on the same object (D-135, Out of Scope for v1.18). +- The vendored Atelier snapshot (`mcp/atelier/vendor/`) is read by the + plugin tool functions (not shown in the skeleton); the plugins load + the markdown files lazily on first tool call and cache the parsed + structure in module-level dicts (C8 Economy — don't re-parse the + matrix on every lookup). + +--- + +### 3. Submission-Readiness Gap Analysis + +#### 3.1 `contract.schema.json` defines SHAPE, not the readiness gate + +Confirmed by reading `/root/acdl/schemas/contract.schema.json` (51 +lines). The schema defines the **contract shape** only: +- `required`: `["id", "name", "environment", "infrastructure"]` +- `id`: pattern `^[a-z][a-z0-9-]{2,5}$` (3–6 char acronym) +- `name`: minLength 3 +- `environment`: enum `["dev", "qa", "prod", "dr"]` +- `infrastructure`: map keyed by module name, each entry has `version` + (optional semver) + `inputs` (required, additionalProperties allowed) +- `additionalProperties: false` (top-level + per-module) + +**What it does NOT define (the gap):** +- ❌ No `tags` field (the 5 required Nova tags per D-054) +- ❌ No per-env mandatory metadata (the W3.E table: dev=stack+environment; + qa+=e2eSuite+loadTest; prod+=runbook+dashboard+oncall; dr+=drDrillRef) +- ❌ No `policyPreconditions` field (declared policy expectations) +- ❌ No `profile` field (`developer` | `agentic`; agentic requires + `naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`) +- ❌ No `appSource` field (repo + ref pointer for runtime fetch) +- ❌ No `contractId` field at the top level (the ingestor payload has + `contractId` in the Lambda envelope, but the contract *blob* itself + does not — the readiness schema promotes it to a required field per + REQ-217) + +The schema's own description confirms this is the shape: "A consumer +contract declares intent: which infrastructure to deploy, in which +environment, with which inputs." It is the *intent shape*, not the +*ready-to-start gate*. + +#### 3.2 The readiness schema is a SUPERSET gate ABOVE contract-schema validity + +Confirmed by PROJECT.md (lines 635–643, the v1.18 scope statement) and +REQ-217. The relationship: + +``` +contract.schema.json (SHAPE — id/name/environment/infrastructure) + ▲ + │ references but does NOT redefine contract fields + │ +submission-readiness.schema.json (GATE — superset above shape validity) + = contract-shape-valid (delegate to contract.schema.json) + + tags (5 required Nova tags, D-054) + + per-env mandatory (W3.E table) + + policyPreconditions (declared policy expectations) + + profile (developer | agentic + agentic markers) + + appSource (repo + ref pointer) + + contractId (non-empty, promoted to required) +``` + +PROJECT.md hard constraint (line 674–676): "The submission-readiness +schema is a superset gate above `contract.schema.json`, NOT a +duplicate — it references but does not redefine contract fields." + +This means `submission-readiness.schema.json` uses +`$ref` to `contract.schema.json` for the contract shape (or validates +the contract blob against it as a first step), then adds the gate +fields *alongside* it. The validator (REQ-218) calls +`contract.schema.json` validation **first** (the existing +`_validate_contract_schema` in the ingestor), then the readiness +checks. This is a two-layer gate, not a merged schema. + +#### 3.3 Fields the new `schemas/submission-readiness.schema.json` must add + +Per REQ-217 + W3.E (PROJECT.md line 888) + D-054 (tagging standard): + +| Field | Type | Required | Source / rule | +|-------|------|----------|---------------| +| `contractId` | string (non-empty) | **YES** | REQ-217. Promoted from the Lambda envelope to a contract-level required field. | +| `environment` | enum `dev/qa/prod/dr` | **YES** | Already in `contract.schema.json`; the readiness schema references it (does not redefine) and uses it to select the per-env mandatory set. | +| `tags` | object | **YES** | D-054 / `schemas/tagging-standard.json`. Required keys: `nova:owner`, `nova:contract`, `nova:environment`, `nova:cost-center` (`nova:ref` optional). The readiness schema references `tagging-standard.json`'s `required_tags` shape. | +| `policyPreconditions` | object (map of string→boolean/string) | **YES** | REQ-217. Declared policy expectations the platform will enforce (e.g. `{"public-ingress": false}`). | +| `profile` | enum `developer` \| `agentic` | **YES** | REQ-217 / W3.E. | +| `profile` == `agentic` → requires: `naturalLanguageIntent` (string), `confidenceAtSubmission` (number 0–1), `agentTrace` (object/string) | per W3.E | **conditional** | REQ-22 / W3.E. These are "optional everywhere" per W3.E (a `developer` profile omits them) but **required when profile is `agentic`**. | +| `appSource` | object `{repo: string, ref: string}` | **YES** | REQ-217. Repo + ref pointer for runtime fetch. | +| **Per-env mandatory (W3.E):** | | | | +| `dev` | `stack` + `environment` | **YES** | W3.E. (These are the base contract fields; the readiness schema enforces their presence for dev.) | +| `qa` adds | `validation.e2eSuite` + `validation.loadTest` | **YES for qa** | W3.E. | +| `prod` adds | `runbook` + `dashboard` + `oncall` | **YES for prod** | W3.E. | +| `dr` adds | `drDrillRef` | **YES for dr** | W3.E. | +| `inputs` map | object | optional everywhere | W3.E ("inputs map is always optional"). | + +The per-env mandatory table is a **conditional `allOf`** in JSON Schema +draft 2020-12: an `if`/`then` keyed on `environment` that requires the +env-specific fields. The reason code +`ENV_MISSING_MANDATORY::` (REQ-218) maps directly to this +conditional check. + +#### 3.4 How `contract_ingestor.py` currently works (P3 wiring point) + +Read `/root/acdl/core/lambda/contract_ingestor.py` (502 lines). The +current entry point + dispatch: + +- **Entry point:** `lambda_handler(event, context)` (line 460). Parses + `event["body"]` (JSON string) → `payload`. Reads `action` (default + `"submit_contract"`). +- **Identity validation:** `_validate_caller_identity(event, payload)` + (line 293) — checks IAM caller ARN, `consumerRepo` format, + `contractId` format (regex `^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$`), + `environment` enum (from `core/environments/*.json`, P10/REQ-174), + error-length cap. Fails closed if no IAM identity (P10). +- **Action dispatch (line 475):** + - `submit_contract` → `_submit_contract(payload)` (line 135): + validates required fields (`consumerRepo`, `contractId`, `contract`, + `environment`), size-caps the contract blob (256 KB, P11/REQ-175), + calls `_validate_contract_schema(contract)` (line 57 — validates + against `schemas/contract.schema.json` via `jsonschema`; no-op if + schema/jsonschema unavailable; bypassed by `NOVA_LAMBDA_LOCAL_BYPASS`), + writes to DynamoDB `nova-contracts` (PK `consumerRepo`, SK + `contractId#submittedAt`). + - `report_error` → `_report_error` (D-055, GitHub/Gitea issue). + - `validate_change_request` → `_validate_change_request` (REQ-93). + - `onboard_consumer` → `_onboard_consumer` (P18/REQ-182, validates + against `schemas/onboarding.schema.json`). +- **Error mapping:** ValueError → 400 (or 401 for identity failures); + other Exception → 500 (defensive top-level guard, `pragma: no cover`). + +**Where P3 adds `--check-readiness` (D-133):** + +The ingestor is a **Lambda handler**, not a CLI. D-133 says the +validator is "invoked as `contract_ingestor.py --check-readiness` +subcommand" — this is a **local CLI mode** for citizen-developer +pre-flight validation, NOT a new Lambda action. The implementation +pattern (confirmed by the existing code structure): + +1. Add a `if __name__ == "__main__":` block at the bottom of + `contract_ingestor.py` that parses `sys.argv` (argparse or manual). + The existing file has NO `__main__` block (it's Lambda-only); P3 + adds one. +2. The `--check-readiness` subcommand loads a contract file (or reads + stdin), validates it against + `schemas/submission-readiness.schema.json` (REQ-217) via the new + `core/submission_readiness.py` validator (REQ-218), and prints a + structured `ReadinessResult` (pass/fail per check + reason codes). +3. The validator (`core/submission_readiness.py`) calls + `_validate_contract_schema(contract)` first (reusing the existing + function — the shape gate), then runs the readiness checks (tags, + per-env mandatory, policyPreconditions, profile:agentic markers, + appSource). +4. On fail → the CLI exits non-zero with a **citizen-developer-facing + error** (not a stack trace) — REQ-218. On pass → proceeds to + existing ingestion (in the Lambda path, the readiness check would + be a pre-write gate; in the CLI path, it's a pre-flight check that + returns 0). + +**Reason codes (REQ-218, the validator's return vocabulary):** +`MISSING_TAGS`, `ENV_MISSING_MANDATORY::`, +`AGENTIC_MISSING_INTENT`, `MISSING_APP_SOURCE`, +`POLICY_PRECONDITION_MISSING`. Each maps to a failed check in the +schema's conditional `allOf`. The validator returns a list of these +(not a single error) so a citizen developer sees *all* gaps at once, +not one-at-a-time (C2 Clarity — the reader understands the full scope +of fixes needed). + +#### 3.5 Gitea release-asset API endpoint (for `scripts/attach_release_asset.py`) + +Confirmed from the existing `scripts/ship_phase.sh` (line 38) which +already uses the Gitea releases API, and from the Gitea API swagger +(`https://gitea.com/api/swagger`, fetched — the OpenAPI/Swagger JSON is +published there; the endpoint is standard Gitea). + +**Release creation (existing pattern, `ship_phase.sh` line 38):** +``` +POST https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/acdl/releases +Authorization: token +Content-Type: application/json +Body: {"tag_name": "...", "name": "...", "body": "..."} +Response: {"id": , ...} +``` + +**Release asset attachment (the new endpoint, for +`attach_release_asset.py`):** +``` +POST https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/acdl/releases/{release_id}/assets +Authorization: token +Content-Type: multipart/form-data +Form fields: + name = + attachment = +Response: {"id": , "name": "...", "size": ..., "download_count": 0, ...} +``` + +The Gitea API endpoint is `POST +/api/v1/repos/{owner}/{repo}/releases/{id}/assets` with a **multipart +form** containing `name` (the display filename) and `attachment` (the +file binary). The `{id}` is the numeric release ID returned by the +release-creation call (the `d.get('id')` in `ship_phase.sh` line 40). +`attach_release_asset.py` (REQ-228) takes a release tag (or ID) + a +file path, resolves the tag → release ID (GET +`/api/v1/repos/.../releases/tags/{tag}` if only the tag is known), then +POSTs the multipart form. The token comes from `.env.secrets` +(`NOVA_GITEA_TOKEN`, same as `ship_phase.sh` line 35). + +**Implementation note:** `urllib` (used throughout `contract_ingestor.py` +and `ship_phase.sh`) does not natively produce multipart form bodies — +`attach_release_asset.py` must either (a) construct the multipart +boundary + body manually (the standard `urllib` pattern), or (b) use +`requests` if available. The repo's convention is stdlib-only +(`urllib`, no `requests` dependency in the ingestor), so the script +should construct the multipart body manually (C3 Simplicity — no new +dependency for one script; C8 Economy — stdlib is sufficient). A +~30-line `multipart_encode(fields, files)` helper is the standard +stdlib pattern. + +--- + +### 4. Marp PPTX Theme Fidelity + +#### 4.1 The PPTX export path and inline-CSS survival + +Source: the Marp CLI README (`https://github.com/marp-team/marp-cli`, +fetched) + the existing `docs/presentations/README.md` (lines 93–105) ++ the v1.9.2 theme commit `ae0cb58` (verified via `git show`). + +**Confirmed export command (from `docs/presentations/README.md` line +96–99):** +```bash +CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \ + npx --yes @marp-team/marp-cli@latest --allow-local-files \ + docs/presentations/nova-no-humans-platform-marp.md \ + -o .pptx +``` + +**How PPTX export works (from the Marp CLI README, `--pptx` section):** +The default (non-editable) PPTX "consists of **pre-rendered background +images**." Marp renders each slide in a headless browser (Chrome/Chromium +via the `--browser-path` / `CHROME_PATH` env), captures the rendered +slide as a high-resolution image (default scale factor 2x — the README +states: "By default, Marp CLI will use 2 as the default scale factor in +PPTX"), and embeds those images as full-slide background pictures in the +PPTX. Presenter notes are supported; the PPTX opens in PowerPoint, +Keynote, Google Slides, LibreOffice Impress. + +**Inline `style:` CSS survival — CONFIRMED YES.** Because the slides +are **rasterized in a headless browser**, the browser's rendering engine +applies the inline `style:` CSS block (H1/H2 `#D6002A`, title-slide bg +`#1B1B1B` with 8px `#D6002A` accent, body text `#1B1B1B`, blockquote +border `#D6002A`, table headers `#F0F0F0`, font `'Akkurat Pro'` + +fallbacks) exactly as it does for HTML export. The CSS is *baked into +the pixels* of each slide image. The PPTX is a sequence of images, not +editable PPTX shapes — so there is no "CSS stripping" step. The S&P +Global Energy theme **survives PPTX export** in the standard +(non-editable) path. + +The current unified deck (`docs/presentations/nova-no-humans-platform-marp.md`) +already has the `style: |` block in its frontmatter (verified: line 8 +`style: |`, line 2 `marp: true`, line 3 `theme: default`). So the S&P +theme is already inline; PPTX export will honor it. + +**Caveat — `--pptx-editable` (NOT used):** The experimental +`--pptx-editable` flag generates editable PPTX (texts/shapes, not +images), and the README warns: "If the theme and inline styles are +providing complex styles into the slide, `--pptx-editable` may throw an +error or output the incomplete result." Nova does NOT use +`--pptx-editable` (the S&P theme is complex inline CSS); the standard +image-based PPTX is the path. REQ-228 specifies `--pptx +--allow-local-files`, not `--pptx-editable`. + +#### 4.2 Fallback (NOT needed, documented for completeness) + +If PPTX export ever strips inline CSS (it does NOT in the standard +path, per §4.1), the fallback is a **Marp custom theme CSS file** +referenced via `--theme `: + +```bash +CHROME_PATH=... npx @marp-team/marp-cli@latest --allow-local-files \ + --theme docs/presentations/assets/sp-theme.css \ + docs/presentations/nova-no-humans-platform-marp.md \ + -o output.pptx +``` + +Marp CLI supports custom theme CSS files via `--theme ` (the +README's "Use custom theme" section: "A custom theme created by user +also can use easily by passing the path of CSS file"). The CSS file +would be `docs/presentations/assets/sp-theme.css` containing the same +rules currently in the inline `style:` block, prefixed with the +`@theme` meta comment (Marpit convention: `/* @theme sp-energy */`). +The deck's frontmatter `theme:` directive would then be set to the +custom theme name instead of `default`. + +**Recommendation: do NOT use the fallback.** The inline `style:` block +survives the standard PPTX path (rasterized images). The fallback adds +a file to maintain in sync with the inline block (a DRY violation — +two sources of truth for the S&P colors). REQ-214 restores the S&P +theme *in the unified deck's inline `style:` block* (the v1.9.2 +pattern); the PPTX export uses the same deck file. **The S&P colors +survive PPTX export via the inline `style:` block. No `--theme` flag, +no separate CSS file needed.** Confidence 0.90 (the only residual risk +is a Marp CLI version regression that changes the rasterization path — +mitigated by `@marp-team/marp-cli@latest` pinning in the render script +and the PPTX slide-count/media verification step already in +`docs/presentations/README.md` lines 332–340). + +#### 4.3 `ship_phase.sh` release pattern + `attach_release_asset.py` extension + +Confirmed from `scripts/ship_phase.sh` (read in full, 46 lines): + +- **Line 38:** `POST + https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/acdl/releases` + with `Authorization: token ` (read from + `.env.secrets`, line 35) + JSON body `{"tag_name", "name", "body"}` + (line 37). The response's `id` is the release ID (line 40: + `d.get('id')`). +- The script creates the tag, pushes, creates the release, prints + `release_id: tag: `. + +**How `attach_release_asset.py` extends it (REQ-228):** +`attach_release_asset.py` is a **separate script** (not a modification +to `ship_phase.sh`) that runs *after* the release exists. It takes a +release tag (or ID) + a file path, then: +1. **Resolve tag → release ID** (if only the tag is known): `GET + /api/v1/repos/continuous-intelligence/acdl/releases/tags/{tag}` → + the release object's `id`. +2. **Upload the asset:** `POST + /api/v1/repos/continuous-intelligence/acdl/releases/{id}/assets` + with multipart form (`name` = filename, `attachment` = file binary) + + `Authorization: token ` (same `.env.secrets` + source). +3. **Print** `asset_id: release: file: ` for the + ship log. + +The render+attach flow (REQ-228, triggered by any +`docs/presentations/*-marp.md` or `docs/presentations/assets/` change, +D-142): +``` +render_deck.sh → HTML (committed) + PPTX (committed, D-141) + ↓ +attach_release_asset.py → PPTX uploaded to the phase's Gitea release +``` + +PPTX is a **first-class artifact** (PROJECT.md line 682–683): committed +to git (history) + attached to the release (download) — both always, +not optional. This is the D-141 decision (no LFS — the binary is +committed directly). + +--- + +### 5. Assumptions logged (v1.18) + +- **A1 (0.92):** The Atelier `v0.3.6` tag is the correct pin. It is the + latest release (2026-08-05), the v0.4 milestone release, and the + complete matrix state (19 domains, 190 P-rules). `-11 commits to main + since this release` confirms `main` is a moving target — pinning is + required for audit reproducibility (D-136). Risk: a v0.5 lands before + P5 ships — mitigated by VERSION.md + update script (deliberate + upgrade, not silent drift). +- **A2 (0.88):** The MCP Python SDK v2 high-level server class is + `MCPServer` (import `from mcp.server import MCPServer`), NOT + `FastMCP`. The docs (landing page + Tools page) use `MCPServer` + consistently; `FastMCP` was the v1 name. D-137 (MCP Python SDK v2) + resolves to this import. Risk: the v1→v2 rename — if a future SDK + patch restores a `FastMCP` alias, both imports would work, but the v2 + canonical name is `MCPServer`. +- **A3 (0.85):** `mcp.run()` starts the stdio transport by default (no + explicit transport argument needed for the stdio path). The docs + show `uv run mcp dev server.py` (Inspector) and the "no protocol + handling" promise implies `mcp.run()` is the single entry point. The + exact `run()` signature for stdio vs HTTP is not spelled out on the + landing page (it's in the "Running your server" section, not fetched + in full); the D-135 decision (stdio now, HTTP-ready on the same + object) is consistent with a single `run()` entry point. P5 + implementation should verify the exact run call from the + "Running your server" docs page. +- **A4 (0.90):** The standard (non-editable) PPTX export bakes inline + `style:` CSS into the rasterized slide images. The Marp README + states PPTX "consists of pre-rendered background images" — the + browser rendering applies the CSS before rasterization. The S&P + theme survives PPTX export. The `--pptx-editable` path (NOT used) is + the only path that could strip CSS, and Nova does not use it. +- **A5 (0.88):** The Gitea release-asset endpoint is `POST + /api/v1/repos/{owner}/{repo}/releases/{id}/assets` with multipart + `name` + `attachment`. This is the standard Gitea API (the swagger at + `gitea.com/api/swagger` publishes the OpenAPI spec); the existing + `ship_phase.sh` uses the sibling `.../releases` endpoint, confirming + the API root + auth pattern. The `{id}` is the numeric release ID + (resolvable from the tag via `GET .../releases/tags/{tag}`). +- **A6 (0.85):** The submission-readiness schema uses JSON Schema draft + 2020-12 conditional `allOf` / `if-then` for the per-env mandatory + table (W3.E). This is the standard pattern for "if environment=qa + then require validation.e2eSuite + validation.loadTest." The + `jsonschema` library (already a dependency, used in + `contract_ingestor.py`) supports draft 2020-12 conditionals. The + validator (`core/submission_readiness.py`) may implement the per-env + check in Python (clearer reason codes) rather than relying solely on + schema conditionals — the schema is the *shape*, the validator is + the *gate* with the citizen-developer-facing reason codes (REQ-218). +- **A7 (0.80):** The `--check-readiness` CLI mode is added as a + `if __name__ == "__main__":` block in `contract_ingestor.py` (which + currently has none — it's Lambda-only). D-133 says "invoked as + `contract_ingestor.py --check-readiness`" — this is a local + pre-flight CLI, not a new Lambda action. The validator lives in + `core/submission_readiness.py` (REQ-218); the ingestor dispatches to + it. This keeps the Lambda path unchanged (the readiness gate is a + pre-write step in `_submit_contract` only if desired; the CLI path + is the citizen-developer pre-flight). Risk: the exact wiring (does + the Lambda also gate on readiness, or only the CLI?) is a P3 + implementation decision — REQ-218 says "On pass → proceeds to + existing contract ingestion," implying the gate is in the + submission path, but the CLI mode is the pre-flight surface. +- **A8 (0.90):** The 9-skill list in REQ-221 is final (no adjustment). + The research confirms the 9 Atelier domains map cleanly to the BA.A + 5-skill catalog; the 4 "reference-only" domains (Performance, + Documentation, Concurrency, AI/ML) are correctly NOT elevated to + skills. Adding a 10th skill would break REQ-221's exact list and the + BA.A mapping. +- **A9 (0.88):** The `mcp-engineer` persona is NOT needed — it folds + into backend-engineer. The MCP plugin-registry (D-140) is a Python + backend pattern (decorators, type hints, stdio, urllib). The SDK v2 + API surface is small and FastAPI/Pydantic-style (already in + backend-engineer's range). D-143 logged in PERSONAS.md records this.