Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d882cf0c6e | |||
| 564d4a4ca3 | |||
| c524ad731e | |||
| 8bcf7296d5 |
@@ -343,7 +343,13 @@ attachments to the Gitea release.
|
||||
|
||||
## Current decks
|
||||
|
||||
| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML (Step 3) | Talking points (Step 4) | Slides | Audience |
|
||||
| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML + PPTX (Step 3) | Talking points (Step 4) | Slides | Audience |
|
||||
|---|---|---|---|---|---|---|
|
||||
| How the Platform Works | `how-the-platform-works.md` | `how-the-platform-works-marp.md` | `how-the-platform-works.html` | `how-the-platform-works-talking-points.md` | 11 main + TOC + 8 appendix (20) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
|
||||
| The Developer Experience | `the-developer-experience.md` | `the-developer-experience-marp.md` | `the-developer-experience.html` | `the-developer-experience-talking-points.md` | 11 main + TOC + 7 appendix (19) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
|
||||
| Nova — The No-Humans Infrastructure Platform | `nova-no-humans-platform.md` | `nova-no-humans-platform-marp.md` | `nova-no-humans-platform.html` + `.pptx` (committed + release-attached) | `nova-no-humans-platform-talking-points.md` | 19 main + 2 appendix (21) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
|
||||
|
||||
> **v1.18 (D-130):** the two legacy decks (How the Platform Works + The
|
||||
> Developer Experience) were consolidated into a single unified narrative
|
||||
> deck with a 5-act arc (Problem → Vision → How → Proof → Roadmap). v1.18
|
||||
> (REQ-226) adds 3 slides (17 Scope, 18 RACI, 19 Atelier) → 21 total. The
|
||||
> S&P Global Energy theme is restored (REQ-214, P1). PPTX is committed to
|
||||
> git + attached to the Gitea release (REQ-228, D-141).
|
||||
@@ -337,6 +337,20 @@ From `docs/METRICS_DEFERRED_ROADMAP.md`.
|
||||
|
||||
---
|
||||
|
||||
## Slide 19 — Production-Grade Guidance via Atelier
|
||||
|
||||
**Nova instructs the citizen developer's AI agent on production-grade engineering — skills + an MCP server with agentic validation beyond deterministic scanners.**
|
||||
|
||||
- **Skills (9):** markdown files under `skills/` keyed to Atelier domain paths (api, security, data, testing, observability, errors, devops, infrastructure-as-code, compliance) — extending the BA.A 5-skill catalog
|
||||
- **MCP server:** `mcp/atelier/server.py` (plugin-registry, stdio) — 4 tools: `lookup_principle`, `list_domains`, `matrix_lookup`, `validate_against_principles`
|
||||
- **Agentic validation:** catches C1 correctness + C2 clarity + C7 observability gaps that Wiz/Checkmarx/Mend cannot — deterministic tools check policy/secrets; the MCP server checks engineering discipline
|
||||
- **Vendored Atelier** (pinned tag v0.3.6): audit reproducibility — a validation result is replayable against the exact principles that produced it
|
||||
- Cites `docs/skills.md` + `mcp/atelier/README.md`
|
||||
|
||||
**Benefit:** you now know the citizen developer is not unguided — Nova provides production-grade engineering principles via skills + an MCP server, so the AI agent's submissions meet the same standards regardless of upstream source.
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: title -->
|
||||
<!-- _paginate: false -->
|
||||
|
||||
|
||||
@@ -117,6 +117,13 @@
|
||||
- The compliance-standard equivalence is the key: the source does not matter; the submission does
|
||||
- **Key takeaway:** you bring FRs + UAT; Nova provides NFRs + infra + QA + prod deploy; the release is co-owned with you at the stage gate
|
||||
|
||||
### Slide 19 — Production-Grade Guidance via Atelier
|
||||
- Nova instructs the citizen developer's AI agent via skills (9 markdown files) + an MCP server (4 tools, plugin-registry, stdio)
|
||||
- The MCP server provides agentic validation beyond deterministic scanners — catches correctness, clarity, observability gaps that Wiz/Checkmarx/Mend cannot
|
||||
- Atelier is vendored (pinned tag) for audit reproducibility — a validation result is replayable
|
||||
- This is how Nova ensures the citizen developer's submissions meet production-grade standards regardless of upstream source
|
||||
- **Key takeaway:** the citizen developer is not unguided — Nova provides engineering principles via skills + MCP, so every submission meets the same standards
|
||||
|
||||
### Appendix A1 — Metrics Glossary
|
||||
- Reference for every metric mentioned in the deck
|
||||
- Use if the audience asks "what does X mean?"
|
||||
|
||||
@@ -92,7 +92,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-1--arc-preview">Slide 1 — Arc Preview</h2>
|
||||
<p><strong>This deck proves Nova is the no-humans infrastructure platform — and shows you the metrics that make the claim defensible.</strong></p>
|
||||
@@ -146,7 +146,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-2--the-no-humans-imperative">Slide 2 — The No-Humans Imperative</h2>
|
||||
<p><strong>Why the operator is the bottleneck — and why removing them from operations (not accountability) is the imperative.</strong></p>
|
||||
@@ -197,7 +197,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-3--novas-vision">Slide 3 — Nova's Vision</h2>
|
||||
<blockquote>
|
||||
@@ -248,7 +248,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-4--strategic-objectives--anti-goals">Slide 4 — Strategic Objectives + Anti-Goals</h2>
|
||||
<p><strong>4 Strategic Objectives:</strong></p>
|
||||
@@ -307,7 +307,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-5--12%E2%80%9318-month-targets">Slide 5 — 12–18 Month Targets</h2>
|
||||
<p><strong>Current-milestone targets (grounded/derived):</strong></p>
|
||||
@@ -421,7 +421,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-6--the-platform-pipeline">Slide 6 — The Platform Pipeline</h2>
|
||||
<p><strong>How intent becomes verified infrastructure without an operator.</strong></p>
|
||||
@@ -472,7 +472,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-7--the-decision-ledger">Slide 7 — The Decision Ledger</h2>
|
||||
<p><strong>Every AI decision captured with confidence, alternatives, and outcome.</strong></p>
|
||||
@@ -524,7 +524,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-8--the-8-concern-attestation-matrix">Slide 8 — The 8-Concern Attestation Matrix</h2>
|
||||
<p><strong>Designed controls that keep humans at stage gates.</strong></p>
|
||||
@@ -634,7 +634,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-9--telemetry-architecture">Slide 9 — Telemetry Architecture</h2>
|
||||
<p><strong>How Nova instruments itself — CloudEvents envelope, cold store, PowerBI export.</strong></p>
|
||||
@@ -684,7 +684,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-10--capability-health--confidence-distribution">Slide 10 — Capability Health + Confidence Distribution</h2>
|
||||
<p><strong>Grounded proof: capability health and confidence distribution from real runs.</strong></p>
|
||||
@@ -759,7 +759,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-11--decision-ledger--attestation-coverage">Slide 11 — Decision Ledger + Attestation Coverage</h2>
|
||||
<p><strong>Trust metrics — both 100%.</strong></p>
|
||||
@@ -811,7 +811,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-12--zero-touch-efficiency">Slide 12 — Zero-Touch Efficiency</h2>
|
||||
<p><strong>Touchless resolution, human escalation, and MTTR.</strong></p>
|
||||
@@ -862,7 +862,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-13--cost--roi">Slide 13 — Cost & ROI</h2>
|
||||
<p><strong>Cost estimates and the ROI formula — with honest caveats.</strong></p>
|
||||
@@ -913,7 +913,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-14--whats-deferred--and-why">Slide 14 — What's Deferred — and Why</h2>
|
||||
<p><strong>Honesty about what isn't measured yet.</strong></p>
|
||||
@@ -1010,7 +1010,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-15--roadmap-to-the-north-star">Slide 15 — Roadmap to the North Star</h2>
|
||||
<p><strong>The path from v1.17's grounded metrics to the 12–18 month targets.</strong></p>
|
||||
@@ -1061,7 +1061,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-16--recap--ask">Slide 16 — Recap + Ask</h2>
|
||||
<p><strong>The 5-act recap + the business decision.</strong></p>
|
||||
@@ -1115,7 +1115,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-17--scope-downstream-of-pdlc">Slide 17 — Scope: Downstream of PDLC</h2>
|
||||
<p><strong>Nova governs infrastructure + delivery. The PDLC (product backlog, code authorship, IDE) is upstream — Nova never penetrates it.</strong></p>
|
||||
@@ -1167,7 +1167,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="21">
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-18--raci-who-owns-what">Slide 18 — RACI: Who Owns What</h2>
|
||||
<p><strong>Three roles, one matrix — the citizen developer owns FRs + UAT, the platform owns NFRs + infra + QA + prod deploy, release management is co-owned.</strong></p>
|
||||
@@ -1233,7 +1233,59 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
<p><strong>Benefit:</strong> 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).</p>
|
||||
<footer>Act %{page}/5 — v1.17</footer>
|
||||
</section>
|
||||
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="20" data-header="Nova — The No-Humans Infrastructure Platform" data-footer="Act %{page}/5 — v1.17" data-class="title" data-theme="default" data-style="section {
|
||||
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="20" data-paginate="true" data-header="Nova — The No-Humans Infrastructure Platform" data-footer="Act %{page}/5 — v1.17" data-theme="default" data-style="section {
|
||||
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
|
||||
font-size: 22px;
|
||||
color: #1B1B1B;
|
||||
}
|
||||
h1 { color: #D6002A; font-size: 34px; margin-bottom: 0.3em; }
|
||||
h2 { color: #D6002A; font-size: 26px; margin-bottom: 0.2em; }
|
||||
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
|
||||
section.title h1 { color: #fff; }
|
||||
table { font-size: 18px; width: 100%; }
|
||||
th { background: #F0F0F0; }
|
||||
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 20px; }
|
||||
img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
.badge {
|
||||
display: inline-block; padding: 2px 8px; border-radius: 4px;
|
||||
font-size: 14px; font-weight: 600;
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
" lang="C" data-marpit-pagination="20" style="--paginate:true;--header:Nova — The No-Humans Infrastructure Platform;--footer:Act %{page}/5 — v1.17;--theme:default;--style:section {
|
||||
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
|
||||
font-size: 22px;
|
||||
color: #1B1B1B;
|
||||
}
|
||||
h1 { color: #D6002A; font-size: 34px; margin-bottom: 0.3em; }
|
||||
h2 { color: #D6002A; font-size: 26px; margin-bottom: 0.2em; }
|
||||
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
|
||||
section.title h1 { color: #fff; }
|
||||
table { font-size: 18px; width: 100%; }
|
||||
th { background: #F0F0F0; }
|
||||
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 20px; }
|
||||
img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
.badge {
|
||||
display: inline-block; padding: 2px 8px; border-radius: 4px;
|
||||
font-size: 14px; font-weight: 600;
|
||||
}
|
||||
.badge.today { background: #c6f6d5; color: #22543d; }
|
||||
.badge.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="22">
|
||||
<header>Nova — The No-Humans Infrastructure Platform</header>
|
||||
<h2 id="slide-19--production-grade-guidance-via-atelier">Slide 19 — Production-Grade Guidance via Atelier</h2>
|
||||
<p><strong>Nova instructs the citizen developer's AI agent on production-grade engineering — skills + an MCP server with agentic validation beyond deterministic scanners.</strong></p>
|
||||
<ul>
|
||||
<li><strong>Skills (9):</strong> markdown files under <code>skills/</code> keyed to Atelier domain paths (api, security, data, testing, observability, errors, devops, infrastructure-as-code, compliance) — extending the BA.A 5-skill catalog</li>
|
||||
<li><strong>MCP server:</strong> <code>mcp/atelier/server.py</code> (plugin-registry, stdio) — 4 tools: <code>lookup_principle</code>, <code>list_domains</code>, <code>matrix_lookup</code>, <code>validate_against_principles</code></li>
|
||||
<li><strong>Agentic validation:</strong> catches C1 correctness + C2 clarity + C7 observability gaps that Wiz/Checkmarx/Mend cannot — deterministic tools check policy/secrets; the MCP server checks engineering discipline</li>
|
||||
<li><strong>Vendored Atelier</strong> (pinned tag v0.3.6): audit reproducibility — a validation result is replayable against the exact principles that produced it</li>
|
||||
<li>Cites <code>docs/skills.md</code> + <code>mcp/atelier/README.md</code></li>
|
||||
</ul>
|
||||
<p><strong>Benefit:</strong> 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.</p>
|
||||
<footer>Act %{page}/5 — v1.17</footer>
|
||||
</section>
|
||||
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="21" data-header="Nova — The No-Humans Infrastructure Platform" data-footer="Act %{page}/5 — v1.17" data-class="title" data-theme="default" data-style="section {
|
||||
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
|
||||
font-size: 22px;
|
||||
color: #1B1B1B;
|
||||
@@ -1353,7 +1405,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
|
||||
</table>
|
||||
<footer>Act %{page}/5 — v1.17</footer>
|
||||
</section>
|
||||
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="21" data-header="Nova — The No-Humans Infrastructure Platform" data-footer="Act %{page}/5 — v1.17" data-class="title" data-theme="default" data-style="section {
|
||||
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="22" data-header="Nova — The No-Humans Infrastructure Platform" data-footer="Act %{page}/5 — v1.17" data-class="title" data-theme="default" data-style="section {
|
||||
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
|
||||
font-size: 22px;
|
||||
color: #1B1B1B;
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
# mcp/atelier — Nova Atelier MCP server package (v1.18)
|
||||
@@ -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).
|
||||
@@ -0,0 +1 @@
|
||||
# mcp/atelier package
|
||||
@@ -0,0 +1 @@
|
||||
# mcp/atelier/plugins package
|
||||
@@ -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) → domain→core 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]}
|
||||
@@ -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.",
|
||||
}
|
||||
@@ -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()
|
||||
Vendored
+21
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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
@@ -0,0 +1,50 @@
|
||||
# Agent Pre-Completion Checklist (Vendored)
|
||||
|
||||
Every AI agent runs this checklist before completing a task.
|
||||
|
||||
## Core Principles Checklist (C1–C8)
|
||||
|
||||
### 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
|
||||
Executable
+45
@@ -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."
|
||||
@@ -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 domain→core 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()
|
||||
Reference in New Issue
Block a user