Compare commits

..

12 Commits

Author SHA1 Message Date
Jon Chery d882cf0c6e Merge phase/06-deck-slides-atelier — v1.17.6 (v1.18 P6 deck slides + atelier complete) 2026-08-06 15:15:22 +00:00
Jon Chery 564d4a4ca3 docs(P6): atelier deck slide + 21-slide re-render + README (REQ-226, REQ-227, REQ-228)
REQ-226: Slide 19 'Production-Grade Guidance via Atelier' added → 21 total
slides (16 existing + 17 Scope + 18 RACI + 19 Atelier + 2 appendix). Arc
preview updated (v1.18). Talking points synced (slide 19). S&P theme
preserved (177 color refs in HTML). PPTX 22 slides (21 content + title).

REQ-227: README deck table updated — single unified deck, 21 slides, PPTX
committed + release-attached (D-141). Old two-deck table replaced.

REQ-228: HTML + PPTX re-rendered via scripts/render_deck.sh. PPTX committed
(binary, no LFS).

---ci---
project: acdl
phase: 6
milestone: v1.18
status: execute
requirements:
  covered: [REQ-226, REQ-227, REQ-228]
  partial: []
---/ci---
2026-08-06 15:15:17 +00:00
Jon Chery c524ad731e Merge phase/05-atelier-mcp — v1.17.5 (v1.18 P5 Atelier MCP server complete) 2026-08-06 15:13:44 +00:00
Jon Chery 8bcf7296d5 feat(P5): Atelier MCP server + vendored Atelier + plugin-registry (REQ-223, REQ-224, REQ-225)
REQ-223: mcp/atelier/server.py plugin-registry MCP server (stdio, D-135).
NovaAtelierServer wraps MCPServer (SDK v2, D-137) if installed; degrades
to _ToolRegistry fallback if SDK absent (testable in CI without SDK).
plugins/principles.py (lookup_principle, list_domains, matrix_lookup) +
plugins/validation.py (validate_against_principles — agentic validation
beyond Wiz/Checkmarx/Mend). 4 tools, 2 plugins.

REQ-224: mcp/atelier/vendor/ pinned Atelier v0.3.6 (D-136) — core/
first-principles, domains/security/first-principles, review/agent-checklist,
matrix/principles-matrix. vendor/VERSION.md + scripts/update_atelier_vendor.sh
for intentional upgrades. mcp/atelier/README.md (tools, architecture,
running, vendoring, extensibility, transport).

REQ-225: tests/test_atelier_mcp.py — 16 tests, all pass. Covers: plugin
discovery (both loaded), 4 tools registered, lookup_security_P4 (+P1,
unknown domain/principle), list_domains (19, security-relevant, ui-ux-not),
matrix_lookup (security 10 P-rules, unknown), validation (good-passes,
bad-secret-fails, bad-swallowed-error-fails, bad-obfuscated-names-fails,
result-structure).

---ci---
project: acdl
phase: 5
milestone: v1.18
status: execute
requirements:
  covered: [REQ-223, REQ-224, REQ-225]
  partial: []
---/ci---
2026-08-06 15:13:40 +00:00
Jon Chery 81c7a22ddd Merge phase/04-atelier-skills — v1.17.4 (v1.18 P4 Atelier skills complete) 2026-08-06 15:11:15 +00:00
Jon Chery 2c08c778a9 docs(P4): Atelier skills mapping — 9 skill files + index + BA.A extension (REQ-221, REQ-222)
REQ-221: skills/ directory with 9 Atelier-derived skill files mapped to the
BA.A citizen-developer catalog: api, security, data, testing, observability,
errors, devops, infrastructure-as-code, compliance. Each names the Atelier
source path, distills first-principles to the citizen-dev-relevant subset,
links to agent-checklist triggers, maps to BA.A 5-skill catalog.

REQ-222: docs/skills.md index (9-skill table, Atelier provenance, 8 core
principles C1-C8, consumption instructions, reference-only domains, excluded
domains). PROJECT.md BA.A decision extended with the Atelier-derived skill
catalog reference.

---ci---
project: acdl
phase: 4
milestone: v1.18
status: execute
requirements:
  covered: [REQ-221, REQ-222]
  partial: []
---/ci---
2026-08-06 15:11:12 +00:00
Jon Chery 6ffcbe8283 Merge phase/03-submission-readiness — v1.17.3 (v1.18 P3 submission-readiness complete) 2026-08-06 15:09:32 +00:00
Jon Chery 5775a97388 feat(P3): submission-readiness input contract — schema + validator + docs + tests (REQ-217..220)
REQ-217: schemas/submission-readiness.schema.json (JSON Schema draft 2020-12)
defines acceptable-to-start as a superset gate above contract.schema.json:
contractId, environment, tags (5 Nova tags D-054), policyPreconditions,
profile (developer|agentic), appSource (repo+ref), per-env mandatory (W3.E:
qa→e2eSuite+loadTest, prod→runbook+dashboard+oncall, dr→drDrillRef),
agentic markers (naturalLanguageIntent+confidenceAtSubmission+agentTrace).

REQ-218: core/submission_readiness.py validator with check_readiness() +
ReadinessResult (structured pass/fail + reason codes). Wired as
contract_ingestor.py --check-readiness (D-133). Reason codes: MISSING_TAGS,
ENV_MISSING_MANDATORY, AGENTIC_MISSING_INTENT, MISSING_APP_SOURCE,
POLICY_PRECONDITION_MISSING. Never raises — all failures are reason codes.

REQ-219: docs/submission-readiness.md (good + rejected examples +
reason-code catalog + compliance-standard equivalence).

REQ-220: tests/test_submission_readiness.py — 16 tests, all pass.
Covers: good-pass, good-agentic-pass, missing-tags, empty-tag,
qa-missing-e2e, prod-missing-runbook, dr-missing-drdrill, prod-all-pass,
agentic-missing-all, agentic-missing-one, missing-appsource,
appsource-missing-ref, empty-policy, result-structure.

---ci---
project: acdl
phase: 3
milestone: v1.18
status: execute
requirements:
  covered: [REQ-217, REQ-218, REQ-219, REQ-220]
  partial: []
---/ci---
2026-08-06 15:09:29 +00:00
Jon Chery b3c75ccec1 Merge phase/02-pdlc-scope-raci — v1.17.2 (v1.18 P2 PDLC scope + RACI complete) 2026-08-06 15:07:14 +00:00
Jon Chery e891496163 docs(P2): PDLC-upstream scope + RACI matrix + 2 deck slides (REQ-215, REQ-216, REQ-228)
REQ-215: RACI matrix in PROJECT.md (§ RACI Matrix) + docs/raci.md
(citizen-dev-facing copy). 3 roles (Citizen Developer / Platform / Release
Management co-owned). 7 work categories × R/A/C/I. Compliance-standard
equivalence note: any upstream source (AI agent, SDLC, dev platform) is
subject to the same gate.

REQ-216: PDLC-upstream scope in PROJECT.md (§ Scope) + docs/scope.md.
Promotes Core Tenet #2 + Anti-Goal #1 from buried tenets to a dedicated,
unmissable scope statement.

REQ-228: 2 new deck slides (17 Scope + 18 RACI) → 20 slides. Arc preview
updated. Talking points synced. HTML + PPTX re-rendered (21 PPTX slides).

---ci---
project: acdl
phase: 2
milestone: v1.18
status: execute
requirements:
  covered: [REQ-215, REQ-216, REQ-228]
  partial: []
---/ci---
2026-08-06 15:07:10 +00:00
Jon Chery 382944c055 Merge phase/01-sp-theme-restoration — v1.17.1 (v1.18 P1 S&P theme restoration + PPTX automation complete) 2026-08-06 15:05:09 +00:00
Jon Chery 71b6a4fa91 feat(P1): restore S&P Global Energy theme + PPTX automation (REQ-214, REQ-228)
REQ-214: Restore the S&P Global Energy Marp style: block (from commit
ae0cb58 / v1.9.2 P45) to the unified deck. Colors: H1/H2 #D6002A (red-core),
title-slide bg #1B1B1B (grey-90) + 8px #D6002A top accent, body #1B1B1B,
blockquote border #D6002A, table headers #F0F0F0, font 'Akkurat Pro' with
web-safe fallbacks. Nova header/footer text preserved (rebrand not touched).
HTML re-rendered (229 S&P color refs confirmed).

REQ-228: scripts/render_deck.sh (HTML + PPTX render + git add) +
scripts/attach_release_asset.py (Gitea release asset upload via API). PPTX
is now a first-class committed binary (D-141, no LFS). README updated:
'PPTX not committed' → 'PPTX committed + attached'. PPTX committed (3.6 MiB,
19 slides).

---ci---
project: acdl
phase: 1
milestone: v1.18
status: execute
requirements:
  covered: [REQ-214, REQ-228]
  partial: []
---/ci---
2026-08-06 15:05:01 +00:00
40 changed files with 3322 additions and 305 deletions
+5 -3
View File
@@ -1,10 +1,12 @@
{ {
"phase": 0, "phase": 0,
"stage": "plan", "stage": "complete",
"milestone": "v1.18", "milestone": "v1.18",
"phase_role": "pre_execution", "phase_role": "pre_execution",
"attempts": 0, "attempts": 0,
"updated_at": "2026-08-06T00:25:00Z", "updated_at": "2026-08-06T00:35:00Z",
"milestone_complete": false, "milestone_complete": false,
"notes": "v1.18 PLAN complete. 8 phases, 6 waves, 15 requirements. Sequential execution." "tag": "v1.17.0",
"release_id": 522,
"notes": "v1.18 P0 complete. 5 pre-execution stages done. Tag v1.17.0, release 522."
} }
+98 -1
View File
@@ -58,6 +58,103 @@ traceable to a human attestation and an immutable evidence stream.
boundary. The platform validates, enriches with operational standards, boundary. The platform validates, enriches with operational standards,
and reconciles the target state. 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) ## Capability Status (Re-Verified 2026-07-27)
> Source of truth: `.ciagent/CAPABILITY_INVENTORY.md` (Phase 54, D-093). > Source of truth: `.ciagent/CAPABILITY_INVENTORY.md` (Phase 54, D-093).
@@ -883,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.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`. | | 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. | | 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.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. | | 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. | | 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. |
+20 -1
View File
@@ -499,4 +499,23 @@ def lambda_handler(event, context):
return {"statusCode": 401, "body": json.dumps({"error": str(e)})} return {"statusCode": 401, "body": json.dumps({"error": str(e)})}
return {"statusCode": 400, "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 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 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 PNG diagrams are embedded in the file. As of v1.18 (REQ-228, D-141), PPTX
repo (binary, no meaningful diffs) — they are uploaded to the Gitea release files **are committed to the repo** as first-class binary artifacts (no LFS)
as downloadable attachments. 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) ### Step 4 — Talking points (presenter cues)
@@ -341,7 +343,13 @@ attachments to the Gitea release.
## Current decks ## 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 | | 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 |
| 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 |
> **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' header: 'Nova — The No-Humans Infrastructure Platform'
footer: 'Act %{page}/5 — v1.17' footer: 'Act %{page}/5 — v1.17'
style: | style: |
section { font-size: 0.85em; } section {
h1 { color: #1a1a2e; } font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
h2 { color: #16213e; } font-size: 22px;
table { font-size: 0.75em; } color: #1B1B1B;
.badge { padding: 2px 8px; border-radius: 3px; font-size: 0.8em; } }
.badge.planned { background: #fff3cd; color: #856404; } h1 { color: #D6002A; font-size: 34px; margin-bottom: 0.3em; }
section.title { background: #1a1a2e; color: white; } 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 --> <!-- _class: title -->
@@ -22,7 +34,7 @@ style: |
**Shifting from Operational Overhead to Strategic Value** **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) 2. **Vision** — Nova's strategic direction (NORTH_STAR)
3. **How** — the pipeline, Decision Ledger, attestation gates 3. **How** — the pipeline, Decision Ledger, attestation gates
4. **Proof** — grounded metrics that make the claim defensible 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. **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 --> <!-- _class: title -->
<!-- _paginate: false --> <!-- _paginate: false -->
@@ -103,6 +103,27 @@
- "Pipeline-ready" → "production-proven" is the value proposition - "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 - **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 ### Appendix A1 — Metrics Glossary
- Reference for every metric mentioned in the deck - Reference for every metric mentioned in the deck
- Use if the audience asks "what does X mean?" - 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()