merge(milestone): v1.30 Single-shot Leadership Deck (polished) to main (release v1.29.5)
acdl-ci / Lint (push) Successful in 12s
acdl-ci / Test (push) Failing after 25s
Nova Slides Render / render (push) Failing after 22s
nova-release / Compute semver + update tags (push) Failing after 10m33s
acdl-ci / Platform check-only (offline) (push) Failing after 10m42s
acdl-ci / forge_parity_disabled (push) Failing after 11m7s
---ci--- project: acdl phase: 4 milestone: v1.30 status: complete tag: v1.29.5 milestone_release: true merged_to_main: true ---/ci---
@@ -1,37 +1,19 @@
|
||||
{
|
||||
"phase": 2,
|
||||
"phase": 3,
|
||||
"stage": "complete",
|
||||
"milestone": "v1.30",
|
||||
"phase_role": "final",
|
||||
"phase_role": "execution",
|
||||
"attempts": 0,
|
||||
"updated_at": "2026-08-20T14:40:00Z",
|
||||
"updated_at": "2026-08-20T15:30:00Z",
|
||||
"project": "acdl",
|
||||
"projects": ["acdl", "nova-blockchain-exchange"],
|
||||
"active_milestone": "v1.30",
|
||||
"milestone_branch": null,
|
||||
"phase_branch": null,
|
||||
"milestone_branch": "milestone/v1.30-leadership-deck",
|
||||
"phase_branch": "phase/03-deck-polish",
|
||||
"tag_line": "v1.29.x",
|
||||
"phase_name": "final-review-ship",
|
||||
"phase_name": "deck-polish",
|
||||
"milestone_type": "feature",
|
||||
"reqs_covered": ["REQ-372.1","REQ-372.2","REQ-372.3","REQ-372.4","REQ-372.5","REQ-372.6","REQ-372.7","REQ-372.8","REQ-372.9","REQ-372.10","REQ-372.11","REQ-372.12"],
|
||||
"reqs_partial": [],
|
||||
"previous_milestone": {
|
||||
"milestone": "v1.30",
|
||||
"tag": "v1.29.3",
|
||||
"complete": true,
|
||||
"merged_to_main": "96765fe",
|
||||
"branches_deleted": true,
|
||||
"releases_created": true,
|
||||
"release_ids": [811, 812, 813]
|
||||
},
|
||||
"ship": {
|
||||
"tag": "v1.29.3",
|
||||
"release_id": 813,
|
||||
"release_url": "https://git.cloudinit.dev/continuous-intelligence/acdl/releases/tag/v1.29.3",
|
||||
"merged_to_main": true,
|
||||
"merge_commit": "96765fe",
|
||||
"branches_deleted": ["phase/02-final-review-ship", "milestone/v1.30-leadership-deck"],
|
||||
"local_only": false
|
||||
},
|
||||
"notes": "v1.30 MILESTONE COMPLETE. Tag v1.29.3 (milestone release), Gitea release id=813. Merged to main 96765fe. All milestone branches deleted (local + remote). REQ-372.1..12 all complete. CAP-042 + D-241..D-243 recorded. 3 Gitea releases: 811 (P0 v1.29.1), 812 (P1 v1.29.2), 813 (P2 v1.29.3). Checkpoint cleared — next run starts a new milestone."
|
||||
"notes": "v1.30 P3 (deck-polish) COMPLETE. D-244 (cover), D-245 (slide 7 what-works-now), D-246 (7 mermaid diagrams). F1-F5 polish fixes. 7 S&P-themed diagrams rendered via local mmdc (vendored Chromium libs). All checks pass. Next: SHIP (v1.29.4)."
|
||||
}
|
||||
@@ -206,8 +206,118 @@ at the v1.30 ship wave (CAP-042 row + intake assumption correction).
|
||||
|
||||
---
|
||||
|
||||
## Polish-phase decisions (D-244, D-245, D-246 — full autonomy)
|
||||
|
||||
The first draft (v1.29.3) passed all 12 REQs but the rendered PPTX
|
||||
has 7 polish issues (vertical imbalance, text clipping, double bullet
|
||||
markers, no visual variety, near-invisible footer, slide-6 red wall,
|
||||
blockquote grey). The user requested: (1) polish all 7 issues, (2)
|
||||
add a "What works now" emphasis to slide 7, (3) generate mermaid
|
||||
diagrams for every slide. These decisions record the deviations from
|
||||
prior binding decisions.
|
||||
|
||||
### D-244 — Slide 1 becomes a black-background cover (overrides grill G-1)
|
||||
|
||||
**Polish issue #4.** Grill G-1 locked "all 7 slides as white content
|
||||
slides." The user approved making slide 1 a black-background cover
|
||||
(strong opener for a live leadership presentation) while keeping
|
||||
slides 2-7 as white content slides. The on-slide body text from the
|
||||
Slide Content Map is preserved verbatim — only the visual treatment
|
||||
(background + text color) changes. REQ-372.7's "deviation requires
|
||||
CLARIFY" clause applies to *content* deviations; this is a
|
||||
presentation polish, not a content change. The source change: slide
|
||||
1's `## The friction...` → `# The friction...` (H1) + `<!-- _class:
|
||||
title -->` directive, which triggers `render_title_slide` (black bg,
|
||||
red top bar, white text).
|
||||
**Confidence:** 1.0 (user-confirmed).
|
||||
|
||||
### D-245 — Slide 7 "What works now" content addition (deviation from Slide Content Map)
|
||||
|
||||
**User request.** A new "What works now" block is added to slide 7's
|
||||
on-slide body, before the existing "What we ask" block. This is a
|
||||
*content* deviation — the Slide Content Map specifies slide 7 as:
|
||||
What we ask / Why now / What comes back + closer. The new block
|
||||
emphasizes what's proven today (L1/L2 stack deployment works, live
|
||||
apply to the sandbox/dev AWS account, confidence 0.800) and the
|
||||
next-steps arc (ingest greenfield pilot projects → promote from
|
||||
sandbox to production → integrate with the SPGE constitutional
|
||||
library → serve as the infrastructure layer).
|
||||
|
||||
**Factual grounding (verified in RESEARCH):**
|
||||
- 13 L1 primitives + 2 L2 modules in `modules/registry.json` (all at
|
||||
`1.0.0`): alb, cloudfront, dynamodb, ecr, ecs-cluster, ecs-service,
|
||||
iam-role, kms-key, rds, s3, uptime, vpc, waf + microservice, static-
|
||||
assets.
|
||||
- CAP-031: live `terraform apply` against AWS account `581513795199`
|
||||
(the dev environment, `mode: full`, dev-only per D-209), producing
|
||||
real ARNs (ALB, ECS, DynamoDB, S3, KMS) at confidence 0.800. This is
|
||||
a **live AWS apply**, NOT "tested locally" and NOT a "sandbox"
|
||||
account (the codebase has no sandbox account; the pilot-readiness
|
||||
policy D-208 blocks placeholder accounts).
|
||||
|
||||
**"sandbox" colloquial term note:** The user explicitly chose to keep
|
||||
"sandbox" as a colloquial term for the dev AWS account after I flagged
|
||||
that the codebase fact is "dev account `581513795199`, live apply."
|
||||
The slide uses "sandbox" (colloquial); the speaker notes carry the
|
||||
precise facts (dev account, live apply, confidence 0.800).
|
||||
|
||||
**"SPGE constitutional library" unverified-in-repo note:** "SPGE"
|
||||
and "constitutional library" appear nowhere in the codebase (0
|
||||
matches across all `.md`/`.py`/`.json`/`.yml`). The user confirmed it
|
||||
is an external system the audience recognizes. The slide includes it
|
||||
as a named next-step integration target, not as a current capability.
|
||||
The speaker notes do not assert it exists in the codebase.
|
||||
|
||||
The Slide Content Map in PROJECT.md is updated to reflect the new
|
||||
slide 7 structure.
|
||||
**Confidence:** 0.90.
|
||||
|
||||
### D-246 — Mermaid diagrams on all 7 slides
|
||||
|
||||
**User request.** Each of the 7 slides gets a leader-friendly,
|
||||
non-technical mermaid diagram covering its core concept. Different
|
||||
mermaid shapes per slide for visual variety.
|
||||
|
||||
**Render path (verified):** The local `mmdc` (mermaid-cli) is blocked
|
||||
by missing Chromium shared libraries (10+ system libs absent, no
|
||||
root). Resolution: 24 Debian bookworm `.deb` packages fetched from the
|
||||
Debian mirror, `.so` files extracted to `/home/opencode/tmp/so2/`,
|
||||
`LD_LIBRARY_PATH` set to include them. Chromium (puppeteer cache at
|
||||
`/home/opencode/.cache/puppeteer/chrome/.../chrome`) launches with
|
||||
the vendored libs; `mmdc` renders with the S&P theme (`sp-theme.json`)
|
||||
+ 2x scale + transparent background. Verified: test diagram renders
|
||||
to a valid PNG with all 4 S&P colors present. The `.mmd` source files
|
||||
+ rendered PNGs are committed for reproducibility; a future
|
||||
environment with system Chromium can re-render via the existing
|
||||
`scripts/render_slides.sh` mermaid step.
|
||||
|
||||
**Theme:** Inline `%%{init: {...}}%%` directive in each `.mmd` with
|
||||
S&P theme variables (`primaryColor:#1B1B1B`, `primaryBorderColor:
|
||||
#D6002A`, `primaryTextColor:#fff`, `secondaryColor:#fff`,
|
||||
`tertiaryColor:#F0F0F0`, `lineColor:#1B1B1B`) — only the 4 S&P tokens.
|
||||
Transparent background. No component names, no technical jargon —
|
||||
concepts only (leader-friendly).
|
||||
|
||||
**Diagram-type-per-slide mapping:**
|
||||
1. flowchart LR — 3 frictions → Nova absorbs them
|
||||
2. flowchart TB — Central IT golden image → Nova one layer up
|
||||
3. flowchart LR with subgraphs — two principles → everything inherits
|
||||
4. flowchart LR (3 columns) — Live · Attested · Stays human
|
||||
5. flowchart LR (two halves) — In Nova's lane / Outside Nova's lane
|
||||
6. timeline — the 18-month shape (α→β→γ→δ)
|
||||
7. flowchart LR — what works now → next steps arc
|
||||
|
||||
The Slide Content Map in PROJECT.md is updated to include a "Diagram"
|
||||
field per slide.
|
||||
**Confidence:** 0.88.
|
||||
|
||||
---
|
||||
|
||||
## Requirements impact
|
||||
|
||||
No requirements are added, removed, or re-scoped by these decisions.
|
||||
D-241–D-243 are load-bearing context for executing REQ-372.1–.12 as
|
||||
written. The spec is locked (v1.0, 2026-08-20); no spec text changes.
|
||||
No requirements are added, removed, or re-scoped by D-241–D-243.
|
||||
D-244–D-246 are polish-phase decisions that record deviations from
|
||||
prior binding decisions (G-1) and the Slide Content Map (slide 7).
|
||||
The spec is locked (v1.0, 2026-08-20); no spec text changes. The
|
||||
Slide Content Map in PROJECT.md is updated to reflect D-245 (slide 7
|
||||
structure) and D-246 (diagram field per slide).
|
||||
@@ -0,0 +1,23 @@
|
||||
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1B1B1B','primaryBorderColor':'#D6002A','primaryTextColor':'#fff','secondaryColor':'#fff','secondaryBorderColor':'#D6002A','secondaryTextColor':'#1B1B1B','tertiaryColor':'#F0F0F0','lineColor':'#1B1B1B','fontFamily':'"Helvetica Neue","Arial",sans-serif'}}}%%
|
||||
flowchart LR
|
||||
subgraph F["The friction"]
|
||||
F1["Authored by<br/>non-specialists"]
|
||||
F2["Every change<br/>gated"]
|
||||
F3["Compliance<br/>checked late"]
|
||||
end
|
||||
F1 --- F2
|
||||
F2 --- F3
|
||||
F --> N
|
||||
subgraph N["Nova absorbs all three"]
|
||||
N1["Owned<br/>building blocks"]
|
||||
N2["Separation<br/>of concerns"]
|
||||
N3["Attested compliance<br/>up front"]
|
||||
end
|
||||
style F fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style N fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style F1 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style F2 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style F3 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style N1 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style N2 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style N3 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
@@ -0,0 +1,10 @@
|
||||
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1B1B1B','primaryBorderColor':'#D6002A','primaryTextColor':'#fff','secondaryColor':'#fff','secondaryBorderColor':'#D6002A','secondaryTextColor':'#1B1B1B','tertiaryColor':'#F0F0F0','lineColor':'#1B1B1B','fontFamily':'"Helvetica Neue","Arial",sans-serif'}}}%%
|
||||
flowchart TB
|
||||
CIT["Central IT curates<br/>the golden OS image"]
|
||||
NOVA["Nova curates<br/>the cloud infrastructure"]
|
||||
CONSUMER["Consumer declares<br/>a contract"]
|
||||
CIT -->|own, patch, ship| NOVA
|
||||
NOVA -->|own, patch, attest| CONSUMER
|
||||
style CIT fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style NOVA fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style CONSUMER fill:#fff,color:#1B1B1B,stroke:#D6002A
|
||||
@@ -0,0 +1,17 @@
|
||||
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1B1B1B','primaryBorderColor':'#D6002A','primaryTextColor':'#fff','secondaryColor':'#fff','secondaryBorderColor':'#D6002A','secondaryTextColor':'#1B1B1B','tertiaryColor':'#F0F0F0','lineColor':'#1B1B1B','fontFamily':'"Helvetica Neue","Arial",sans-serif'}}}%%
|
||||
flowchart LR
|
||||
T1["Sovereign<br/>boundary"]
|
||||
T2["Lower autonomous<br/>higher attested"]
|
||||
I1["Four-layer<br/>model"]
|
||||
I2["HITL<br/>gates"]
|
||||
I3["Contract<br/>schema"]
|
||||
T1 --> I1
|
||||
T1 --> I3
|
||||
T2 --> I2
|
||||
T2 --> I3
|
||||
T1 -.-> T2
|
||||
style T1 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style T2 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style I1 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style I2 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style I3 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
@@ -0,0 +1,9 @@
|
||||
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1B1B1B','primaryBorderColor':'#D6002A','primaryTextColor':'#fff','secondaryColor':'#fff','secondaryBorderColor':'#D6002A','secondaryTextColor':'#1B1B1B','tertiaryColor':'#F0F0F0','lineColor':'#1B1B1B','fontFamily':'"Helvetica Neue","Arial",sans-serif'}}}%%
|
||||
flowchart LR
|
||||
L["Live today<br/>41 capabilities<br/>12 domains"]
|
||||
A["Attested on<br/>promotion<br/>named human approver"]
|
||||
H["Stays human<br/>by design<br/>escalation below threshold"]
|
||||
L --> A --> H
|
||||
style L fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style A fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style H fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
@@ -0,0 +1,23 @@
|
||||
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1B1B1B','primaryBorderColor':'#D6002A','primaryTextColor':'#fff','secondaryColor':'#fff','secondaryBorderColor':'#D6002A','secondaryTextColor':'#1B1B1B','tertiaryColor':'#F0F0F0','lineColor':'#1B1B1B','fontFamily':'"Helvetica Neue","Arial",sans-serif'}}}%%
|
||||
flowchart LR
|
||||
subgraph IN["In Nova's lane"]
|
||||
I1["Infrastructure<br/>primitives"]
|
||||
I2["Operational<br/>guardrails"]
|
||||
I3["CVE response<br/>infra layer"]
|
||||
end
|
||||
C["The<br/>contract"]
|
||||
subgraph OUT["Outside Nova's lane"]
|
||||
O1["Application<br/>business logic"]
|
||||
O2["IDE & sprint<br/>workflows"]
|
||||
O3["AppSec &<br/>dependency review"]
|
||||
end
|
||||
IN --- C --- OUT
|
||||
style IN fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style OUT fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style C fill:#fff,color:#D6002A,stroke:#D6002A
|
||||
style I1 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style I2 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style I3 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style O1 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style O2 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style O3 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
@@ -0,0 +1,11 @@
|
||||
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1B1B1B','primaryBorderColor':'#D6002A','primaryTextColor':'#fff','secondaryColor':'#fff','secondaryBorderColor':'#D6002A','secondaryTextColor':'#1B1B1B','tertiaryColor':'#F0F0F0','lineColor':'#1B1B1B','fontFamily':'"Helvetica Neue","Arial",sans-serif'}}}%%
|
||||
flowchart LR
|
||||
A["α<br/>Now → Q4'26<br/>Operating model +<br/>federated governance"]
|
||||
B["β<br/>Q1'27<br/>Auto-published<br/>infra observability"]
|
||||
G["γ<br/>Q2'27<br/>Runbook generation<br/>from telemetry"]
|
||||
D["δ<br/>Q3'27 → Q4'27<br/>Audit ledger<br/>tamper-resistant"]
|
||||
A --> B --> G --> D
|
||||
style A fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style B fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style G fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style D fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
@@ -0,0 +1,13 @@
|
||||
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1B1B1B','primaryBorderColor':'#D6002A','primaryTextColor':'#fff','secondaryColor':'#fff','secondaryBorderColor':'#D6002A','secondaryTextColor':'#1B1B1B','tertiaryColor':'#F0F0F0','lineColor':'#1B1B1B','fontFamily':'"Helvetica Neue","Arial",sans-serif'}}}%%
|
||||
flowchart LR
|
||||
W["What works now<br/>L1 & L2 stacks live<br/>confidence 0.800"]
|
||||
N1["Ingest greenfield<br/>pilot projects"]
|
||||
N2["Promote sandbox<br/>to production"]
|
||||
N3["Integrate SPGE<br/>constitutional library"]
|
||||
N4["Serve as the<br/>infrastructure layer"]
|
||||
W --> N1 --> N2 --> N3 --> N4
|
||||
style W fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
style N1 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style N2 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style N3 fill:#F0F0F0,color:#1B1B1B,stroke:#D6002A
|
||||
style N4 fill:#1B1B1B,color:#fff,stroke:#D6002A
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -14,6 +14,11 @@
|
||||
Vision grounding: [1] citations in slides 3, 5, 7 speaker notes
|
||||
resolve to docs/vision.md (the spec's acdl-vision.md reference).
|
||||
|
||||
Diagrams: 7 S&P-themed mermaid diagrams (D-246), one per slide,
|
||||
rendered via scripts/render_leadership_diagrams.sh (mmdc + vendored
|
||||
Chromium libs). Sources: assets/mmd/leadership-slide-N.mmd.
|
||||
PNGs: assets/png/leadership-slide-N.png.
|
||||
|
||||
Render: python3 scripts/render_pptx.py docs/presentations/nova-leadership-deck-marp.md \
|
||||
--output docs/presentations/nova-leadership-deck.pptx
|
||||
Smoke test: bash scripts/check_leadership_deck.sh
|
||||
@@ -42,7 +47,10 @@ style: |
|
||||
@media print { section { overflow: hidden; } }
|
||||
---
|
||||
|
||||
## The friction every delivery team lives today
|
||||
<!-- _class: title -->
|
||||
<!-- _paginate: false -->
|
||||
|
||||
# The friction every delivery team lives today
|
||||
|
||||
> *Velocity is up; the coordination surface around each change is up faster.*
|
||||
|
||||
@@ -52,6 +60,8 @@ style: |
|
||||
|
||||
> *Nova absorbs all three — owned building blocks, separation of concerns, attested compliance up front.*
|
||||
|
||||

|
||||
|
||||
<!--
|
||||
Three patterns drive the friction every delivery team lives today, and all three trace back to one binding constraint: software delivery scales with the coordination surface around it, not the engineering inside it [1]. That is the claim the vision document opens with, and it is the lens for everything Nova does.
|
||||
|
||||
@@ -70,13 +80,11 @@ Nova absorbs all three. Owned building blocks, separation of concerns, attested
|
||||
|
||||
> *You already recognize this pattern.*
|
||||
|
||||
Every Central IT team curates a golden image for Windows, for Linux, for macOS. They own it. They patch it. They ship it. Consumers consume it without thinking about what's inside.
|
||||

|
||||
|
||||
Nova plays the same role one layer up — for everything that runs your cloud. S3 buckets with SSE-KMS posture. RDS instances with deletion protection and PITR. Lambda containers with static ABAC binaries. ALBs, ECS services, KMS keys, DynamoDB tables. Each one is owned by the platform team, patched by the platform team, attested by the platform team, and consumed by anyone who declares a contract.
|
||||
Central IT curates the golden image; Nova curates the cloud infrastructure. Owned, patched, attested, consumed by contract.
|
||||
|
||||
The difference: every primitive is versioned, tested across its entire lifecycle, and bounded by policy before any consumer ever touches it.
|
||||
|
||||
> *Nova's lane is the infrastructure beneath the application. AppSec, dependency review, and runtime application security stay where they have always been — with the application team.*
|
||||
> *Nova's lane is the infrastructure beneath the application. AppSec stays with the application team.*
|
||||
|
||||
<!--
|
||||
The Central IT golden-image pattern is one every leadership team already recognizes. Central IT curates the Windows image, the Linux image, the macOS image. They own it, they patch it, they ship it, and consumers consume it without thinking about what is inside. That trade — per-application control for uniform operability — is a trade every enterprise has already made at the OS layer.
|
||||
@@ -92,13 +100,13 @@ And the lane stays narrow. Nova's lane is the infrastructure beneath the applica
|
||||
|
||||
## Two principles that organize everything else
|
||||
|
||||
> *The architecture is principled, not improvised. Two tenets discipline every other decision.*
|
||||
> *Two tenets discipline every other decision.*
|
||||
|
||||
**Sovereign boundary.** Nova governs the delivery lifecycle; it does not reach upstream into product or software development [1]. Integration with SDLC and PDLC partners happens exclusively through the validated, published contract surface. What lives outside the contract is not Nova's domain.
|
||||

|
||||
|
||||
**Lower autonomous · higher attested.** Lower environments proceed through agentic automation. Promotion to higher environments requires deliberate human attestation — not as a rubber stamp, but as policy-mandated accountability [1]. The compute the platform makes; the choice the human keeps.
|
||||
**Sovereign boundary** — Nova governs the delivery lifecycle; it does not reach upstream [1]. Integration with SDLC and PDLC happens exclusively through the contract surface.
|
||||
|
||||
> *Everything else in the architecture inherits from these two.*
|
||||
**Lower autonomous · higher attested** — lower environments proceed through agentic automation; promotion requires deliberate human attestation [1]. The compute the platform makes; the choice the human keeps.
|
||||
|
||||
<!--
|
||||
The architecture is principled, not improvised. Two tenets discipline every other decision the platform makes, and both come straight from the vision document [1].
|
||||
@@ -114,14 +122,13 @@ And the point for this room: these two tenets are not aspirational. They are loa
|
||||
|
||||
## Live · Attested · Stays human
|
||||
|
||||
**Live today**
|
||||
41 capabilities across 12 domains. Contract ingestor, audit outbox, state buckets, and the live pilot run have been operating in our AWS estate since v1.7; pilot evidence at v1.26 returned confidence 0.800. DORA + adoption + policy-conformance metrics flow to PowerBI from the same audit stream as the lineage. Every finding carries one owner, one patch state, one audit entry — one pane, no second source of truth. A POC is production-grade by construction: there is no "POC that became prod" surprise.
|
||||

|
||||
|
||||
**Attested on promotion**
|
||||
qa, prod, and dr require a named human approver distinct from the PR author. Rubber stamps cannot be silently issued.
|
||||
**Live today** — 41 capabilities across 12 domains; pilot confidence 0.800. One pane, no second source of truth.
|
||||
|
||||
**Stays human — by design**
|
||||
Confidence below the autonomy threshold at qa, prod, or dr triggers human escalation [1]. Some categories of decision are preserved for human judgment, and the platform says so out loud.
|
||||
**Attested on promotion** — qa, prod, dr require a named human approver distinct from the PR author.
|
||||
|
||||
**Stays human — by design** — confidence below the autonomy threshold triggers human escalation [1].
|
||||
|
||||
<!--
|
||||
Three columns, three claims, one disambiguation. The claims are real, observable, and disciplined — and the distinction matters for this room.
|
||||
@@ -137,20 +144,13 @@ Disciplined: attested on promotion, stays human by design. qa, prod, and dr requ
|
||||
|
||||
## The boundary keeps us honest
|
||||
|
||||
> *Nova stays where it belongs.*
|
||||

|
||||
|
||||
**In Nova's lane**
|
||||
- → Infrastructure primitives: S3, RDS, Lambda, ECS, DynamoDB, KMS, CloudFront.
|
||||
- → Operational guardrails: confidence, policy, attestation, audit lineage.
|
||||
- → CVE response at the infrastructure layer.
|
||||
**In Nova's lane** — infrastructure primitives, operational guardrails, CVE response at the infra layer.
|
||||
|
||||
**Outside Nova's lane**
|
||||
- → Application business logic.
|
||||
- → IDE, sprint, author workflows [1].
|
||||
- → Application-layer security: AppSec, dependency review, runtime threat modeling.
|
||||
- → VM, bare-metal, OS lifecycles [1].
|
||||
**Outside Nova's lane** — application business logic, IDE & sprint workflows [1], AppSec, VM/bare-metal/OS lifecycles [1].
|
||||
|
||||
> *The line is the contract. Everything below the contract is Nova. Everything above it stays where it has always been.*
|
||||
> *The line is the contract. Everything below it is Nova. Everything above it stays where it has always been.*
|
||||
|
||||
<!--
|
||||
The boundary is not a defensive posture. It is an operating principle — and it is the principle that keeps the architecture honest [1].
|
||||
@@ -168,13 +168,12 @@ The line is the contract. Everything below the contract is Nova. Everything abov
|
||||
|
||||
> *Where CDLC meets SDLC + PDLC — through the contract surface, not above it.*
|
||||
|
||||
**α (now → Q4'26) — Operating model + federated governance.** A named platform-ops body owns the platform; SLAs on every L2 are ratifiable by platform + consumer. The operating model is published; integration surfaces for SDLC and PDLC harnesses are documented at the contract boundary.
|
||||

|
||||
|
||||
**β (Q1'27) — Auto-published infra observability.** Every consumer stack ships with CloudWatch dashboards, uptime-kuma monitors, and alert routing on apply — infrastructure primitives publish observability as a property, no per-team authoring required.
|
||||
|
||||
**γ (Q2'27) — Runbook generation from telemetry.** Every L1 primitive ships with an auto-generated incident runbook derived from observed patterns. SREs get a starting runbook, not a blank page.
|
||||
|
||||
**δ (Q3'27 → Q4'27) — Audit ledger, tamper-resistant + externally addressable.** The SQLite hash-evidence stream migrates to S3 Object Lock + JWS signatures. External counsel verifies any production change back to a named human attestation.
|
||||
**α (now → Q4'26)** Operating model + federated governance.
|
||||
**β (Q1'27)** Auto-published infra observability.
|
||||
**γ (Q2'27)** Runbook generation from telemetry.
|
||||
**δ (Q3'27 → Q4'27)** Audit ledger, tamper-resistant.
|
||||
|
||||
> *Nova absorbs no IDE, no editor, no sprint tool, no agent harness.*
|
||||
|
||||
@@ -196,23 +195,22 @@ Nova absorbs no IDE, no editor, no sprint tool, no agent harness. The contract s
|
||||
|
||||
## What we ask · What comes back
|
||||
|
||||
**What we ask.**
|
||||
Architecture endorsement. Runway to the next milestone.
|
||||

|
||||
|
||||
**Why now.**
|
||||
Agentic SDLC is reshaping the delivery curve. What is barely keepable today — incident response, compliance reconciliation, security remediation — does not compress at the same rate as the velocity it has to keep pace with. By the end of 2027, the gap between delivery acceleration and operational absorption is the structural risk.
|
||||
**What works now.** Deploying L1 & L2 stacks works today in the sandbox AWS account — 13 L1 primitives and 2 L2 modules, live-applied at confidence 0.800. Next steps: ingest greenfield pilot projects, promote from sandbox to production, integrate with the SPGE constitutional library, and serve as the infrastructure layer.
|
||||
|
||||
**What comes back.**
|
||||
The infrastructure foundation that absorbs the velocity. Metrics that tell us where to push next. Audit lineage that closes the regulatory question. The next milestone, **by November 2026**.
|
||||
**What we ask.** Architecture endorsement. Runway to the next milestone.
|
||||
|
||||
**Why now.** Agentic SDLC is reshaping the delivery curve. What is barely keepable today — incident response, compliance reconciliation, security remediation — does not compress at the same rate as the velocity it has to keep pace with. By the end of 2027, the gap between delivery acceleration and operational absorption is the structural risk.
|
||||
|
||||
**What comes back.** The infrastructure foundation that absorbs the velocity. Metrics that tell us where to push next. Audit lineage that closes the regulatory question. The next milestone, **by November 2026**.
|
||||
|
||||
> *What we do not ask for: an IDE, a sprint tool, an author workflow, an upstream pipeline. Nova stays in its lane [1].*
|
||||
|
||||
<!--
|
||||
This is presented to Infrastructure and Operations leadership in August 2026. The ask is two things: architecture endorsement, and runway to the next milestone by November 2026.
|
||||
This is presented to Infrastructure and Operations leadership in August 2026. What works now: deploying L1 and L2 stacks works today. 13 L1 primitives and 2 L2 modules are live in the registry; the pilot ran a live terraform apply against the dev AWS account at v1.26 and returned confidence 0.800 — a measured number, not a forecast. We refer to the dev account colloquially as the sandbox. Next steps: ingest greenfield pilot projects (greenfield consumers with no legacy to reconcile), promote from sandbox to production (the promotion path with HITL gates exists; the pilot was dev-only, so activating qa, prod, dr is the next step), integrate with the SPGE constitutional library (an external governance system the audience recognizes; the platform meets it at the contract surface), and serve as the infrastructure layer.
|
||||
|
||||
Why now. Agentic SDLC is reshaping the delivery curve. The velocity is up, and it is going to keep going up. What is barely keepable today — incident response, compliance reconciliation, security remediation — does not compress at the same rate as the velocity it has to keep pace with. The operational absorption side of the curve is steeper than the delivery acceleration side, and the gap between them is the structural risk. By the end of 2027, that gap is the thing that breaks cadence. Internal directional target: a 60% reduction in the operational absorption gap is the goal we are holding ourselves to — not a sourced claim, a directional target, and the metrics the platform already produces will tell us whether we are closing it.
|
||||
The ask is architecture endorsement and runway to the next milestone by November 2026. Why now: agentic SDLC is reshaping the delivery curve. What is barely keepable today — incident response, compliance reconciliation, security remediation — does not compress at the same rate as the velocity. By end of 2027, the gap between delivery acceleration and operational absorption is the structural risk. Internal directional target: a 60% reduction in that gap — not a sourced claim, a directional target the platform's metrics will track.
|
||||
|
||||
What comes back. The infrastructure foundation that absorbs the velocity. Metrics that tell us where to push next. Audit lineage that closes the regulatory question. And the next milestone, by November 2026. That is the runway ask: not a budget, not a headcount, not a re-org. Runway to land the architecture endorsement and demonstrate the next milestone.
|
||||
|
||||
What we do not ask for: an IDE, a sprint tool, an author workflow, an upstream pipeline. Nova stays in its lane [1]. The sovereign boundary is the discipline that makes the ask small and the return large. Use the runway to land the architecture endorsement.
|
||||
What comes back: the infrastructure foundation that absorbs the velocity, metrics that tell us where to push next, audit lineage that closes the regulatory question, and the next milestone by November 2026. Not a budget, not a headcount — runway to land the architecture endorsement. What we do not ask for: an IDE, a sprint tool, an author workflow, an upstream pipeline. Nova stays in its lane [1]. The sovereign boundary makes the ask small and the return large.
|
||||
-->
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# render_leadership_diagrams.sh — render the 7 leadership-deck mermaid
|
||||
# diagrams to PNG via mmdc. Single-shot helper (NOT a CI gate, per
|
||||
# D-241/D-246). Uses vendored Chromium shared libraries (D-246).
|
||||
#
|
||||
# Usage: bash scripts/render_leadership_diagrams.sh
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
MMD_DIR="docs/presentations/assets/mmd"
|
||||
PNG_DIR="docs/presentations/assets/png"
|
||||
THEME_JSON="$MMD_DIR/sp-theme.json"
|
||||
PUPPETEER_CFG="docs/presentations/assets/puppeteer-config.json"
|
||||
|
||||
# Vendored Chromium shared libraries (D-246) — extracted from Debian
|
||||
# bookworm .deb packages to /home/opencode/tmp/so2/
|
||||
VENDORED_SO="/home/opencode/tmp/so2"
|
||||
export LD_LIBRARY_PATH="${VENDORED_SO}:${LD_LIBRARY_PATH:-}"
|
||||
|
||||
export CHROME_PATH="/home/opencode/.cache/puppeteer/chrome/linux-152.0.7977.42/chrome-linux64/chrome"
|
||||
|
||||
echo "=== Rendering leadership deck mermaid diagrams (S&P theme, 2x, transparent) ==="
|
||||
for mmd in "$MMD_DIR"/leadership-slide-*.mmd; do
|
||||
name="$(basename "$mmd" .mmd)"
|
||||
png="$PNG_DIR/$name.png"
|
||||
echo " $name.mmd → $name.png"
|
||||
npx --yes @mermaid-js/mermaid-cli@11.16.0 \
|
||||
--configFile "$THEME_JSON" \
|
||||
--puppeteerConfigFile "$PUPPETEER_CFG" \
|
||||
-s 2 -b transparent \
|
||||
--input "$mmd" \
|
||||
--output "$png" 2>&1 | grep -v "^$" || true
|
||||
done
|
||||
echo " Rendered $(ls "$PNG_DIR"/leadership-slide-*.png 2>/dev/null | wc -l) leadership PNGs."
|
||||
@@ -31,7 +31,7 @@ try:
|
||||
from pptx.util import Inches, Pt, Emu
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR, MSO_AUTO_SIZE
|
||||
from pptx.oxml.ns import qn
|
||||
except ImportError:
|
||||
print("ERROR: python-pptx not installed.", file=sys.stderr)
|
||||
@@ -206,11 +206,13 @@ def _add_title_bar(slide):
|
||||
return bar
|
||||
|
||||
|
||||
def _add_footer(slide, text: str):
|
||||
def _add_footer(slide, text: str, dark_bg: bool = False):
|
||||
"""Right-aligned footer textbox at the bottom of every slide.
|
||||
|
||||
REQ-372.5 / D-242: the python-pptx path does not read the Marp
|
||||
`footer:` directive, so the footer is rendered as a textbox.
|
||||
F4 polish: dark text on white content slides, light text on the
|
||||
black cover slide.
|
||||
"""
|
||||
if not text:
|
||||
return None
|
||||
@@ -225,7 +227,7 @@ def _add_footer(slide, text: str):
|
||||
r.text = text
|
||||
r.font.name = FONT_NAME
|
||||
r.font.size = Pt(10)
|
||||
r.font.color.rgb = GREY_HEADER
|
||||
r.font.color.rgb = GREY_HEADER if dark_bg else BODY_TEXT
|
||||
return tb
|
||||
|
||||
|
||||
@@ -308,9 +310,15 @@ def _strip_inline_emphasis(text: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def _inline_runs(p, text: str, *, size: int = 18, base_color: RGBColor = BODY_TEXT):
|
||||
"""Add inline runs to paragraph `p`, rendering **bold** as red strong,
|
||||
`code` as monospace, *italic* as italic. Other text is plain."""
|
||||
def _inline_runs(p, text: str, *, size: int = 18, base_color: RGBColor = BODY_TEXT,
|
||||
bold_color: RGBColor = None):
|
||||
"""Add inline runs to paragraph `p`, rendering **bold** as strong,
|
||||
`code` as monospace, *italic* as italic. Other text is plain.
|
||||
F5(b): bold_color defaults to RED (legacy behavior) but can be
|
||||
overridden to base_color to de-emphasize bold on slides with many
|
||||
bold sections (avoids a red wall)."""
|
||||
if bold_color is None:
|
||||
bold_color = RED
|
||||
# Tokenize on `**...**`, `*...*`, `` `...` ``
|
||||
tokens = re.split(r"(\*\*[^*]+\*\*|`[^`]+`|\*[^*]+\*)", text)
|
||||
for tok in tokens:
|
||||
@@ -322,7 +330,7 @@ def _inline_runs(p, text: str, *, size: int = 18, base_color: RGBColor = BODY_TE
|
||||
r.font.name = FONT_NAME
|
||||
r.font.size = Pt(size)
|
||||
r.font.bold = True
|
||||
r.font.color.rgb = RED
|
||||
r.font.color.rgb = bold_color
|
||||
elif tok.startswith("`") and tok.endswith("`"):
|
||||
r = p.add_run()
|
||||
r.text = tok[1:-1]
|
||||
@@ -586,7 +594,7 @@ def parse_slide(slide_src: str):
|
||||
}
|
||||
|
||||
|
||||
def render_title_slide(prs, slide_data, footer_text: str = ""):
|
||||
def render_title_slide(prs, slide_data, deck_dir: Path, footer_text: str = ""):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank
|
||||
_set_bg(slide, BLACK)
|
||||
# red top bar
|
||||
@@ -635,7 +643,12 @@ def render_title_slide(prs, slide_data, footer_text: str = ""):
|
||||
r_b.font.italic = True
|
||||
r_b.font.color.rgb = WHITE
|
||||
cur_top += Inches(0.85)
|
||||
_add_footer(slide, footer_text)
|
||||
elif kind == "image":
|
||||
# image on title slide (D-246 diagrams on cover)
|
||||
img_path = deck_dir / item[1]
|
||||
_add_picture(slide, img_path, top=cur_top)
|
||||
cur_top += Inches(3.6)
|
||||
_add_footer(slide, footer_text, dark_bg=True)
|
||||
|
||||
|
||||
def render_content_slide(prs, slide_data, deck_dir: Path, footer_text: str = ""):
|
||||
@@ -644,63 +657,102 @@ def render_content_slide(prs, slide_data, deck_dir: Path, footer_text: str = "")
|
||||
_add_title_bar(slide)
|
||||
_add_title_text(slide, slide_data["title"], color=RED, size=28, top=0.25,
|
||||
bold=True, height=0.7)
|
||||
cur_top = Inches(1.05)
|
||||
# F5(b): if a slide has >=4 lead/bold-prefixed blocks, de-emphasize
|
||||
# them to black+bold (section labels) instead of red+bold (avoids a
|
||||
# red wall on milestone-timeline slides like slide 6). Count both
|
||||
# explicit lead blocks (**...** on own line) and plain blocks that
|
||||
# start with **bold** inline.
|
||||
def _is_bold_block(item):
|
||||
if item[0] == "lead":
|
||||
return True
|
||||
if item[0] == "plain" and item[1].lstrip().startswith("**"):
|
||||
return True
|
||||
return False
|
||||
bold_count = sum(1 for item in slide_data["body"] if _is_bold_block(item))
|
||||
lead_color = BODY_TEXT if bold_count >= 4 else RED
|
||||
# F1: vertical balance — pre-compute the content height to center
|
||||
# the block between the title (bottom ~0.95") and the footer
|
||||
# (top ~7.12"). Content starts at 1.05" by default; if the total
|
||||
# content height is short, push it down to vertically center.
|
||||
INC = {"lead": 0.50, "plain": 0.45, "quote": 0.55, "bullet": 0.40,
|
||||
"ordered": 0.40, "image": 4.10, "table": 0.42, "code": 0.50,
|
||||
"benefit": 0.75}
|
||||
total_h = sum(INC.get(item[0], 0.40) for item in slide_data["body"])
|
||||
content_start = 1.05
|
||||
available = 7.12 - content_start # footer at 7.12
|
||||
if total_h < available - 0.5 and total_h > 0:
|
||||
offset = (available - total_h) / 2.0
|
||||
offset = min(max(offset, 0), 1.2) # cap at +1.2"
|
||||
content_start = round(content_start + offset, 2)
|
||||
cur_top = Inches(content_start)
|
||||
for item in slide_data["body"]:
|
||||
kind = item[0]
|
||||
if kind == "lead":
|
||||
tb = slide.shapes.add_textbox(MARGIN_X, cur_top, CONTENT_W, Inches(0.5))
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = PP_ALIGN.LEFT
|
||||
_inline_runs(p, item[1], size=18, base_color=RED)
|
||||
# make the whole lead bold-strong-red
|
||||
_inline_runs(p, item[1], size=18, base_color=lead_color,
|
||||
bold_color=lead_color)
|
||||
# make the whole lead bold
|
||||
for r in p.runs:
|
||||
r.font.bold = True
|
||||
r.font.color.rgb = RED
|
||||
cur_top += Inches(0.5)
|
||||
r.font.color.rgb = lead_color
|
||||
cur_top += Inches(0.50)
|
||||
elif kind == "plain":
|
||||
tb = slide.shapes.add_textbox(MARGIN_X, cur_top, CONTENT_W, Inches(0.4))
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = PP_ALIGN.LEFT
|
||||
_inline_runs(p, item[1], size=18, base_color=BODY_TEXT)
|
||||
cur_top += Inches(0.4)
|
||||
_inline_runs(p, item[1], size=18, base_color=BODY_TEXT,
|
||||
bold_color=lead_color)
|
||||
cur_top += Inches(0.45)
|
||||
elif kind == "quote":
|
||||
tb = slide.shapes.add_textbox(
|
||||
MARGIN_X + Inches(0.3), cur_top, CONTENT_W - Inches(0.3), Inches(0.6)
|
||||
)
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = PP_ALIGN.LEFT
|
||||
_inline_runs(p, item[1], size=18, base_color=GREY_TEXT)
|
||||
_inline_runs(p, item[1], size=18, base_color=BODY_TEXT)
|
||||
# italicize the whole blockquote
|
||||
for r in p.runs:
|
||||
r.font.italic = True
|
||||
r.font.color.rgb = GREY_TEXT
|
||||
cur_top += Inches(0.6)
|
||||
r.font.color.rgb = BODY_TEXT
|
||||
cur_top += Inches(0.55)
|
||||
elif kind == "bullet":
|
||||
# accumulate consecutive bullets into one text frame
|
||||
# (handled below in a second pass; we render single here as fallback)
|
||||
tb = slide.shapes.add_textbox(MARGIN_X, cur_top, CONTENT_W, Inches(0.35))
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = PP_ALIGN.LEFT
|
||||
p.level = item[1]
|
||||
r = p.add_run()
|
||||
prefix = "• " if item[1] == 0 else ("– " if item[1] == 1 else "· ")
|
||||
r.text = prefix + _strip_inline_emphasis(item[2])
|
||||
# F3: if the bullet text starts with →, omit the bullet glyph
|
||||
stripped_text = _strip_inline_emphasis(item[2])
|
||||
if item[1] == 0:
|
||||
prefix = "" if stripped_text.lstrip().startswith("→") else "• "
|
||||
else:
|
||||
prefix = "– " if item[1] == 1 else "· "
|
||||
r.text = prefix + stripped_text
|
||||
r.font.name = FONT_NAME
|
||||
r.font.size = Pt(18 if item[1] == 0 else 16)
|
||||
r.font.color.rgb = BODY_TEXT
|
||||
cur_top += Inches(0.35)
|
||||
cur_top += Inches(0.40)
|
||||
elif kind == "ordered":
|
||||
tb = slide.shapes.add_textbox(MARGIN_X, cur_top, CONTENT_W, Inches(0.35))
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = PP_ALIGN.LEFT
|
||||
r = p.add_run()
|
||||
@@ -708,7 +760,7 @@ def render_content_slide(prs, slide_data, deck_dir: Path, footer_text: str = "")
|
||||
r.font.name = FONT_NAME
|
||||
r.font.size = Pt(18)
|
||||
r.font.color.rgb = BODY_TEXT
|
||||
cur_top += Inches(0.35)
|
||||
cur_top += Inches(0.40)
|
||||
elif kind == "image":
|
||||
img_path = deck_dir / item[1]
|
||||
_add_picture(slide, img_path, top=cur_top)
|
||||
@@ -721,6 +773,7 @@ def render_content_slide(prs, slide_data, deck_dir: Path, footer_text: str = "")
|
||||
tb = slide.shapes.add_textbox(MARGIN_X, cur_top, CONTENT_W, Inches(0.6))
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = PP_ALIGN.LEFT
|
||||
r = p.add_run()
|
||||
@@ -751,7 +804,7 @@ def render_deck(md_path: Path, pptx_path: Path):
|
||||
is_title = (idx == 0) or data["is_title_class"] or data["title_is_h1"]
|
||||
# The appendix is a content slide (rendered normally)
|
||||
if idx == 0 and (data["title_is_h1"] or data["is_title_class"]):
|
||||
render_title_slide(prs, data, footer_text=footer_text)
|
||||
render_title_slide(prs, data, deck_dir, footer_text=footer_text)
|
||||
elif data["is_title_class"] and not data["title_is_h1"] and idx != 0:
|
||||
# Marp _class: title on a non-H1 slide (e.g., appendix) — render as
|
||||
# content but with a title-style bar. Keep it simple: content slide.
|
||||
|
||||