Compare commits

..

14 Commits

Author SHA1 Message Date
Jon Chery be51fc15fa docs(P1): ship complete — checkpoint update (Gitea release id 530)
acdl-ci / Lint (push) Successful in 9s
acdl-ci / Test (push) Failing after 24s
acdl-ci / Platform check-only (offline) (push) Successful in 24s
---ci---
project: acdl
phase: 1
milestone: v1.19
status: complete
phase_role: execution
requirements:
  covered: [REQ-229]
  partial: []
---/ci---
2026-08-06 15:43:12 +00:00
Jon Chery e3f4ce17d4 verify(P1): 4-layer verify PASS + ship — sync_to_nova.sh (REQ-229)
acdl-ci / Lint (push) Successful in 10s
acdl-ci / Test (push) Failing after 23s
acdl-ci / Platform check-only (offline) (push) Successful in 24s
L1 structural: bash -n clean, shellcheck 0 warnings.
L2 behavioral: manual gate exits 2 without --release; --list-domains prints
13 ordered domains; rsync exclude list correct; .git protected via filter.
L3 security: no hardcoded secrets; .coverage runtime artifact gitignored.
L4 quality: 8/8 TestSyncToNovaScript tests pass (gate, domain order, exclude
list, consumer-script inclusion, .git filter, conventional regex).

---ci---
project: acdl
phase: 1
milestone: v1.19
status: verify
---/ci---
2026-08-06 15:42:50 +00:00
Jon Chery e0d01ad2ef docs(P1): checkpoint — execute complete (REQ-229)
acdl-ci / Lint (push) Successful in 10s
acdl-ci / Test (push) Failing after 24s
acdl-ci / Platform check-only (offline) (push) Successful in 23s
---ci---
project: acdl
phase: 1
milestone: v1.19
status: execute
---/ci---
2026-08-06 15:40:22 +00:00
Jon Chery a4c5f332f6 feat(P1): sync_to_nova.sh — manual-only 2nd-release pipeline into ~/nova (REQ-229)
Replaces scripts/sync_to_gl.sh (kitchen-sink mirror sync into ~/gl/acdl) with
scripts/sync_to_nova.sh — a manual-only, consumer-subset, domain-committed
2nd-release pipeline into ~/nova (GitLab jonathanchery/nova, separate repo +
history, consumer/platform-team audience).

- Manual-only gate: refuses without --release / RELEASE_CONFIRMED=1 (exit 2).
  Never triggerable by CI.
- Consumer subset: excludes .ciagent/, .gitea/, .env*, terraform/, demo/,
  runtime metrics artifacts, and 18 internal-only scripts (EXCLUDE_SCRIPTS).
  Keeps consumer runbooks + metrics export views (README, powerbi,
  TRUST_SNAPSHOT). Protects ~/nova/.git via rsync --filter=P .git.
- Domain-based commits: 13 fixed-order domains (config, core, adapters,
  modules, contracts, schemas, pipelines, mcp, skills, scripts, tests, docs,
  workflows). Each changed domain gets its own conventional commit supplied
  positionally via repeated -m flags. No kitchen-sink commit.
- Conventional-commit validation: regex-enforced (feat|fix|docs|chore|...);
  bypass via --no-verify-format.
- Modes: --list-domains, --dry-run, --no-push, -v, -h.
- Tests: TestSyncToNovaScript (8 tests) covers gate, domain order, exclude
  list, consumer-script inclusion, .git protection filter, conventional
  regex.

Decisions: D-143 (target ~/nova), D-144 (conventional commits per domain,
not ---ci--- audit blocks), D-145 (manual-only trigger), D-146 (13 fixed
domains, positional-over-changed mapping), D-147 (coreci/Atelier review
gate deferred).

---ci---
project: acdl
phase: 1
milestone: v1.19
status: execute
requirements:
  covered: [REQ-229]
  partial: []
---/ci---
2026-08-06 15:40:11 +00:00
Jon Chery 9e20b7ba95 docs(ship): v1.17.7 milestone complete — checkpoint update (Gitea release id 529)
acdl-ci / Lint (push) Successful in 9s
acdl-ci / Test (push) Failing after 24s
acdl-ci / Platform check-only (offline) (push) Successful in 23s
2026-08-06 15:17:46 +00:00
Jon Chery 6da538c936 Merge milestone/v1.18-citizen-developer-guidance — v1.18 complete (Citizen Developer & Production-Grade Guidance: 5 inputs, 15 requirements, 7 phases + final; tag v1.17.7)
acdl-ci / Lint (push) Successful in 10s
acdl-ci / Test (push) Failing after 26s
acdl-ci / Platform check-only (offline) (push) Successful in 24s
2026-08-06 15:17:03 +00:00
Jon Chery 4e03817ea6 Merge phase/07-final-review-ship — v1.17.7 (v1.18 P7 final review + audit + milestone complete) 2026-08-06 15:16:59 +00:00
Jon Chery 951ad56576 docs(milestone): complete v1.18 — Citizen Developer & Production-Grade Guidance
15 requirements (REQ-214..228) satisfied. 32 tests pass. S&P Global theme
restored. PDLC-upstream scope + RACI matrix authored. Submission-readiness
schema + validator shipped. 9 Atelier skills + docs/skills.md. MCP server
(plugin-registry, stdio, vendored Atelier v0.3.6) with 4 tools + agentic
validation. 21-slide deck (3 new: scope/RACI/atelier) with PPTX committed +
release-attached. 10 decisions locked (D-133..D-142).

---ci---
project: acdl
phase: 7
milestone: v1.18
status: complete
requirements:
  covered: [REQ-214, REQ-215, REQ-216, REQ-217, REQ-218, REQ-219, REQ-220, REQ-221, REQ-222, REQ-223, REQ-224, REQ-225, REQ-226, REQ-227, REQ-228]
  partial: []
---/ci---
2026-08-06 15:16:54 +00:00
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
38 changed files with 1979 additions and 234 deletions
+8 -7
View File
@@ -1,12 +1,13 @@
{
"phase": 0,
"phase": 1,
"stage": "complete",
"milestone": "v1.18",
"phase_role": "pre_execution",
"milestone": "v1.19",
"phase_role": "execution",
"attempts": 0,
"updated_at": "2026-08-06T00:35:00Z",
"updated_at": "2026-08-06T20:00:00Z",
"milestone_complete": false,
"tag": "v1.17.0",
"release_id": 522,
"notes": "v1.18 P0 complete. 5 pre-execution stages done. Tag v1.17.0, release 522."
"tag": "v1.18.0",
"release_id": 530,
"requirements": ["REQ-229"],
"notes": "v1.19 P1 SHIP complete: tag v1.18.0 (first patch on v1.18.x line), Gitea release id 530 created. 4-layer verify PASS. Next: P2 final-review-ship (review + audit + milestone complete)."
}
+77 -4
View File
@@ -689,8 +689,9 @@ DX: 16 total). Key changes:
10. Old two-surfaces diagram replaced by scope boundary diagram.
Source markdown, talking points, and README all updated to mirror the new
structure. Also includes scripts/sync_to_gl.sh (GitLab mirror sync
utility, unrelated to presentations).
structure. Also includes scripts/sync_to_nova.sh (manual-only "2nd release"
into ~/nova — a separate GitLab consumer-facing repo with its own history;
domain-based conventional commits, never triggered by CI; REQ-229).
No code changes; 494 tests pass; `run_ci.sh` + `run_platform.sh --check-only`
green. PPTX files uploaded to Gitea release.
@@ -980,7 +981,7 @@ or user-directed scope). New v1.7 decisions:
| W1.A | AI-refinement trigger | **Accept recommendation.** Joint condition: N ≥ 50 consecutive changes with zero rollbacks AND no L1/L2 incident in last 6 months AND Infra & Ops unilateral override. |
| W1.B | Multi-stack edge case rule | **Accept recommendation.** Permitted only for (a) DR-region mirror, (b) time-boxed experimental stack with TTL ≤ 30d, (c) explicit Infra & Ops approval with `multiStack.justification`. |
| W2.A | Tag mutability for prod | **Accept recommendation (Path B).** Tag for dev/qa, SHA for prod. Platform CLI resolves tag→SHA for prod-bound workflows. Justified by the "Audit truth lives outside the repository" bet. |
| BA.A | Initial L3B skill catalog | **Accept recommendation.** 5 skills: web API, worker, scheduled job, static asset, basic observability bootstrap. Addition criteria: (a) reviewable for sensitive data, (b) expressible as a single contract submission, (c) documented use case. |
| BA.A | Initial L3B skill catalog | **Accept recommendation.** 5 skills: web API, worker, scheduled job, static asset, basic observability bootstrap. Addition criteria: (a) reviewable for sensitive data, (b) expressible as a single contract submission, (c) documented use case. **Extended v1.18 (REQ-221/222):** the BA.A 5-skill catalog is extended with 9 Atelier-derived production-grade engineering skills under `skills/` (api, security, data, testing, observability, errors, devops, infrastructure-as-code, compliance), indexed by `docs/skills.md`. The Atelier skills extend, not replace, the BA.A catalog. |
| W3.D | L1/L2 standard versioning | **Decided.** Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (same as the v1.0 demo D-rule, lifted to the real platform). Pin model: L2 contracts pin L1 by `name@semver`; the resolver picks the highest compatible. Evolution: MAJOR bumps require a new registry entry (immutable publication); old entry enters a 12-month deprecation window. |
| W3.E | Schema mandatory vs optional inputs | **Decided.** Per-env mandatory table: dev requires `stack` + `environment`; qa adds `validation.e2eSuite` + `validation.loadTest`; prod adds `runbook` + `dashboard` + `oncall`; dr adds `drDrillRef`. `inputs` map is always optional. `profile: agentic` fields (`naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`) optional everywhere. |
| BA.B | Confidence threshold tuning | **Decided.** Starting thresholds frozen for v1. Tuning begins in v1.2: track FP/FN per environment quarterly; override authority = Infra & Ops + SRE joint sign-off; any override is itself a confidence-event in the audit stream. |
@@ -1332,4 +1333,76 @@ constraints or user-directed scope). New v1.18 decisions:
| D-139 | RACI role names = Citizen Developer / Platform / Release Management (co-owned). | User-specified. The 3 roles are the columns of the RACI table. Release Management is co-owned: QA + SRE attestations are required by the actual release (performed agentically, overseen & triggered by the Citizen Developer). | P2 authors the RACI with these 3 roles. |
| D-140 | MCP server extensibility = plugin-registry (`plugins/<name>.py` implementing `register(mcp)`). | Future capabilities (new scanners, policy evaluators, cost tools) drop in as new plugin files — no `server.py` edits. `server.py` scans `plugins/` and calls `register` on each. This is the extensibility insurance: plugins are decoupled from the server entrypoint. | P5 implements the plugin-registry; initial plugins are `principles.py` + `validation.py`. |
| D-141 | PPTX storage = commit binary directly to `docs/presentations/` (no LFS). | Decks are small (~1-5 MiB); git handles binary blobs. LFS requires server-side support (unverified for git.cloudinit.dev) + client config. Committing directly is simplest and works without any repo/server config. Binary diffs are not delta-friendly, but deck changes are infrequent. | P1/P2/P6 commit .pptx directly. |
| D-142 | Deck render trigger = any phase modifying `docs/presentations/*-marp.md` or `docs/presentations/assets/` must re-render HTML + PPTX, commit PPTX, and attach to the Gitea release. | PPTX was previously manual + release-only (not committed). v1.18 makes it a first-class artifact: committed (history) + attached (download), both always, not optional. Automated via `scripts/render_deck.sh` + `scripts/attach_release_asset.py`. | P1/P2/P6 run the render+commit+attach pipeline. |
| D-142 | Deck render trigger = any phase modifying `docs/presentations/*-marp.md` or `docs/presentations/assets/` must re-render HTML + PPTX, commit PPTX, and attach to the Gitea release. | PPTX was previously manual + release-only (not committed). v1.18 makes it a first-class artifact: committed (history) + attached (download), both always, not optional. Automated via `scripts/render_deck.sh` + `scripts/attach_release_asset.py`. | P1/P2/P6 run the render+commit+attach pipeline. |
## Objective for Milestone v1.19 (active — Nova 2nd-Release Sync)
> **NFR-only chore milestone.** Ships a patch on the v1.18.x line (tag
> `v1.18.0`). Single execution phase. Establishes the manual-only "2nd
> release" pipeline from `~/acdl` (CIAgent-managed source of truth, full audit
> trail) into `~/nova` (GitLab `jonathanchery/nova` — separate repo, separate
> history, consumer / platform-team audience).
### Why
`~/acdl` is the engineering source of truth and carries the full CIAgent
audit trail (`.ciagent/`, milestone branches, `---ci---` blocks, Gitea
releases). Consumers and the platform team should consume a clean,
conventional-commit-shaped tree without the CIAgent plumbing. The old
`scripts/sync_to_gl.sh` mirrored `~/acdl → ~/gl/acdl` with a single
kitchen-sink `chore: sync from source mirror <ts>` commit — wrong audience,
wrong commit standard, wrong repo.
### What
- **`scripts/sync_to_nova.sh`** replaces `scripts/sync_to_gl.sh`.
- **Manual-only gate**: refuses without `--release` / `RELEASE_CONFIRMED=1`
(exit 2). Never triggerable by CI.
- **Consumer subset only**: excludes `.ciagent/`, `.gitea/`, `.env*`,
`terraform/`, `demo/`, runtime metrics artifacts, and internal-only scripts
(the `EXCLUDE_SCRIPTS` list — CIAgent/ops/release plumbing). Keeps
consumer-facing runbooks (`run_ci.sh`, `run_platform.sh`, etc.) and the
metrics export views (`metrics/README.md`, `powerbi/`, `TRUST_SNAPSHOT.md`).
- **Destination history protected**: rsync `--filter=P .git` ensures
`~/nova/.git` is never touched.
- **Domain-based commits**: 13 fixed-order domains (config → core → adapters
→ modules → contracts → schemas → pipelines → mcp → skills → scripts →
tests → docs → workflows). Each changed domain gets its own conventional
commit, supplied positionally via repeated `-m` flags. No kitchen-sink.
- **Conventional-commit validation**: regex-enforced
(`feat|fix|docs|chore|refactor|perf|test|build|ci|style|revert`); bypass via
`--no-verify-format`.
- **Modes**: `--list-domains` (print order), `--dry-run` (preview rsync +
messages), `--no-push` (commit without pushing), `-v` (verbose).
### Out of Scope
- **coreci / Atelier review gate on the synced tree** — deferred. A future
milestone may run a vendored-Atelier review pass before commit and block on
P0 findings.
- **Tagging releases on the `~/nova` side** — could add `--tag <semver>`
later.
- **Deleting `~/gl`** — the old mirror dir is left on disk; only the sync
script targeting it is removed.
### Requirements
- **REQ-229** — `scripts/sync_to_nova.sh` replaces `sync_to_gl.sh` with the
manual-only, consumer-subset, domain-committed 2nd-release pipeline
described above. (Phase P1)
### Phase Plan
| Phase | Name | Status |
|-------|------|--------|
| P1 | nova-sync-script | complete |
| P2 | final-review-ship | pending |
### Decisions
| ID | Decision | Rationale | Outcome |
|----|----------|-----------|---------|
| D-143 | 2nd release target = `~/nova` (separate GitLab repo), not `~/gl/acdl`. | `~/nova` is consumer/platform-team-facing with its own history; `~/gl/acdl` was an internal mirror with a kitchen-sink commit standard. Separate audience → separate repo → separate commit standard. | `sync_to_nova.sh` targets `~/nova`; `sync_to_gl.sh` removed. |
| D-144 | Commit standard for `~/nova` = real conventional commits per domain (not the `---ci---` audit blocks used in `~/acdl`). | `~/acdl` commits carry CIAgent audit metadata (`---ci---` blocks) for the ciagent auditing workflow; that's noise for platform consumers. `~/nova` gets clean `feat/fix/docs/chore(scope): subject` commits grouped by domain. | Script validates conventional format; domain-based commits via positional `-m`. |
| D-145 | Trigger = manual-only (`--release` / `RELEASE_CONFIRMED=1`). | The 2nd release is a deliberate human action, not a CI side-effect. The gate guarantees it can never fire from Gitea Actions, GitHub Actions, or accidental invocation. | Script exits 2 without `--release`. |
| D-146 | Domain grouping = 13 fixed-order domains by path prefix; messages map positionally over CHANGED domains only. | Avoids the kitchen-sink commit; gives `~/nova` a reviewable, conventional history tailored to platform consumers. Positional-over-changed mapping lets the human supply exactly the messages needed, in domain order, without padding for unchanged domains. | `--list-domains` prints order; `--dry-run` previews; count-mismatch errors clearly. |
| D-147 | coreci / Atelier review gate = deferred this milestone. | The vendored Atelier (`mcp/atelier/vendor`) could review the synced tree before commit and block on P0, but that's an additive hardening step, not part of establishing the pipeline. Deferred to a future milestone. | Sync ships consumer contents as-is; no review gate. |
+56 -15
View File
@@ -1339,18 +1339,59 @@ with documented schemas.
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-214 | P1 | pending |
| REQ-215 | P2 | pending |
| REQ-216 | P2 | pending |
| REQ-217 | P3 | pending |
| REQ-218 | P3 | pending |
| REQ-219 | P3 | pending |
| REQ-220 | P3 | pending |
| REQ-221 | P4 | pending |
| REQ-222 | P4 | pending |
| REQ-223 | P5 | pending |
| REQ-224 | P5 | pending |
| REQ-225 | P5 | pending |
| REQ-226 | P6 | pending |
| REQ-227 | P6 | pending |
| REQ-228 | P1/P2/P6 | pending |
| REQ-214 | P1 | complete |
| REQ-215 | P2 | complete |
| REQ-216 | P2 | complete |
| REQ-217 | P3 | complete |
| REQ-218 | P3 | complete |
| REQ-219 | P3 | complete |
| REQ-220 | P3 | complete |
| REQ-221 | P4 | complete |
| REQ-222 | P4 | complete |
| REQ-223 | P5 | complete |
| REQ-224 | P5 | complete |
| REQ-225 | P5 | complete |
| REQ-226 | P6 | complete |
| REQ-227 | P6 | complete |
| REQ-228 | P1/P2/P6 | complete |
## v1.19 — Nova 2nd-Release Sync (GitLab consumer mirror)
> **NFR-only chore milestone.** A single execution phase shipping a patch on
> the v1.18.x line (tag `v1.18.0`). Establishes the manual-only "2nd release"
> pipeline from `~/acdl` (CIAgent-managed source of truth) into `~/nova`
> (GitLab `jonathanchery/nova` — a separate repo, separate history, consumer /
> platform-team audience). `~/acdl` retains the full CIAgent audit trail;
> `~/nova` receives only the consumer subset, committed with real
> conventional commits per domain (no kitchen-sink "sync from source mirror").
- **REQ-229** — `scripts/sync_to_nova.sh` replaces `scripts/sync_to_gl.sh`.
The script: (1) refuses to run without `--release` / `RELEASE_CONFIRMED=1`
(manual-only — never triggerable by CI); (2) rsyncs the consumer subset of
`~/acdl` into `~/nova`, excluding `.ciagent/`, `.gitea/`, `.env*`, `terraform/`,
`demo/`, runtime metrics artifacts, and internal-only scripts (full list in
`EXCLUDE_SCRIPTS`), while protecting `~/nova/.git` history via rsync
`--filter=P .git`; (3) commits changes domain-by-domain in a fixed order
(config → core → adapters → modules → contracts → schemas → pipelines →
mcp → skills → scripts → tests → docs → workflows) using one
conventional-commit message per changed domain passed via repeated `-m`
flags (positional mapping over changed domains only — no kitchen-sink
commit); (4) validates conventional-commit format (`feat|fix|docs|chore|…`)
unless `--no-verify-format`; (5) pushes to the branch upstream unless
`--no-push`. `--list-domains`, `--dry-run`, `-v` supported. The old
`sync_to_gl.sh` is removed. (Phase P1)
### Out of Scope (v1.19)
- **coreci / Atelier review gate on the synced tree** — deferred; the sync
ships consumer contents as-is. A future milestone may run a vendored-Atelier
review pass before commit and block on P0 findings.
- **Tagging releases on the `~/nova` side** — could add `--tag <semver>` later.
- **Deleting `~/gl`** — the old GitLab `acdl` mirror is left on disk; only the
sync script targeting it is removed.
### v1.19 Traceability
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-229 | P1 | complete |
+55 -1
View File
@@ -1684,7 +1684,7 @@ deferred (D-113/D-114).
Ship tag at milestone COMPLETE: `v1.15.26` (NFR milestone; final patch IS
the release). **DONE.**
## v1.18 (active — Citizen Developer & Production-Grade Guidance, tag line `v1.17.x`)
## v1.18 (complete — Citizen Developer & Production-Grade Guidance, tag line `v1.17.x`)
Nova advances from a platform that governs infrastructure delivery to one
that **instructs the citizen developer on production-grade engineering**
@@ -1742,3 +1742,57 @@ phase's Gitea release.
D-134 (deck slide budget), D-135 (MCP transport), D-136 (Atelier vendoring),
D-137 (MCP server language), D-138 (skill format), D-139 (RACI roles),
D-140 (MCP plugin-registry), D-141 (PPTX storage), D-142 (deck render trigger).
**Outcome:** 15 requirements (REQ-214..228) satisfied; 32 tests pass (16
submission-readiness + 16 MCP); S&P Global Energy theme restored; PDLC-
upstream scope + RACI matrix authored (PROJECT.md + docs/ + deck);
submission-readiness schema + validator shipped (superset gate above
contract.schema.json); 9 Atelier-derived skills + docs/skills.md; MCP
server (plugin-registry, stdio, vendored Atelier v0.3.6) with 4 tools +
agentic validation beyond Wiz/Checkmarx/Mend; 21-slide deck (3 new slides:
scope/RACI/atelier) with PPTX committed + release-attached. 10 decisions
locked (D-133..D-142).
Ship tag at milestone COMPLETE: `v1.17.7` (feature milestone; final patch
IS the release). **DONE.**
## v1.19 (active — Nova 2nd-Release Sync, tag line `v1.18.x`)
> **NFR-only chore milestone.** Single execution phase. Establishes the
> manual-only "2nd release" pipeline `~/acdl → ~/nova` (GitLab
> `jonathanchery/nova`, separate repo + history, consumer/platform-team
> audience). Replaces the old `~/gl/acdl` mirror sync.
### Phase P1 — nova-sync-script (Wave 1)
- **Description:** Replace `scripts/sync_to_gl.sh` (kitchen-sink mirror sync
into `~/gl/acdl`) with `scripts/sync_to_nova.sh` — a manual-only,
consumer-subset, domain-committed 2nd-release pipeline into `~/nova`.
Excludes `.ciagent/`, `terraform/`, `demo/`, runtime metrics, and
internal-only scripts. Protects `~/nova/.git`. Commits per domain in a fixed
order using positional `-m` conventional-commit messages. Validates
conventional format. Never triggerable by CI (`--release` gate).
- **Status:** complete
- **Depends on:** —
- **Requirements:** REQ-229
- **Success Criteria:**
- `scripts/sync_to_nova.sh` exists with `set -euo pipefail`.
- Refuses without `--release` (exit 2); `--list-domains` prints 13 domains.
- rsync excludes `.ciagent`, `terraform`, `demo`, internal scripts, runtime
metrics; protects destination `.git`.
- Domain commits in fixed order; positional `-m` mapping; conventional
format validated.
- `scripts/sync_to_gl.sh` removed.
- `pytest` passes; `run_ci.sh` exits 0.
### Phase P2 — final-review-ship (Final Phase)
- **Description:** Final review + audit + milestone ship. Merge to main, tag
`v1.18.0` (first patch on the v1.18.x line), create Gitea release.
- **Status:** pending
- **Depends on:** [P1]
- **Requirements:** REQ-229
- **Success Criteria:**
- Review + audit clean (no P0).
- `phase/02-final-review-ship` merged to `milestone/v1.19-nova-sync` then to
`main`.
- Tag `v1.18.0` created; release notes summarize REQ-229.
- Milestone branches deleted; CHECKPOINT cleared.
+1 -1
View File
@@ -8,7 +8,7 @@
],
"active_project": "acdl",
"active_projects": ["acdl"],
"active_milestone": "v1.18",
"active_milestone": "v1.19",
"autonomy": {
"level": "full",
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
+1 -1
View File
@@ -40,4 +40,4 @@ metrics/lifecycle/
*.cer
*.crt
*.jks
*.keystore
*.keystore.coverage
+9 -3
View File
@@ -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?"
+72 -20
View File
@@ -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 — 1218 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 &amp; 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 1218 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: &quot;Akkurat Pro&quot;, &quot;Helvetica Neue&quot;, &quot;Arial&quot;, 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: &quot;Akkurat Pro&quot;, &quot;Helvetica Neue&quot;, &quot;Arial&quot;, 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: &quot;Akkurat Pro&quot;, &quot;Helvetica Neue&quot;, &quot;Arial&quot;, 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: &quot;Akkurat Pro&quot;, &quot;Helvetica Neue&quot;, &quot;Arial&quot;, sans-serif;
font-size: 22px;
color: #1B1B1B;
Binary file not shown.
+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
+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
-173
View File
@@ -1,173 +0,0 @@
#!/usr/bin/env bash
# scripts/sync_to_gl.sh - copy ~/acdl contents to ~/gl/acdl and push.
#
# Copies the ACDL source tree into the GitLab mirror at ~/gl/acdl.
# Hidden files/dirs are NOT copied EXCEPT for .github (so GitLab CI
# workflows stay current) and .gitignore. The terraform/ tree is
# omitted entirely, and .gitignore patterns are honored. The
# destination's existing .git directory is preserved untouched.
#
# After syncing, commits any changes on the current branch with a
# timestamped message and pushes it to its upstream (origin/main).
#
# Run manually:
# bash scripts/sync_to_gl.sh # sync + commit + push
# bash scripts/sync_to_gl.sh -v # verbose (list copied files)
# bash scripts/sync_to_gl.sh --no-push # sync + commit only, no push
# bash scripts/sync_to_gl.sh --dry-run # show what would happen
# SRC=~/acdl DST=~/gl/acdl bash scripts/sync_to_gl.sh
set -euo pipefail
SRC="${SRC:-$HOME/acdl}"
DST="${DST:-$HOME/gl/acdl}"
VERBOSE=0
NO_PUSH=0
DRY_RUN=0
for arg in "$@"; do
case "$arg" in
-v|--verbose) VERBOSE=1 ;;
--no-push) NO_PUSH=1 ;;
--dry-run) DRY_RUN=1 ;;
-h|--help)
sed -n '2,21p' "$0"
exit 0
;;
*) echo "FAIL: unknown argument: $arg" >&2; exit 1 ;;
esac
done
fail() { echo "FAIL: $*" >&2; exit 1; }
run() {
if [ "$DRY_RUN" = "1" ]; then
echo " [dry-run] $*"
else
"$@"
fi
}
[ -d "$SRC" ] || fail "source not found: $SRC"
[ -d "$DST" ] || fail "destination not found: $DST (create it first)"
[ -d "$DST/.git" ] || fail "destination has no .git: $DST/.git (restore it first)"
echo "=== sync_to_gl ==="
echo "source: $SRC"
echo "destination: $DST"
[ "$NO_PUSH" = "1" ] && echo "mode: sync + commit (no push)"
[ "$DRY_RUN" = "1" ] && echo "mode: dry-run (no changes made)"
echo ""
# Sanity: refuse if DST is not inside ~/gl or is the same as SRC.
case "$DST" in
"$HOME"/gl/*) : ;;
*) fail "destination must live under ~/gl (got $DST)" ;;
esac
[ "$SRC" != "$DST" ] || fail "source and destination are identical"
# --- sync (rsync) -----------------------------------------------------------
# Build rsync exclude list: every hidden entry in SRC except .github
# and .gitignore.
EXCLUDES=()
for hidden in "$SRC"/.*; do
name="$(basename "$hidden")"
case "$name" in
.|...) continue ;;
.github|.gitignore) continue ;; # keep
esac
EXCLUDES+=("--exclude=/$name")
done
# Never touch the destination's .git. "protect" makes rsync skip it
# entirely (neither transfer nor delete) even under --delete; this is
# stronger than --exclude, which --delete-excluded would wipe out.
# Drop it from the transfer set too.
EXCLUDES+=("--exclude=/.git")
# Omit the terraform/ tree entirely.
EXCLUDES+=("--exclude=/terraform")
# rsync filters: protect .git, then honor per-directory .gitignore
# via dir-merge (:-) semantics so patterns anchor like git does.
FILTERS=(
"--filter=P .git"
"--filter=:- .gitignore"
)
# Use --delete (prune extras in the synced tree) but NOT --delete-excluded:
# that would wipe destination paths covered by our --exclude rules, which
# is exactly what must NOT happen for .git.
RSYNC_ARGS=(-a --delete)
[ "$VERBOSE" = "1" ] && RSYNC_ARGS+=(-v)
echo "rsync excludes: ${EXCLUDES[*]}"
echo "rsync filters: ${FILTERS[*]}"
echo ""
if [ "$DRY_RUN" = "1" ]; then
echo "[dry-run] rsync would run:"
printf ' %q ' rsync "${RSYNC_ARGS[@]}" "${FILTERS[@]}" "${EXCLUDES[@]}" "$SRC/" "$DST/"; echo
else
rsync "${RSYNC_ARGS[@]}" "${FILTERS[@]}" "${EXCLUDES[@]}" "$SRC/" "$DST/"
echo "rsync: OK"
fi
echo ""
# --- git commit + push ------------------------------------------------------
cd "$DST"
# Refuse to run inside a merge/rebase/conflict state.
git rev-parse --is-inside-work-tree >/dev/null
git_dir_state() {
local f
for f in MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD BISECT_LOG; do
[ -e ".git/$f" ] && return 1
done
[ -d ".git/rebase-merge" -o -d ".git/rebase-apply" ] && return 1
return 0
}
git_dir_state || fail "destination .git is mid-operation (merge/rebase/etc); resolve it then re-run"
branch="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
[ -n "$branch" ] || fail "HEAD is detached; checkout a branch first (got $(git rev-parse --short HEAD))"
# Stage everything in the working tree (including deletions).
run git add -A
# Commit only if there is something staged.
if git diff --cached --quiet; then
echo "git: no changes to commit on branch '$branch'"
else
ts="$(date -u +%Y-%m-%d\ %H:%M\ UTC)"
msg="chore: sync from source mirror $ts"
echo "git: committing on branch '$branch'"
[ "$VERBOSE" = "1" ] && git diff --cached --stat
run git commit -m "$msg"
fi
# Push (current branch to its upstream) unless suppressed.
if [ "$NO_PUSH" = "1" ]; then
echo "git: --no-push set, skipping push"
PUSHED=0
else
upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)"
if [ -z "$upstream" ]; then
fail "no upstream configured for branch '$branch'; set one with: git -C $DST branch --set-upstream-to=origin/$branch $branch"
fi
if [ "$DRY_RUN" = "1" ]; then
echo " [dry-run] git push to $upstream"
else
echo "git: pushing '$branch' to $upstream"
git push
echo "git: push OK"
fi
fi
echo ""
echo "=== sync_to_gl OK ==="
echo "copied $SRC -> $DST"
[ "$DRY_RUN" = "1" ] && echo "(dry-run: nothing actually written or pushed)"
[ "$NO_PUSH" = "1" ] && echo "(no-push: changes committed but not pushed)"
exit 0
+397
View File
@@ -0,0 +1,397 @@
#!/usr/bin/env bash
# scripts/sync_to_nova.sh — manual-only "2nd release" of ~/acdl into ~/nova.
#
# ~/nova is a SEPARATE GitLab repo (jonathanchery/nova) with its own history,
# consumer/platform-team audience, and conventional commit standards. It is
# NOT a mirror of ~/acdl (the CIAgent audit trail lives only in ~/acdl).
#
# This script:
# 1. Refuses to run unless --release (or RELEASE_CONFIRMED=1) is set — it can
# NEVER be triggered by CI or accidentally. This is a human-only gate.
# 2. rsyncs the CONSUMER SUBSET of ~/acdl into ~/nova (internal-only paths —
# .ciagent, terraform, demo, internal scripts, runtime metrics — are
# excluded; the destination's .git history is protected untouched).
# 3. Commits changes DOMAIN BY DOMAIN in a fixed order, using one
# conventional-commit message per changed domain passed via repeated -m
# flags. NO kitchen-sink "sync from source mirror" commit. Messages are
# consumed positionally over the changed domains (in the order printed by
# --list-domains / --dry-run).
# 4. Pushes the current branch to its upstream (unless --no-push).
#
# Usage:
# bash scripts/sync_to_nova.sh --release -m "feat(core): add X" -m "docs(contracts): refresh Y"
# bash scripts/sync_to_nova.sh --dry-run --release -m "chore(core): sync"
# bash scripts/sync_to_nova.sh --list-domains
# bash scripts/sync_to_nova.sh --no-push --release -m "fix(schemas): tighten validation"
#
# SRC=~/acdl DST=~/nova bash scripts/sync_to_nova.sh --release -m "..."
#
# Domain order (first match wins; a domain with no staged changes is skipped
# and does NOT consume a -m message — messages map positionally over the
# CHANGED domains only):
# 1 config README.md, pyproject.toml, requirements-test.txt, .gitignore
# 2 core core/**
# 3 adapters adapters/**
# 4 modules modules/**
# 5 contracts contracts/**
# 6 schemas schemas/**
# 7 pipelines pipelines/**
# 8 mcp mcp/**
# 9 skills skills/**
# 10 scripts scripts/** (consumer runbooks only; internal scripts excluded)
# 11 tests tests/**
# 12 docs docs/**
# 13 workflows .github/**, workflows-src/**
set -euo pipefail
SRC="${SRC:-$HOME/acdl}"
DST="${DST:-$HOME/nova}"
VERBOSE=0
NO_PUSH=0
DRY_RUN=0
RELEASE=0
LIST_DOMAINS=0
NO_VERIFY_FORMAT=0
COMMIT_MSGS=()
usage() { sed -n '2,40p' "$0"; }
while [ $# -gt 0 ]; do
case "$1" in
--release) RELEASE=1; shift ;;
RELEASE_CONFIRMED=1) RELEASE=1; shift ;;
-v|--verbose) VERBOSE=1; shift ;;
--no-push) NO_PUSH=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--list-domains) LIST_DOMAINS=1; shift ;;
--no-verify-format) NO_VERIFY_FORMAT=1; shift ;;
-m|--message) shift; [ $# -gt 0 ] || { echo "FAIL: -m requires a value" >&2; exit 1; }; COMMIT_MSGS+=("$1"); shift ;;
-m=*) COMMIT_MSGS+=("${1#-m=}"); shift ;;
--message=*) COMMIT_MSGS+=("${1#--message=}"); shift ;;
-h|--help) usage; exit 0 ;;
*) echo "FAIL: unknown argument: $1" >&2; exit 1 ;;
esac
done
# --- domains (ordered; first match wins) ------------------------------------
#
# Each entry: "<name>|<pathspec1 pathspec2 ...>". Pathspecs are relative to
# DST and use git pathspec semantics. The order here IS the commit order and
# the order messages are consumed in.
DOMAINS=(
"config|README.md pyproject.toml requirements-test.txt .gitignore"
"core|core"
"adapters|adapters"
"modules|modules"
"contracts|contracts"
"schemas|schemas"
"pipelines|pipelines"
"mcp|mcp"
"skills|skills"
"scripts|scripts"
"tests|tests"
"docs|docs"
"workflows|.github workflows-src"
)
# Internal-only scripts that must NEVER be synced to ~/nova. These are
# CIAgent/ops/release plumbing that only makes sense in ~/acdl. Anything in
# scripts/ NOT in this list is a consumer-facing runbook and IS synced.
EXCLUDE_SCRIPTS=(
sync_to_gl.sh
sync_to_nova.sh
ship_phase.sh
update_atelier_vendor.sh
post_stage_comment.sh
rotate_spike_key.sh
run_l2_lifecycle_destroy.sh
run_lifecycle_destroy.sh
run_lifecycle_test.sh
migrate_dynamodb_data.py
migrate_ssm_paths.py
untag_acdl_keys.py
seed_uptime_monitors.py
push_consumer_image.py
sync_workflows.py
attach_release_asset.py
check_north_star_diff.sh
render_deck.sh
)
CONV_RE='^(feat|fix|docs|chore|refactor|perf|test|build|ci|style|revert)(\([^)]+\))?: .+'
# --- helpers ---------------------------------------------------------------
fail() { echo "FAIL: $*" >&2; exit 1; }
# Print the domain list (name | paths) for --list-domains.
print_domains() {
printf '%-12s %s\n' "DOMAIN" "PATHS"
for entry in "${DOMAINS[@]}"; do
name="${entry%%|*}"; paths="${entry#*|}"
printf '%-12s %s\n' "$name" "$paths"
done
}
# --- --list-domains (no side effects, no gate) ------------------------------
if [ "$LIST_DOMAINS" = "1" ]; then
print_domains
exit 0
fi
# --- manual-only gate ------------------------------------------------------
if [ "$RELEASE" = "0" ]; then
echo "sync_to_nova: this is a MANUAL-ONLY 2nd release into ~/nova (separate repo," >&2
echo "separate history, consumer-facing). It is never triggered by CI." >&2
echo "" >&2
echo "To confirm intent, re-run with --release (or set RELEASE_CONFIRMED=1):" >&2
echo " bash scripts/sync_to_nova.sh --release -m \"<conventional commit>\" [-m ...]" >&2
echo "" >&2
echo "Use --list-domains to see the domain order, or --dry-run --release to preview." >&2
exit 2
fi
# --- sanity checks ---------------------------------------------------------
[ -d "$SRC" ] || fail "source not found: $SRC"
[ -d "$DST" ] || fail "destination not found: $DST (create it first)"
[ -d "$DST/.git" ] || fail "destination has no .git: $DST/.git (restore it first)"
# Refuse if DST is not inside $HOME or is the same as SRC.
case "$DST" in
"$HOME"/*) : ;;
*) fail "destination must live under \$HOME (got $DST)" ;;
esac
[ "$SRC" != "$DST" ] || fail "source and destination are identical"
# Refuse to run inside a merge/rebase/conflict state in DST.
git_dir_state() {
local f
for f in MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD BISECT_LOG; do
[ -e "$DST/.git/$f" ] && return 1
done
[ -d "$DST/.git/rebase-merge" ] || [ -d "$DST/.git/rebase-apply" ] && return 1
return 0
}
git_dir_state || fail "destination .git is mid-operation (merge/rebase/etc); resolve it then re-run"
echo "=== sync_to_nova (manual 2nd release) ==="
echo "source: $SRC"
echo "destination: $DST"
[ "$VERBOSE" = "1" ] && echo "mode: verbose"
[ "$NO_PUSH" = "1" ] && echo "mode: sync + commit (no push)"
[ "$DRY_RUN" = "1" ] && echo "mode: dry-run (no changes made)"
echo ""
# --- rsync (consumer subset) -----------------------------------------------
#
# Strategy: explicit excludes for everything internal-only, then a protected
# .git filter, then .gitignore dir-merge semantics so consumer-visible ignored
# files (pyc, .env, etc.) are also dropped. --delete prunes extras in the
# synced tree so removals in ~/acdl propagate to ~/nova. --delete-excluded is
# NOT used so the protected .git survives.
# Hidden dirs/files in SRC that are NOT consumer-facing. .github is kept.
EXCLUDES=(
--exclude=/.ciagent
--exclude=/.gitea
--exclude=/.env
--exclude=/.env.secrets
--exclude=/.coverage
--exclude=/.pytest_cache
--exclude=/.git
--exclude=/terraform
--exclude=/demo
)
# Runtime metrics artifacts (keep README.md, powerbi/, TRUST_SNAPSHOT.md).
EXCLUDES+=(
--exclude=/metrics/nova_metrics.db
--exclude=/metrics/decision_ledger.db
--exclude=/metrics/events.jsonl
--exclude=/metrics/test-results.xml
--exclude=/metrics/test-report.json
--exclude=/metrics/coverage.json
--exclude=/metrics/runs
--exclude=/metrics/lifecycle
)
# Internal-only scripts (by basename).
for s in "${EXCLUDE_SCRIPTS[@]}"; do
EXCLUDES+=("--exclude=/scripts/$s")
done
# Universal noise.
EXCLUDES+=(
--exclude=**/__pycache__
--exclude=**/*.pyc
--exclude=**/*.pyo
--exclude=**/.DS_Store
)
# .git protection (P = protect from --delete) + per-directory .gitignore merge.
# Each --filter is a single token: "<rule> <pattern>".
FILTERS=(
"--filter=P .git"
"--filter=:- .gitignore"
)
RSYNC_ARGS=(-a --delete)
[ "$VERBOSE" = "1" ] && RSYNC_ARGS+=(-v)
echo "rsync excludes: ${EXCLUDES[*]}"
echo "rsync filters: ${FILTERS[*]}"
echo ""
if [ "$DRY_RUN" = "1" ]; then
echo "[dry-run] rsync would run:"
printf ' %q ' rsync "${RSYNC_ARGS[@]}" "${FILTERS[@]}" "${EXCLUDES[@]}" "$SRC/" "$DST/"; echo
else
rsync "${RSYNC_ARGS[@]}" "${FILTERS[@]}" "${EXCLUDES[@]}" "$SRC/" "$DST/"
echo "rsync: OK"
fi
echo ""
# --- domain-based commits ---------------------------------------------------
cd "$DST"
branch="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
[ -n "$branch" ] || fail "HEAD is detached; checkout a branch first (got $(git rev-parse --short HEAD))"
# Stage everything (including deletions) so we can diff per-domain.
# In dry-run we do NOT run rsync (nothing is written), so per-domain change
# detection against the destination is meaningless — dry-run is a preview
# only (rsync command + message validation). Domain mapping is computed in
# real mode below.
if [ "$DRY_RUN" = "0" ]; then
git add -A
fi
# Determine which domains have changes (positional, in DOMAIN order).
changed_names=()
changed_pathspecs=()
if [ "$DRY_RUN" = "1" ]; then
# Preview: print the fixed domain order so the user can line up -m messages.
echo "domain order (messages map positionally over CHANGED domains only):"
for entry in "${DOMAINS[@]}"; do
name="${entry%%|*}"; paths="${entry#*|}"
printf ' %-12s %s\n' "$name" "$paths"
done
echo ""
echo "provided -m messages (${#COMMIT_MSGS[@]}):"
for i in "${!COMMIT_MSGS[@]}"; do
printf ' %2d %s\n' "$((i+1))" "${COMMIT_MSGS[$i]}"
done
echo ""
echo "(dry-run: rsync not run — actual changed-domain detection happens in real mode;"
echo " re-run without --dry-run, or with --no-push to commit without pushing.)"
else
for entry in "${DOMAINS[@]}"; do
name="${entry%%|*}"; paths="${entry#*|}"
# shellcheck disable=SC2086
if ! git diff --cached --quiet -- $paths 2>/dev/null; then
changed_names+=("$name"); changed_pathspecs+=("$paths")
fi
done
n_changed=${#changed_names[@]}
echo "changed domains (in commit order):"
if [ "$n_changed" = "0" ]; then
echo " (none)"
else
for i in "${!changed_names[@]}"; do
printf ' %2d %-12s %s\n' "$((i+1))" "${changed_names[$i]}" "${changed_pathspecs[$i]}"
done
fi
echo ""
fi
# Validate -m count matches changed-domain count (real mode only).
n_msgs=${#COMMIT_MSGS[@]}
if [ "$DRY_RUN" = "1" ]; then
# In dry-run we can't know how many domains will change, so we only
# validate conventional-commit format. Count check happens in real mode.
if [ "$NO_VERIFY_FORMAT" = "0" ]; then
for i in "${!COMMIT_MSGS[@]}"; do
msg="${COMMIT_MSGS[$i]}"
if ! [[ "$msg" =~ $CONV_RE ]]; then
echo "FAIL: message $((i+1)) is not a conventional commit:" >&2
echo " \"$msg\"" >&2
echo " expected: <type>[optional(scope)]: <subject>" >&2
echo " types: feat|fix|docs|chore|refactor|perf|test|build|ci|style|revert" >&2
echo " (use --no-verify-format to skip this check)" >&2
exit 1
fi
done
fi
echo "git: (dry-run) no commits made"
else
if [ "$n_changed" = "0" ]; then
echo "git: no changes to commit on branch '$branch'"
else
if [ "$n_msgs" -ne "$n_changed" ]; then
echo "FAIL: $n_changed domain(s) changed but $n_msgs -m message(s) provided." >&2
echo " Messages map POSITIONALLY to the changed domains above (in order)." >&2
echo " Re-run with exactly $n_changed -m flag(s), or --list-domains to" >&2
echo " see the order, or --dry-run to preview." >&2
exit 1
fi
# Validate conventional-commit format (unless --no-verify-format).
if [ "$NO_VERIFY_FORMAT" = "0" ]; then
for i in "${!COMMIT_MSGS[@]}"; do
msg="${COMMIT_MSGS[$i]}"
if ! [[ "$msg" =~ $CONV_RE ]]; then
echo "FAIL: message $((i+1)) is not a conventional commit:" >&2
echo " \"$msg\"" >&2
echo " expected: <type>[optional(scope)]: <subject>" >&2
echo " types: feat|fix|docs|chore|refactor|perf|test|build|ci|style|revert" >&2
echo " (use --no-verify-format to skip this check)" >&2
exit 1
fi
done
fi
# Commit per domain in order.
for i in "${!changed_names[@]}"; do
name="${changed_names[$i]}"
paths="${changed_pathspecs[$i]}"
msg="${COMMIT_MSGS[$i]}"
echo "git: committing domain '$name' on branch '$branch'"
[ "$VERBOSE" = "1" ] && { git diff --cached --stat -- $paths 2>/dev/null || true; }
git reset HEAD -- . >/dev/null 2>&1 || true
# shellcheck disable=SC2086
git add -- $paths
git commit -m "$msg" >/dev/null
done
fi
fi
# --- push ------------------------------------------------------------------
if [ "$NO_PUSH" = "1" ]; then
echo "git: --no-push set, skipping push"
else
upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)"
if [ -z "$upstream" ]; then
fail "no upstream configured for branch '$branch'; set one with: git -C $DST branch --set-upstream-to=origin/$branch $branch"
fi
if [ "$DRY_RUN" = "1" ]; then
echo " [dry-run] git push to $upstream"
else
echo "git: pushing '$branch' to $upstream"
git push
echo "git: push OK"
fi
fi
echo ""
echo "=== sync_to_nova OK ==="
echo "copied (consumer subset) $SRC -> $DST"
[ "${n_changed:-0}" -gt 0 ] && echo "$n_changed domain commit(s) on branch '$branch'"
[ "$DRY_RUN" = "1" ] && echo "(dry-run: nothing actually written, committed, or pushed)"
[ "$NO_PUSH" = "1" ] && echo "(no-push: changes committed but not pushed)"
exit 0
+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()
+63 -9
View File
@@ -66,17 +66,71 @@ class TestPushConsumerImage:
assert "ecr" in cmd
class TestSyncToGlScript:
"""scripts/sync_to_gl.sh — test structure (set flags, usage)."""
def test_has_set_flags(self):
"""v1.14 (P16): sync_to_gl.sh should have set -euo pipefail."""
script = (ROOT / "scripts" / "sync_to_gl.sh").read_text()
# P16 will add this; for now just verify the script exists
assert "cp" in script or "rsync" in script
class TestSyncToNovaScript:
"""scripts/sync_to_nova.sh — manual-only 2nd release into ~/nova (REQ-229)."""
def test_script_exists(self):
assert (ROOT / "scripts" / "sync_to_gl.sh").is_file()
assert (ROOT / "scripts" / "sync_to_nova.sh").is_file()
def test_has_set_flags(self):
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
assert "set -euo pipefail" in script
def test_manual_gate_refuses_without_release(self):
"""Without --release the script must exit non-zero and never rsync."""
result = subprocess.run(
["bash", str(ROOT / "scripts" / "sync_to_nova.sh")],
capture_output=True,
text=True,
)
assert result.returncode == 2
assert "MANUAL-ONLY" in result.stderr or "manual" in result.stderr
def test_list_domains_prints_ordered_domains(self):
"""--list-domains prints the 13 domains in commit order."""
result = subprocess.run(
["bash", str(ROOT / "scripts" / "sync_to_nova.sh"), "--list-domains"],
capture_output=True,
text=True,
)
assert result.returncode == 0
lines = [l for l in result.stdout.splitlines() if l and not l.startswith("DOMAIN")]
names = [l.split()[0] for l in lines]
# The 13 consumer domains, in commit order.
assert names == [
"config", "core", "adapters", "modules", "contracts",
"schemas", "pipelines", "mcp", "skills", "scripts",
"tests", "docs", "workflows",
]
def test_internal_scripts_are_excluded(self):
"""The EXCLUDE_SCRIPTS list must include the internal-only scripts."""
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
# Isolate the EXCLUDE_SCRIPTS=( ... ) block.
block = script.split("EXCLUDE_SCRIPTS=(")[1].split(")")[0]
for internal in ("sync_to_gl.sh", "sync_to_nova.sh", "ship_phase.sh",
"update_atelier_vendor.sh", "rotate_spike_key.sh",
"post_stage_comment.sh", "untag_acdl_keys.py"):
assert internal in block, f"{internal} missing from EXCLUDE_SCRIPTS"
def test_consumer_scripts_not_excluded(self):
"""Consumer-facing runbooks must NOT be in the exclude list."""
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
for consumer in ("run_ci.sh", "run_platform.sh", "run_regression.sh"):
# They appear in scripts/ but must not be in EXCLUDE_SCRIPTS.
assert f"\"{consumer}\"" not in script.split("EXCLUDE_SCRIPTS=(")[1].split(")")[0], \
f"{consumer} should NOT be excluded (it's a consumer runbook)"
def test_git_filter_uses_protect_pattern(self):
"""rsync must protect the destination's .git history (filter=P)."""
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
assert "--filter=P .git" in script
def test_conventional_commit_regex_present(self):
"""The script validates conventional commit format."""
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
assert "CONV_RE" in script
assert "feat|fix|docs|chore|refactor|perf|test|build|ci|style|revert" in script
class TestPostStageComment: