Compare commits

...

11 Commits

Author SHA1 Message Date
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
28 changed files with 1538 additions and 234 deletions
+8 -8
View File
@@ -1,12 +1,12 @@
{ {
"phase": 0, "phase": 1,
"stage": "complete", "stage": "execute",
"milestone": "v1.18", "milestone": "v1.19",
"phase_role": "pre_execution", "phase_role": "execution",
"attempts": 0, "attempts": 0,
"updated_at": "2026-08-06T00:35:00Z", "updated_at": "2026-08-06T19:30:00Z",
"milestone_complete": false, "milestone_complete": false,
"tag": "v1.17.0", "tag": null,
"release_id": 522, "requirements": ["REQ-229"],
"notes": "v1.18 P0 complete. 5 pre-execution stages done. Tag v1.17.0, release 522." "notes": "v1.19 P1 complete: scripts/sync_to_nova.sh replaces sync_to_gl.sh. Manual-only 2nd-release pipeline into ~/nova (separate GitLab repo, consumer/platform-team audience). Consumer subset rsync, 13 fixed-order domain commits via positional -m, conventional-commit validation. 8 new tests pass (TestSyncToNovaScript). Pre-existing test_attestation_event_emission failure (env-dependent: NOVA_ATTESTATION_SIGNING_KEY_ID unset, D-089) reproduced on clean main — unrelated. Next: P2 final-review-ship -> tag v1.18.0."
} }
+76 -3
View File
@@ -689,8 +689,9 @@ DX: 16 total). Key changes:
10. Old two-surfaces diagram replaced by scope boundary diagram. 10. Old two-surfaces diagram replaced by scope boundary diagram.
Source markdown, talking points, and README all updated to mirror the new Source markdown, talking points, and README all updated to mirror the new
structure. Also includes scripts/sync_to_gl.sh (GitLab mirror sync structure. Also includes scripts/sync_to_nova.sh (manual-only "2nd release"
utility, unrelated to presentations). 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` No code changes; 494 tests pass; `run_ci.sh` + `run_platform.sh --check-only`
green. PPTX files uploaded to Gitea release. green. PPTX files uploaded to Gitea release.
@@ -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-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-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-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 | Phase | Status |
|-----|-------|--------| |-----|-------|--------|
| REQ-214 | P1 | pending | | REQ-214 | P1 | complete |
| REQ-215 | P2 | pending | | REQ-215 | P2 | complete |
| REQ-216 | P2 | pending | | REQ-216 | P2 | complete |
| REQ-217 | P3 | pending | | REQ-217 | P3 | complete |
| REQ-218 | P3 | pending | | REQ-218 | P3 | complete |
| REQ-219 | P3 | pending | | REQ-219 | P3 | complete |
| REQ-220 | P3 | pending | | REQ-220 | P3 | complete |
| REQ-221 | P4 | pending | | REQ-221 | P4 | complete |
| REQ-222 | P4 | pending | | REQ-222 | P4 | complete |
| REQ-223 | P5 | pending | | REQ-223 | P5 | complete |
| REQ-224 | P5 | pending | | REQ-224 | P5 | complete |
| REQ-225 | P5 | pending | | REQ-225 | P5 | complete |
| REQ-226 | P6 | pending | | REQ-226 | P6 | complete |
| REQ-227 | P6 | pending | | REQ-227 | P6 | complete |
| REQ-228 | P1/P2/P6 | pending | | 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 Ship tag at milestone COMPLETE: `v1.15.26` (NFR milestone; final patch IS
the release). **DONE.** 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 Nova advances from a platform that governs infrastructure delivery to one
that **instructs the citizen developer on production-grade engineering** 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-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-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). 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_project": "acdl",
"active_projects": ["acdl"], "active_projects": ["acdl"],
"active_milestone": "v1.18", "active_milestone": "v1.19",
"autonomy": { "autonomy": {
"level": "full", "level": "full",
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"], "escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
+1 -1
View File
@@ -40,4 +40,4 @@ metrics/lifecycle/
*.cer *.cer
*.crt *.crt
*.jks *.jks
*.keystore *.keystore.coverage
+9 -3
View File
@@ -343,7 +343,13 @@ attachments to the Gitea release.
## Current decks ## Current decks
| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML (Step 3) | Talking points (Step 4) | Slides | Audience | | Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML + PPTX (Step 3) | Talking points (Step 4) | Slides | Audience |
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| How the Platform Works | `how-the-platform-works.md` | `how-the-platform-works-marp.md` | `how-the-platform-works.html` | `how-the-platform-works-talking-points.md` | 11 main + TOC + 8 appendix (20) | CTO, Head of Cloud, Head of Infra, Head of DevOps | | Nova — The No-Humans Infrastructure Platform | `nova-no-humans-platform.md` | `nova-no-humans-platform-marp.md` | `nova-no-humans-platform.html` + `.pptx` (committed + release-attached) | `nova-no-humans-platform-talking-points.md` | 19 main + 2 appendix (21) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
| The Developer Experience | `the-developer-experience.md` | `the-developer-experience-marp.md` | `the-developer-experience.html` | `the-developer-experience-talking-points.md` | 11 main + TOC + 7 appendix (19) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
> **v1.18 (D-130):** the two legacy decks (How the Platform Works + The
> Developer Experience) were consolidated into a single unified narrative
> deck with a 5-act arc (Problem → Vision → How → Proof → Roadmap). v1.18
> (REQ-226) adds 3 slides (17 Scope, 18 RACI, 19 Atelier) → 21 total. The
> S&P Global Energy theme is restored (REQ-214, P1). PPTX is committed to
> git + attached to the Gitea release (REQ-228, D-141).
@@ -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 --> <!-- _class: title -->
<!-- _paginate: false --> <!-- _paginate: false -->
@@ -117,6 +117,13 @@
- The compliance-standard equivalence is the key: the source does not matter; the submission does - 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 - **Key takeaway:** you bring FRs + UAT; Nova provides NFRs + infra + QA + prod deploy; the release is co-owned with you at the stage gate
### Slide 19 — Production-Grade Guidance via Atelier
- Nova instructs the citizen developer's AI agent via skills (9 markdown files) + an MCP server (4 tools, plugin-registry, stdio)
- The MCP server provides agentic validation beyond deterministic scanners — catches correctness, clarity, observability gaps that Wiz/Checkmarx/Mend cannot
- Atelier is vendored (pinned tag) for audit reproducibility — a validation result is replayable
- This is how Nova ensures the citizen developer's submissions meet production-grade standards regardless of upstream source
- **Key takeaway:** the citizen developer is not unguided — Nova provides engineering principles via skills + MCP, so every submission meets the same standards
### Appendix A1 — Metrics Glossary ### Appendix A1 — Metrics Glossary
- Reference for every metric mentioned in the deck - Reference for every metric mentioned in the deck
- Use if the audience asks "what does X mean?" - Use if the audience asks "what does X mean?"
+72 -20
View File
@@ -92,7 +92,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
} }
.badge.today { background: #c6f6d5; color: #22543d; } .badge.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-1--arc-preview">Slide 1 — Arc Preview</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-2--the-no-humans-imperative">Slide 2 — The No-Humans Imperative</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-3--novas-vision">Slide 3 — Nova's Vision</h2> <h2 id="slide-3--novas-vision">Slide 3 — Nova's Vision</h2>
<blockquote> <blockquote>
@@ -248,7 +248,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
} }
.badge.today { background: #c6f6d5; color: #22543d; } .badge.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-4--strategic-objectives--anti-goals">Slide 4 — Strategic Objectives + Anti-Goals</h2> <h2 id="slide-4--strategic-objectives--anti-goals">Slide 4 — Strategic Objectives + Anti-Goals</h2>
<p><strong>4 Strategic Objectives:</strong></p> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-5--12%E2%80%9318-month-targets">Slide 5 — 1218 Month Targets</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-6--the-platform-pipeline">Slide 6 — The Platform Pipeline</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-7--the-decision-ledger">Slide 7 — The Decision Ledger</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <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> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-9--telemetry-architecture">Slide 9 — Telemetry Architecture</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-10--capability-health--confidence-distribution">Slide 10 — Capability Health + Confidence Distribution</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-11--decision-ledger--attestation-coverage">Slide 11 — Decision Ledger + Attestation Coverage</h2> <h2 id="slide-11--decision-ledger--attestation-coverage">Slide 11 — Decision Ledger + Attestation Coverage</h2>
<p><strong>Trust metrics — both 100%.</strong></p> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-12--zero-touch-efficiency">Slide 12 — Zero-Touch Efficiency</h2> <h2 id="slide-12--zero-touch-efficiency">Slide 12 — Zero-Touch Efficiency</h2>
<p><strong>Touchless resolution, human escalation, and MTTR.</strong></p> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-13--cost--roi">Slide 13 — Cost &amp; ROI</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-14--whats-deferred--and-why">Slide 14 — What's Deferred — and Why</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <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> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-16--recap--ask">Slide 16 — Recap + Ask</h2> <h2 id="slide-16--recap--ask">Slide 16 — Recap + Ask</h2>
<p><strong>The 5-act recap + the business decision.</strong></p> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-17--scope-downstream-of-pdlc">Slide 17 — Scope: Downstream of PDLC</h2> <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> <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.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; } .badge.planned { background: #fef3c7; color: #78350f; }
;" data-marpit-pagination-total="21"> ;" data-marpit-pagination-total="22">
<header>Nova — The No-Humans Infrastructure Platform</header> <header>Nova — The No-Humans Infrastructure Platform</header>
<h2 id="slide-18--raci-who-owns-what">Slide 18 — RACI: Who Owns What</h2> <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> <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> <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> <footer>Act %{page}/5 — v1.17</footer>
</section> </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-family: &quot;Akkurat Pro&quot;, &quot;Helvetica Neue&quot;, &quot;Arial&quot;, sans-serif;
font-size: 22px; font-size: 22px;
color: #1B1B1B; color: #1B1B1B;
@@ -1353,7 +1405,7 @@ img { display: block; margin: 0 auto; max-height: 320px; }
</table> </table>
<footer>Act %{page}/5 — v1.17</footer> <footer>Act %{page}/5 — v1.17</footer>
</section> </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-family: &quot;Akkurat Pro&quot;, &quot;Helvetica Neue&quot;, &quot;Arial&quot;, sans-serif;
font-size: 22px; font-size: 22px;
color: #1B1B1B; color: #1B1B1B;
Binary file not shown.
+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."
+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 assert "ecr" in cmd
class TestSyncToGlScript: class TestSyncToNovaScript:
"""scripts/sync_to_gl.sh — test structure (set flags, usage).""" """scripts/sync_to_nova.sh — manual-only 2nd release into ~/nova (REQ-229)."""
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
def test_script_exists(self): 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: class TestPostStageComment: