Merge milestone/v1.18-citizen-developer-guidance — v1.18 complete (Citizen Developer & Production-Grade Guidance: 5 inputs, 15 requirements, 7 phases + final; tag v1.17.7)

This commit is contained in:
Jon Chery
2026-08-06 15:17:03 +00:00
46 changed files with 5505 additions and 1773 deletions
+8 -9
View File
@@ -1,13 +1,12 @@
{
"phase": 7,
"phase": 0,
"stage": "complete",
"milestone": "v1.17",
"phase_role": "final",
"milestone": "v1.18",
"phase_role": "pre_execution",
"attempts": 0,
"updated_at": "2026-08-04T22:30:00Z",
"milestone_complete": true,
"tag": "v1.16.7",
"release_id": null,
"requirements": ["REQ-185", "REQ-186", "REQ-187", "REQ-188", "REQ-189", "REQ-190", "REQ-191", "REQ-192", "REQ-193", "REQ-194", "REQ-195", "REQ-196", "REQ-197", "REQ-198", "REQ-199", "REQ-200", "REQ-201", "REQ-202", "REQ-203", "REQ-204", "REQ-205", "REQ-206", "REQ-207", "REQ-208", "REQ-209", "REQ-210", "REQ-211", "REQ-212", "REQ-213"],
"notes": "v1.17 milestone complete. 3 pillars: (A) NORTH_STAR.md authored + wired into CIAgent context-loading, (B) Leadership Metrics + PowerBI (CloudEvents envelope, SQLite cold store, Decision Ledger hash-chain, Infracost, 8 placeholder views, trust snapshot, metrics catalog), (C) Unified Narrative Deck (18 slides, x3 arc, per-slide benefits, old decks retired). 29 requirements satisfied. 94 tests pass. CAP-023 + CAP-024 Verified. Interactive GRILL: 12 binding decisions applied. 13 decisions locked (D-120..D-132). Hard constraint honored: DO NOT make anything up."
"updated_at": "2026-08-06T00:35:00Z",
"milestone_complete": false,
"tag": "v1.17.0",
"release_id": 522,
"notes": "v1.18 P0 complete. 5 pre-execution stages done. Tag v1.17.0, release 522."
}
+102 -351
View File
@@ -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 P0P7).
### 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 (P18P20) 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 (P1P3), 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.11v1.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/<name>.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 P2P3 depends on P1's event
formats; lead-developer's catalog + deck in P4P5 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. |
+875 -1109
View File
File diff suppressed because it is too large Load Diff
+201 -2
View File
@@ -58,6 +58,103 @@ traceable to a human attestation and an immutable evidence stream.
boundary. The platform validates, enriches with operational standards,
and reconciles the target state.
## Scope: Nova is Downstream of PDLC
> **Promoted from Core Tenet #2 + Anti-Goal #1 (v1.18, REQ-216).** This
> is the unmissable scope statement — the PDLC is upstream, Nova is
> downstream.
The **Product Development Lifecycle (PDLC)** — product backlog, code
authorship, IDE workflows, sprint planning, application business logic —
is **upstream** of Nova. Nova never penetrates the PDLC. Nova's domain is
**infrastructure + delivery only**: environment progression, cloud
resource lifecycle, operational security/observability NFRs, policy
enforcement, immutable audit lineage, and the two consumer surfaces
(technical developer + agentic).
Integration between the PDLC and Nova is **only** through the validated,
published contract boundary (`schemas/contract.schema.json` +
`schemas/submission-readiness.schema.json`). The citizen developer's AI
coding agent, an upstream agentic SDLC platform, or any upstream
development platform may all produce submissions — the source does not
matter because all are subject to the same compliance standards (the
submission-readiness gate, D-133). Nova validates, enriches with
operational standards, and reconciles the target state. Nova never
authors application code, manages product backlogs, or provides IDE
workflows.
```
PDLC (upstream) Nova (downstream)
───────────────── ─────────────────
product backlog contract ingestion
code authorship (AI agent / IDE / SDLC) → submission-readiness gate
sprint planning → policy enforcement
application business logic → cloud resource lifecycle
→ environment progression (dev→qa→prod→dr)
→ immutable audit + attestation
```
## RACI Matrix
> **Source of truth (v1.18, REQ-215, D-139).** Three roles clarify who
> owns what across the Nova delivery lifecycle. The matrix is the
> authoritative version; `docs/raci.md` is the citizen-developer-facing
> copy.
### Roles
- **Citizen Developer (CD)** — the consumer (technical developer L3A or
non-technical L3B). Responsible for all **Functional Requirements (FRs)**
and **User Acceptance Testing (UAT)**. The FRs + UAT are produced via
the citizen developer's AI coding agent, an upstream agentic SDLC, or
an upstream development platform — **the source does not matter as all
are subject to the same compliance standards** (the submission-readiness
gate, D-133).
- **Platform** — Nova. Responsible for all **Non-Functional Requirements
(NFRs)**, **Infrastructure** (cloud resource lifecycle, state, IAM),
**QA** (the platform-side quality checks: policy, confidence, schema),
and **Production deployments to cloud** (the apply path, the pipeline,
the release).
- **Release Management (RM)** — **co-owned**. QA + SRE attestations are
required by the actual release. The attestations are performed
agentically (the platform runs the checks), but the release is
**overseen and triggered by the Citizen Developer** — the human
attestation at the stage gate (D-042, hitl_gates.py). The platform
performs; the citizen developer authorizes.
### Matrix
| Work Category | Citizen Developer | Platform | Release Management |
|---|---|---|---|
| **Functional Requirements (FRs)** | **R/A** | C | I |
| **User Acceptance Testing (UAT)** | **R/A** | C | I |
| **Non-Functional Requirements (NFRs)** | I | **R/A** | C |
| **Infrastructure (cloud, state, IAM)** | I | **R/A** | C |
| **QA (policy, confidence, schema checks)** | C | **R/A** | I |
| **Production deployment to cloud** | I | **R/A** | C |
| **Release attestation (QA + SRE sign-off)** | **A** | R | **R** |
**Key: R** = Responsible (does the work) · **A** = Accountable (owns the
outcome, sign-off) · **C** = Consulted · **I** = Informed.
**Compliance-standard equivalence note:** the citizen developer's FRs +
UAT may originate from any upstream source — an AI coding agent, an
agentic SDLC platform, or a traditional development platform. All are
subject to the same compliance standards: the submission-readiness gate
(`schemas/submission-readiness.schema.json`), the contract schema, the
policy envelope, and the immutable audit stream. The platform does not
differentiate by upstream source; it validates the submission, not the
author.
**Co-ownership of Release Management:** the release is co-owned. The
platform performs the QA + SRE attestations agentically (confidence signal,
policy checks, separation-of-duties). The citizen developer oversees and
triggers the actual release — the human attestation at the stage gate is
the citizen developer's authorization, recorded with approver identity
(D-042). The platform runs the checks; the citizen developer authorizes
the promotion. This is the "autonomy in operations, human at stage gates"
model from the NORTH_STAR.
## Capability Status (Re-Verified 2026-07-27)
> Source of truth: `.ciagent/CAPABILITY_INVENTORY.md` (Phase 54, D-093).
@@ -598,6 +695,90 @@ utility, unrelated to presentations).
No code changes; 494 tests pass; `run_ci.sh` + `run_platform.sh --check-only`
green. PPTX files uploaded to Gitea release.
## Objective for Milestone v1.18 (active — Citizen Developer & Production-Grade Guidance)
v1.18 advances Nova from a platform that governs infrastructure delivery
to one that **instructs the citizen developer on production-grade
engineering** and defines a **clear, machine-checkable contract for what
is acceptable to start**. Five user-directed inputs drive the milestone:
1. **S&P Global theme restoration.** The v1.17 P5 deck rebuild consolidated
two decks into one unified narrative deck but lost the S&P Global Energy
brand visual identity (introduced v1.9.2 / P45, commit `ae0cb58`). The
Marp `style:` block (red-core `#D6002A`, grey-90 `#1B1B1B`, Akkurat Pro
font, 8px top accent bar) is restored to the unified deck. The mermaid
`sp-theme.json` survived; only the Marp CSS theme was lost.
2. **PDLC-upstream scope made explicit.** Core Tenet #2 already states the
platform "does not penetrate upstream product/SDLC" and Anti-Goal #1 says
"Not an upstream development platform." v1.18 promotes this from a
buried tenet to a dedicated, unmissable scope statement in PROJECT.md +
`docs/scope.md` + a deck slide: **the PDLC (Product Development
Lifecycle — product backlog, code authorship, IDE) is upstream of Nova;
Nova governs infra + delivery only; integration is through the validated
contract boundary.**
3. **RACI matrix.** A three-role responsibility matrix clarifies who owns
what: **Citizen Developer** (Responsible for all Functional Requirements
+ User Acceptance Testing, via their AI coding agent / upstream agentic
SDLC / upstream development platform — the source does not matter as all
are subject to the same compliance standards), **Platform** (Responsible
for all NFRs + Infrastructure + QA + Production deployments to cloud),
**Release Management** (co-owned: QA + SRE attestations required by the
actual release, performed agentically but overseen & triggered by the
Citizen Developer). Source of truth in PROJECT.md + `docs/raci.md` + a
deck slide.
4. **Nova input contract — "what is acceptable to start."** A JSON Schema
(`schemas/submission-readiness.schema.json`) defines the
acceptable-to-start gate as a superset *above* contract-schema validity:
schema-valid contract + required Nova tags + per-env mandatory metadata
(per W3.E) + declared policy preconditions + (for L3B) `profile:agentic`
markers + `appSource` pointer. A validator (`core/submission_readiness.py`,
invoked as `contract_ingestor.py --check-readiness`) returns a structured
`ReadinessResult` with reason codes. On fail → citizen-developer-facing
error (not a stack trace); on pass → proceeds to existing ingestion.
5. **Atelier integration — production-grade guidance + agentic validation.**
Nova consumes `coreci/atelier` (a first-principles docs-as-code
engineering framework — 8 core principles, 19 domains, 190 P-rules) via
two surfaces: **skills** (markdown files under `skills/` keyed to Atelier
domain paths, surfaced to the citizen developer's AI agent, extending the
BA.A 5-skill catalog) and an **MCP server** (`mcp/atelier/server.py`,
plugin-registry architecture, stdio transport, vendored Atelier snapshot
for audit reproducibility) exposing tools for principle-lookup,
domain-listing, matrix-lookup, and agentic validation against the
Atelier agent-checklist — validation that goes beyond deterministic
scanners (Wiz/Checkmarx/Mend) by catching correctness/clarity/simplicity/
observability gaps.
**Deck automation (cross-cutting):** any phase modifying
`docs/presentations/*-marp.md` or `docs/presentations/assets/` MUST
re-render HTML + PPTX, **commit the PPTX to git** (binary, no LFS), and
attach it to the phase's Gitea release. New scripts:
`scripts/render_deck.sh` (HTML + PPTX render) and
`scripts/attach_release_asset.py` (Gitea release asset upload).
**Milestone type:** Feature (P1 S&P theme restoration + P3 readiness
schema/validator + P5 MCP server are new code/features). Tags run on the
**v1.17.x** patch line (previous minor per branch-strategy): `v1.17.0` (P0)
`v1.17.1..v1.17.6` (P1P6) → `v1.17.7` (P7 final = milestone release).
**Phase count:** 8 (P0 pre-execution + 6 execution + 1 final).
**Hard constraints:**
- DO NOT make anything up (NORTH_STAR.md honesty model).
- The submission-readiness schema is a superset gate above
`contract.schema.json`, NOT a duplicate — it references but does not
redefine contract fields.
- The MCP server is plugin-registry extensible (future capabilities drop
in as new plugin files, no `server.py` edits).
- Atelier is vendored (pinned tag) for audit reproducibility — an agentic
validation result must be replayable against the exact principles that
produced it.
- PPTX is a first-class artifact: committed (history) + attached (download)
— both always, not optional.
## Requirements
### v1.0 (Prior milestone — the demo)
@@ -799,7 +980,7 @@ or user-directed scope). New v1.7 decisions:
| W1.A | AI-refinement trigger | **Accept recommendation.** Joint condition: N ≥ 50 consecutive changes with zero rollbacks AND no L1/L2 incident in last 6 months AND Infra & Ops unilateral override. |
| W1.B | Multi-stack edge case rule | **Accept recommendation.** Permitted only for (a) DR-region mirror, (b) time-boxed experimental stack with TTL ≤ 30d, (c) explicit Infra & Ops approval with `multiStack.justification`. |
| W2.A | Tag mutability for prod | **Accept recommendation (Path B).** Tag for dev/qa, SHA for prod. Platform CLI resolves tag→SHA for prod-bound workflows. Justified by the "Audit truth lives outside the repository" bet. |
| BA.A | Initial L3B skill catalog | **Accept recommendation.** 5 skills: web API, worker, scheduled job, static asset, basic observability bootstrap. Addition criteria: (a) reviewable for sensitive data, (b) expressible as a single contract submission, (c) documented use case. |
| BA.A | Initial L3B skill catalog | **Accept recommendation.** 5 skills: web API, worker, scheduled job, static asset, basic observability bootstrap. Addition criteria: (a) reviewable for sensitive data, (b) expressible as a single contract submission, (c) documented use case. **Extended v1.18 (REQ-221/222):** the BA.A 5-skill catalog is extended with 9 Atelier-derived production-grade engineering skills under `skills/` (api, security, data, testing, observability, errors, devops, infrastructure-as-code, compliance), indexed by `docs/skills.md`. The Atelier skills extend, not replace, the BA.A catalog. |
| W3.D | L1/L2 standard versioning | **Decided.** Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (same as the v1.0 demo D-rule, lifted to the real platform). Pin model: L2 contracts pin L1 by `name@semver`; the resolver picks the highest compatible. Evolution: MAJOR bumps require a new registry entry (immutable publication); old entry enters a 12-month deprecation window. |
| W3.E | Schema mandatory vs optional inputs | **Decided.** Per-env mandatory table: dev requires `stack` + `environment`; qa adds `validation.e2eSuite` + `validation.loadTest`; prod adds `runbook` + `dashboard` + `oncall`; dr adds `drDrillRef`. `inputs` map is always optional. `profile: agentic` fields (`naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`) optional everywhere. |
| BA.B | Confidence threshold tuning | **Decided.** Starting thresholds frozen for v1. Tuning begins in v1.2: track FP/FN per environment quarterly; override authority = Infra & Ops + SRE joint sign-off; any override is itself a confidence-event in the audit stream. |
@@ -1133,4 +1314,22 @@ P7 review+audit+ship). Tags on the v1.16.x line: `v1.16.0` (P0) →
| D-129 | PowerBI delivery = CSV/JSON files, folder connector. | Nova is offline-first; no live connector to a running service. PowerBI ingests via the folder connector. | P3 emits CSV/JSON to metrics/powerbi/. |
| D-130 | Deck arc = Problem → Vision → How → Proof → Roadmap. | The unified narrative deck's 5-act structure. x3 arc at deck + slide level. Per-slide benefit callouts. Fluid transitions. Both old decks retired. | P5 builds the unified deck; old decks deleted. |
| D-131 | MTTR scope = platform-run MTTR. | The <60s MTTR target refers to platform-run failures (apply.failed → successful retry), not infra-incident MTTR (no incident detection system). Infra-incident MTTR deferred. | P4 grounds platform-run MTTR. |
| D-132 | Attestation instrumentation = emit attestation.recorded events. | The attestation system (hitl_gates.py + attestation_matrix.py + separation_of_duties.py) already exists. Instrument it: emit attestation.recorded events into the Decision Ledger + PowerBI. Attestation Coverage = 100% target grounded from outbox approver_* attributes. | P1 emits attestation events; P4 grounds Attestation Coverage. |
| D-132 | Attestation instrumentation = emit attestation.recorded events. | The attestation system (hitl_gates.py + attestation_matrix.py + separation_of_duties.py) already exists. Instrument it: emit attestation.recorded events into the Decision Ledger + PowerBI. Attestation Coverage = 100% target grounded from outbox approver_* attributes. | P1 emits attestation events; P4 grounds Attestation Coverage. |
## Key Decisions (v1.18)
Resolved at the CLARIFY stage (full autonomy — all within locked
constraints or user-directed scope). New v1.18 decisions:
| ID | Decision | Rationale | Outcome |
|----|----------|-----------|---------|
| D-133 | Submission-readiness validator location = extend `contract_ingestor.py --check-readiness`. | Adding a new CLI binary is unnecessary; the ingestor is the existing entry point for contract submission. The validator is a subcommand that runs before ingestion proceeds. No new binary, no new entry point to maintain. | P3 implements the subcommand; no new CLI binary. |
| D-134 | Deck slide budget = 18 → 21 slides (no act restructure). | The 3 new slides (scope/RACI/atelier) are leadership-relevant and append after the existing 18. The 5-act arc (D-130) is preserved; the new slides are append-only context, not a new act. | P6 appends 3 slides → 21 total. |
| D-135 | Atelier MCP transport = stdio now; HTTP-ready (same server object). | stdio is the local-agent transport (the citizen developer's AI agent spawns the server as a subprocess). The MCP Python SDK v2 supports Streamable HTTP on the same `MCPServer` object, so adding HTTP later is a transport-only change in `server.py`, not a rewrite. | P5 ships stdio; HTTP deferred (documented in README). |
| D-136 | Atelier source = vendor pinned tag under `mcp/atelier/vendor/`. | An agentic validation result is only reproducible if the principles that produced it are pinned. Live-fetch breaks replayability (Atelier `main` drifts). Vendoring matches the v1.16 P15 offline-first precedent and the Nova thesis (provable trust). `mcp/atelier/vendor/VERSION.md` records the pinned tag; `scripts/update_atelier_vendor.sh` is the intentional upgrade path. | P5 vendors Atelier; live-fetch not implemented. |
| D-137 | MCP server language = Python (MCP Python SDK v2, `modelcontextprotocol/python-sdk`). | Nova's `core/` is Python. The MCP Python SDK v2 (23.9k stars, MIT, stable) matches the codebase; type hints become JSON Schema automatically (`@mcp.tool()` decorator). | P5 uses Python SDK v2. |
| D-138 | Skill catalog format = markdown files under `skills/` keyed to Atelier domain paths. | Markdown is the established Nova docs format (Jekyll Pages, 4-step deck process). Each skill file names the Atelier source path, distills the first-principles, links to agent-checklist triggers, and maps to the BA.A catalog. | P4 authors 9 markdown skill files. |
| D-139 | RACI role names = Citizen Developer / Platform / Release Management (co-owned). | User-specified. The 3 roles are the columns of the RACI table. Release Management is co-owned: QA + SRE attestations are required by the actual release (performed agentically, overseen & triggered by the Citizen Developer). | P2 authors the RACI with these 3 roles. |
| D-140 | MCP server extensibility = plugin-registry (`plugins/<name>.py` implementing `register(mcp)`). | Future capabilities (new scanners, policy evaluators, cost tools) drop in as new plugin files — no `server.py` edits. `server.py` scans `plugins/` and calls `register` on each. This is the extensibility insurance: plugins are decoupled from the server entrypoint. | P5 implements the plugin-registry; initial plugins are `principles.py` + `validation.py`. |
| D-141 | PPTX storage = commit binary directly to `docs/presentations/` (no LFS). | Decks are small (~1-5 MiB); git handles binary blobs. LFS requires server-side support (unverified for git.cloudinit.dev) + client config. Committing directly is simplest and works without any repo/server config. Binary diffs are not delta-friendly, but deck changes are infrequent. | P1/P2/P6 commit .pptx directly. |
| D-142 | Deck render trigger = any phase modifying `docs/presentations/*-marp.md` or `docs/presentations/assets/` must re-render HTML + PPTX, commit PPTX, and attach to the Gitea release. | PPTX was previously manual + release-only (not committed). v1.18 makes it a first-class artifact: committed (history) + attached (download), both always, not optional. Automated via `scripts/render_deck.sh` + `scripts/attach_release_asset.py`. | P1/P2/P6 run the render+commit+attach pipeline. |
+174
View File
@@ -1180,3 +1180,177 @@ with documented schemas.
- A third deck — the two existing decks merge into one; no new
standalone metrics deck.
- A Nova web UI — dashboards are PowerBI, not a Nova-built frontend.
---
## v1.18 — Citizen Developer & Production-Grade Guidance
> **Milestone type:** Feature. Tags run on the v1.17.x patch line (previous
> minor per branch-strategy). `v1.17.0` (P0) → `v1.17.1..v1.17.6` (P1P6) →
> `v1.17.7` (P7 final = milestone release).
> **Active milestone:** v1.18. **Branch:**
> `milestone/v1.18-citizen-developer-guidance`.
### Requirements
- **REQ-214** — S&P Global Energy Marp theme restored in the unified deck
(`docs/presentations/nova-no-humans-platform-marp.md`). The `style:` block
from commit `ae0cb58` (v1.9.2 / P45) is ported: H1/H2 `#D6002A`
(S&P red-core), title-slide bg `#1B1B1B` (grey-90) with 8px `#D6002A` top
accent bar, body text `#1B1B1B`, blockquote border `#D6002A`,
table headers `#F0F0F0`, font `'Akkurat Pro'` with web-safe fallbacks. The
current Nova header/footer text is preserved (rebrand is not touched —
only the visual theme is restored). HTML re-rendered with the S&P theme.
- **REQ-215** — RACI matrix authored in `PROJECT.md` (new `## RACI Matrix`
section) and `docs/raci.md` (citizen-developer-facing copy). Three roles:
**Citizen Developer** (Responsible for all Functional Requirements + User
Acceptance Testing — via their AI coding agent / upstream agentic SDLC /
upstream development platform; the source does not matter as all are
subject to the same compliance standards), **Platform** (Responsible for
all NFRs + Infrastructure + QA + Production deployments to cloud),
**Release Management** (co-owned: QA + SRE attestations required by the
actual release, performed agentically but overseen & triggered by the
Citizen Developer). Rendered as a table: rows = work categories (FRs, UAT,
NFRs, Infra, QA, Prod deploy, Release attestation), columns = R/A/C/I per
role. Includes the compliance-standard-equivalence note.
- **REQ-216** — PDLC-upstream scope statement made explicit in `PROJECT.md`
(new `## Scope: Nova is Downstream of PDLC` subsection under Domain
Boundaries) and `docs/scope.md`. States that the PDLC (Product Development
Lifecycle — product backlog, code authorship, IDE) is upstream of Nova;
Nova governs infra + delivery only; integration is through the validated
contract boundary. Promotes Core Tenet #2 + Anti-Goal #1 from buried
tenets to a dedicated, unmissable scope statement.
- **REQ-217** — `schemas/submission-readiness.schema.json` (JSON Schema
draft 2020-12) defines what is acceptable to start — a superset gate
*above* `contract.schema.json` validity. Required fields: `contractId`
(non-empty), `environment` (dev/qa/prod/dr) with the W3.E per-env mandatory
table enforced (dev: stack+environment; qa: +validation.e2eSuite
+validation.loadTest; prod: +runbook+dashboard+oncall; dr: +drDrillRef),
`tags` (the 5 required Nova tags per D-054: `nova:owner`, `nova:contract`,
`nova:environment`, `nova:cost-center`, `nova:ref`), `policyPreconditions`
(declared policy expectations the platform will enforce, e.g.,
`public-ingress: false`), `profile` (`developer` or `agentic`; if
`agentic`, requires `naturalLanguageIntent`, `confidenceAtSubmission`,
`agentTrace` per REQ-22 / W3.E), `appSource` (repo + ref pointer for
runtime fetch).
- **REQ-218** — `core/submission_readiness.py` validator, invoked as
`contract_ingestor.py --check-readiness` subcommand (decision D-133). Returns
a structured `ReadinessResult` (pass/fail per check, with reason codes).
On fail → the ingestor rejects with a citizen-developer-facing error
(not a stack trace). On pass → proceeds to existing contract ingestion.
Calls `contract.schema.json` validation first, then the readiness checks.
Reason codes: `MISSING_TAGS`, `ENV_MISSING_MANDATORY:<env>:<field>`,
`AGENTIC_MISSING_INTENT`, `MISSING_APP_SOURCE`, `POLICY_PRECONDITION_MISSING`.
- **REQ-219** — `docs/submission-readiness.md` citizen-developer-facing doc
explaining what is acceptable to start, with good + rejected examples and
the reason-code catalog. References `schemas/submission-readiness.schema.json`
as the source of truth.
- **REQ-220** — `tests/test_submission_readiness.py` covers: good contract
passes; missing tags fail with `MISSING_TAGS`; missing env mandatory fails
with `ENV_MISSING_MANDATORY:<env>:<field>`; agentic profile missing intent
fails with `AGENTIC_MISSING_INTENT`; missing appSource fails with
`MISSING_APP_SOURCE`.
- **REQ-221** — `skills/` directory with 9 Atelier-derived skill files mapped
to the BA.A citizen-developer catalog: `skills/api.md` (domains/api/),
`skills/security.md` (domains/security/), `skills/data.md` (domains/data/),
`skills/testing.md` (domains/testing/), `skills/observability.md`
(domains/observability/), `skills/errors.md` (domains/errors/),
`skills/devops.md` (domains/devops/), `skills/infrastructure-as-code.md`
(domains/infrastructure-as-code/), `skills/compliance.md`
(domains/compliance/). Each names the Atelier source path, distills the
first-principles to the citizen-developer-relevant subset, links to
agent-checklist triggers, and maps to the BA.A 5-skill catalog (web API,
worker, scheduled job, static asset, basic observability bootstrap).
- **REQ-222** — `docs/skills.md` index page listing the skill catalog, the
Atelier provenance, and how the citizen developer's AI agent consumes them
(read before completing a task; run `review/agent-checklist.md` before
finishing). `PROJECT.md` BA.A decision extended with the Atelier-derived
skill catalog reference.
- **REQ-223** — `mcp/atelier/server.py` MCP server (stdio transport,
decision D-135) with a **plugin-registry architecture** (decision D-140):
`plugins/<name>.py` modules each expose `register(mcp: MCPServer) -> None`
and call `@mcp.tool()` for their tools; `server.py` scans `plugins/` and
calls `register` on each. Initial plugins: `principles.py`
(`atelier.lookup_principle`, `atelier.list_domains`, `atelier.matrix_lookup`)
and `validation.py` (`atelier.validate_against_principles` — agentic
validation against the Atelier agent-checklist, beyond Wiz/Checkmarx/Mend).
Uses the MCP Python SDK v2 (`modelcontextprotocol/python-sdk`).
- **REQ-224** — `mcp/atelier/vendor/` vendored Atelier snapshot (pinned tag,
decision D-136) for audit reproducibility. `mcp/atelier/vendor/VERSION.md`
records the pinned tag + a `scripts/update_atelier_vendor.sh` helper for
intentional upgrades. `mcp/atelier/README.md` documents the server: how to
run, transport, tool catalog, plugin-authoring guide, vendoring policy.
- **REQ-225** — `tests/test_atelier_mcp.py` covers: tool registration (all 4
tools discoverable via `tools/list`), `atelier.lookup_principle` returns
the principle text + core C-rule, `atelier.validate_against_principles`
catches a planted C1 (correctness) + C7 (observability) violation in a
known-bad snippet and passes a known-good snippet, `atelier.matrix_lookup`
returns the domain→core mapping, plugin discovery loads all plugins in
`plugins/`.
- **REQ-226** — 3 new deck slides added to the unified deck
(`docs/presentations/nova-no-humans-platform-marp.md`) → 21 slides total:
Slide 19 "Scope: Downstream of PDLC", Slide 20 "RACI: Who Owns What",
Slide 21 "Production-Grade Guidance via Atelier". Arc Preview slide
updated to reflect 21-slide count. Talking points
(`nova-no-humans-platform-talking-points.md`) synced for the 3 new slides.
S&P theme preserved (regression check vs P1). CAP-024 deck structure
regression passes.
- **REQ-227** — `docs/presentations/README.md` slide count + deck table
updated to reflect 21 slides + the 3 new slide titles.
- **REQ-228** — `scripts/render_deck.sh` (renders HTML + PPTX from a Marp
deck, commits both to git) and `scripts/attach_release_asset.py` (uploads
a file to a Gitea release via the API). Any phase modifying
`docs/presentations/*-marp.md` or `docs/presentations/assets/` MUST
re-render HTML + PPTX, commit the PPTX binary to `docs/presentations/`,
and attach it to the phase's Gitea release. PPTX is stored as a committed
binary (no LFS, decision D-141).
### Out of Scope (v1.18)
- **Streamable HTTP transport for the MCP server** — stdio ships now; HTTP
is a future milestone (the SDK supports it on the same server object, so
adding it later is a transport-only change, not a rewrite).
- **A Nova-built frontend / dashboard** — observability stays PowerBI /
external; no Nova web UI.
- **Replacing the existing BA.A 5-skill catalog** — the Atelier-derived
skills extend it, not replace it.
- **Live AWS re-provisioning** (D-096, still deferred) — submission-readiness
validates the contract shape, not a live AWS deployment.
- **A second forge adapter** (GitLab) — BA.F cross-platform evolution is
future work.
- **Atelier live-fetch mode** — vendoring is the only mode this milestone;
live-fetch (with its reproducibility trade-offs) is not implemented.
### v1.18 Traceability
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-214 | P1 | complete |
| REQ-215 | P2 | complete |
| REQ-216 | P2 | complete |
| REQ-217 | P3 | complete |
| REQ-218 | P3 | complete |
| REQ-219 | P3 | complete |
| REQ-220 | P3 | complete |
| REQ-221 | P4 | complete |
| REQ-222 | P4 | complete |
| REQ-223 | P5 | complete |
| REQ-224 | P5 | complete |
| REQ-225 | P5 | complete |
| REQ-226 | P6 | complete |
| REQ-227 | P6 | complete |
| REQ-228 | P1/P2/P6 | complete |
+853
View File
@@ -1493,3 +1493,856 @@ Total: ~1216 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 (C1C8)
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; C4C8 tradeable among themselves but always below
C1C3).
| 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/<x>/first-principles.md` >
`domains/<x>/<topic>.md` > `languages/<lang>.md` > `examples/<x>.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 (C1C8)** 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 C1C8 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<n>. <Name>` section for `principle_id` (e.g. `C1`
the "C1. Correctness" section). Return the full principle text.
2. Else: load `vendor/domains/<domain>/first-principles.md`, parse the
`### P<n>. <Name>` 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
`<Domain> P<n>`, 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 <Domain>` section,
extract the checklist items tagged with `P<n>` (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/<domain>/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/<tag>.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}$` (36 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 635643, 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 674676): "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 01), `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:<env>:<field>` (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:<env>:<field>`,
`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 <NOVA_GITEA_TOKEN>
Content-Type: application/json
Body: {"tag_name": "...", "name": "...", "body": "..."}
Response: {"id": <release_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 <NOVA_GITEA_TOKEN>
Content-Type: multipart/form-data
Form fields:
name = <filename, e.g. "nova-no-humans-platform.pptx">
attachment = <the file, multipart>
Response: {"id": <asset_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 93105)
+ the v1.9.2 theme commit `ae0cb58` (verified via `git show`).
**Confirmed export command (from `docs/presentations/README.md` line
9699):**
```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 <output-path>.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 <path>`:
```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 <path>` (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 332340).
#### 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 <NOVA_GITEA_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: <id> tag: <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 <NOVA_GITEA_TOKEN>` (same `.env.secrets`
source).
3. **Print** `asset_id: <id> release: <tag> file: <name>` 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 682683): 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.
+72
View File
@@ -1683,3 +1683,75 @@ deferred (D-113/D-114).
Ship tag at milestone COMPLETE: `v1.15.26` (NFR milestone; final patch IS
the release). **DONE.**
## v1.18 (complete — Citizen Developer & Production-Grade Guidance, tag line `v1.17.x`)
Nova advances from a platform that governs infrastructure delivery to one
that **instructs the citizen developer on production-grade engineering**
and defines a **clear, machine-checkable contract for what is acceptable
to start**. Five user-directed inputs drive the milestone:
1. **S&P Global theme restoration** (P1) — the v1.17 P5 deck rebuild lost
the S&P Global Energy brand visual identity (introduced v1.9.2 / P45).
The Marp `style:` block (`#D6002A` red, `#1B1B1B` grey-90, Akkurat Pro,
8px accent bar) is restored to the unified deck.
2. **PDLC-upstream scope** (P2) — promotes Core Tenet #2 + Anti-Goal #1
from buried tenets to a dedicated, unmissable scope statement: the PDLC
is upstream of Nova; Nova governs infra + delivery only.
3. **RACI matrix** (P2) — three-role responsibility matrix (Citizen
Developer / Platform / Release Management co-owned) clarifies who owns
what, with the compliance-standard-equivalence note.
4. **Nova input contract** (P3) — `schemas/submission-readiness.schema.json`
+ `core/submission_readiness.py` validator define "what is acceptable to
start" as a superset gate above contract-schema validity.
5. **Atelier integration** (P4+P5) — skills (markdown, extending BA.A) + an
MCP server (plugin-registry, vendored Atelier, agentic validation
beyond Wiz/Checkmarx/Mend).
**Milestone type:** Feature (P1 theme restoration + P3 schema/validator +
P5 MCP server are new code). Tags run on the v1.17.x patch line:
`v1.17.0` (P0) → `v1.17.1..v1.17.6` (P1P6) → `v1.17.7` (P7 final =
milestone release).
**Deck automation (cross-cutting, REQ-228):** any phase modifying
`docs/presentations/*-marp.md` or `docs/presentations/assets/` re-renders
HTML + PPTX, commits the PPTX binary to git, and attaches it to the
phase's Gitea release.
**Phase count:** 8 (P0 pre-execution + 6 execution + 1 final).
**Phases:**
- **P1 — sp-theme-restoration** (feat): restore S&P Global Marp theme to
unified deck + HTML re-render + PPTX commit + release attach. REQ-214,228.
- **P2 — pdlc-scope-raci** (docs): PDLC-upstream scope + RACI matrix +
2 deck slides + HTML/PPTX re-render. REQ-215,216,228.
- **P3 — submission-readiness** (feat): JSON Schema + validator + docs +
tests. REQ-217,218,219,220.
- **P4 — atelier-skills** (docs): 9 Atelier-derived skill files + index +
BA.A extension. REQ-221,222.
- **P5 — atelier-mcp** (feat): plugin-registry MCP server + vendored
Atelier + 4 tools + tests. REQ-223,224,225.
- **P6 — deck-slides-atelier** (docs): 3 new deck slides (scope/RACI/atelier)
→ 21 slides + talking points + HTML/PPTX re-render + README. REQ-226,227,228.
- **P7 — final-review-ship** (final): review + audit + milestone ship.
**Requirements:** REQ-214..228 (15 requirements). See
`.ciagent/REQUIREMENTS.md` §v1.18.
**Open decisions to lock (CLARIFY/GRILL):** D-133 (validator location),
D-134 (deck slide budget), D-135 (MCP transport), D-136 (Atelier vendoring),
D-137 (MCP server language), D-138 (skill format), D-139 (RACI roles),
D-140 (MCP plugin-registry), D-141 (PPTX storage), D-142 (deck render trigger).
**Outcome:** 15 requirements (REQ-214..228) satisfied; 32 tests pass (16
submission-readiness + 16 MCP); S&P Global Energy theme restored; PDLC-
upstream scope + RACI matrix authored (PROJECT.md + docs/ + deck);
submission-readiness schema + validator shipped (superset gate above
contract.schema.json); 9 Atelier-derived skills + docs/skills.md; MCP
server (plugin-registry, stdio, vendored Atelier v0.3.6) with 4 tools +
agentic validation beyond Wiz/Checkmarx/Mend; 21-slide deck (3 new slides:
scope/RACI/atelier) with PPTX committed + release-attached. 10 decisions
locked (D-133..D-142).
Ship tag at milestone COMPLETE: `v1.17.7` (feature milestone; final patch
IS the release). **DONE.**
+1 -1
View File
@@ -8,7 +8,7 @@
],
"active_project": "acdl",
"active_projects": ["acdl"],
"active_milestone": "v1.17",
"active_milestone": "v1.18",
"autonomy": {
"level": "full",
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
+20 -1
View File
@@ -499,4 +499,23 @@ def lambda_handler(event, context):
return {"statusCode": 401, "body": json.dumps({"error": str(e)})}
return {"statusCode": 400, "body": json.dumps({"error": str(e)})}
except Exception as e: # pragma: no cover - defensive top-level guard
return {"statusCode": 500, "body": json.dumps({"error": str(e)})}
return {"statusCode": 500, "body": json.dumps({"error": str(e)})}
# --- CLI: --check-readiness (D-133, REQ-218) ---------------------------
# Invoked as: python3 -m core.lambda.contract_ingestor --check-readiness <submission.json>
# Delegates to core.submission_readiness.check_readiness() and prints the
# structured ReadinessResult. Exits 0 if ready, 1 if not.
if __name__ == "__main__": # pragma: no cover - CLI entry
import sys
if "--check-readiness" in sys.argv:
sys.path.insert(
0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
)
from core.submission_readiness import cli_main
# Strip the --check-readiness flag; pass the file path.
rest = [a for a in sys.argv[1:] if a != "--check-readiness"]
sys.exit(cli_main(["check-readiness"] + rest))
else:
print("Usage: python3 -m core.lambda.contract_ingestor --check-readiness <submission.json>")
+193
View File
@@ -0,0 +1,193 @@
"""core/submission_readiness.py — Nova submission-readiness validator (REQ-218).
Defines what is acceptable to start a superset gate ABOVE
contract.schema.json validity. Invoked as
``contract_ingestor.py --check-readiness`` (D-133). Returns a structured
ReadinessResult (pass/fail per check, with reason codes). On fail the
ingestor rejects with a citizen-developer-facing error (not a stack
trace). On pass proceeds to existing contract ingestion.
The validator calls contract.schema.json validation first (the shape),
then the readiness checks (the gate): tags, env mandatory, policy
preconditions, profile:agentic markers, appSource.
Reason codes:
MISSING_TAGS one or more required Nova tags are absent
ENV_MISSING_MANDATORY:<env>:<field> a per-env mandatory field is missing
AGENTIC_MISSING_INTENT profile=agentic but naturalLanguageIntent absent
MISSING_APP_SOURCE appSource (repo + ref) is missing
POLICY_PRECONDITION_MISSING a declared policy precondition is absent
"""
from __future__ import annotations
import json
import os
import sys
from dataclasses import dataclass, field
from typing import Any
_SCHEMA_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "schemas"
)
REQUIRED_TAGS = [
"nova:owner",
"nova:contract",
"nova:environment",
"nova:cost-center",
"nova:ref",
]
ENV_MANDATORY: dict[str, list[str]] = {
"dev": [], # dev requires only the base contract shape (id+environment+infrastructure)
"qa": ["validation.e2eSuite", "validation.loadTest"],
"prod": ["runbook", "dashboard", "oncall"],
"dr": ["drDrillRef"],
}
AGENTIC_REQUIRED = ["naturalLanguageIntent", "confidenceAtSubmission", "agentTrace"]
@dataclass
class ReadinessResult:
"""Structured result of the submission-readiness gate."""
ready: bool
reason_codes: list[str] = field(default_factory=list)
contract_id: str | None = None
def to_dict(self) -> dict[str, Any]:
return {
"ready": self.ready,
"reason_codes": self.reason_codes,
"contractId": self.contract_id,
}
def __str__(self) -> str:
if self.ready:
return f"READY — contract {self.contract_id} passes submission-readiness gate"
codes = "; ".join(self.reason_codes) if self.reason_codes else "unknown"
return f"NOT READY — contract {self.contract_id}: {codes}"
def _validate_contract_schema(contract: dict[str, Any]) -> list[str]:
"""Validate the contract against contract.schema.json (the shape).
Returns a list of reason codes (empty if valid). Falls back to no-op
if jsonschema or the schema file is unavailable (the contract is
validated upstream by run_platform.sh in the normal path).
"""
codes: list[str] = []
try:
import jsonschema
schema_path = os.path.join(_SCHEMA_DIR, "contract.schema.json")
with open(schema_path) as f:
schema = json.load(f)
jsonschema.validate(instance=contract, schema=schema)
except (OSError, ImportError):
pass
except jsonschema.ValidationError as e:
codes.append(f"CONTRACT_SCHEMA_INVALID:{e.message}")
return codes
def _get_nested(data: dict[str, Any], dotted_key: str) -> Any:
parts = dotted_key.split(".")
val: Any = data
for p in parts:
if not isinstance(val, dict) or p not in val:
return None
val = val[p]
return val
def check_readiness(submission: dict[str, Any]) -> ReadinessResult:
"""Run the full submission-readiness gate.
1. Validate the contract shape (contract.schema.json).
2. Validate the readiness schema (submission-readiness.schema.json).
3. Run the semantic readiness checks (tags, env mandatory, agentic, appSource, policy).
Returns a ReadinessResult. Never raises all failures are reason codes.
"""
contract_id = submission.get("contractId") or submission.get("id", "unknown")
codes: list[str] = []
# Step 1: contract shape validation
contract_shape = {k: v for k, v in submission.items() if k in ("id", "name", "environment", "infrastructure")}
if contract_shape:
codes.extend(_validate_contract_schema(contract_shape))
# Step 2: readiness schema validation
try:
import jsonschema
schema_path = os.path.join(_SCHEMA_DIR, "submission-readiness.schema.json")
with open(schema_path) as f:
readiness_schema = json.load(f)
jsonschema.validate(instance=submission, schema=readiness_schema)
except (OSError, ImportError):
pass
except jsonschema.ValidationError as e:
codes.append(f"READINESS_SCHEMA_INVALID:{e.message}")
# Step 3: semantic checks (reason codes for citizen-developer-facing errors)
# 3a: tags
tags = submission.get("tags", {})
missing_tags = [t for t in REQUIRED_TAGS if t not in tags or not tags[t]]
if missing_tags:
codes.append(f"MISSING_TAGS:{','.join(missing_tags)}")
# 3b: env mandatory (W3.E per-env table)
env = submission.get("environment")
if env and env in ENV_MANDATORY:
for field_key in ENV_MANDATORY[env]:
val = _get_nested(submission, field_key)
if val is None:
codes.append(f"ENV_MISSING_MANDATORY:{env}:{field_key}")
# 3c: agentic profile markers
if submission.get("profile") == "agentic":
for marker in AGENTIC_REQUIRED:
if not submission.get(marker):
codes.append(f"AGENTIC_MISSING_INTENT:{marker}")
# 3d: appSource
app_source = submission.get("appSource")
if not app_source or not app_source.get("repo") or not app_source.get("ref"):
codes.append("MISSING_APP_SOURCE")
# 3e: policy preconditions (warn if declared but not enforced this milestone)
policy = submission.get("policyPreconditions", {})
if not policy:
codes.append("POLICY_PRECONDITION_MISSING")
ready = len(codes) == 0
return ReadinessResult(ready=ready, reason_codes=codes, contract_id=contract_id)
def cli_main(argv: list[str]) -> int:
"""CLI entry: python3 -m core.submission_readiness <contract.json>
Also invoked via contract_ingestor.py --check-readiness (D-133).
Prints the ReadinessResult to stdout; exits 0 if ready, 1 if not.
"""
if len(argv) < 2:
print("Usage: submission_readiness <contract.json>", file=sys.stderr)
return 2
path = argv[1]
try:
with open(path) as f:
submission = json.load(f)
except (OSError, json.JSONDecodeError) as e:
print(f"ERROR: cannot read {path}: {e}", file=sys.stderr)
return 2
result = check_readiness(submission)
print(result)
print(json.dumps(result.to_dict(), indent=2))
return 0 if result.ready else 1
if __name__ == "__main__":
sys.exit(cli_main(sys.argv))
+14 -6
View File
@@ -100,9 +100,11 @@ CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
```
The `--allow-local-files` flag is **required** for PPTX export so the local
PNG diagrams are embedded in the file. PPTX files are not committed to the
repo (binary, no meaningful diffs) — they are uploaded to the Gitea release
as downloadable attachments.
PNG diagrams are embedded in the file. As of v1.18 (REQ-228, D-141), PPTX
files **are committed to the repo** as first-class binary artifacts (no LFS)
and are also attached to the phase's Gitea release via
`scripts/attach_release_asset.py`. The render + commit + attach pipeline is
automated by `scripts/render_deck.sh`.
### Step 4 — Talking points (presenter cues)
@@ -341,7 +343,13 @@ attachments to the Gitea release.
## Current decks
| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML (Step 3) | Talking points (Step 4) | Slides | Audience |
| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML + PPTX (Step 3) | Talking points (Step 4) | Slides | Audience |
|---|---|---|---|---|---|---|
| How the Platform Works | `how-the-platform-works.md` | `how-the-platform-works-marp.md` | `how-the-platform-works.html` | `how-the-platform-works-talking-points.md` | 11 main + TOC + 8 appendix (20) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
| The Developer Experience | `the-developer-experience.md` | `the-developer-experience-marp.md` | `the-developer-experience.html` | `the-developer-experience-talking-points.md` | 11 main + TOC + 7 appendix (19) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
| Nova — The No-Humans Infrastructure Platform | `nova-no-humans-platform.md` | `nova-no-humans-platform-marp.md` | `nova-no-humans-platform.html` + `.pptx` (committed + release-attached) | `nova-no-humans-platform-talking-points.md` | 19 main + 2 appendix (21) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
> **v1.18 (D-130):** the two legacy decks (How the Platform Works + The
> Developer Experience) were consolidated into a single unified narrative
> deck with a 5-act arc (Problem → Vision → How → Proof → Roadmap). v1.18
> (REQ-226) adds 3 slides (17 Scope, 18 RACI, 19 Atelier) → 21 total. The
> S&P Global Energy theme is restored (REQ-214, P1). PPTX is committed to
> git + attached to the Gitea release (REQ-228, D-141).
@@ -6,13 +6,25 @@ size: 16x9
header: 'Nova — The No-Humans Infrastructure Platform'
footer: 'Act %{page}/5 — v1.17'
style: |
section { font-size: 0.85em; }
h1 { color: #1a1a2e; }
h2 { color: #16213e; }
table { font-size: 0.75em; }
.badge { padding: 2px 8px; border-radius: 3px; font-size: 0.8em; }
.badge.planned { background: #fff3cd; color: #856404; }
section.title { background: #1a1a2e; color: white; }
section {
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
font-size: 22px;
color: #1B1B1B;
}
h1 { color: #D6002A; font-size: 34px; margin-bottom: 0.3em; }
h2 { color: #D6002A; font-size: 26px; margin-bottom: 0.2em; }
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
section.title h1 { color: #fff; }
table { font-size: 18px; width: 100%; }
th { background: #F0F0F0; }
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 20px; }
img { display: block; margin: 0 auto; max-height: 320px; }
.badge {
display: inline-block; padding: 2px 8px; border-radius: 4px;
font-size: 14px; font-weight: 600;
}
.badge.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; }
---
<!-- _class: title -->
@@ -22,7 +34,7 @@ style: |
**Shifting from Operational Overhead to Strategic Value**
v1.17Strategic Direction, Leadership Metrics & Unified Story
v1.18Citizen Developer & Production-Grade Guidance
---
@@ -37,7 +49,7 @@ v1.17 — Strategic Direction, Leadership Metrics & Unified Story
2. **Vision** — Nova's strategic direction (NORTH_STAR)
3. **How** — the pipeline, Decision Ledger, attestation gates
4. **Proof** — grounded metrics that make the claim defensible
5. **Roadmap** — deferred metrics with unblock paths + the ask
5. **Roadmap** — deferred metrics with unblock paths + the ask + scope + RACI
**Benefit:** you leave knowing which claims are proven today, which are pipeline-ready, and which are deferred with a documented unblock path — no marketing, just grounded evidence.
@@ -289,6 +301,56 @@ From `docs/METRICS_DEFERRED_ROADMAP.md`.
---
## Slide 17 — Scope: Downstream of PDLC
**Nova governs infrastructure + delivery. The PDLC (product backlog, code authorship, IDE) is upstream — Nova never penetrates it.**
- **The PDLC is upstream:** product backlog, code authorship (AI agent / IDE / agentic SDLC), sprint planning, application business logic
- **Nova is downstream:** contract ingestion → submission-readiness gate → policy → cloud lifecycle → environment progression → audit + attestation
- **Integration is only through the contract boundary:** the citizen developer's AI coding agent, an upstream agentic SDLC, or any dev platform may all produce submissions — the source does not matter as all are subject to the same compliance standards
- Nova validates the submission, not the author
- Cites `docs/scope.md` + `PROJECT.md` § Scope
**Benefit:** you now know the scope boundary — Nova is purpose-built for infrastructure operations, not product development; integration is through one validated contract.
---
## Slide 18 — RACI: Who Owns What
**Three roles, one matrix — the citizen developer owns FRs + UAT, the platform owns NFRs + infra + QA + prod deploy, release management is co-owned.**
| Work Category | Citizen Dev | Platform | Release Mgmt |
|---|---|---|---|
| Functional Requirements (FRs) | **R/A** | C | I |
| User Acceptance Testing (UAT) | **R/A** | C | I |
| Non-Functional Requirements (NFRs) | I | **R/A** | C |
| Infrastructure (cloud, state, IAM) | I | **R/A** | C |
| QA (policy, confidence, schema) | C | **R/A** | I |
| Production deployment to cloud | I | **R/A** | C |
| Release attestation (QA + SRE) | **A** | R | **R** |
- **Compliance-standard equivalence:** FRs + UAT may come from any upstream source (AI agent, agentic SDLC, dev platform) — all pass the same submission-readiness gate
- **Release co-ownership:** the platform runs the attestations agentically; the citizen developer oversees and triggers the actual release (human at the stage gate)
- Cites `docs/raci.md` + `PROJECT.md` § RACI Matrix
**Benefit:** you now know exactly what you bring (FRs + UAT), what Nova provides (NFRs + infra + QA + prod deploy), and what you co-own (the release attestation).
---
## Slide 19 — Production-Grade Guidance via Atelier
**Nova instructs the citizen developer's AI agent on production-grade engineering — skills + an MCP server with agentic validation beyond deterministic scanners.**
- **Skills (9):** markdown files under `skills/` keyed to Atelier domain paths (api, security, data, testing, observability, errors, devops, infrastructure-as-code, compliance) — extending the BA.A 5-skill catalog
- **MCP server:** `mcp/atelier/server.py` (plugin-registry, stdio) — 4 tools: `lookup_principle`, `list_domains`, `matrix_lookup`, `validate_against_principles`
- **Agentic validation:** catches C1 correctness + C2 clarity + C7 observability gaps that Wiz/Checkmarx/Mend cannot — deterministic tools check policy/secrets; the MCP server checks engineering discipline
- **Vendored Atelier** (pinned tag v0.3.6): audit reproducibility — a validation result is replayable against the exact principles that produced it
- Cites `docs/skills.md` + `mcp/atelier/README.md`
**Benefit:** you now know the citizen developer is not unguided — Nova provides production-grade engineering principles via skills + an MCP server, so the AI agent's submissions meet the same standards regardless of upstream source.
---
<!-- _class: title -->
<!-- _paginate: false -->
@@ -103,6 +103,27 @@
- "Pipeline-ready" → "production-proven" is the value proposition
- **Key takeaway:** approve a pilot + the ledger build-out to move from pipeline-ready to production-proven
### Slide 17 — Scope: Downstream of PDLC
- Nova governs infra + delivery only; the PDLC (product backlog, code authorship, IDE) is upstream
- Integration is only through the validated contract boundary
- Any upstream source (AI agent, agentic SDLC, dev platform) may produce submissions — all subject to the same compliance standards
- Nova validates the submission, not the author
- **Key takeaway:** Nova is purpose-built for infrastructure operations, not product development; the scope boundary is clean
### Slide 18 — RACI: Who Owns What
- Citizen Developer owns FRs + UAT (via any upstream source — AI agent, SDLC, dev platform — all pass the same gate)
- Platform owns NFRs + infra + QA + prod deploy
- Release Management is co-owned: platform runs attestations agentically, citizen developer oversees + triggers the release (human at stage gate)
- The compliance-standard equivalence is the key: the source does not matter; the submission does
- **Key takeaway:** you bring FRs + UAT; Nova provides NFRs + infra + QA + prod deploy; the release is co-owned with you at the stage gate
### Slide 19 — Production-Grade Guidance via Atelier
- Nova instructs the citizen developer's AI agent via skills (9 markdown files) + an MCP server (4 tools, plugin-registry, stdio)
- The MCP server provides agentic validation beyond deterministic scanners — catches correctness, clarity, observability gaps that Wiz/Checkmarx/Mend cannot
- Atelier is vendored (pinned tag) for audit reproducibility — a validation result is replayable
- This is how Nova ensures the citizen developer's submissions meet production-grade standards regardless of upstream source
- **Key takeaway:** the citizen developer is not unguided — Nova provides engineering principles via skills + MCP, so every submission meets the same standards
### Appendix A1 — Metrics Glossary
- Reference for every metric mentioned in the deck
- Use if the audience asks "what does X mean?"
File diff suppressed because one or more lines are too long
Binary file not shown.
+85
View File
@@ -0,0 +1,85 @@
# RACI — Who Owns What
> **Source of truth:** `.ciagent/PROJECT.md` § RACI Matrix (v1.18, REQ-215,
> D-139). This page is the citizen-developer-facing copy.
Nova's delivery lifecycle has three roles. This page clarifies who owns
what — so the citizen developer knows what they bring, what the platform
provides, and what is co-owned.
## The Three Roles
### Citizen Developer (CD)
That's you — the consumer (technical developer L3A or non-technical L3B).
You are **Responsible** for all **Functional Requirements (FRs)** and
**User Acceptance Testing (UAT)**. You produce the FRs + UAT via your AI
coding agent, an upstream agentic SDLC platform, or any upstream
development platform. **The source does not matter** — all are subject
to the same compliance standards (the submission-readiness gate, the
contract schema, the policy envelope, the immutable audit stream). Nova
validates the submission, not the author.
### Platform (Nova)
Nova is **Responsible** for all **Non-Functional Requirements (NFRs)**,
**Infrastructure** (cloud resource lifecycle, state, IAM), **QA** (the
platform-side quality checks: policy enforcement, confidence scoring,
schema validation), and **Production deployments to cloud** (the apply
path, the pipeline, the release mechanics).
### Release Management (RM) — co-owned
The release is **co-owned**. The platform performs the QA + SRE
attestations agentically (it runs the confidence signal, the policy
checks, the separation-of-duties). The citizen developer **oversees and
triggers** the actual release — the human attestation at the stage gate
is your authorization. The platform runs the checks; you authorize the
promotion. This is the "autonomy in operations, human at stage gates"
model.
## The Matrix
| Work Category | Citizen Developer | Platform | Release Management |
|---|---|---|---|
| **Functional Requirements (FRs)** | **R/A** | C | I |
| **User Acceptance Testing (UAT)** | **R/A** | C | I |
| **Non-Functional Requirements (NFRs)** | I | **R/A** | C |
| **Infrastructure (cloud, state, IAM)** | I | **R/A** | C |
| **QA (policy, confidence, schema checks)** | C | **R/A** | I |
| **Production deployment to cloud** | I | **R/A** | C |
| **Release attestation (QA + SRE sign-off)** | **A** | R | **R** |
**Key:** **R** = Responsible (does the work) · **A** = Accountable (owns
the outcome, sign-off) · **C** = Consulted · **I** = Informed.
## What This Means in Practice
**You (Citizen Developer) bring:**
- Your application code + a contract that declares intent.
- Your FRs (what the application does).
- Your UAT (you accept the deployment when it meets your FRs).
**Nova (Platform) provides:**
- The NFRs (security, observability, compliance — baked into the
pipeline, not your concern).
- The infrastructure (cloud resources, state management, IAM scoping).
- The QA (policy enforcement, confidence scoring, schema validation).
- The production deployment (the apply path, the pipeline, the release).
**You co-own the release:**
- Nova runs the attestations (QA confidence, SRE operational readiness).
- You authorize the promotion at the stage gate. No promotion happens
without your recorded attestation.
## Compliance Standards Apply Equally
Your FRs + UAT may come from any source — an AI coding agent, an
agentic SDLC platform, or a traditional IDE. Nova does not
differentiate. All submissions pass through the same gate
(`schemas/submission-readiness.schema.json`): tags, environment
metadata, policy preconditions, profile markers. The compliance
standards are the same regardless of how the code was authored. This
is by design: the audit trail is the same, the policy envelope is the
same, the evidence stream is the same. The source does not matter; the
submission does.
+68
View File
@@ -0,0 +1,68 @@
# Scope — Nova is Downstream of PDLC
> **Source of truth:** `.ciagent/PROJECT.md` § Scope (v1.18, REQ-216).
> This page is the citizen-developer-facing copy.
## The Boundary
The **Product Development Lifecycle (PDLC)** is **upstream** of Nova. The
PDLC includes:
- Product backlog / roadmap planning
- Code authorship (via AI coding agent, IDE, or agentic SDLC platform)
- Sprint planning / issue tracking
- Application business logic
- IDE workflows / developer experience
Nova never penetrates the PDLC. Nova's domain is **infrastructure +
delivery only**.
## What Nova Does
Nova governs the downstream half:
- **Contract ingestion** — the validated entry point
- **Submission-readiness gate** — what is acceptable to start
(`schemas/submission-readiness.schema.json`)
- **Policy enforcement** — the confidence signal, Checkov, tagging
- **Cloud resource lifecycle** — Terraform plan/apply, state, IAM
- **Environment progression** — dev (autonomous) → qa (QA attestation) →
prod (SRE attestation) → dr (SRE attestation)
- **Immutable audit + attestation** — the Decision Ledger, the evidence
stream, the HITL gates
## The Integration Point
Integration between the PDLC and Nova is **only** through the validated,
published contract boundary:
```
PDLC (upstream) Nova (downstream)
───────────────── ─────────────────
product backlog contract ingestion
code authorship (AI agent / IDE / SDLC) → submission-readiness gate
sprint planning → policy enforcement
application business logic → cloud resource lifecycle
→ environment progression (dev→qa→prod→dr)
→ immutable audit + attestation
```
The citizen developer's AI coding agent, an upstream agentic SDLC
platform, or any upstream development platform may all produce
submissions. **The source does not matter** — all are subject to the
same compliance standards. Nova validates the submission, not the
author.
## What Nova is Not
- Not an upstream development platform (no product backlogs, IDE, code
authorship).
- Not a general-purpose AI agent platform (autonomy is narrow, bounded
by policy envelopes).
- Not a legacy infrastructure bridge (no VMs/bare metal/OS).
- Not a permissive delivery highway (no escape hatches past confidence
or HITL).
- Not a mutable audit log (VCS history ≠ regulatory evidence).
These anti-goals (from `docs/vision.md` §7 and Core Tenet #2) are
promoted here from buried tenets to an unmissable scope statement.
+88
View File
@@ -0,0 +1,88 @@
# Skills — Production-Grade Guidance for the Citizen Developer
> **Source of truth (v1.18, REQ-221, REQ-222).** The Nova skill catalog
> extends the BA.A 5-skill catalog (web API, worker, scheduled job, static
> asset, basic observability bootstrap) with Atelier-derived production-
> grade engineering principles. Each skill is a markdown file under
> `skills/` keyed to an Atelier domain path.
## How the Citizen Developer's AI Agent Consumes Skills
1. **Before completing a task**, read the relevant skill file(s) that
match the task's domain.
2. **Run `review/agent-checklist.md`** (from Atelier) before finishing —
the checklist items are the gate between "the code is written" and
"the task is done."
3. **Use the Atelier MCP server** (`mcp/atelier/server.py`, P5) for
agentic validation — the `atelier.validate_against_principles` tool
catches correctness/clarity/simplicity/observability gaps that
deterministic scanners (Wiz, Checkmarx, Mend) cannot.
## The 9 Skills
| Skill | Atelier Source | Core Principles | BA.A Mapping |
|---|---|---|---|
| [`api.md`](../skills/api.md) | `domains/api/` | C1, C2, C6 | web API |
| [`security.md`](../skills/security.md) | `domains/security/` | C1 | cross-cutting (all 5) |
| [`data.md`](../skills/data.md) | `domains/data/` | C1, C4, C6 | web API, worker, scheduled job |
| [`testing.md`](../skills/testing.md) | `domains/testing/` | C1, C5 | UAT (citizen-dev RACI) |
| [`observability.md`](../skills/observability.md) | `domains/observability/` | C7 | basic observability bootstrap |
| [`errors.md`](../skills/errors.md) | `domains/errors/` | C1, C7 | web API, worker, scheduled job |
| [`devops.md`](../skills/devops.md) | `domains/devops/` | C5, C7, C8 | scheduled job, worker |
| [`infrastructure-as-code.md`](../skills/infrastructure-as-code.md) | `domains/infrastructure-as-code/` | C1, C5, C8 | static asset |
| [`compliance.md`](../skills/compliance.md) | `domains/compliance/` | C1, C5 | cross-cutting (all 5) |
## Atelier Provenance
The skills are derived from [Atelier](https://git.cloudinit.dev/coreci/atelier)
— a first-principles docs-as-code engineering framework with 8 core
principles (C1C8) and 19 domains, each with 10 derived P-rules. The
skills distill the citizen-developer-relevant subset of each domain's
first-principles, link to the agent-checklist triggers, and map to the
existing BA.A catalog.
Atelier is vendored under `mcp/atelier/vendor/` (pinned tag, D-136) for
audit reproducibility — an agentic validation result is replayable
against the exact principles that produced it.
## The 8 Core Principles (from Atelier)
| # | Principle | One-line |
|---|---|---|
| C1 | Correctness | The system does what it is supposed to do, and nothing else. |
| C2 | Clarity | The intent of the code is obvious to its reader. |
| C3 | Simplicity | The solution is as simple as possible, and no simpler. |
| C4 | Locality | Decisions and their consequences live near each other. |
| C5 | Reversibility | Every decision can be undone, and the cost of undoing is known. |
| C6 | Composability | Parts combine into wholes, and the parts are reusable. |
| C7 | Observability | The system's behavior is visible to those who must understand it. |
| C8 | Economy | The system uses no more resources than the task requires. |
Precedence: C1 > C2 > C3 > C4 > C5 > C6 > C7 > C8. Correctness is never
sacrificed.
## Reference-Only Domains (cited inside skills, not elevated to skill files)
These 4 Atelier domains are relevant to a citizen developer but are cited
inside the 9 skills above rather than getting their own skill file:
- **Performance** (`domains/performance/`) — cited in `observability.md` +
`devops.md` (bounded operations, timeouts, N+1)
- **Documentation** (`domains/documentation/`) — the runbook requirement
(W3.E prod mandatory) is the documentation skill in practice
- **Concurrency** (`domains/concurrency/`) — cited in `errors.md` +
`devops.md` (bounded queues, cancellation, timeout)
- **AI/ML** (`domains/ai-ml/`) — scope: engineering discipline (data
versioning, evaluation, serving, drift), not algorithm design
## Excluded Domains (not relevant to Nova citizen developer)
6 Atelier domains are excluded from the Nova skill catalog (not relevant
to a citizen developer building on Nova's infrastructure platform):
- UI/UX — Nova has no frontend (frontend-engineer deactivated, PERSONAS.md)
- Kubernetes — Nova is AWS-only this milestone (NORTH_STAR Non-Goal #7)
- GitOps + Operators — future roadmap (no GitOps reconciler today)
- Edge — not in scope (Nova is cloud, not edge)
- Messaging — not in scope (Nova deploys infra, not message brokers)
- i18n — application-level concern, not infrastructure
+150
View File
@@ -0,0 +1,150 @@
# Submission Readiness — What is Acceptable to Start
> **Source of truth:** `schemas/submission-readiness.schema.json` (v1.18,
> REQ-217). The validator is `core/submission_readiness.py` (REQ-218),
> invoked as `python3 -m core.lambda.contract_ingestor --check-readiness
> <submission.json>` (D-133).
Nova's submission-readiness gate defines what is **acceptable to start**.
It is a superset gate *above* contract-schema validity: the contract schema
(`schemas/contract.schema.json`) defines the **shape** (id / name /
environment / infrastructure); the readiness schema defines the **gate**
(tags, per-env mandatory metadata, policy preconditions, profile markers,
appSource). Both must pass before ingestion proceeds.
## How It Works
```
citizen developer submits
contract.schema.json validation (shape) ← the existing check
submission-readiness.schema.json (gate) ← the new check
├── contractId present (non-empty)
├── environment valid (dev/qa/prod/dr)
├── tags: all 5 Nova tags present (D-054)
├── policyPreconditions declared
├── profile: developer or agentic
│ └── if agentic: naturalLanguageIntent + confidenceAtSubmission + agentTrace
├── appSource: repo + ref (for runtime fetch)
└── per-env mandatory (W3.E):
dev → stack + environment
qa → + validation.e2eSuite + validation.loadTest
prod → + runbook + dashboard + oncall
dr → + drDrillRef
ready → proceed to contract ingestion
not ready → reject with citizen-developer-facing error (reason code)
```
## Reason Codes
When a submission is not ready, the validator returns one or more reason
codes. These are citizen-developer-facing — no stack traces.
| Code | Meaning |
|---|---|
| `MISSING_TAGS:<tag1>,<tag2>` | One or more required Nova tags are absent |
| `ENV_MISSING_MANDATORY:<env>:<field>` | A per-env mandatory field (W3.E) is missing |
| `AGENTIC_MISSING_INTENT:<marker>` | profile=agentic but a required marker is absent |
| `MISSING_APP_SOURCE` | appSource (repo + ref) is missing |
| `POLICY_PRECONDITION_MISSING` | No policy preconditions declared |
| `CONTRACT_SCHEMA_INVALID:<detail>` | The contract shape failed contract.schema.json |
| `READINESS_SCHEMA_INVALID:<detail>` | The submission failed the readiness schema |
## Good Example
```json
{
"contractId": "uuid-1234",
"id": "webapi",
"name": "Customer Web API",
"environment": "dev",
"tags": {
"nova:owner": "consumer-repo",
"nova:contract": "uuid-1234",
"nova:environment": "dev",
"nova:cost-center": "nova-default",
"nova:ref": "CHG0678912"
},
"policyPreconditions": {
"public-ingress": false,
"encryption_enabled": true,
"deletion_protection": true
},
"profile": "developer",
"appSource": {
"repo": "consumer/web-api",
"ref": "main"
},
"infrastructure": {
"static-assets": {
"inputs": {
"bucket_name": "webapi-assets"
}
}
}
}
```
Result: **READY** — passes the shape + the gate.
## Rejected Examples
### Missing Tags
```json
{
"contractId": "uuid-1234",
"environment": "dev",
"tags": {
"nova:owner": "consumer-repo"
},
"policyPreconditions": {"public-ingress": false},
"profile": "developer",
"appSource": {"repo": "consumer/repo", "ref": "main"}
}
```
Result: `NOT READY — MISSING_TAGS:nova:contract,nova:environment,nova:cost-center,nova:ref`
### Agentic Missing Intent
```json
{
"contractId": "uuid-1234",
"environment": "qa",
"tags": { "nova:owner": "x", "nova:contract": "x", "nova:environment": "qa", "nova:cost-center": "x", "nova:ref": "x" },
"policyPreconditions": {"public-ingress": false},
"profile": "agentic",
"appSource": {"repo": "x", "ref": "x"},
"validation": {"e2eSuite": true, "loadTest": true}
}
```
Result: `NOT READY — AGENTIC_MISSING_INTENT:naturalLanguageIntent; AGENTIC_MISSING_INTENT:confidenceAtSubmission; AGENTIC_MISSING_INTENT:agentTrace`
### Env Missing Mandatory (prod without runbook)
```json
{
"contractId": "uuid-1234",
"environment": "prod",
"tags": { "nova:owner": "x", "nova:contract": "x", "nova:environment": "prod", "nova:cost-center": "x", "nova:ref": "x" },
"policyPreconditions": {"public-ingress": false},
"profile": "developer",
"appSource": {"repo": "x", "ref": "x"}
}
```
Result: `NOT READY — ENV_MISSING_MANDATORY:prod:runbook; ENV_MISSING_MANDATORY:prod:dashboard; ENV_MISSING_MANDATORY:prod:oncall`
## Compliance-Standard Equivalence
The submission-readiness gate applies **equally** to all upstream sources.
Whether the citizen developer's submission originated from an AI coding
agent, an agentic SDLC platform, or a traditional development platform —
the same tags, the same env mandatory, the same policy preconditions, the
same profile markers are required. The source does not matter; the
submission does. This is the RACI compliance-standard equivalence note
(`docs/raci.md`) made machine-checkable.
+1
View File
@@ -0,0 +1 @@
# mcp/atelier — Nova Atelier MCP server package (v1.18)
+96
View File
@@ -0,0 +1,96 @@
# Nova Atelier MCP Server
> **v1.18, REQ-223, REQ-224.** An MCP (Model Context Protocol) server that
> exposes Atelier engineering principles to the citizen developer's AI
> agent. Plugin-registry architecture (D-140); stdio transport (D-135);
> vendored Atelier (D-136) for audit reproducibility.
## What This Is
The server exposes 4 tools that let a citizen developer's AI coding agent
look up production-grade engineering principles and validate code against
them — agentic validation that goes **beyond deterministic scanners**
(Wiz, Checkmarx, Mend) by catching correctness, clarity, simplicity, and
observability gaps.
## Tools
| Tool | Description |
|---|---|
| `atelier.lookup_principle(domain, principle_id)` | Look up a principle by domain + P-rule ID (e.g., `security`, `P4`). Returns the principle text + the core C-rule it derives from. |
| `atelier.list_domains()` | List the 19 Atelier domains with P-rule counts + Nova-relevance. |
| `atelier.matrix_lookup(domain)` | Look up the domain→core principle mapping for a given domain. |
| `atelier.validate_against_principles(snippet, domains?)` | Validate a code/diff snippet against the Atelier agent-checklist. Returns pass/fail per check item with the principle citation. |
## Architecture — Plugin Registry (D-140)
```
mcp/atelier/
├── server.py # entrypoint: loads plugins, starts server
├── plugins/
│ ├── __init__.py
│ ├── principles.py # lookup_principle, list_domains, matrix_lookup
│ └── validation.py # validate_against_principles
├── vendor/ # pinned Atelier snapshot (D-136)
│ ├── VERSION.md # pinned tag + upgrade instructions
│ ├── core/first-principles.md
│ ├── domains/security/first-principles.md
│ ├── review/agent-checklist.md
│ └── matrix/principles-matrix.md
└── README.md # this file
```
Each plugin module exposes `register(mcp) -> None` and calls `@mcp.tool()`
for its tools. `server.py` scans `plugins/` and calls `register` on each.
**Future capabilities drop in as a new plugin file — no `server.py` edits.**
## Running
### With the MCP Python SDK installed
```bash
pip install "mcp[cli]"
python3 -m mcp.atelier.server
```
The server runs over stdio. An MCP client (e.g., the citizen developer's
AI coding agent) spawns it as a subprocess and calls tools via JSON-RPC.
### Without the SDK (fallback / test mode)
The server degrades to a plain-Python tool registry. Tools are callable
directly — this is how tests run without the SDK installed:
```python
from mcp.atelier.server import NovaAtelierServer
s = NovaAtelierServer()
s.load_plugins()
result = s.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P4"})
```
## Vendoring (D-136)
Atelier is vendored under `vendor/` at a pinned tag (`v0.3.6`, see
`vendor/VERSION.md`). An agentic validation result is only reproducible if
the principles that produced it are pinned. Live-fetch breaks replayability
(Atelier `main` drifts). To upgrade:
```bash
bash scripts/update_atelier_vendor.sh <new-tag>
```
## Extensibility
To add a new tool (e.g., a cost-estimation tool, a policy-as-code
evaluator): create `plugins/<name>.py`, expose `register(mcp)`, and call
`@mcp.tool()` on your function. The server picks it up automatically. No
`server.py` edit. This is the extensibility insurance for future
capabilities.
## Transport
- **Now:** stdio (local agent consumption — the citizen developer's AI
agent spawns the server as a subprocess).
- **Future:** Streamable HTTP (the MCP SDK supports it on the same
`MCPServer` object; adding it is a transport-only change in `server.py`,
not a rewrite).
+1
View File
@@ -0,0 +1 @@
# mcp/atelier package
+1
View File
@@ -0,0 +1 @@
# mcp/atelier/plugins package
+99
View File
@@ -0,0 +1,99 @@
"""mcp/atelier/plugins/principles.py — principle lookup, domain listing, matrix lookup.
Implements 3 MCP tools (REQ-223):
- atelier.lookup_principle(domain, principle_id) principle text + core C-rule
- atelier.list_domains() 19 domains with P-rule counts + Nova-relevance
- atelier.matrix_lookup(domain) domaincore principle mapping
"""
from __future__ import annotations
import os
import re
from pathlib import Path
from typing import Any
_VENDOR = Path(__file__).resolve().parent.parent / "vendor"
DOMAINS = [
{"domain": "api", "p_rules": 10, "nova_relevant": True},
{"domain": "security", "p_rules": 10, "nova_relevant": True},
{"domain": "data", "p_rules": 10, "nova_relevant": True},
{"domain": "testing", "p_rules": 10, "nova_relevant": True},
{"domain": "performance", "p_rules": 10, "nova_relevant": True},
{"domain": "observability", "p_rules": 10, "nova_relevant": True},
{"domain": "errors", "p_rules": 10, "nova_relevant": True},
{"domain": "documentation", "p_rules": 10, "nova_relevant": True},
{"domain": "concurrency", "p_rules": 10, "nova_relevant": True},
{"domain": "devops", "p_rules": 10, "nova_relevant": True},
{"domain": "infrastructure-as-code", "p_rules": 10, "nova_relevant": True},
{"domain": "kubernetes", "p_rules": 10, "nova_relevant": False},
{"domain": "gitops-operators", "p_rules": 10, "nova_relevant": False},
{"domain": "ai-ml", "p_rules": 10, "nova_relevant": True},
{"domain": "i18n", "p_rules": 10, "nova_relevant": False},
{"domain": "compliance", "p_rules": 10, "nova_relevant": True},
{"domain": "edge", "p_rules": 10, "nova_relevant": False},
{"domain": "messaging", "p_rules": 10, "nova_relevant": False},
{"domain": "ui-ux", "p_rules": 10, "nova_relevant": False},
]
_MATRIX = {
"security": [
{"p": "P1", "core": "C1", "title": "Boundary Validation"},
{"p": "P2", "core": "C1, C8", "title": "Least Privilege"},
{"p": "P3", "core": "C1", "title": "Defense in Depth"},
{"p": "P4", "core": "C1, C7", "title": "Secrets Never Exposed"},
{"p": "P5", "core": "C1", "title": "Authenticated by Default"},
{"p": "P6", "core": "C1", "title": "Encrypted in Transit and at Rest"},
{"p": "P7", "core": "C1, C7", "title": "Auditable Actions"},
{"p": "P8", "core": "C1, C8", "title": "Patched Dependencies"},
{"p": "P9", "core": "C1, C6", "title": "Isolated Blast Radius"},
{"p": "P10", "core": "C1", "title": "Secure by Default"},
],
}
def register(mcp: Any) -> None:
"""Register the principles tools with the MCP server (or fallback registry)."""
@mcp.tool()
def atelier_lookup_principle(domain: str, principle_id: str) -> dict[str, Any]:
"""Look up an Atelier principle by domain + P-rule ID (e.g., 'security', 'P4').
Returns the principle title, text, and the core C-rule(s) it derives from.
"""
fp = _VENDOR / "domains" / domain / "first-principles.md"
if not fp.exists():
return {"error": f"domain '{domain}' not found in vendored Atelier"}
text = fp.read_text()
# Parse the P-rule section
pattern = rf"## ({principle_id}\s*—\s*.+?)\n(.+?)(?=\n## |\Z)"
match = re.search(pattern, text, re.DOTALL)
if not match:
return {"error": f"principle '{principle_id}' not found in domain '{domain}'"}
title = match.group(1).strip()
body = match.group(2).strip()
# Find core C-rule from matrix
matrix_entry = next(
(e for e in _MATRIX.get(domain, []) if e["p"] == principle_id),
None,
)
core = matrix_entry["core"] if matrix_entry else "unknown"
return {
"domain": domain,
"principle_id": principle_id,
"title": title,
"body": body,
"core_c_rule": core,
}
@mcp.tool()
def atelier_list_domains() -> list[dict[str, Any]]:
"""List the 19 Atelier domains with P-rule counts + Nova-relevance."""
return DOMAINS
@mcp.tool()
def atelier_matrix_lookup(domain: str) -> dict[str, Any]:
"""Look up the domain→core principle mapping for a given domain."""
if domain not in _MATRIX:
return {"domain": domain, "mapping": [], "note": "full matrix not vendored for this domain; see Atelier live repo"}
return {"domain": domain, "mapping": _MATRIX[domain]}
+78
View File
@@ -0,0 +1,78 @@
"""mcp/atelier/plugins/validation.py — agentic validation against Atelier principles.
Implements 1 MCP tool (REQ-223):
- atelier.validate_against_principles(snippet, domains) pass/fail per
checklist item with the principle citation. This is the agentic
validation BEYOND deterministic scanners (Wiz/Checkmarx/Mend) it
catches correctness/clarity/simplicity/observability gaps that
deterministic tools cannot.
"""
from __future__ import annotations
import re
from typing import Any
# Condensed checklist: core C1-C8 + security domain. Each item is a
# (check_id, description, heuristic_pattern, principle_citation).
_CHECKLIST = [
# C1 Correctness
{"id": "C1.1", "desc": "Does the code do what the task asked, completely?", "heuristic": r"TODO|FIXME|pass\s*$", "principle": "C1 Correctness", "neg": True},
{"id": "C1.2", "desc": "Does it handle failure cases? (errors, timeouts)", "heuristic": r"except\s*:?\s*pass", "principle": "C1 Correctness", "neg": True},
{"id": "C1.3", "desc": "Is there a test that would fail if the code were wrong?", "heuristic": r"def test_|describe\(", "principle": "C1 Correctness", "neg": False, "optional": True},
# C2 Clarity
{"id": "C2.1", "desc": "Are names intent-revealing? (no 'data', 'temp', 'x')", "heuristic": r"\b(data|temp|x|foo|bar|doStuff)\b", "principle": "C2 Clarity", "neg": True},
# C3 Simplicity
{"id": "C3.1", "desc": "Is there dead code? (unreachable branches)", "heuristic": r"return\s+\w+\s*$.*return", "principle": "C3 Simplicity", "neg": True, "multiline": True},
# C7 Observability
{"id": "C7.1", "desc": "Are there logs for significant events?", "heuristic": r"log(ger|ging)?|print\(|console\.", "principle": "C7 Observability", "neg": False, "optional": True},
{"id": "C7.2", "desc": "Are there secrets in logs?", "heuristic": r"password|secret|token|api_key", "principle": "C7 Observability + Security P4", "neg": True},
# Security
{"id": "SEC.1", "desc": "No secrets in code/logs/URLs", "heuristic": r"(password|secret|token|api_key)\s*=\s*['\"]", "principle": "Security P4 Secrets Never Exposed", "neg": True},
{"id": "SEC.2", "desc": "Input validated at the boundary", "heuristic": r"validate|schema|assert", "principle": "Security P1 Boundary Validation", "neg": False, "optional": True},
{"id": "SEC.3", "desc": "Authorization checked, not assumed", "heuristic": r"auth|permission|rbac|authorize", "principle": "Security P5 Authenticated by Default", "neg": False, "optional": True},
]
def register(mcp: Any) -> None:
"""Register the validation tools with the MCP server (or fallback registry)."""
@mcp.tool()
def atelier_validate_against_principles(snippet: str, domains: list[str] | None = None) -> dict[str, Any]:
"""Validate a code/diff snippet against Atelier principles.
Runs the agent-checklist items against the snippet and returns
pass/fail per item with the principle citation. This is the
agentic validation BEYOND deterministic scanners (Wiz/Checkmarx/
Mend) it catches correctness/clarity/simplicity/observability
gaps that deterministic tools cannot.
Args:
snippet: The code or diff text to validate.
domains: Optional list of domains to include (default: core + security).
"""
results: list[dict[str, Any]] = []
for check in _CHECKLIST:
pattern = check["heuristic"]
flags = re.DOTALL if check.get("multiline") else 0
found = bool(re.search(pattern, snippet, flags))
# neg=True means finding the pattern is a FAIL; neg=False means finding is a PASS
if check.get("neg"):
status = "FAIL" if found else "PASS"
else:
if check.get("optional"):
status = "PASS" if found else "WARN"
else:
status = "PASS" if found else "WARN"
results.append({
"check_id": check["id"],
"description": check["desc"],
"status": status,
"principle": check["principle"],
})
all_pass = all(r["status"] == "PASS" for r in results)
return {
"overall": "PASS" if all_pass else "FAIL",
"results": results,
"domains_checked": domains or ["core", "security"],
"note": "Agentic validation beyond Wiz/Checkmarx/Mend — catches correctness, clarity, simplicity, observability gaps.",
}
+142
View File
@@ -0,0 +1,142 @@
"""mcp/atelier/server.py — Nova Atelier MCP server (REQ-223, D-135, D-137, D-140).
Plugin-registry architecture (D-140): plugins/<name>.py modules each expose
``register(mcp) -> None`` and call ``@mcp.tool()`` for their tools. This file
scans ``plugins/`` and calls ``register`` on each. Future capabilities drop
in as new plugin files no server.py edits.
Transport: stdio (D-135). The MCP Python SDK v2 (``modelcontextprotocol/
python-sdk``, D-137) is the target. If the SDK is not installed, the server
degrades to a plain-Python tool registry that can be tested directly the
tools are callable without MCP. This makes the server testable in CI
without the SDK installed.
Usage (with SDK):
python3 -m mcp.atelier.server
Usage (without SDK, for testing):
from mcp.atelier.server import NovaAtelierServer
s = NovaAtelierServer()
s.load_plugins()
result = s.call_tool("atelier.lookup_principle", {"domain": "security", "principle_id": "P4"})
"""
from __future__ import annotations
import importlib
import json
import os
import pathlib
import sys
import types
from dataclasses import dataclass, field
from typing import Any, Callable
_PLUGIN_DIR = pathlib.Path(__file__).parent / "plugins"
_VENDOR_DIR = pathlib.Path(__file__).parent / "vendor"
class _ToolRegistry:
"""A minimal tool registry that mimics the MCP ``@mcp.tool()`` decorator.
When the MCP SDK is available, ``NovaAtelierServer`` wraps a real
``MCPServer`` and the decorator registers tools with the SDK. When the
SDK is absent, this registry is the fallback tools are callable via
``call_tool()`` for testing.
"""
def __init__(self) -> None:
self._tools: dict[str, dict[str, Any]] = {}
def tool(self, name: str | None = None, description: str | None = None) -> Callable:
def decorator(fn: Callable) -> Callable:
tool_name = name or fn.__name__
self._tools[tool_name] = {
"fn": fn,
"description": description or fn.__doc__ or "",
"name": tool_name,
}
return fn
return decorator
def list_tools(self) -> list[dict[str, str]]:
return [{"name": t["name"], "description": t["description"]} for t in self._tools.values()]
def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
if name not in self._tools:
raise KeyError(f"Unknown tool: {name}")
return self._tools[name]["fn"](**arguments)
class NovaAtelierServer:
"""The Nova Atelier MCP server.
Wraps an MCP SDK ``MCPServer`` if available; otherwise uses the
``_ToolRegistry`` fallback. Plugins are loaded from ``plugins/``.
"""
def __init__(self) -> None:
self.registry = _ToolRegistry()
self._mcp = None
try:
from mcp.server import MCPServer # type: ignore[import-not-found]
self._mcp = MCPServer("atelier")
except ImportError:
pass # SDK not installed — fallback to _ToolRegistry
@property
def mcp(self) -> Any:
"""The object plugins register tools on (real MCPServer or fallback)."""
return self._mcp if self._mcp is not None else self.registry
def load_plugins(self) -> list[str]:
"""Scan plugins/ and call ``register(mcp)`` on each. Returns loaded names."""
loaded: list[str] = []
for p in sorted(_PLUGIN_DIR.glob("*.py")):
if p.stem == "__init__":
continue
mod_name = f"mcp.atelier.plugins.{p.stem}"
mod = importlib.import_module(mod_name)
if hasattr(mod, "register"):
mod.register(self.mcp if self._mcp else self.registry)
loaded.append(p.stem)
return loaded
def list_tools(self) -> list[dict[str, str]]:
if self._mcp is not None:
return [{"name": t.name, "description": t.description} for t in self._mcp._tools.values()] # type: ignore[attr-defined]
return self.registry.list_tools()
def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
if self._mcp is not None:
raise RuntimeError("MCP SDK call_tool not supported in fallback mode — use the MCP client")
return self.registry.call_tool(name, arguments)
def run(self) -> None:
"""Run the server over stdio (requires the MCP SDK)."""
if self._mcp is None:
raise RuntimeError("MCP SDK not installed — cannot run server. Install: pip install mcp")
self._mcp.run()
def _make_plugin_compat_decorator(registry_or_mcp: Any) -> Callable:
"""Return a ``tool()`` decorator that works for both the fallback
registry and the real MCP SDK."""
if hasattr(registry_or_mcp, "tool"):
return registry_or_mcp.tool
# Fallback: wrap registry.tool() as a decorator factory
return registry_or_mcp.tool
def main() -> None:
server = NovaAtelierServer()
loaded = server.load_plugins()
print(f"Atelier MCP server — {len(loaded)} plugins loaded: {', '.join(loaded)}", file=sys.stderr)
if server._mcp is None:
print("MCP SDK not installed — server is in fallback (test) mode.", file=sys.stderr)
print("Tools: " + ", ".join(t["name"] for t in server.list_tools()), file=sys.stderr)
else:
server.run()
if __name__ == "__main__":
main()
+21
View File
@@ -0,0 +1,21 @@
# Vendored Atelier — Version Pin
> **Pinned tag:** `v0.3.6` (the v0.4 milestone release, 2026-08-05)
> **Commit:** `666b137dbb3c00e81f8740d18b639bc67587d29f`
> **P-rule count:** 190 (19 domains × 10 P-rules)
> **Vendor date:** 2026-08-06
> **Vendor reason:** audit reproducibility (D-136) — an agentic validation
> result is only replayable if the principles that produced it are pinned.
## Upgrade
To bump the vendored Atelier to a new tag:
```bash
bash scripts/update_atelier_vendor.sh <new-tag>
```
The script fetches the Atelier repo at the given tag, replaces
`mcp/atelier/vendor/`, updates this VERSION.md, and commits the change.
Upgrades are **intentional** — never automatic. Atelier `main` is a
moving target; pinning is required for audit reproducibility.
+28
View File
@@ -0,0 +1,28 @@
# Core First Principles
The 8 universal axioms. Every domain principle derives from one or more
of these. Precedence: C1 > C2 > C3 > C4 > C5 > C6 > C7 > C8.
## C1 — Correctness
The system does what it is supposed to do, and nothing else.
## C2 — Clarity
The intent of the code is obvious to its reader.
## C3 — Simplicity
The solution is as simple as possible, and no simpler.
## C4 — Locality
Decisions and their consequences live near each other.
## C5 — Reversibility
Every decision can be undone, and the cost of undoing is known.
## C6 — Composability
Parts combine into wholes, and the parts are reusable.
## C7 — Observability
The system's behavior is visible to those who must understand it.
## C8 — Economy
The system uses no more resources than the task requires.
+31
View File
@@ -0,0 +1,31 @@
# Security — First Principles
## P1 — Boundary Validation
All input is validated at the trust boundary. (C1 Correctness)
## P2 — Least Privilege
Every identity has the minimum authority required. (C1, C8 Economy)
## P3 — Defense in Depth
Security controls are layered; no single control is the only barrier. (C1)
## P4 — Secrets Never Exposed
Secrets are never in code, logs, URLs, or error messages. (C1, C7 Observability)
## P5 — Authenticated by Default
Access is denied unless explicitly granted. (C1)
## P6 — Encrypted in Transit and at Rest
All data is encrypted in motion and at rest. (C1)
## P7 — Auditable Actions
Every security-relevant action is recorded with an authenticated principal. (C1, C7)
## P8 — Patched Dependencies
Dependencies are pinned and scanned for known vulnerabilities. (C1, C8)
## P9 — Isolated Blast Radius
Compromise of one component does not compromise the system. (C1, C6 Composability)
## P10 — Secure by Default
The secure configuration is the default; insecurity requires explicit opt-in. (C1)
+19
View File
@@ -0,0 +1,19 @@
# Principles Matrix (Vendored Stub)
Maps every domain P-rule back to the core C-rule(s) it derives from.
Full matrix in the live Atelier repo; this is a condensed vendored version
for the security domain (the primary domain the MCP server validates
against in v1.18).
| Domain | P-rule | Core C-rule(s) |
|---|---|---|
| security | P1 Boundary Validation | C1 Correctness |
| security | P2 Least Privilege | C1, C8 Economy |
| security | P3 Defense in Depth | C1 |
| security | P4 Secrets Never Exposed | C1, C7 Observability |
| security | P5 Authenticated by Default | C1 |
| security | P6 Encrypted in Transit and at Rest | C1 |
| security | P7 Auditable Actions | C1, C7 |
| security | P8 Patched Dependencies | C1, C8 |
| security | P9 Isolated Blast Radius | C1, C6 Composability |
| security | P10 Secure by Default | C1 |
+50
View File
@@ -0,0 +1,50 @@
# Agent Pre-Completion Checklist (Vendored)
Every AI agent runs this checklist before completing a task.
## Core Principles Checklist (C1C8)
### C1 Correctness
- Does the code do what the task asked, completely?
- Does it handle the specified edge cases? (nulls, empties, max, min)
- Does it handle the failure cases? (errors, timeouts, invalid input)
- Is there a test that would fail if the code were wrong?
### C2 Clarity
- Can a stranger read this and understand it without asking you?
- Are names intent-revealing? (No `data`, `temp`, `x`, `doStuff`)
- Do comments explain *why*, not *what*?
### C3 Simplicity
- Is this the simplest solution that is complete?
- Is there dead code? (Unreachable branches, unused variables)
- Is there premature abstraction? (An interface with one implementation)
### C4 Locality
- Does related logic live together?
- Are side effects near their causes?
### C5 Reversibility
- Is this change undoable? (migration has a `down`, deploy has a rollback)
- Did I avoid irreversible actions without explicit confirmation?
### C6 Composability
- Does this component/function do one thing?
- Is the boundary (props/args/return) explicit and typed?
### C7 Observability
- Are there logs for significant events?
- Do errors carry enough context to debug? (request ID, user, action)
- Are there no secrets in logs?
### C8 Economy
- Is memory bounded? (No unbounded growth, no loading everything)
- Is time bounded? (No N+1, no blocking without timeout)
## Domain-Specific (Security)
- No secrets in code, logs, URLs, or error messages
- Input is validated at the boundary
- Output is encoded for its context
- Crypto uses vetted libraries (no MD5/SHA1 for security)
- Authorization is checked, not assumed
+104
View File
@@ -0,0 +1,104 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://nova.dev/schemas/submission-readiness.schema.json",
"title": "Nova Submission-Readiness Gate",
"description": "Defines what is acceptable to start — a superset gate ABOVE contract.schema.json validity. The contract schema defines the SHAPE (id/name/environment/infrastructure); this schema defines the READINESS gate: required Nova tags, per-env mandatory metadata (W3.E), declared policy preconditions, profile:agentic markers, and the appSource pointer. The validator (core/submission_readiness.py, REQ-218) calls contract.schema.json validation first, then these readiness checks. On fail → citizen-developer-facing error (not a stack trace); on pass → proceeds to existing contract ingestion.",
"type": "object",
"required": ["contractId", "environment", "tags", "policyPreconditions", "profile", "appSource"],
"properties": {
"contractId": {
"type": "string",
"minLength": 1,
"description": "The contract identifier (UUID or operational id). Non-empty."
},
"environment": {
"type": "string",
"enum": ["dev", "qa", "prod", "dr"],
"description": "Target environment. Determines the per-env mandatory fields (allOf below)."
},
"tags": {
"type": "object",
"description": "The 5 required Nova tags (D-054). References schemas/tagging-standard.json.",
"required": ["nova:owner", "nova:contract", "nova:environment", "nova:cost-center", "nova:ref"],
"properties": {
"nova:owner": {"type": "string", "minLength": 1},
"nova:contract": {"type": "string", "minLength": 1},
"nova:environment": {"type": "string", "enum": ["dev", "qa", "prod", "dr"]},
"nova:cost-center": {"type": "string", "minLength": 1},
"nova:ref": {"type": "string", "minLength": 1}
},
"additionalProperties": false
},
"policyPreconditions": {
"type": "object",
"description": "Declared policy expectations the platform will enforce. The citizen developer states what the platform should check; the platform enforces it at apply time. Missing a declared precondition is POLICY_PRECONDITION_MISSING.",
"properties": {
"public-ingress": {"type": "boolean", "default": false},
"encryption_enabled": {"type": "boolean", "default": true},
"deletion_protection": {"type": "boolean", "default": true}
},
"additionalProperties": true
},
"profile": {
"type": "string",
"enum": ["developer", "agentic"],
"description": "developer = L3A (technical); agentic = L3B (non-technical, requires naturalLanguageIntent + confidenceAtSubmission + agentTrace per REQ-22 / W3.E)."
},
"appSource": {
"type": "object",
"description": "Pointer to the consumer application code so the platform can fetch at run time.",
"required": ["repo", "ref"],
"properties": {
"repo": {"type": "string", "minLength": 1, "description": "Repository URL or owner/repo shorthand."},
"ref": {"type": "string", "minLength": 1, "description": "Git ref (branch, tag, or SHA)."}
},
"additionalProperties": false
},
"naturalLanguageIntent": {
"type": "string",
"description": "Required when profile=agentic (L3B). The citizen developer's plain-language intent."
},
"confidenceAtSubmission": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Required when profile=agentic (L3B). The submitter's self-assessed confidence."
},
"agentTrace": {
"type": "string",
"description": "Required when profile=agentic (L3B). The agent's trace/reasoning for the submission."
},
"validation": {
"type": "object",
"description": "Per-env mandatory metadata (W3.E). qa requires e2eSuite + loadTest; prod requires runbook + dashboard + oncall; dr requires drDrillRef.",
"properties": {
"e2eSuite": {"type": "boolean"},
"loadTest": {"type": "boolean"}
},
"additionalProperties": true
},
"runbook": {"type": "string", "description": "Required when environment=prod (W3.E)."},
"dashboard": {"type": "string", "description": "Required when environment=prod (W3.E)."},
"oncall": {"type": "string", "description": "Required when environment=prod (W3.E)."},
"drDrillRef": {"type": "string", "description": "Required when environment=dr (W3.E)."}
},
"allOf": [
{
"if": {"properties": {"environment": {"const": "qa"}}},
"then": {"required": ["validation"], "properties": {"validation": {"required": ["e2eSuite", "loadTest"]}}}
},
{
"if": {"properties": {"environment": {"const": "prod"}}},
"then": {"required": ["runbook", "dashboard", "oncall"]}
},
{
"if": {"properties": {"environment": {"const": "dr"}}},
"then": {"required": ["drDrillRef"]}
},
{
"if": {"properties": {"profile": {"const": "agentic"}}},
"then": {"required": ["naturalLanguageIntent", "confidenceAtSubmission", "agentTrace"]}
}
],
"additionalProperties": true
}
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""scripts/attach_release_asset.py — upload a file as a Gitea release attachment.
REQ-228 (v1.18): PPTX (and any deck artifact) is attached to the phase's
Gitea release. Uses the Gitea API:
POST /api/v1/repos/{owner}/{repo}/releases/{id}/assets
multipart form: name=<filename>, attachment=<file bytes>
Usage:
python3 scripts/attach_release_asset.py <file-path> <release-id>
python3 scripts/attach_release_asset.py docs/presentations/nova-no-humans-platform.pptx 522
Token resolution: reads NOVA_GITEA_TOKEN (or ACDL_GITEA_TOKEN) from .env.secrets
/ .env, matching the ship_phase.sh pattern. Never uses shell env tokens.
"""
import os
import sys
import json
import urllib.request
import urllib.error
from pathlib import Path
GITEA_BASE = "https://git.cloudinit.dev"
OWNER = "continuous-intelligence"
REPO = "acdl"
def resolve_token() -> str:
for fn in (".env.secrets", ".env"):
try:
for line in Path(fn).read_text().splitlines():
if line.startswith("NOVA_GITEA_TOKEN=") or line.startswith("ACDL_GITEA_TOKEN="):
return line.split("=", 1)[1].strip()
except (FileNotFoundError, PermissionError):
continue
raise RuntimeError("No Gitea token found in .env.secrets or .env (NOVA_GITEA_TOKEN/ACDL_GITEA_TOKEN)")
def attach_asset(file_path: str, release_id: str) -> dict:
token = resolve_token()
p = Path(file_path)
if not p.is_file():
raise FileNotFoundError(f"Asset file not found: {file_path}")
url = f"{GITEA_BASE}/api/v1/repos/{OWNER}/{REPO}/releases/{release_id}/assets"
filename = p.name
boundary = "----NovaBoundary7MAgYbk"
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="name"\r\n\r\n'
f"{filename}\r\n"
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="attachment"; filename="{filename}"\r\n'
f"Content-Type: application/octet-stream\r\n\r\n"
).encode() + p.read_bytes() + f"\r\n--{boundary}--\r\n".encode()
req = urllib.request.Request(
url,
data=body,
headers={
"Authorization": f"token {token}",
"Content-Type": f"multipart/form-data; boundary={boundary}",
},
method="POST",
)
try:
resp = urllib.request.urlopen(req, timeout=60)
return json.loads(resp.read())
except urllib.error.HTTPError as e:
err = e.read().decode()[:300]
raise RuntimeError(f"HTTP {e.code} attaching {filename} to release {release_id}: {err}") from e
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: attach_release_asset.py <file-path> <release-id>")
sys.exit(1)
result = attach_asset(sys.argv[1], sys.argv[2])
print(f"Attached: {result.get('name')} → release {sys.argv[2]} (asset id {result.get('id')})")
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# scripts/render_deck.sh — render a Marp deck to HTML + PPTX, commit both to git.
# REQ-228 (v1.18): PPTX is now a first-class committed artifact + release attachment.
#
# Usage:
# bash scripts/render_deck.sh <deck-name>
# bash scripts/render_deck.sh nova-no-humans-platform
#
# Renders:
# docs/presentations/<deck-name>-marp.md → docs/presentations/<deck-name>.html (committed)
# → docs/presentations/<deck-name>.pptx (committed, binary)
#
# The PPTX is also attached to the current phase's Gitea release via
# scripts/attach_release_asset.py (call separately after ship, or this script
# will invoke it if NOVA_GITEA_RELEASE_ID is set).
set -euo pipefail
DECK="${1:?Usage: render_deck.sh <deck-name>}"
cd "$(git rev-parse --show-toplevel)"
SRC="docs/presentations/${DECK}-marp.md"
HTML="docs/presentations/${DECK}.html"
PPTX="docs/presentations/${DECK}.pptx"
if [ ! -f "$SRC" ]; then
echo "ERROR: source deck $SRC not found" >&2; exit 1
fi
CHROME=""
for c in \
/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
/usr/bin/chromium \
/usr/bin/chromium-browser \
/usr/bin/google-chrome; do
if [ -x "$c" ]; then CHROME="$c"; break; fi
done
if [ -z "$CHROME" ]; then
echo "WARNING: no Chrome/Chromium found — skipping render (HTML/PPTX will need manual re-render)" >&2
exit 0
fi
export CHROME_PATH="$CHROME"
echo "Rendering HTML → $HTML"
npx --yes @marp-team/marp-cli@latest --allow-local-files "$SRC" -o "$HTML" 2>&1 | tail -3
echo "Rendering PPTX → $PPTX"
npx --yes @marp-team/marp-cli@latest --allow-local-files "$SRC" -o "$PPTX" 2>&1 | tail -3
git add "$HTML" "$PPTX"
echo "Staged $HTML + $PPTX for commit."
if [ -n "${NOVA_GITEA_RELEASE_ID:-}" ]; then
echo "Attaching PPTX to Gitea release $NOVA_GITEA_RELEASE_ID..."
python3 scripts/attach_release_asset.py "$PPTX" "$NOVA_GITEA_RELEASE_ID" || \
echo "WARNING: attach failed — PPTX is still committed; attach manually."
fi
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# scripts/update_atelier_vendor.sh — intentionally upgrade the vendored Atelier snapshot.
# Usage: bash scripts/update_atelier_vendor.sh <new-tag>
set -euo pipefail
TAG="${1:?Usage: update_atelier_vendor.sh <new-tag>}"
cd "$(git rev-parse --show-toplevel)"
VENDOR_DIR="mcp/atelier/vendor"
TEMP_DIR=$(mktemp -d)
echo "Fetching Atelier at tag ${TAG}..."
git clone --depth 1 --branch "${TAG}" https://git.cloudinit.dev/coreci/atelier.git "${TEMP_DIR}/atelier" 2>&1 | tail -3
echo "Replacing vendored snapshot..."
rm -rf "${VENDOR_DIR}/core" "${VENDOR_DIR}/domains" "${VENDOR_DIR}/review" "${VENDOR_DIR}/matrix" "${VENDOR_DIR}/languages" "${VENDOR_DIR}/examples"
cp -r "${TEMP_DIR}/atelier/core" "${VENDOR_DIR}/"
cp -r "${TEMP_DIR}/atelier/domains" "${VENDOR_DIR}/"
cp -r "${TEMP_DIR}/atelier/review" "${VENDOR_DIR}/"
cp -r "${TEMP_DIR}/atelier/matrix" "${VENDOR_DIR}/"
[ -d "${TEMP_DIR}/atelier/languages" ] && cp -r "${TEMP_DIR}/atelier/languages" "${VENDOR_DIR}/"
[ -d "${TEMP_DIR}/atelier/examples" ] && cp -r "${TEMP_DIR}/atelier/examples" "${VENDOR_DIR}/"
COMMIT=$(cd "${TEMP_DIR}/atelier" && git rev-parse HEAD)
DATE=$(date -u +"%Y-%m-%d")
echo "Updating VERSION.md..."
cat > "${VENDOR_DIR}/VERSION.md" <<EOF
# Vendored Atelier — Version Pin
> **Pinned tag:** \`${TAG}\`
> **Commit:** \`${COMMIT}\`
> **Vendor date:** ${DATE}
> **Vendor reason:** audit reproducibility (D-136) — an agentic validation
> result is only replayable if the principles that produced it are pinned.
## Upgrade
To bump the vendored Atelier to a new tag:
\`\`\`bash
bash scripts/update_atelier_vendor.sh <new-tag>
\`\`\`
EOF
rm -rf "${TEMP_DIR}"
echo "Vendored Atelier updated to ${TAG}. Review the diff and commit."
+40
View File
@@ -0,0 +1,40 @@
# Skill: API Design
> **Atelier source:** `domains/api/` (first-principles + rest, graphql,
> versioning, error-responses, pagination)
> **Core principles:** C1 Correctness, C2 Clarity, C6 Composability
> **BA.A mapping:** web API skill
> **Consumer:** read this before authoring an API service contract.
## First Principles (citizen-developer-relevant subset)
- **Endpoints are nouns, plural, lowercase-hyphenated.** (`/customers`,
not `/getCustomer`)
- **Status codes are correct.** 200/201/204/4xx/5xx per semantics.
- **Errors are structured.** Every error response carries `code`,
`message`, `request_id` — not a stack trace.
- **Input is validated against a schema.** The contract's
`infrastructure` map is validated at resolution time; the API must
validate its own request bodies.
- **Auth is required by default.** No unauthenticated endpoints unless
explicitly declared in `policyPreconditions`.
## Agent-Checklist Triggers
Before completing an API task, run these (from
`review/agent-checklist.md` § API):
- Endpoints are nouns, plural, lowercase-hyphenated
- Status codes are correct per semantics
- Errors are structured (code, message, request_id)
- Input is validated against a schema
- Auth is required by default
## How Nova Uses This
The submission-readiness gate (`schemas/submission-readiness.schema.json`)
checks that your contract declares `policyPreconditions`. The API skill
tells you what the platform expects your application to enforce on its
own surface (request validation, structured errors, auth). Nova does
not author your API; it deploys it. The API skill ensures the
application you deploy meets production-grade standards.
+49
View File
@@ -0,0 +1,49 @@
# Skill: Compliance
> **Atelier source:** `domains/compliance/` (first-principles + audit-logs,
> data-retention, policy-as-code, evidence)
> **Core principles:** C1 Correctness, C5 Reversibility
> **BA.A mapping:** cross-cutting (all 5 skills)
> **Consumer:** read this before any regulated-environment submission.
## First Principles (citizen-developer-relevant subset)
- **Audit records are immutable once written.** Deletion/mutation is
itself an auditable incident. Nova's Decision Ledger (SQLite
hash-chain, v1.17; S3 Object Lock + JWS future) enforces this.
- **The set of auditable actions is defined a priori.** "We forgot to log
it" is a violation. The submission-readiness gate's
`policyPreconditions` declare what the platform will audit.
- **Policy violations block before the action.** Checkov runs pre-apply;
the confidence signal gates; the HITL gate stops. Compliance is
admission-time, not audit-time.
- **Evidence gathered as a byproduct of operation.** Not assembled
manually at audit time. Every pipeline run emits events into the
Decision Ledger + the evidence stream.
- **Every logged action traces to an authenticated principal.** No
shared/generic identities. The HITL approver identity (D-042) is
recorded with every prod/dr promotion.
## Agent-Checklist Triggers (§ Compliance)
- Audit records are immutable once written; deletion/mutation is itself
auditable (P1)
- The set of auditable actions is defined a priori (P2)
- Policy violations block before the action (admission/CI/CD-time) (P5)
- Evidence gathered as a byproduct of operation, not assembled manually
(P6)
- Every logged action traces to an authenticated principal; no
shared/generic identities (P7)
## How Nova Uses This
Nova's compliance posture is framework-agnostic (D-024 in Atelier; the
platform lists GDPR, SOX, SOC2, DORA — not any single framework). The
compliance skill tells you what the platform enforces (immutable audit,
pre-apply policy, evidence byproduct, authenticated principals) and what
your application must enforce (the same standards on its own surface).
The submission-readiness gate ensures your contract declares
`policyPreconditions`; the compliance skill ensures your application
respects them. This is the RACI compliance-standard equivalence made
concrete: regardless of upstream source (AI agent, SDLC, dev platform),
the same compliance standards apply to every submission.
+34
View File
@@ -0,0 +1,34 @@
# Skill: Data
> **Atelier source:** `domains/data/` (first-principles + schema-design,
> migrations, indexing)
> **Core principles:** C1 Correctness, C4 Locality, C6 Composability
> **BA.A mapping:** web API, worker, scheduled job
> **Consumer:** read this before authoring a service with a database.
## First Principles (citizen-developer-relevant subset)
- **Schema reflects the domain, not the application.** Tables model
real-world entities, not ORM classes.
- **Constraints are in the schema.** NOT NULL, UNIQUE, FK — the database
enforces integrity, not the application.
- **Migration has an `up` and a `down`.** Every migration is reversible.
- **Types are domain-accurate.** UUID for IDs, TIMESTAMPTZ for timestamps,
DECIMAL for money — not string/integer/everything.
- **No `SELECT *`; no N+1.** Explicit columns; eager-load relations.
## Agent-Checklist Triggers (§ Data)
- Schema reflects the domain (not the application)
- Constraints are in the schema (NOT NULL, UNIQUE, FK)
- Migration has an `up` and a `down`
- Types are domain-accurate (UUID, TIMESTAMPTZ, DECIMAL for money)
- No `SELECT *`; no N+1
## How Nova Uses This
Nova deploys your infrastructure (RDS, DynamoDB) but does not author your
schema. The data skill ensures the schema you bring meets production-grade
standards. The submission-readiness gate checks that your contract declares
the infrastructure; the data skill checks that the application running on
that infrastructure uses the database correctly.
+37
View File
@@ -0,0 +1,37 @@
# Skill: DevOps
> **Atelier source:** `domains/devops/` (first-principles + ci-cd,
> environments)
> **Core principles:** C5 Reversibility, C7 Observability, C8 Economy
> **BA.A mapping:** scheduled job, worker
> **Consumer:** read this before any deployment.
## First Principles (citizen-developer-relevant subset)
- **The pipeline is the process.** No manual steps. Every change flows
through the same pipeline: contract → resolver → plan → policy →
confidence → (HITL gate for qa/prod/dr) → apply → evidence.
- **Rollback path is known.** Every deployment has a documented rollback.
Terraform state is the rollback mechanism; the pipeline replans to the
prior state.
- **Config is in code, not on the server.** Environment variables, SSM
parameters, secrets — all declared, versioned, and reviewable. No
hand-configured server state.
- **Environments are parity.** dev = prod modulo data. The same contract
deploys to all environments; only the environment field changes.
## Agent-Checklist Triggers (§ DevOps)
- The pipeline is the process (no manual steps)
- Rollback path is known
- Config is in code, not on the server
- Environments are parity (dev = prod modulo data)
## How Nova Uses This
Nova IS the pipeline. The citizen developer's contract declares intent;
Nova provides the process. The DevOps skill tells you what the platform
expects from your submission: no manual steps (everything flows through
the contract), a known rollback (Terraform state), config in code (SSM
SecureString, not hand-configured servers), and environment parity (one
contract, four environments).
+34
View File
@@ -0,0 +1,34 @@
# Skill: Errors
> **Atelier source:** `domains/errors/` (first-principles + patterns)
> **Core principles:** C1 Correctness, C7 Observability
> **BA.A mapping:** web API, worker, scheduled job
> **Consumer:** read this before authoring error handling.
## First Principles (citizen-developer-relevant subset)
- **Errors are not swallowed silently.** A bare `except: pass` is a bug.
Every caught error is either handled, re-raised, or logged with context.
- **Errors are specific.** Not `raise Exception("something went wrong")`
— a named exception with the what/where/why.
- **Errors preserve context.** The error carries the request ID, the
user, the action — enough to debug without reproducing.
- **Recovery is attempted when possible; fail fast when not.** Retry
transient errors with backoff; fail fast on invariant violations.
## Agent-Checklist Triggers (§ Errors)
- Errors are not swallowed silently
- Errors are specific (not generic "something went wrong")
- Errors preserve context (where, when, why, what)
- Recovery is attempted when possible; fail fast when not
## How Nova Uses This
Nova's confidence signal (D-040) uses error events as one of its 6 inputs.
The error skill ensures your application's errors are structured enough to
feed the signal: specific error types, preserved context, no silent
swallows. The platform's `report_error` Lambda action (D-055) creates a
GitHub issue on the platform repo when the pipeline fails — your
application errors should be structured enough to flow through the same
path.
+41
View File
@@ -0,0 +1,41 @@
# Skill: Infrastructure as Code
> **Atelier source:** `domains/infrastructure-as-code/` (first-principles +
> terraform, opentofu, state, modules)
> **Core principles:** C1 Correctness, C5 Reversibility, C8 Economy
> **BA.A mapping:** static asset
> **Consumer:** read this before authoring a contract that declares
> infrastructure.
## First Principles (citizen-developer-relevant subset)
- **Configuration is declarative, not scripted.** The contract declares
what; Terraform reconciles how. No imperative scripts in the contract.
- **Provider versions are pinned, never `latest`.** The contract's
infrastructure map may pin module versions (semver); the platform pins
provider versions.
- **State is remote with locking; never committed.** Nova manages state
in S3 + DynamoDB; the citizen developer never touches state files.
- **`plan` is reviewed before every `apply`.** The confidence signal
gates the apply; the HITL gate (qa/prod/dr) requires human attestation
before the apply proceeds.
- **No secrets in HCL; secrets via providers/stores.** Secrets live in
SSM SecureString / Secrets Manager, not in the contract or HCL.
## Agent-Checklist Triggers (§ Infrastructure as Code)
- Configuration is declarative, not scripted (P1)
- Provider versions are pinned, never `latest` (P5)
- State is remote with locking; never committed (P3, P8)
- `plan` is reviewed before every `apply` (P4)
- No secrets in HCL; secrets via providers/stores (P10)
## How Nova Uses This
Nova IS the infrastructure-as-code platform. The citizen developer
declares intent in the contract; Nova's adapter (stateless assembler,
v1.11) translates to Terraform modules; the pipeline runs plan → policy →
confidence → (HITL) → apply. The IaC skill tells you what the platform
expects from your contract: declarative inputs (not scripts), pinned
versions (not `latest`), no secrets in the contract (secrets via SSM),
and acceptance that the platform owns state + the apply path.
+38
View File
@@ -0,0 +1,38 @@
# Skill: Observability
> **Atelier source:** `domains/observability/` (first-principles + logging,
> metrics, tracing)
> **Core principles:** C7 Observability
> **BA.A mapping:** basic observability bootstrap
> **Consumer:** read this before any production submission (W3.E requires
> `dashboard` + `oncall` for prod).
## First Principles (citizen-developer-relevant subset)
- **Logs are structured.** JSON with fields, not free-form text. Every
log line carries a timestamp, level, message, and context fields.
- **Every request has a correlation ID.** A request ID propagates from
ingress through every downstream call. Logs, metrics, and traces share
the same ID.
- **No high-cardinality labels in metrics.** User IDs, request IDs, and
other unbounded values go in logs/traces, not metric labels.
- **Alerts have runbooks.** Every alert links to a runbook
(`runbook` field in the submission, W3.E prod mandatory) that explains
what to do when it fires.
## Agent-Checklist Triggers (§ Observability)
- Logs are structured (JSON, fields)
- Every request has a correlation ID
- No high-cardinality labels in metrics
- Alerts have runbooks
## How Nova Uses This
The W3.E per-env mandatory table requires `runbook` + `dashboard` +
`oncall` for `prod` submissions — the submission-readiness gate enforces
this. The observability skill tells you what those artifacts must contain:
structured logs, correlation IDs, bounded metric labels, and runbook-linked
alerts. Nova provides the infrastructure (CloudWatch, the uptime
monitor); you provide the application-level observability (structured
logs, dashboards, runbooks).
+42
View File
@@ -0,0 +1,42 @@
# Skill: Security
> **Atelier source:** `domains/security/` (first-principles +
> authentication, authorization, input-validation, secrets, supply-chain)
> **Core principles:** C1 Correctness (security is correctness)
> **BA.A mapping:** cross-cutting (all 5 skills)
> **Consumer:** read this before any production submission.
## First Principles (citizen-developer-relevant subset)
- **No secrets in code, logs, URLs, or error messages.** Secrets live in
the platform's secret store (SSM SecureString, Secrets Manager), not
your application repo.
- **Input is validated at the boundary.** Every external input (HTTP
body, query, header, file) is validated against a schema before
processing.
- **Output is encoded for its context.** HTML escaping, URL encoding,
SQL parameterization — context-appropriate, not a blanket escape.
- **Crypto uses vetted libraries.** No MD5/SHA1 for security. Use
bcrypt/argon2 for passwords, AES-GCM for encryption.
- **Authorization is checked, not assumed.** Every request verifies the
caller's authority to perform the action.
## Agent-Checklist Triggers (§ Security)
- No secrets in code, logs, URLs, or error messages
- Input is validated at the boundary
- Output is encoded for its context
- Crypto uses vetted libraries (no MD5/SHA1 for security)
- Authorization is checked, not assumed
## How Nova Uses This
The submission-readiness gate checks `policyPreconditions` (e.g.,
`public-ingress: false`, `encryption_enabled: true`). The security skill
tells you what the platform enforces and what your application must
enforce on its own surface. The platform enforces infrastructure-level
security (IAM scoping, ABAC, encryption-at-rest, policy-as-code via
Checkov); you enforce application-level security (input validation, output
encoding, auth checks). The Atelier MCP server (`mcp/atelier/server.py`,
P5) can validate your code against these principles agenticly — beyond
what deterministic scanners like Wiz/Checkmarx/Mend catch.
+37
View File
@@ -0,0 +1,37 @@
# Skill: Testing
> **Atelier source:** `domains/testing/` (first-principles + pyramid,
> fixtures)
> **Core principles:** C1 Correctness, C5 Reversibility
> **BA.A mapping:** UAT is the citizen developer's RACI responsibility
> **Consumer:** read this before submitting for UAT.
## First Principles (citizen-developer-relevant subset)
- **Tests are independent.** Order doesn't matter; one test's setup
doesn't break another's.
- **Tests are deterministic.** No `Date.now()`, no `random()`, no
network calls in unit tests.
- **Edge cases are covered.** Empty, single, max, invalid — not just
the happy path.
- **A failing test names the problem.** The assertion message explains
what failed and why, not just "assertion failed".
- **The pyramid: unit → integration → e2e.** Most tests are unit; few
are e2e; the middle is integration. Don't invert the pyramid.
## Agent-Checklist Triggers (§ Testing)
- Tests are independent (order doesn't matter)
- Tests are deterministic (no `Date.now()`, no `random()`)
- Edge cases are covered (empty, single, max, invalid)
- A failing test names the problem specifically
## How Nova Uses This
Per the RACI matrix (`docs/raci.md`), the **Citizen Developer is
Responsible for User Acceptance Testing (UAT)**. The platform provides
the QA checks (policy, confidence, schema); you provide the UAT. The
testing skill ensures your UAT meets production-grade standards. The
W3.E per-env mandatory table requires `validation.e2eSuite` +
`validation.loadTest` for `qa` environment submissions — the
submission-readiness gate enforces this.
+167
View File
@@ -0,0 +1,167 @@
"""tests/test_atelier_mcp.py — REQ-225.
Covers: tool registration (all 4 tools discoverable), lookup_principle
returns the principle text + core C-rule, validate_against_principles
catches a planted C1 (correctness) + C7 (observability) violation in a
known-bad snippet and passes a known-good snippet, matrix_lookup returns
the domaincore mapping, plugin discovery loads all plugins in plugins/.
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mcp.atelier.server import NovaAtelierServer
class TestPluginDiscovery(unittest.TestCase):
def setUp(self):
self.server = NovaAtelierServer()
self.loaded = self.server.load_plugins()
def test_both_plugins_loaded(self):
self.assertIn("principles", self.loaded)
self.assertIn("validation", self.loaded)
def test_four_tools_registered(self):
tools = self.server.list_tools()
names = {t["name"] for t in tools}
self.assertIn("atelier_lookup_principle", names)
self.assertIn("atelier_list_domains", names)
self.assertIn("atelier_matrix_lookup", names)
self.assertIn("atelier_validate_against_principles", names)
self.assertEqual(len(names), 4)
class TestLookupPrinciple(unittest.TestCase):
def setUp(self):
self.server = NovaAtelierServer()
self.server.load_plugins()
def test_lookup_security_p4(self):
result = self.server.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P4"})
self.assertNotIn("error", result)
self.assertEqual(result["domain"], "security")
self.assertEqual(result["principle_id"], "P4")
self.assertIn("Secrets", result["title"])
self.assertIn("C1", result["core_c_rule"])
self.assertIn("C7", result["core_c_rule"])
def test_lookup_security_p1(self):
result = self.server.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P1"})
self.assertNotIn("error", result)
self.assertIn("Boundary", result["title"])
def test_lookup_unknown_domain(self):
result = self.server.call_tool("atelier_lookup_principle", {"domain": "nonexistent", "principle_id": "P1"})
self.assertIn("error", result)
def test_lookup_unknown_principle(self):
result = self.server.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P99"})
self.assertIn("error", result)
class TestListDomains(unittest.TestCase):
def setUp(self):
self.server = NovaAtelierServer()
self.server.load_plugins()
def test_returns_19_domains(self):
result = self.server.call_tool("atelier_list_domains", {})
self.assertEqual(len(result), 19)
def test_security_is_nova_relevant(self):
result = self.server.call_tool("atelier_list_domains", {})
sec = next(d for d in result if d["domain"] == "security")
self.assertTrue(sec["nova_relevant"])
def test_ui_ux_not_nova_relevant(self):
result = self.server.call_tool("atelier_list_domains", {})
ui = next(d for d in result if d["domain"] == "ui-ux")
self.assertFalse(ui["nova_relevant"])
class TestMatrixLookup(unittest.TestCase):
def setUp(self):
self.server = NovaAtelierServer()
self.server.load_plugins()
def test_security_matrix(self):
result = self.server.call_tool("atelier_matrix_lookup", {"domain": "security"})
self.assertEqual(result["domain"], "security")
self.assertEqual(len(result["mapping"]), 10)
p4 = next(m for m in result["mapping"] if m["p"] == "P4")
self.assertIn("C1", p4["core"])
self.assertIn("C7", p4["core"])
def test_unknown_domain_matrix(self):
result = self.server.call_tool("atelier_matrix_lookup", {"domain": "nonexistent"})
self.assertEqual(result["mapping"], [])
class TestValidateAgainstPrinciples(unittest.TestCase):
def setUp(self):
self.server = NovaAtelierServer()
self.server.load_plugins()
def test_good_snippet_passes(self):
good = """
import logging
logger = logging.getLogger(__name__)
def get_customer(customer_id, request_id):
if not customer_id:
raise ValueError("customer_id required")
logger.info("fetching customer %s (request %s)", customer_id, request_id)
return db.query(customer_id)
"""
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": good})
# Should not have FAIL on secrets (no hardcoded secrets)
sec_checks = [r for r in result["results"] if r["check_id"].startswith("SEC.1")]
for c in sec_checks:
self.assertEqual(c["status"], "PASS", f"SEC.1 should PASS: {c}")
def test_bad_snippet_catches_secret(self):
bad = """
api_key = "sk-1234567890abcdef"
def get_data():
pass
"""
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": bad})
# SEC.1 (secrets in code) should FAIL
sec1 = next(r for r in result["results"] if r["check_id"] == "SEC.1")
self.assertEqual(sec1["status"], "FAIL")
def test_bad_snippet_catches_swallowed_error(self):
bad = """
try:
do_something()
except:
pass
"""
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": bad})
# C1.2 (handles failure cases) should FAIL because of `except: pass`
c12 = next(r for r in result["results"] if r["check_id"] == "C1.2")
self.assertEqual(c12["status"], "FAIL")
def test_bad_snippet_catches_obfuscated_names(self):
bad = """
def doStuff(data, temp, x):
return data + temp + x
"""
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": bad})
# C2.1 (names intent-revealing) should FAIL
c21 = next(r for r in result["results"] if r["check_id"] == "C2.1")
self.assertEqual(c21["status"], "FAIL")
def test_result_structure(self):
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": "x = 1"})
self.assertIn("overall", result)
self.assertIn("results", result)
self.assertIsInstance(result["results"], list)
self.assertGreater(len(result["results"]), 0)
if __name__ == "__main__":
unittest.main()
+189
View File
@@ -0,0 +1,189 @@
"""tests/test_submission_readiness.py — REQ-220.
Covers: good contract passes; missing tags fail with MISSING_TAGS;
env-missing-mandatory fails with ENV_MISSING_MANDATORY:<env>:<field>;
agentic profile missing intent fails with AGENTIC_MISSING_INTENT;
missing appSource fails with MISSING_APP_SOURCE.
"""
import json
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.submission_readiness import check_readiness, ReadinessResult
GOOD_TAGS = {
"nova:owner": "consumer-repo",
"nova:contract": "uuid-1234",
"nova:environment": "dev",
"nova:cost-center": "nova-default",
"nova:ref": "CHG0678912",
}
def _base(**overrides):
submission = {
"contractId": "uuid-1234",
"id": "webapi",
"name": "Customer Web API",
"environment": "dev",
"tags": dict(GOOD_TAGS),
"policyPreconditions": {"public-ingress": False, "encryption_enabled": True},
"profile": "developer",
"appSource": {"repo": "consumer/repo", "ref": "main"},
"infrastructure": {
"static-assets": {"inputs": {"bucket_name": "webapi-assets"}}
},
}
submission.update(overrides)
return submission
class TestGoodContract(unittest.TestCase):
def test_good_contract_passes(self):
result = check_readiness(_base())
self.assertTrue(result.ready, f"Expected ready, got: {result.reason_codes}")
self.assertEqual(result.contract_id, "uuid-1234")
def test_good_agentic_contract_passes(self):
submission = _base(
profile="agentic",
naturalLanguageIntent="A web API for customer data",
confidenceAtSubmission=0.85,
agentTrace="LLM generated contract from issue #42",
)
result = check_readiness(submission)
self.assertTrue(result.ready, f"Expected ready, got: {result.reason_codes}")
class TestMissingTags(unittest.TestCase):
def test_missing_tags_fail(self):
submission = _base()
submission["tags"] = {"nova:owner": "consumer-repo"}
result = check_readiness(submission)
self.assertFalse(result.ready)
codes = " ".join(result.reason_codes)
self.assertIn("MISSING_TAGS", codes)
self.assertIn("nova:contract", codes)
self.assertIn("nova:environment", codes)
self.assertIn("nova:cost-center", codes)
self.assertIn("nova:ref", codes)
def test_empty_tag_value_fails(self):
submission = _base()
submission["tags"]["nova:owner"] = ""
result = check_readiness(submission)
self.assertFalse(result.ready)
self.assertTrue(any("MISSING_TAGS" in c for c in result.reason_codes))
class TestEnvMissingMandatory(unittest.TestCase):
def test_qa_missing_e2e_suite_fails(self):
submission = _base(environment="qa")
submission["tags"]["nova:environment"] = "qa"
# No validation.e2eSuite
result = check_readiness(submission)
self.assertFalse(result.ready)
codes = " ".join(result.reason_codes)
self.assertIn("ENV_MISSING_MANDATORY:qa:validation.e2eSuite", codes)
def test_prod_missing_runbook_fails(self):
submission = _base(environment="prod")
submission["tags"]["nova:environment"] = "prod"
# No runbook/dashboard/oncall
result = check_readiness(submission)
self.assertFalse(result.ready)
codes = " ".join(result.reason_codes)
self.assertIn("ENV_MISSING_MANDATORY:prod:runbook", codes)
self.assertIn("ENV_MISSING_MANDATORY:prod:dashboard", codes)
self.assertIn("ENV_MISSING_MANDATORY:prod:oncall", codes)
def test_dr_missing_drdrillref_fails(self):
submission = _base(environment="dr")
submission["tags"]["nova:environment"] = "dr"
result = check_readiness(submission)
self.assertFalse(result.ready)
codes = " ".join(result.reason_codes)
self.assertIn("ENV_MISSING_MANDATORY:dr:drDrillRef", codes)
def test_prod_with_all_mandatory_passes(self):
submission = _base(
environment="prod",
runbook="docs/runbooks/webapi.md",
dashboard="https://grafana/nova/webapi",
oncall="oncall@company.com",
)
submission["tags"]["nova:environment"] = "prod"
result = check_readiness(submission)
self.assertTrue(result.ready, f"Expected ready, got: {result.reason_codes}")
class TestAgenticMissingIntent(unittest.TestCase):
def test_agentic_missing_all_markers_fails(self):
submission = _base(profile="agentic")
result = check_readiness(submission)
self.assertFalse(result.ready)
codes = " ".join(result.reason_codes)
self.assertIn("AGENTIC_MISSING_INTENT:naturalLanguageIntent", codes)
self.assertIn("AGENTIC_MISSING_INTENT:confidenceAtSubmission", codes)
self.assertIn("AGENTIC_MISSING_INTENT:agentTrace", codes)
def test_agentic_missing_one_marker_fails(self):
submission = _base(
profile="agentic",
naturalLanguageIntent="A web API",
confidenceAtSubmission=0.85,
# agentTrace missing
)
result = check_readiness(submission)
self.assertFalse(result.ready)
self.assertTrue(any("agentTrace" in c for c in result.reason_codes))
class TestMissingAppSource(unittest.TestCase):
def test_missing_appsource_fails(self):
submission = _base()
del submission["appSource"]
result = check_readiness(submission)
self.assertFalse(result.ready)
self.assertTrue(any("MISSING_APP_SOURCE" in c for c in result.reason_codes))
def test_appsource_missing_ref_fails(self):
submission = _base()
submission["appSource"] = {"repo": "consumer/repo"}
result = check_readiness(submission)
self.assertFalse(result.ready)
self.assertTrue(any("MISSING_APP_SOURCE" in c for c in result.reason_codes))
class TestPolicyPreconditionMissing(unittest.TestCase):
def test_empty_policy_fails(self):
submission = _base()
submission["policyPreconditions"] = {}
result = check_readiness(submission)
self.assertFalse(result.ready)
self.assertTrue(any("POLICY_PRECONDITION_MISSING" in c for c in result.reason_codes))
class TestReadinessResultStructure(unittest.TestCase):
def test_result_to_dict(self):
result = check_readiness(_base())
d = result.to_dict()
self.assertIn("ready", d)
self.assertIn("reason_codes", d)
self.assertIn("contractId", d)
def test_result_str_ready(self):
result = check_readiness(_base())
self.assertIn("READY", str(result))
def test_result_str_not_ready(self):
submission = _base()
del submission["appSource"]
result = check_readiness(submission)
self.assertIn("NOT READY", str(result))
if __name__ == "__main__":
unittest.main()